Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
<!DOCTYPE html>
<html>
<head>
<title>@aria-owns attribute testing</title>
<link rel="stylesheet" type="text/css"
<script type="application/javascript"
src="../common.js"></script>
<script type="application/javascript"
src="../role.js"></script>
<script type="application/javascript"
src="../events.js"></script>
<script type="application/javascript">
// //////////////////////////////////////////////////////////////////////////
// Test
// //////////////////////////////////////////////////////////////////////////
// enableLogging("tree,eventTree,verbose"); // debug stuff
async function rearrangeARIAOwns(aContainer, aAttr, aIdList, aRoleList) {
// Expect a 'hide' and 'show' for each of the IDs
// along with a 'reorder' on the container.
let events = aIdList.map(id => {
return [[EVENT_HIDE, getNode(id)], [EVENT_SHOW, getNode(id)]];
}).flat(1);
events.push([EVENT_REORDER, getNode(aContainer)]);
let p = waitForEvents(events);
getNode(aContainer).setAttribute("aria-owns", aAttr);
await p;
let tree = { SECTION: [] };
for (let role of aRoleList) {
let ch = {};
ch[role] = [];
tree.SECTION.push(ch);
}
testAccessibleTree(aContainer, tree);
}
async function doTest() {
// test1: change @aria-owns attribute (children swapped by ARIA owns)
{
testAccessibleTree("t1_container", {
SECTION: [
{ CHECKBUTTON: [{ SECTION: [] }] },
{ PUSHBUTTON: [] },
],
});
let p = waitForEvents([
[EVENT_HIDE, getNode("t1_button")],
// no hide for t1_subdiv because it is contained by hidden t1_checkbox
[EVENT_HIDE, getNode("t1_checkbox")],
[EVENT_SHOW, getNode("t1_checkbox")],
[EVENT_SHOW, getNode("t1_button")],
[EVENT_SHOW, getNode("t1_subdiv")],
[EVENT_REORDER, getNode("t1_container")],
]);
getNode("t1_container").setAttribute("aria-owns", "t1_button t1_subdiv");
await p;
testAccessibleTree("t1_container", {
SECTION: [
{ CHECKBUTTON: [] }, // checkbox, native order
{ PUSHBUTTON: [] }, // button, rearranged by ARIA own
{ SECTION: [] }, // subdiv from the subtree, ARIA owned
],
});
}
// test1: remove @aria-owns attribute
{
let p = waitForEvents({
expected: [
[EVENT_HIDE, getNode("t1_button")],
[EVENT_HIDE, getNode("t1_subdiv")],
[EVENT_SHOW, getNode("t1_button")],
[EVENT_SHOW, getNode("t1_subdiv")],
[EVENT_REORDER, getNode("t1_container")],
],
unexpected: [
[EVENT_REORDER, getNode("t1_checkbox")],
],
});
getNode("t1_container").removeAttribute("aria-owns");
await p;
testAccessibleTree("t1_container", {
SECTION: [
{ PUSHBUTTON: [] },
{ CHECKBUTTON: [{ SECTION: [] }] },
],
});
}
// test1: set @aria-owns attribute again
{
let p = waitForEvents([
[EVENT_HIDE, getNode("t1_button")],
[EVENT_HIDE, getNode("t1_subdiv")],
[EVENT_SHOW, getNode("t1_button")],
[EVENT_SHOW, getNode("t1_subdiv")],
[EVENT_REORDER, getNode("t1_container")],
]);
getNode("t1_container").setAttribute("aria-owns", "t1_button t1_subdiv");
await p;
testAccessibleTree("t1_container", {
SECTION: [
{ CHECKBUTTON: [] }, // checkbox
{ PUSHBUTTON: [] }, // button, rearranged by ARIA own
{ SECTION: [] }, // subdiv from the subtree, ARIA owned
],
});
}
// test1: add id to @aria-owns attribute value
{
let p = waitForEvents([
[EVENT_HIDE, getNode("t1_group")],
[EVENT_SHOW, getNode("t1_group")],
[EVENT_REORDER, document],
]);
getNode("t1_container").setAttribute(
"aria-owns", "t1_button t1_subdiv t1_group");
await p;
testAccessibleTree("t1_container", {
SECTION: [
{ CHECKBUTTON: [] }, // t1_checkbox
{ PUSHBUTTON: [] }, // button, t1_button
{ SECTION: [] }, // subdiv from the subtree, t1_subdiv
{ GROUPING: [] }, // group from outside, t1_group
],
});
}
// test1: append child under @aria-owns element
{
let p = waitForEvents([
[EVENT_SHOW, "t1_child3"],
[EVENT_REORDER, getNode("t1_container")],
]);
let div = document.createElement("div");
div.setAttribute("id", "t1_child3");
div.setAttribute("role", "radio");
getNode("t1_container").appendChild(div);
await p;
testAccessibleTree("t1_container", {
SECTION: [
{ CHECKBUTTON: [] }, // existing explicit, t1_checkbox
{ RADIOBUTTON: [] }, // new explicit, t1_child3
{ PUSHBUTTON: [] }, // ARIA owned, t1_button
{ SECTION: [] }, // ARIA owned, t1_subdiv
{ GROUPING: [] }, // ARIA owned, t1_group
],
});
}
// test1: remove a container of ARIA owned element
{
let p = waitForEvents([
[EVENT_HIDE, getNode("t1_subdiv")],
[EVENT_REORDER, getNode("t1_container")],
]);
getNode("t1_span").remove();
await p;
testAccessibleTree("t1_container", {
SECTION: [
{ CHECKBUTTON: [] }, // explicit, t1_checkbox
{ RADIOBUTTON: [] }, // explicit, t1_child3
{ PUSHBUTTON: [] }, // ARIA owned, t1_button
{ GROUPING: [] }, // ARIA owned, t1_group
],
});
}
// test1: remove ID from ARIA owned element
{
let p = waitForEvents([
[EVENT_HIDE, getNode("t1_group")],
[EVENT_SHOW, getNode("t1_group")],
[EVENT_REORDER, document],
]);
getNode("t1_group").removeAttribute("id");
await p;
testAccessibleTree("t1_container", {
SECTION: [
{ CHECKBUTTON: [] },
{ RADIOBUTTON: [] },
{ PUSHBUTTON: [] }, // ARIA owned, t1_button
],
});
}
// test1: set ID that is referred by ARIA owns
{
let p = waitForEvents([
[EVENT_HIDE, getNode("t1_grouptmp")],
[EVENT_SHOW, getNode("t1_grouptmp")],
[EVENT_REORDER, document],
]);
getNode("t1_grouptmp").setAttribute("id", "t1_group");
await p;
testAccessibleTree("t1_container", {
SECTION: [
{ CHECKBUTTON: [] },
{ RADIOBUTTON: [] },
{ PUSHBUTTON: [] }, // ARIA owned, t1_button
{ GROUPING: [] }, // ARIA owned, t1_group, previously t1_grouptmp
],
});
}
// test2: remove an accessible DOM element containing an element referred
// by ARIA owns
{
testAccessibleTree("t2_container1", {
SECTION: [
{ CHECKBUTTON: [] }, // ARIA owned, 't2_owned'
],
});
let p = waitForEvent(EVENT_REORDER, getNode("t2_container1"));
getNode("t2_container2").removeChild(getNode("t2_container3"));
await p;
testAccessibleTree("t2_container1", { SECTION: [] });
}
// test3: steal an element from other ARIA owns element
{
let p = waitForEvent(EVENT_REORDER, getNode("t3_container3"));
getNode("t3_container3").setAttribute("aria-owns", "t3_child t3_child2");
await p;
testAccessibleTree("t3_container1", {
SECTION: [{ CHECKBUTTON: [] }],
});
testAccessibleTree("t3_container2", { SECTION: [] });
testAccessibleTree("t3_container3", {
SECTION: [{ CHECKBUTTON: [] }],
});
}
// test3: append a child under @aria-owns element to trigger children
// recache
{
let p = waitForEvent(EVENT_REORDER, getNode("t3_container3"));
let div = document.createElement("div");
div.setAttribute("role", "radio");
getNode("t3_container3").appendChild(div);
await p;
testAccessibleTree("t3_container2", { SECTION: [] });
testAccessibleTree("t3_container3", {
SECTION: [
{ RADIOBUTTON: [] },
{ CHECKBUTTON: [] }, // ARIA owned
],
});
}
// test4: show hidden ARIA owns referred element
{
testAccessibleTree("t4_container1", {
SECTION: [{ RADIOBUTTON: [] }],
});
let p = waitForEvent(EVENT_REORDER, getNode("t4_container1"));
getNode("t4_child1").style.display = "block";
await p;
testAccessibleTree("t4_container1", {
SECTION: [
{ CHECKBUTTON: [] },
{ RADIOBUTTON: [] },
],
});
}
// test5: rearrange @aria-owns attribute
await rearrangeARIAOwns(
"t5_container", "t5_checkbox t5_radio t5_button",
["t5_checkbox", "t5_radio", "t5_button"],
["CHECKBUTTON", "RADIOBUTTON", "PUSHBUTTON"]);
await rearrangeARIAOwns(
"t5_container", "t5_radio t5_button t5_checkbox",
["t5_radio", "t5_button"],
["RADIOBUTTON", "PUSHBUTTON", "CHECKBUTTON"]);
// test6: remove not ARIA owned child
{
testAccessibleTree("t6_container", {
SECTION: [
{ TEXT_LEAF: [] },
{ GROUPING: [] },
],
});
let p = waitForEvent(EVENT_REORDER, "t6_container");
getNode("t6_container").removeChild(getNode("t6_span"));
await p;
testAccessibleTree("t6_container", {
SECTION: [{ GROUPING: [] }],
});
}
// test7: set ARIA owns on an element, and then remove it, and then
// remove its parent
{
let parentAcc = getAccessible("t7_parent");
let p = waitForEvent(EVENT_HIDE, parentAcc);
getNode("t7_child").setAttribute("aria-owns", "no_id");
getNode("t7_parent").removeChild(getNode("t7_child"));
getNode("t7_parent").remove();
await p;
}
// test8: set ARIA owns on inaccessible span element that contains
// accessible children
{
testAccessibleTree("t8_container", {
SECTION: [
{ PUSHBUTTON: [] },
{ ENTRY: [] },
{ ENTRY: [] },
{ ENTRY: [] },
],
});
let p = waitForEvent(EVENT_REORDER, "t8_container");
getNode("t8_container").setAttribute("aria-owns", "t8_span t8_button");
await p;
testAccessibleTree("t8_container", {
SECTION: [
{ TEXT: [
{ ENTRY: [] },
{ ENTRY: [] },
{ ENTRY: [] },
] },
{ PUSHBUTTON: [] },
],
});
}
// test9: set ARIA owns on a document (part1)
{
let p = waitForEvent(EVENT_DOCUMENT_LOAD_COMPLETE,
e => e.DOMNode === getNode("t9_container").contentDocument);
// The \ before the final /script avoids the script from being terminated
// by the html parser.
getNode("t9_container").src = `data:text/html,
<html><body></body>
<script>
let el = document.createElement('div');
el.id = 'container';
el.innerHTML = "<input id='input'>";
document.documentElement.appendChild(el);
<\/script></html>`;
await p;
testAccessibleTree("t9_container", {
INTERNAL_FRAME: [
{ DOCUMENT: [
{ SECTION: [
{ ENTRY: [] },
] },
] },
],
});
}
// test9: set ARIA owns on a document (part2)
{
let p = waitForEvent(EVENT_SHOW, e => {
let doc = getNode("t9_container").contentDocument;
let input = doc && doc.getElementById("input");
return input && e.DOMNode === input;
});
let doc = getNode("t9_container").contentDocument;
doc.body.setAttribute("aria-owns", "input");
await p;
testAccessibleTree("t9_container", {
INTERNAL_FRAME: [
{ DOCUMENT: [
{ SECTION: [] },
{ ENTRY: [] },
] },
],
});
}
// test9: set ARIA owns on a document (part3)
{
let doc = getNode("t9_container").contentDocument;
let p = waitForEvent(EVENT_REORDER, doc);
doc.body.appendChild(doc.createElement("p"));
await p;
testAccessibleTree("t9_container", {
INTERNAL_FRAME: [
{ DOCUMENT: [
{ PARAGRAPH: [] },
{ SECTION: [] },
{ ENTRY: [] },
] },
],
});
}
// test10: put aria owned child back when aria owner removed
{
testAccessibleTree("t10_container", {
SECTION: [ // t10_container
{ SECTION: [ // t10_owner
{ ENTRY: [] }, // t10_child
] },
],
});
let ownerAcc = getAccessible("t10_owner");
let p = waitForEvent(EVENT_HIDE, ownerAcc);
getNode("t10_owner").remove();
await p;
}
// test10: put aria owned child back when aria owner removed (finish test)
{
let p = waitForEvent(EVENT_REORDER, "t10_container");
getNode("t10_container").append(document.createElement("p"));
await p;
testAccessibleTree("t10_container", {
SECTION: [ // t10_container
{ ENTRY: [] }, // t10_child
{ PARAGRAPH: [] },
],
});
todo(false, "Input accessible has be moved back in the tree");
}
let owned = document.createElement('div');
owned.id = 't11_child';
owned.textContent = 'owned';
let evtPromise = waitForEvent(EVENT_SHOW, "t11_child");
getNode("t11_container").append(owned);
let evt = await evtPromise;
is(evt.accessible.parent.name, "t11_owner");
// Test owning an ancestor which isn't created yet.
testAccessibleTree("t12_container", { SECTION: [ // t12_container
{ SECTION: [ // t12b
{ SECTION: [] }, // t12c
] },
{ SECTION: [] }, // t12d
] });
// Owning t12a would create a cycle, so we expect it to do nothing.
// We own t12d so we get an event when aria-owns relocation is complete.
evtPromise = waitForEvent(EVENT_SHOW, "t12d");
getNode("t12c").setAttribute("aria-owns", "t12a t12d");
await evtPromise;
testAccessibleTree("t12_container", { SECTION: [ // t12_container
{ SECTION: [ // t12b
{ SECTION: [ // t12c
{ SECTION: [] }, // t12d
] },
] },
] });
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="t1_container" aria-owns="t1_checkbox t1_button">
<div role="button" id="t1_button"></div>
<div role="checkbox" id="t1_checkbox">
<span id="t1_span">
<div id="t1_subdiv"></div>
</span>
</div>
</div>
<div id="t1_group" role="group"></div>
<div id="t1_grouptmp" role="group"></div>
<div id="t2_container1" aria-owns="t2_owned"></div>
<div id="t2_container2">
<div id="t2_container3"><div id="t2_owned" role="checkbox"></div></div>
</div>
<div id="t3_container1" aria-owns="t3_child"></div>
<div id="t3_child" role="checkbox"></div>
<div id="t3_container2">
<div id="t3_child2" role="checkbox"></div>
</div>
<div id="t3_container3"></div>
<div id="t4_container1" aria-owns="t4_child1 t4_child2"></div>
<div id="t4_container2">
<div id="t4_child1" style="display:none" role="checkbox"></div>
<div id="t4_child2" role="radio"></div>
</div>
<div id="t5_container">
<div role="button" id="t5_button"></div>
<div role="checkbox" id="t5_checkbox"></div>
<div role="radio" id="t5_radio"></div>
</div>
<div id="t6_container" aria-owns="t6_fake">
<span id="t6_span">hey</span>
</div>
<div id="t6_fake" role="group"></div>
<div id="t7_container">
<div id="t7_parent">
<div id="t7_child"></div>
</div>
</div>
<div id="t8_container">
<input id="t8_button" type="button"><span id="t8_span"><input><input><input></span>
</div>
<iframe id="t9_container"></iframe>
<div id="t10_container">
<div id="t10_owner" aria-owns="t10_child"></div>
<input id="t10_child">
</div>
<div id="t11_container" aria-label="t11_container">
<div aria-owns="t11_child" aria-label="t11_owner"></div>
</div>
<div id="t12_container">
<span id="t12a">
<div id="t12b" aria-owns="t12c"></div>
</span>
<div id="t12c"></div>
<div id="t12d"></div>
</div>
</body>
</html>