<!DOCTYPE html> <html> <head> <title>@aria-owns attribute testing</title> <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> <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"> //////////////////////////////////////////////////////////////////////////// // Invokers //////////////////////////////////////////////////////////////////////////// function changeARIAOwns() { this.eventSeq = [ new invokerChecker(EVENT_HIDE, getNode("t1_button")), // no hide for t1_subdiv because it is contained by hidden t1_checkbox new invokerChecker(EVENT_HIDE, getNode("t1_checkbox")), new invokerChecker(EVENT_SHOW, getNode("t1_checkbox")), new invokerChecker(EVENT_SHOW, getNode("t1_button")), new invokerChecker(EVENT_SHOW, getNode("t1_subdiv")), new invokerChecker(EVENT_REORDER, getNode("t1_container")) ]; this.invoke = function setARIAOwns_invoke() { // children are swapped by ARIA owns var tree = { SECTION: [ { CHECKBUTTON: [ { SECTION: [] } ] }, { PUSHBUTTON: [ ] } ] }; testAccessibleTree("t1_container", tree); getNode("t1_container"). setAttribute("aria-owns", "t1_button t1_subdiv"); } this.finalCheck = function setARIAOwns_finalCheck() { // children are swapped again, button and subdiv are appended to // the children. var tree = { SECTION: [ { CHECKBUTTON: [ ] }, // checkbox, native order { PUSHBUTTON: [ ] }, // button, rearranged by ARIA own { SECTION: [ ] } // subdiv from the subtree, ARIA owned ] }; testAccessibleTree("t1_container", tree); } this.getID = function setARIAOwns_getID() { return "Change @aria-owns attribute"; } } function removeARIAOwns() { this.eventSeq = [ new invokerChecker(EVENT_HIDE, getNode("t1_button")), new invokerChecker(EVENT_HIDE, getNode("t1_subdiv")), new orderChecker(), new asyncInvokerChecker(EVENT_SHOW, getNode("t1_button")), new asyncInvokerChecker(EVENT_SHOW, getNode("t1_subdiv")), new orderChecker(), new invokerChecker(EVENT_REORDER, getNode("t1_container")), new unexpectedInvokerChecker(EVENT_REORDER, getNode("t1_checkbox")) ]; this.invoke = function removeARIAOwns_invoke() { getNode("t1_container").removeAttribute("aria-owns"); } this.finalCheck = function removeARIAOwns_finalCheck() { // children follow the DOM order var tree = { SECTION: [ { PUSHBUTTON: [ ] }, { CHECKBUTTON: [ { SECTION: [] } ] } ] }; testAccessibleTree("t1_container", tree); } this.getID = function removeARIAOwns_getID() { return "Remove @aria-owns attribute"; } } function setARIAOwns() { this.eventSeq = [ new invokerChecker(EVENT_HIDE, getNode("t1_button")), new invokerChecker(EVENT_HIDE, getNode("t1_subdiv")), new invokerChecker(EVENT_SHOW, getNode("t1_button")), new invokerChecker(EVENT_SHOW, getNode("t1_subdiv")), new invokerChecker(EVENT_REORDER, getNode("t1_container")) ]; this.invoke = function setARIAOwns_invoke() { getNode("t1_container"). setAttribute("aria-owns", "t1_button t1_subdiv"); } this.finalCheck = function setARIAOwns_finalCheck() { // children are swapped again, button and subdiv are appended to // the children. var tree = { SECTION: [ { CHECKBUTTON: [ ] }, // checkbox { PUSHBUTTON: [ ] }, // button, rearranged by ARIA own { SECTION: [ ] } // subdiv from the subtree, ARIA owned ] }; testAccessibleTree("t1_container", tree); } this.getID = function setARIAOwns_getID() { return "Set @aria-owns attribute"; } } function addIdToARIAOwns() { this.eventSeq = [ new invokerChecker(EVENT_HIDE, getNode("t1_group")), new invokerChecker(EVENT_SHOW, getNode("t1_group")), new invokerChecker(EVENT_REORDER, document) ]; this.invoke = function addIdToARIAOwns_invoke() { getNode("t1_container"). setAttribute("aria-owns", "t1_button t1_subdiv t1_group"); } this.finalCheck = function addIdToARIAOwns_finalCheck() { // children are swapped again, button and subdiv are appended to // the children. var tree = { SECTION: [ { CHECKBUTTON: [ ] }, // t1_checkbox { PUSHBUTTON: [ ] }, // button, t1_button { SECTION: [ ] }, // subdiv from the subtree, t1_subdiv { GROUPING: [ ] } // group from outside, t1_group ] }; testAccessibleTree("t1_container", tree); } this.getID = function addIdToARIAOwns_getID() { return "Add id to @aria-owns attribute value"; } } function appendEl() { this.eventSeq = [ new invokerChecker(EVENT_SHOW, getNode, "t1_child3"), new invokerChecker(EVENT_REORDER, getNode("t1_container")) ]; this.invoke = function appendEl_invoke() { var div = document.createElement("div"); div.setAttribute("id", "t1_child3"); div.setAttribute("role", "radio") getNode("t1_container").appendChild(div); } this.finalCheck = function appendEl_finalCheck() { // children are invalidated, they includes aria-owns swapped kids and // newly inserted child. var tree = { 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 ] }; testAccessibleTree("t1_container", tree); } this.getID = function appendEl_getID() { return "Append child under @aria-owns element"; } } function removeEl() { this.eventSeq = [ new invokerChecker(EVENT_HIDE, getNode, "t1_checkbox"), new invokerChecker(EVENT_SHOW, getNode, "t1_checkbox"), new invokerChecker(EVENT_REORDER, getNode("t1_container")) ]; this.invoke = function removeEl_invoke() { // remove a container of t1_subdiv getNode("t1_span").parentNode.removeChild(getNode("t1_span")); } this.finalCheck = function removeEl_finalCheck() { // subdiv should go away var tree = { SECTION: [ { CHECKBUTTON: [ ] }, // explicit, t1_checkbox { RADIOBUTTON: [ ] }, // explicit, t1_child3 { PUSHBUTTON: [ ] }, // ARIA owned, t1_button { GROUPING: [ ] } // ARIA owned, t1_group ] }; testAccessibleTree("t1_container", tree); } this.getID = function removeEl_getID() { return "Remove a container of ARIA owned element"; } } function removeId() { this.eventSeq = [ new invokerChecker(EVENT_HIDE, getNode("t1_group")), new invokerChecker(EVENT_SHOW, getNode("t1_group")), new invokerChecker(EVENT_REORDER, document) ]; this.invoke = function removeId_invoke() { getNode("t1_group").removeAttribute("id"); } this.finalCheck = function removeId_finalCheck() { var tree = { SECTION: [ { CHECKBUTTON: [ ] }, { RADIOBUTTON: [ ] }, { PUSHBUTTON: [ ] } // ARIA owned, t1_button ] }; testAccessibleTree("t1_container", tree); } this.getID = function removeId_getID() { return "Remove ID from ARIA owned element"; } } function setId() { this.eventSeq = [ new invokerChecker(EVENT_HIDE, getNode("t1_grouptmp")), new invokerChecker(EVENT_SHOW, getNode("t1_grouptmp")), new invokerChecker(EVENT_REORDER, document) ]; this.invoke = function setId_invoke() { getNode("t1_grouptmp").setAttribute("id", "t1_group"); } this.finalCheck = function setId_finalCheck() { var tree = { SECTION: [ { CHECKBUTTON: [ ] }, { RADIOBUTTON: [ ] }, { PUSHBUTTON: [ ] }, // ARIA owned, t1_button { GROUPING: [ ] } // ARIA owned, t1_group, previously t1_grouptmp ] }; testAccessibleTree("t1_container", tree); } this.getID = function setId_getID() { return "Set ID that is referred by ARIA owns"; } } /** * Remove an accessible DOM element containing an element referred by * ARIA owns. */ function removeA11eteiner() { this.eventSeq = [ new invokerChecker(EVENT_REORDER, getNode("t2_container1")) ]; this.invoke = function removeA11eteiner_invoke() { var tree = { SECTION: [ { CHECKBUTTON: [ ] } // ARIA owned, 't2_owned' ] }; testAccessibleTree("t2_container1", tree); getNode("t2_container2").removeChild(getNode("t2_container3")); } this.finalCheck = function removeA11eteiner_finalCheck() { var tree = { SECTION: [ ] }; testAccessibleTree("t2_container1", tree); } this.getID = function removeA11eteiner_getID() { return "Remove an accessible DOM element containing an element referred by ARIA owns"; } } /** * Steal an element from other ARIA owns element. This use case guarantees * that result of setAttribute/removeAttribute doesn't depend on their order. */ function stealFromOtherARIAOwns() { this.eventSeq = [ new invokerChecker(EVENT_REORDER, getNode("t3_container2")) ]; this.invoke = function stealFromOtherARIAOwns_invoke() { getNode("t3_container2").setAttribute("aria-owns", "t3_child"); } this.finalCheck = function stealFromOtherARIAOwns_finalCheck() { var tree = { SECTION: [ ] }; testAccessibleTree("t3_container1", tree); tree = { SECTION: [ { CHECKBUTTON: [ ] } ] }; testAccessibleTree("t3_container2", tree); } this.getID = function stealFromOtherARIAOwns_getID() { return "Steal an element from other ARIA owns element"; } } function appendElToRecacheChildren() { this.eventSeq = [ new invokerChecker(EVENT_REORDER, getNode("t3_container2")) ]; this.invoke = function appendElToRecacheChildren_invoke() { var div = document.createElement("div"); div.setAttribute("role", "radio") getNode("t3_container2").appendChild(div); } this.finalCheck = function appendElToRecacheChildren_finalCheck() { var tree = { SECTION: [ ] }; testAccessibleTree("t3_container1", tree); tree = { SECTION: [ { RADIOBUTTON: [ ] }, { CHECKBUTTON: [ ] } // ARIA owned ] }; testAccessibleTree("t3_container2", tree); } this.getID = function appendElToRecacheChildren_getID() { return "Append a child under @aria-owns element to trigger children recache"; } } function showHiddenElement() { this.eventSeq = [ new invokerChecker(EVENT_REORDER, getNode("t4_container1")) ]; this.invoke = function showHiddenElement_invoke() { var tree = { SECTION: [ { RADIOBUTTON: [] } ] }; testAccessibleTree("t4_container1", tree); getNode("t4_child1").style.display = "block"; } this.finalCheck = function showHiddenElement_finalCheck() { var tree = { SECTION: [ { CHECKBUTTON: [] }, { RADIOBUTTON: [] } ] }; testAccessibleTree("t4_container1", tree); } this.getID = function showHiddenElement_getID() { return "Show hidden ARIA owns referred element"; } } function rearrangeARIAOwns(aContainer, aAttr, aIdList, aRoleList) { this.eventSeq = []; for (var id of aIdList) { this.eventSeq.push(new invokerChecker(EVENT_HIDE, getNode(id))); } for (var id of aIdList) { this.eventSeq.push(new invokerChecker(EVENT_SHOW, getNode(id))); } this.eventSeq.push(new invokerChecker(EVENT_REORDER, getNode(aContainer))); this.invoke = function rearrangeARIAOwns_invoke() { getNode(aContainer).setAttribute("aria-owns", aAttr); } this.finalCheck = function rearrangeARIAOwns_finalCheck() { var tree = { SECTION: [ ] }; for (var role of aRoleList) { var ch = {}; ch[role] = []; tree["SECTION"].push(ch); } testAccessibleTree(aContainer, tree); } this.getID = function rearrangeARIAOwns_getID() { return `Rearrange @aria-owns attribute to '${aAttr}'`; } } function removeNotARIAOwnedEl(aContainer, aChild) { this.eventSeq = [ new invokerChecker(EVENT_REORDER, aContainer) ]; this.invoke = function removeNotARIAOwnedEl_invoke() { var tree = { SECTION: [ { TEXT_LEAF: [ ] }, { GROUPING: [ ] } ] }; testAccessibleTree(aContainer, tree); getNode(aContainer).removeChild(getNode(aChild)); } this.finalCheck = function removeNotARIAOwnedEl_finalCheck() { var tree = { SECTION: [ { GROUPING: [ ] } ] }; testAccessibleTree(aContainer, tree); } this.getID = function removeNotARIAOwnedEl_getID() { return `remove not ARIA owned child`; } } function setARIAOwnsOnElToRemove(aParent, aChild) { this.eventSeq = [ new invokerChecker(EVENT_HIDE, getAccessible(aParent)) ]; this.invoke = function setARIAOwnsOnElToRemove_invoke() { getNode(aChild).setAttribute("aria-owns", "no_id"); getNode(aParent).removeChild(getNode(aChild)); getNode(aParent).parentNode.removeChild(getNode(aParent)); } this.getID = function setARIAOwnsOnElToRemove_getID() { return `set ARIA owns on an element, and then remove it, and then remove its parent`; } } /** * Set ARIA owns on inaccessible span element that contains * accessible children. This will move children from the container for * the span. */ function test8() { this.eventSeq = [ new invokerChecker(EVENT_REORDER, "t8_container") ]; this.invoke = function test8_invoke() { var tree = { SECTION: [ { PUSHBUTTON: [] }, { ENTRY: [] }, { ENTRY: [] }, { ENTRY: [] } ] }; testAccessibleTree("t8_container", tree); getNode(t8_container).setAttribute("aria-owns", "t8_span t8_button"); } this.finalCheck = function test8_finalCheck() { var tree = { SECTION: [ { TEXT: [ { ENTRY: [] }, { ENTRY: [] }, { ENTRY: [] } ] }, { PUSHBUTTON: [] } ] }; testAccessibleTree("t8_container", tree); } this.getID = function test8_getID() { return `Set ARIA owns on inaccessible span element that contains accessible children`; } } //////////////////////////////////////////////////////////////////////////// // Test //////////////////////////////////////////////////////////////////////////// //gA11yEventDumpToConsole = true; //enableLogging("tree,eventTree,verbose"); // debug stuff var gQueue = null; function doTest() { gQueue = new eventQueue(); // test1 gQueue.push(new changeARIAOwns()); gQueue.push(new removeARIAOwns()); gQueue.push(new setARIAOwns()); gQueue.push(new addIdToARIAOwns()); gQueue.push(new appendEl()); gQueue.push(new removeEl()); gQueue.push(new removeId()); gQueue.push(new setId()); // test2 gQueue.push(new removeA11eteiner()); // test3 gQueue.push(new stealFromOtherARIAOwns()); gQueue.push(new appendElToRecacheChildren()); // test4 gQueue.push(new showHiddenElement()); // test5 gQueue.push(new rearrangeARIAOwns( "t5_container", "t5_checkbox t5_radio t5_button", [ "t5_checkbox", "t5_radio", "t5_button" ], [ "CHECKBUTTON", "RADIOBUTTON", "PUSHBUTTON" ])); gQueue.push(new rearrangeARIAOwns( "t5_container", "t5_radio t5_button t5_checkbox", [ "t5_radio", "t5_button" ], [ "RADIOBUTTON", "PUSHBUTTON", "CHECKBUTTON" ])); gQueue.push(new removeNotARIAOwnedEl("t6_container", "t6_span")); gQueue.push(new setARIAOwnsOnElToRemove("t7_parent", "t7_child")); gQueue.push(new test8()); gQueue.invoke(); // SimpleTest.finish() will be called in the end } 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> <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> </body> </html>