blob: d04a95672e311290a74c051eb48ba8e88dda1df4 [file] [edit]
<html>
<head>
<script src="../resources/accessibility-helper.js"></script>
<script src="../resources/js-test.js"></script>
</head>
<body>
<div id="owner" role="group"></div>
<div id="parent" role="group">
<button id="child">Child</button>
</div>
<div id="movedOwner" role="group"></div>
<div id="movedParent" role="group">
<button id="movedChild">Moved child</button>
</div>
<script>
var testOutput = "This test makes sure the isolated tree resolves a node correctly when a subtree removal and a later re-append of that same node reach the accessibility thread in one committed batch.\n\n";
// Waits without touching the accessibility tree. Any AX query forces the AX thread to apply
// whatever has been committed so far, which would split the mutations below into separate
// batches and defeat the point of the test.
async function waitFramesWithoutQueryingAX(count)
{
for (var i = 0; i < count; i++)
await new Promise(resolve => requestAnimationFrame(() => setTimeout(resolve, 0)));
}
if (window.accessibilityController) {
window.jsTestIsAsync = true;
setTimeout(async function() {
window.expectedButtonRole = "AXRole: AXButton";
var axParent = await waitForElementById("parent");
await waitFor(() => axParent.childrenCount === 1);
// aria-owns moves the button in the accessibility tree without touching the DOM or the
// render tree, so the object keeps its AXID. That is what puts one AXID in both the
// append and the subtree-removal vector of a single batch. A DOM reparent can't do this,
// as it destroys and recreates the renderer, so the new parent's append is for a new AXID.
//
// Stealing the button queues a removal of it from #parent. Deleting the owner then makes
// it a child of #parent again, queueing an append of that same AXID under #parent. Both
// land in one batch, with the append being the newer of the two, so deciding staleness by
// comparing parents alone cannot tell them apart and discards the append -- dropping the
// button from the tree even though it is a child of #parent again.
//
// Nothing may query the accessibility tree between these two mutations. Doing so applies
// the first batch, and then there is no merged batch left to get wrong.
document.getElementById("owner").setAttribute("aria-owns", "child");
await waitFramesWithoutQueryingAX(4);
document.getElementById("owner").remove();
await waitFramesWithoutQueryingAX(4);
window.parentCountAfterOwnerRemoved = axParent.childrenCount;
testOutput += expect("parentCountAfterOwnerRemoved", "1");
window.restoredRole = axParent.childrenCount ? axParent.childAtIndex(0).role : "<no child>";
testOutput += expect("restoredRole", "expectedButtonRole");
// The same batch shape as above, except the append genuinely is a move, with the removal and the
// append being from different parents, so the append must be kept rather than treated as stale.
var axMovedOwner = await waitForElementById("movedOwner");
var axMovedParent = await waitForElementById("movedParent");
await waitFor(() => axMovedParent.childrenCount === 1);
document.getElementById("movedOwner").setAttribute("aria-owns", "movedChild");
await waitFor(() => axMovedOwner.childrenCount === 1);
window.movedOwnerCount = axMovedOwner.childrenCount;
testOutput += expect("movedOwnerCount", "1");
window.movedRole = axMovedOwner.childrenCount ? axMovedOwner.childAtIndex(0).role : "<no child>";
testOutput += expect("movedRole", "expectedButtonRole");
window.movedParentCount = axMovedParent.childrenCount;
testOutput += expect("movedParentCount", "0");
debug(testOutput);
finishJSTest();
}, 0);
}
</script>
</body>
</html>