Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
<!DOCTYPE html>
<html>
<head>
<title>Test accessible delayed removal</title>
<link rel="stylesheet" type="text/css"
<style>
.gentext:before {
content: "START"
}
.gentext:after {
content: "END"
}
</style>
<script type="application/javascript"
src="../common.js"></script>
<script type="application/javascript"
src="../role.js"></script>
<script type="application/javascript"
src="../promisified-events.js"></script>
<script type="application/javascript">
async function hideDivFromInsideSpan() {
let msg = "hideDivFromInsideSpan";
info(msg);
let events = waitForOrderedEvents([
[EVENT_HIDE, "div1"], [EVENT_TEXT_REMOVED, "span1"],
[EVENT_REORDER, "span1"]
], msg);
document.body.offsetTop; // Flush layout.
getNode("div1").style.display = "none";
await events;
testAccessibleTree("c1", { SECTION: [ { REGION: [] }, ] });
}
async function showDivFromInsideSpan() {
let msg = "showDivFromInsideSpan";
info(msg);
let events = waitForOrderedEvents(
[[EVENT_SHOW, "div2"], [EVENT_REORDER, "span2"]], msg);
document.body.offsetTop; // Flush layout.
getNode("div2").style.display = "block";
await events;
testAccessibleTree("c2",
{ SECTION: [ { REGION: [{ SECTION: [ { TEXT_LEAF: [] } ] }] }, ] });
}
async function removeDivFromInsideSpan() {
let msg = "removeDivFromInsideSpan";
info(msg);
let events = waitForOrderedEvents([
[EVENT_HIDE, getNode("div3")], [EVENT_TEXT_REMOVED, "span3"],
[EVENT_REORDER, "span3"]
], msg);
document.body.offsetTop; // Flush layout.
getNode("div3").remove();
await events;
testAccessibleTree("c3", { SECTION: [ { REGION: [] }, ] });
}
// Test to see that generated content is inserted
async function addCSSGeneratedContent() {
let msg = "addCSSGeneratedContent";
let c4_child = getAccessible("c4_child");
info(msg);
let events = waitForOrderedEvents([
[EVENT_SHOW, evt => evt.accessible == c4_child.firstChild],
[EVENT_SHOW, evt => evt.accessible == c4_child.lastChild],
[EVENT_REORDER, c4_child]], msg);
document.body.offsetTop; // Flush layout.
getNode("c4_child").classList.add('gentext');
await events;
testAccessibleTree("c4", { SECTION: [ // container
{ SECTION: [ // inserted node
{ STATICTEXT: [] }, // :before
{ TEXT_LEAF: [] }, // primary text
{ STATICTEXT: [] }, // :after
] },
] });
}
// Test to see that generated content gets removed
async function removeCSSGeneratedContent() {
let msg = "removeCSSGeneratedContent";
let c5_child = getAccessible("c5_child");
info(msg);
let events = waitForEvents([
[EVENT_HIDE, c5_child.firstChild],
[EVENT_HIDE, c5_child.lastChild],
[EVENT_REORDER, c5_child]], msg);
document.body.offsetTop; // Flush layout.
getNode("c5_child").classList.remove('gentext');
await events;
testAccessibleTree("c5",{ SECTION: [ // container
{ SECTION: [ // inserted node
{ TEXT_LEAF: [] }, // primary text
] },
] });
}
// Test to see that a non-accessible intermediate container gets its accessible
// descendants removed and inserted correctly.
async function intermediateNonAccessibleContainers() {
let msg = "intermediateNonAccessibleContainers";
info(msg);
testAccessibleTree("c6",{ SECTION: [
{ SECTION: [
{ role: ROLE_PUSHBUTTON, name: "Hello" },
] },
] });
let events = waitForOrderedEvents(
[[EVENT_HIDE, "b1"], [EVENT_SHOW, "b2"], [EVENT_REORDER, "scrollarea"]], msg);
document.body.offsetTop; // Flush layout.
getNode("scrollarea").style.overflow = "auto";
document.querySelector("#scrollarea > div > div:first-child").style.display = "none";
document.querySelector("#scrollarea > div > div:last-child").style.display = "block";
await events;
testAccessibleTree("c6",{ SECTION: [
{ SECTION: [
{ role: ROLE_PUSHBUTTON, name: "Goodbye" },
] },
] });
}
// Test to see that the button gets reparented into the new accessible container.
async function intermediateNonAccessibleContainerBecomesAccessible() {
let msg = "intermediateNonAccessibleContainerBecomesAccessible";
info(msg);
testAccessibleTree("c7",{ SECTION: [
{ role: ROLE_PUSHBUTTON, name: "Hello" },
{ TEXT_LEAF: [] }
] });
let events = waitForOrderedEvents(
[[EVENT_HIDE, "b3"],
// b3 show event coalesced into its new container
[EVENT_SHOW, evt => evt.DOMNode.classList.contains('intermediate')],
[EVENT_REORDER, "c7"]], msg);
document.body.offsetTop; // Flush layout.
document.querySelector("#c7 > div").style.display = "block";
await events;
testAccessibleTree("c7",{ SECTION: [
{ SECTION: [ { role: ROLE_PUSHBUTTON, name: "Hello" } ] }
] });
}
// Test to ensure that relocated accessibles are removed when a DOM
// ancestor is hidden.
async function removeRelocatedWhenDomAncestorHidden() {
info("removeRelocatedWhenDomAncestorHidden");
testAccessibleTree("c8",{ SECTION: [
{ EDITCOMBOBOX: [ // c8_owner
{ COMBOBOX_LIST: [] }, // c8_owned
]},
{ SECTION: [] }, // c8_owned_container
] });
let events = waitForOrderedEvents([
[EVENT_HIDE, "c8_owned_container"],
[EVENT_HIDE, "c8_owned"],
[EVENT_REORDER, "c8"],
], "removeRelocatedWhenDomAncestorHidden");
document.body.offsetTop; // Flush layout.
getNode("c8_owned_container").hidden = true;
await events;
testAccessibleTree("c8",{ SECTION: [
{ EDITCOMBOBOX: [] }, // c8_owner
] });
}
async function removeShadowRootHost() {
info("removeShadowRootHost");
document.body.offsetTop; // Flush layout.
let event = waitForEvent(EVENT_REORDER, "c9", "removeShadowRootHost");
getNode("c9").firstElementChild.attachShadow({mode: "open"});
getNode("c9").firstElementChild.replaceWith("");
await event;
}
function listItemReframe() {
testAccessibleTree("li",{ LISTITEM: [
{ LISTITEM_MARKER: [] },
{ TEXT_LEAF: [] },
] });
getNode("li").style.listStylePosition = "inside";
document.body.offsetTop; // Flush layout.
window.windowUtils.advanceTimeAndRefresh(100);
testAccessibleTree("li",{ LISTITEM: [
{ LISTITEM_MARKER: [] },
{ TEXT_LEAF: [] },
] });
window.windowUtils.restoreNormalRefresh();
}
// Check to see that a reframed body gets its children pruned correctly.
async function bodyReframe() {
// Load sub-document in iframe.
let event = waitForEvent(EVENT_REORDER, "iframe", "bodyReframe");
getNode("iframe").src =
`data:text/html,<div>Hello</div><div style="display: none">World</div>`;
await event;
// Initial tree should have one section leaf.
testAccessibleTree("c10",{ SECTION: [
{ INTERNAL_FRAME: [
{ DOCUMENT: [
{ SECTION: [
{ role: ROLE_TEXT_LEAF, name: "Hello" }
] }
]}
] }
] });
let iframeDoc = getNode("iframe").contentWindow.document;
// Trigger coalesced reframing. Both the body node and its children
// will need reframing.
event = waitForEvent(EVENT_REORDER, iframeDoc, "bodyReframe");
iframeDoc.body.style.display = "inline-block";
iframeDoc.querySelector("div:first-child").style.display = "none";
iframeDoc.querySelector("div:last-child").style.display = "block";
await event;
// Only the second section should be showing
testAccessibleTree("c10",{ SECTION: [
{ INTERNAL_FRAME: [
{ DOCUMENT: [
{ SECTION: [
{ role: ROLE_TEXT_LEAF, name: "World" }
] }
]}
] }
] });
}
// Ensure that embed elements recreate their Accessible if they started
// without an src and then an src is set later.
async function embedBecomesOuterDoc() {
let msg = "embedBecomesOuterDoc";
info(msg);
testAccessibleTree("c12", { SECTION: [
{ TEXT: [] }
] });
let events = waitForOrderedEvents([
[EVENT_HIDE, "embed"],
[EVENT_SHOW, "embed"],
[EVENT_REORDER, "c12"],
], msg);
getNode("embed").src = "data:text/html,";
await events;
testAccessibleTree("c12", { SECTION: [
{ INTERNAL_FRAME: [
{ DOCUMENT: [] }
] }
] });
}
// Test that we get a text removed event when removing generated content from a button
async function testCSSGeneratedContentRemovedFromButton() {
let msg = "testCSSGeneratedContentRemovedFromButton";
info(msg);
testAccessibleTree("c13", { SECTION: [
{ role: ROLE_PUSHBUTTON, name: "beforego",
children: [{ STATICTEXT: [] }, { TEXT_LEAF: [] }] }
] });
let events = waitForOrderedEvents([
[EVENT_HIDE, evt => evt.accessible.name == "before"],
[EVENT_TEXT_REMOVED, evt => evt.accessible.role == ROLE_PUSHBUTTON],
[EVENT_SHOW, evt => evt.DOMNode.tagName == "HR"],
[EVENT_REORDER, "c13"],
], msg);
getNode("b13").click();
await events;
testAccessibleTree("c13", { SECTION: [
{ role: ROLE_PUSHBUTTON, name: "go",
children: [{ TEXT_LEAF: [] }] },
{ SEPARATOR: [] }
] });
}
// Slack seems to often restyle containers and change children
// simultaneously, this results in an insertion queue filled with
// redundant insertions and unparented nodes.
// This test duplicates some of this.
async function testSlack() {
let msg = "testSlack";
info(msg);
window.windowUtils.advanceTimeAndRefresh(100);
let event = waitForEvent(EVENT_REORDER, "c14", "testSlack");
let keyContainer = document.querySelector("#c14 .intermediate");
keyContainer.style.display = "inline-block";
document.body.offsetTop; // Flush layout.
let one = document.querySelector("#c14 [aria-label='one']");
let three = document.querySelector("#c14 [aria-label='three']");
one.remove();
three.remove();
// insert one first
keyContainer.firstChild.before(one.cloneNode());
// insert three last
keyContainer.lastChild.after(three.cloneNode());
keyContainer.style.display = "flex";
document.body.offsetTop; // Flush layout.
window.windowUtils.restoreNormalRefresh();
await event;
is(getAccessible("c14").name, "one two three", "subtree has correct order");
}
// Ensure that a node is removed when visibility: hidden is set but the
// layout frame is reconstructed; e.g. because of position: fixed. Also
// ensure that visible children aren't clobbered.
async function visibilityHiddenWithReframe() {
let msg = "visibilityHiddenWithReframe";
info(msg);
testAccessibleTree("c15", { SECTION: [ // c15
{ SECTION: [ // c15_inner
{ TEXT_LEAF: [] }, // Text
{ PARAGRAPH: [
{ TEXT_LEAF: [] } // Para
] },
{ HEADING: [ // c15_visible
{ TEXT_LEAF: [] } // Visible
] }, // c15_visible
] } // c15_inner
] });
let events = waitForOrderedEvents([
[EVENT_HIDE, "c15_inner"],
[EVENT_SHOW, "c15_visible"],
[EVENT_REORDER, "c15"],
], msg);
getNode("c15_inner").style = "visibility: hidden; position: fixed;";
await events;
testAccessibleTree("c15", { SECTION: [ // c15
{ HEADING: [ // c15_visible
{ TEXT_LEAF: [] } // Visible
] }, // c15_visible
] });
}
async function doTest() {
await hideDivFromInsideSpan();
await showDivFromInsideSpan();
await removeDivFromInsideSpan();
await addCSSGeneratedContent();
await removeCSSGeneratedContent();
await intermediateNonAccessibleContainers();
await intermediateNonAccessibleContainerBecomesAccessible();
await removeRelocatedWhenDomAncestorHidden();
await removeShadowRootHost();
listItemReframe();
await bodyReframe();
await embedBecomesOuterDoc();
await testCSSGeneratedContentRemovedFromButton();
await testSlack();
await visibilityHiddenWithReframe();
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTest);
</script>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
<div id="c1">
<span role="region" id="span1" aria-label="region"><div id="div1">hello</div></span>
</div>
<div id="c2">
<span role="region" id="span2" aria-label="region"><div id="div2" style="display: none">hello</div></span>
</div>
<div id="c3">
<span role="region" id="span3" aria-label="region"><div id="div3">hello</div></span>
</div>
<div id="c4"><div id="c4_child">text</div></div>
<div id="c5"><div id="c5_child" class="gentext">text</div></div>
<div id="c6">
<div id="scrollarea" style="overflow:hidden;">
<div><div role="none"><button id="b1">Hello</button></div><div role="none" style="display: none"><button id="b2">Goodbye</button></div></div>
</div>
</div>
<div id="c7">
<div style="display: inline;" class="intermediate">
<button id="b3">Hello</button>
</div>
</div>
<div id="c8">
<div id="c8_owner" role="combobox" aria-owns="c8_owned"></div>
<div id="c8_owned_container">
<div id="c8_owned" role="listbox"></div>
</div>
</div>
<div id="c9">
<div><dir>a</dir></div>
</div>
<div id="c11">
<ul>
<li id="li">Test</li>
</ul>
</div>
<div id="c12"><embed id="embed"></embed></div>
<div id="c10">
<iframe id="iframe"></iframe>
</div>
<div id="c13">
<style>
.before::before { content: 'before' }
</style>
<button id="b13" class="before" onclick="this.className = ''; this.insertAdjacentElement('afterend', document.createElement('hr'))">go</button>
</div>
<div role="heading" id="c14" data-qa="virtual-list-item">
<div class="intermediate">
<div role="img" aria-label="one"></div> two <div role="img"
aria-label="three"></div>
</div>
</div>
<div id="c15"><div id="c15_inner">
Text
<p>Para</p>
<h1 id="c15_visible" style="visibility: visible;">Visible</h1>
</div></div>
<div id="eventdump"></div>
</body>
</html>