<html> <head> <title>Accessible mutation events coalescence 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="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> <script type="application/javascript" src="../common.js"></script> <script type="application/javascript" src="../states.js"></script> <script type="application/javascript" src="../events.js"></script> <script type="application/javascript"> //////////////////////////////////////////////////////////////////////////// // Invoker base classes const kRemoveElm = 1; const kHideElm = 2; const kAddElm = 3; const kShowElm = 4; /** * Base class to test of mutation events coalescence. */ function coalescenceBase(aChildAction, aParentAction, aPerformActionOnChildInTheFirstPlace) { // Invoker interface this.invoke = function coalescenceBase_invoke() { if (aPerformActionOnChildInTheFirstPlace) { this.invokeAction(this.childNode, aChildAction); this.invokeAction(this.parentNode, aParentAction); } else { this.invokeAction(this.parentNode, aParentAction); this.invokeAction(this.childNode, aChildAction); } } this.getID = function coalescenceBase_getID() { var childAction = this.getActionName(aChildAction) + " child"; var parentAction = this.getActionName(aParentAction) + " parent"; if (aPerformActionOnChildInTheFirstPlace) return childAction + " and then " + parentAction; return parentAction + " and then " + childAction; } this.finalCheck = function coalescenceBase_check() { if (this.getEventType(aChildAction) == EVENT_HIDE) { testIsDefunct(this.child); } if (this.getEventType(aParentAction) == EVENT_HIDE) { testIsDefunct(this.parent); } } // Implementation details this.invokeAction = function coalescenceBase_invokeAction(aNode, aAction) { switch (aAction) { case kRemoveElm: aNode.parentNode.removeChild(aNode); break; case kHideElm: aNode.style.display = "none"; break; case kAddElm: if (aNode == this.parentNode) this.hostNode.appendChild(this.parentNode); else this.parentNode.appendChild(this.childNode); break; case kShowElm: aNode.style.display = "block"; break; default: return INVOKER_ACTION_FAILED; } } this.getEventType = function coalescenceBase_getEventType(aAction) { switch (aAction) { case kRemoveElm: case kHideElm: return EVENT_HIDE; case kAddElm: case kShowElm: return EVENT_SHOW; } } this.getActionName = function coalescenceBase_getActionName(aAction) { switch (aAction) { case kRemoveElm: return "remove"; case kHideElm: return "hide"; case kAddElm: return "add"; case kShowElm: return "show"; default: return "??"; } } this.initSequence = function coalescenceBase_initSequence() { // expected events var eventType = this.getEventType(aParentAction); this.eventSeq = [ new invokerChecker(eventType, this.parentNode), new invokerChecker(EVENT_REORDER, this.hostNode) ]; // unexpected events this.unexpectedEventSeq = [ new invokerChecker(this.getEventType(aChildAction), this.childNode), new invokerChecker(EVENT_REORDER, this.parentNode) ]; } } /** * Remove or hide mutation events coalescence testing. */ function removeOrHideCoalescenceBase(aChildID, aParentID, aChildAction, aParentAction, aPerformActionOnChildInTheFirstPlace) { this.__proto__ = new coalescenceBase(aChildAction, aParentAction, aPerformActionOnChildInTheFirstPlace); this.init = function removeOrHideCoalescenceBase_init() { this.childNode = getNode(aChildID); this.parentNode = getNode(aParentID); this.child = getAccessible(this.childNode); this.parent = getAccessible(this.parentNode); this.hostNode = this.parentNode.parentNode; } // Initalization this.init(); this.initSequence(); } //////////////////////////////////////////////////////////////////////////// // Invokers /** * Remove child node and then its parent node from DOM tree. */ function removeChildNParent(aChildID, aParentID) { this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID, kRemoveElm, kRemoveElm, true); } /** * Remove parent node and then its child node from DOM tree. */ function removeParentNChild(aChildID, aParentID) { this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID, kRemoveElm, kRemoveElm, false); } /** * Hide child node and then its parent node. */ function hideChildNParent(aChildID, aParentID) { this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID, kHideElm, kHideElm, true); } /** * Hide parent node and then its child node. */ function hideParentNChild(aChildID, aParentID) { this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID, kHideElm, kHideElm, false); } /** * Hide child node and then remove its parent node. */ function hideChildNRemoveParent(aChildID, aParentID) { this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID, kHideElm, kRemoveElm, true); } /** * Hide parent node and then remove its child node. */ function hideParentNRemoveChild(aChildID, aParentID) { this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID, kRemoveElm, kHideElm, false); } /** * Remove child node and then hide its parent node. */ function removeChildNHideParent(aChildID, aParentID) { this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID, kRemoveElm, kHideElm, true); } /** * Remove parent node and then hide its child node. */ function removeParentNHideChild(aChildID, aParentID) { this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID, kHideElm, kRemoveElm, false); } /** * Create and append parent node and create and append child node to it. */ function addParentNChild(aHostID, aPerformActionOnChildInTheFirstPlace) { this.init = function addParentNChild_init() { this.hostNode = getNode(aHostID); this.parentNode = document.createElement("select"); this.childNode = document.createElement("option"); this.childNode.textContent = "testing"; } this.__proto__ = new coalescenceBase(kAddElm, kAddElm, aPerformActionOnChildInTheFirstPlace); this.init(); this.initSequence(); } /** * Show parent node and show child node to it. */ function showParentNChild(aParentID, aChildID, aPerformActionOnChildInTheFirstPlace) { this.init = function showParentNChild_init() { this.parentNode = getNode(aParentID); this.hostNode = this.parentNode.parentNode; this.childNode = getNode(aChildID); } this.__proto__ = new coalescenceBase(kShowElm, kShowElm, aPerformActionOnChildInTheFirstPlace); this.init(); this.initSequence(); } /** * Create and append child node to the DOM and then show parent node. */ function showParentNAddChild(aParentID, aPerformActionOnChildInTheFirstPlace) { this.init = function showParentNAddChild_init() { this.parentNode = getNode(aParentID); this.hostNode = this.parentNode.parentNode; this.childNode = document.createElement("option"); this.childNode.textContent = "testing"; } this.__proto__ = new coalescenceBase(kAddElm, kShowElm, aPerformActionOnChildInTheFirstPlace); this.init(); this.initSequence(); } /** * Remove children and parent */ function removeGrandChildrenNHideParent(aChild1Id, aChild2Id, aParentId) { this.child1 = getNode(aChild1Id); this.child2 = getNode(aChild2Id); this.parent = getNode(aParentId); this.eventSeq = [ new invokerChecker(EVENT_HIDE, getAccessible(aParentId)), new invokerChecker(EVENT_REORDER, getNode(aParentId).parentNode), new unexpectedInvokerChecker(EVENT_HIDE, getAccessible(aChild1Id)), new unexpectedInvokerChecker(EVENT_HIDE, getAccessible(aChild2Id)), new unexpectedInvokerChecker(EVENT_REORDER, getAccessible(aParentId)) ]; this.invoke = function removeGrandChildrenNHideParent_invoke() { this.child1.parentNode.removeChild(this.child1); this.child2.parentNode.removeChild(this.child2); this.parent.hidden = true; } this.getID = function removeGrandChildrenNHideParent_getID() { return "remove grand children of different parents and then hide their grand parent"; } } /** * Remove a child, and then its parent. */ function test3() { this.o = getAccessible("t3_o"); this.ofc = getAccessible("t3_o").firstChild; this.eventSeq = [ new invokerChecker(EVENT_HIDE, this.o), new invokerChecker(EVENT_REORDER, "t3_lb"), new unexpectedInvokerChecker(EVENT_HIDE, this.ofc), new unexpectedInvokerChecker(EVENT_REORDER, this.o) ]; this.invoke = function test3_invoke() { getNode("t3_o").textContent = ""; getNode("t3_lb").removeChild(getNode("t3_o")); } this.finalCheck = function test3_finalCheck() { testIsDefunct(this.o); testIsDefunct(this.ofc); } this.getID = function test3_getID() { return "remove a child, and then its parent"; } } /** * Remove children, and then a parent of 2nd child. */ function test4() { this.o1 = getAccessible("t4_o1"); this.o1fc = this.o1.firstChild; this.o2 = getAccessible("t4_o2"); this.o2fc = this.o2.firstChild; this.eventSeq = [ new invokerChecker(EVENT_HIDE, this.o1fc), new invokerChecker(EVENT_HIDE, this.o2), new invokerChecker(EVENT_REORDER, "t4_lb"), new unexpectedInvokerChecker(EVENT_HIDE, this.o2fc), new unexpectedInvokerChecker(EVENT_REORDER, this.o1), new unexpectedInvokerChecker(EVENT_REORDER, this.o2) ]; this.invoke = function test4_invoke() { getNode("t4_o1").textContent = ""; getNode("t4_o2").textContent = ""; getNode("t4_lb").removeChild(getNode("t4_o2")); } this.finalCheck = function test4_finalCheck() { testIsDefunct(this.o1fc); testIsDefunct(this.o2); testIsDefunct(this.o2fc); } this.getID = function test4_getID() { return "remove children, and then a parent of 2nd child"; } } /** * Remove a child, remove a parent sibling, remove the parent */ function test5() { this.o = getAccessible("t5_o"); this.ofc = this.o.firstChild; this.b = getAccessible("t5_b"); this.lb = getAccessible("t5_lb"); this.eventSeq = [ new invokerChecker(EVENT_HIDE, this.b), new invokerChecker(EVENT_HIDE, this.o), new invokerChecker(EVENT_REORDER, "t5"), new unexpectedInvokerChecker(EVENT_HIDE, this.ofc), new unexpectedInvokerChecker(EVENT_REORDER, this.o), new unexpectedInvokerChecker(EVENT_REORDER, this.lb) ]; this.invoke = function test5_invoke() { getNode("t5_o").textContent = ""; getNode("t5").removeChild(getNode("t5_b")); getNode("t5_lb").removeChild(getNode("t5_o")); } this.finalCheck = function test5_finalCheck() { testIsDefunct(this.ofc); testIsDefunct(this.o); testIsDefunct(this.b); } this.getID = function test5_getID() { return "remove a child, remove a parent sibling, remove the parent"; } } /** * Insert accessibles with a child node moved by aria-owns * Markup: * <div id="t6_fc"> * <div id="t6_owns"></div> * </div> * <div id="t6_sc" aria-owns="t6_owns"></div> */ function test6() { this.parent = getNode("t6"); this.fc = document.createElement("div"); this.fc.setAttribute("id", "t6_fc"); this.owns = document.createElement("div"); this.owns.setAttribute("id", "t6_owns"); this.sc = document.createElement("div"); this.sc.setAttribute("id", "t6_sc"); this.eventSeq = [ new invokerChecker(EVENT_SHOW, this.fc), new invokerChecker(EVENT_SHOW, this.sc), new invokerChecker(EVENT_REORDER, this.parent), new unexpectedInvokerChecker(EVENT_REORDER, this.fc), new unexpectedInvokerChecker(EVENT_REORDER, this.sc), new unexpectedInvokerChecker(EVENT_HIDE, this.owns), new unexpectedInvokerChecker(EVENT_SHOW, this.owns) ]; this.invoke = function test6_invoke() { getNode("t6").appendChild(this.fc); getNode("t6_fc").appendChild(this.owns); getNode("t6").appendChild(this.sc); getNode("t6_sc").setAttribute("aria-owns", "t6_owns"); }; this.getID = function test6_getID() { return "Insert accessibles with a child node moved by aria-owns"; }; } /** * Insert text nodes under direct and grand children, and then hide * their container by means of aria-owns. * * Markup: * <div id="t7_moveplace" aria-owns="t7_c"></div> * <div id="t7_c"> * <div id="t7_c_directchild">ha</div> * <div><div id="t7_c_grandchild">ha</div></div> * </div> */ function test7() { this.eventSeq = [ new invokerChecker(EVENT_HIDE, getNode('t7_c')), new invokerChecker(EVENT_SHOW, getNode('t7_c')), new invokerChecker(EVENT_REORDER, getNode('t7')), new unexpectedInvokerChecker(EVENT_REORDER, getNode('t7_c_directchild')), new unexpectedInvokerChecker(EVENT_REORDER, getNode('t7_c_grandchild')), new unexpectedInvokerChecker(EVENT_SHOW, () => getNode('t7_c_directchild').firstChild), new unexpectedInvokerChecker(EVENT_SHOW, () => getNode('t7_c_grandchild').firstChild) ]; this.invoke = function test7_invoke() { getNode('t7_c_directchild').textContent = 'ha'; getNode('t7_c_grandchild').textContent = 'ha'; getNode('t7_moveplace').setAttribute('aria-owns', 't7_c'); }; this.getID = function test7_getID() { return "Show child accessibles and then hide their container"; }; } /** * Move a node by aria-owns from right to left in the tree, so that * the eventing looks this way: * reorder for 't8_c1' * hide for 't8_c1_child' * show for 't8_c2_moved' * reorder for 't8_c2' * hide for 't8_c2_moved' * * The hide event should be delivered before the paired show event. */ function test8() { this.eventSeq = [ new invokerChecker(EVENT_HIDE, getNode('t8_c1_child')), new invokerChecker(EVENT_HIDE, 't8_c2_moved'), new invokerChecker(EVENT_SHOW, 't8_c2_moved'), new invokerChecker(EVENT_REORDER, 't8_c2'), new invokerChecker(EVENT_REORDER, 't8_c1'), ]; this.invoke = function test8_invoke() { // Remove a node from 't8_c1' container to give the event tree a // desired structure (the 't8_c1' container node goes first in the event // tree) getNode('t8_c1_child').remove(); // then move 't8_c2_moved' from 't8_c2' to 't8_c1'. getNode('t8_c1').setAttribute('aria-owns', 't8_c2_moved'); }; this.getID = function test8_getID() { return "Move a node by aria-owns to left within the tree"; }; } /** * Move 't9_c3_moved' node under 't9_c2_moved', and then move 't9_c2_moved' * node by aria-owns (same as test10 but has different aria-owns * ordering), the eventing looks same way as in test10: * reorder for 't9_c1' * hide for 't9_c1_child' * show for 't9_c2_moved' * reorder for 't9_c2' * hide for 't9_c2_child' * hide for 't9_c2_moved' * reorder for 't9_c3' * hide for 't9_c3_moved' * * The hide events for 't9_c2_moved' and 't9_c3_moved' should be delivered * before the show event for 't9_c2_moved'. */ function test9() { this.eventSeq = [ new invokerChecker(EVENT_HIDE, getNode('t9_c1_child')), new invokerChecker(EVENT_HIDE, getNode('t9_c2_child')), new invokerChecker(EVENT_HIDE, 't9_c3_moved'), new invokerChecker(EVENT_HIDE, 't9_c2_moved'), new invokerChecker(EVENT_SHOW, 't9_c2_moved'), new invokerChecker(EVENT_REORDER, 't9_c3'), new invokerChecker(EVENT_REORDER, 't9_c2'), new invokerChecker(EVENT_REORDER, 't9_c1'), new unexpectedInvokerChecker(EVENT_SHOW, 't9_c3_moved') ]; this.invoke = function test9_invoke() { // Remove child nodes from 't9_c1' and 't9_c2' containers to give // the event tree a needed structure ('t9_c1' and 't9_c2' nodes go // first in the event tree), getNode('t9_c1_child').remove(); getNode('t9_c2_child').remove(); // then do aria-owns magic. getNode('t9_c2_moved').setAttribute('aria-owns', 't9_c3_moved'); getNode('t9_c1').setAttribute('aria-owns', 't9_c2_moved'); }; this.getID = function test9_getID() { return "Move node #1 by aria-owns and then move node #2 into node #1"; }; } /** * Move a node 't10_c3_moved' by aria-owns under a node 't10_c2_moved', * moved by under 't10_1', so that the eventing looks this way: * reorder for 't10_c1' * hide for 't10_c1_child' * show for 't10_c2_moved' * reorder for 't10_c2' * hide for 't10_c2_child' * hide for 't10_c2_moved' * reorder for 't10_c3' * hide for 't10_c3_moved' * * The hide events for 't10_c2_moved' and 't10_c3_moved' should be delivered * before the show event for 't10_c2_moved'. */ function test10() { this.eventSeq = [ new invokerChecker(EVENT_HIDE, getNode('t10_c1_child')), new invokerChecker(EVENT_HIDE, getNode('t10_c2_child')), new invokerChecker(EVENT_HIDE, getNode('t10_c2_moved')), new invokerChecker(EVENT_HIDE, getNode('t10_c3_moved')), new invokerChecker(EVENT_SHOW, getNode('t10_c2_moved')), new invokerChecker(EVENT_REORDER, 't10_c2'), new invokerChecker(EVENT_REORDER, 't10_c1'), new invokerChecker(EVENT_REORDER, 't10_c3') ]; this.invoke = function test10_invoke() { // Remove child nodes from 't10_c1' and 't10_c2' containers to give // the event tree a needed structure ('t10_c1' and 't10_c2' nodes go first // in the event tree), getNode('t10_c1_child').remove(); getNode('t10_c2_child').remove(); // then do aria-owns stuff. getNode('t10_c1').setAttribute('aria-owns', 't10_c2_moved'); getNode('t10_c2_moved').setAttribute('aria-owns', 't10_c3_moved'); }; this.getID = function test10_getID() { return "Move a node by aria-owns into a node moved by aria-owns to left within the tree"; }; } /** * Move a node by aria-owns from right to left in the tree, and then * move its parent too by aria-owns. No hide event should be fired for * original node. */ function test11() { this.eventSeq = [ new invokerChecker(EVENT_HIDE, getNode('t11_c1_child')), new invokerChecker(EVENT_HIDE, getNode('t11_c2')), new orderChecker(), new asyncInvokerChecker(EVENT_SHOW, 't11_c2_child'), new asyncInvokerChecker(EVENT_SHOW, 't11_c2'), new orderChecker(), new invokerChecker(EVENT_REORDER, 't11'), new unexpectedInvokerChecker(EVENT_HIDE, 't11_c2_child'), new unexpectedInvokerChecker(EVENT_REORDER, 't11_c1'), new unexpectedInvokerChecker(EVENT_REORDER, 't11_c2'), new unexpectedInvokerChecker(EVENT_REORDER, 't11_c3') ]; this.invoke = function test11_invoke() { // Remove a node from 't11_c1' container to give the event tree a // desired structure (the 't11_c1' container node goes first in // the event tree), getNode('t11_c1_child').remove(); // then move 't11_c2_moved' from 't11_c2' to 't11_c1', and then move // 't11_c2' to 't11_c3'. getNode('t11_c1').setAttribute('aria-owns', 't11_c2_child'); getNode('t11_c3').setAttribute('aria-owns', 't11_c2'); }; this.getID = function test11_getID() { return "Move a node by aria-owns to left within the tree"; }; } //////////////////////////////////////////////////////////////////////////// // Do tests. gA11yEventDumpToConsole = true; // debug stuff //enableLogging("eventTree"); var gQueue = null; function doTests() { gQueue = new eventQueue(); gQueue.push(new removeChildNParent("option1", "select1")); gQueue.push(new removeParentNChild("option2", "select2")); gQueue.push(new hideChildNParent("option3", "select3")); gQueue.push(new hideParentNChild("option4", "select4")); gQueue.push(new hideChildNRemoveParent("option5", "select5")); gQueue.push(new hideParentNRemoveChild("option6", "select6")); gQueue.push(new removeChildNHideParent("option7", "select7")); gQueue.push(new removeParentNHideChild("option8", "select8")); gQueue.push(new addParentNChild("testContainer", false)); gQueue.push(new addParentNChild("testContainer", true)); gQueue.push(new showParentNChild("select9", "option9", false)); gQueue.push(new showParentNChild("select10", "option10", true)); gQueue.push(new showParentNAddChild("select11", false)); gQueue.push(new showParentNAddChild("select12", true)); gQueue.push(new removeGrandChildrenNHideParent("t1_child1", "t1_child2", "t1_parent")); gQueue.push(new test3()); gQueue.push(new test4()); gQueue.push(new test5()); gQueue.push(new test6()); gQueue.push(new test7()); gQueue.push(new test8()); gQueue.push(new test9()); gQueue.push(new test10()); gQueue.push(new test11()); gQueue.invoke(); // Will call SimpleTest.finish(); } SimpleTest.waitForExplicitFinish(); addA11yLoadEvent(doTests); </script> </head> <body> <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=513213" title="coalesce events when new event is appended to the queue"> Mozilla Bug 513213 </a><br> <a target="_blank" title="Rework accessible tree update code" href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275"> Mozilla Bug 570275 </a> <p id="display"></p> <div id="content" style="display: none"></div> <pre id="test"> </pre> <div id="testContainer"> <select id="select1"> <option id="option1">option</option> </select> <select id="select2"> <option id="option2">option</option> </select> <select id="select3"> <option id="option3">option</option> </select> <select id="select4"> <option id="option4">option</option> </select> <select id="select5"> <option id="option5">option</option> </select> <select id="select6"> <option id="option6">option</option> </select> <select id="select7"> <option id="option7">option</option> </select> <select id="select8"> <option id="option8">option</option> </select> <select id="select9" style="display: none"> <option id="option9" style="display: none">testing</option> </select> <select id="select10" style="display: none"> <option id="option10" style="display: none">testing</option> </select> <select id="select11" style="display: none"></select> <select id="select12" style="display: none"></select> </div> <div id="testContainer2"> <div id="t1_parent"> <div id="t1_mid1"><div id="t1_child1"></div></div> <div id="t1_mid2"><div id="t1_child2"></div></div> </div> </div> <div id="t3"> <div role="listbox" id="t3_lb"> <div role="option" id="t3_o">opt</div> </div> </div> <div id="t4"> <div role="listbox" id="t4_lb"> <div role="option" id="t4_o1">opt1</div> <div role="option" id="t4_o2">opt2</div> </div> </div> <div id="t5"> <div role="button" id="t5_b">btn</div> <div role="listbox" id="t5_lb"> <div role="option" id="t5_o">opt</div> </div> </div> <div id="t6"> </div> <div id="t7"> <div id="t7_moveplace"></div> <div id="t7_c"> <div><div id="t7_c_grandchild"></div></div> <div id="t7_c_directchild"></div> </div> </div> <div id="t8"> <div id="t8_c1"><div id="t8_c1_child"></div></div> <div id="t8_c2"> <div id="t8_c2_moved"></div> </div> </div> <div id="t9"> <div id="t9_c1"><div id="t9_c1_child"></div></div> <div id="t9_c2"> <div id="t9_c2_child"></div> <div id="t9_c2_moved"></div> </div> <div id="t9_c3"> <div id="t9_c3_moved"></div> </div> </div> <div id="t10"> <div id="t10_c1"><div id="t10_c1_child"></div></div> <div id="t10_c2"> <div id="t10_c2_child"></div> <div id="t10_c2_moved"></div> </div> <div id="t10_c3"> <div id="t10_c3_moved"></div> </div> </div> <div id="t11"> <div id="t11_c1"><div id="t11_c1_child"></div></div> <div id="t11_c2"><div id="t11_c2_child"></div></div> <div id="t11_c3"></div> </div> </body> </html>