test_mutation.html (20148B)
1 <html> 2 3 <head> 4 <title>Accessible mutation events testing</title> 5 6 <link rel="stylesheet" type="text/css" 7 href="chrome://mochikit/content/tests/SimpleTest/test.css" /> 8 9 <style> 10 div.displayNone a { display:none; } 11 div.visibilityHidden a { visibility:hidden; } 12 </style> 13 14 <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> 15 <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> 16 17 <script type="application/javascript" 18 src="../common.js"></script> 19 <script type="application/javascript" 20 src="../events.js"></script> 21 22 <script type="application/javascript"> 23 /** 24 * Invokers. 25 */ 26 var kNoEvents = 0; 27 28 var kShowEvent = 1; 29 var kHideEvent = 2; 30 var kReorderEvent = 4; 31 var kShowEvents = kShowEvent | kReorderEvent; 32 var kHideEvents = kHideEvent | kReorderEvent; 33 var kHideAndShowEvents = kHideEvents | kShowEvent; 34 35 /** 36 * Base class to test mutation a11y events. 37 * 38 * @param aNodeOrID [in] node invoker's action is executed for 39 * @param aEventTypes [in] events to register (see constants above) 40 * @param aDoNotExpectEvents [in] boolean indicates if events are expected 41 */ 42 function mutateA11yTree(aNodeOrID, aEventTypes, aDoNotExpectEvents) { 43 // Interface 44 this.DOMNode = getNode(aNodeOrID); 45 this.doNotExpectEvents = aDoNotExpectEvents; 46 this.eventSeq = []; 47 this.unexpectedEventSeq = []; 48 49 /** 50 * Change default target (aNodeOrID) registered for the given event type. 51 */ 52 this.setTarget = function mutateA11yTree_setTarget(aEventType, aTarget) { 53 var type = this.getA11yEventType(aEventType); 54 for (var idx = 0; idx < this.getEventSeq().length; idx++) { 55 if (this.getEventSeq()[idx].type == type) { 56 this.getEventSeq()[idx].target = aTarget; 57 return idx; 58 } 59 } 60 return -1; 61 }; 62 63 /** 64 * Replace the default target currently registered for a given event type 65 * with the nodes in the targets array. 66 */ 67 this.setTargets = function mutateA11yTree_setTargets(aEventType, aTargets) { 68 var targetIdx = this.setTarget(aEventType, aTargets[0]); 69 70 var type = this.getA11yEventType(aEventType); 71 for (var i = 1; i < aTargets.length; i++) { 72 let checker = new invokerChecker(type, aTargets[i]); 73 this.getEventSeq().splice(++targetIdx, 0, checker); 74 } 75 }; 76 77 // Implementation 78 this.getA11yEventType = function mutateA11yTree_getA11yEventType(aEventType) { 79 if (aEventType == kReorderEvent) 80 return nsIAccessibleEvent.EVENT_REORDER; 81 82 if (aEventType == kHideEvent) 83 return nsIAccessibleEvent.EVENT_HIDE; 84 85 if (aEventType == kShowEvent) 86 return nsIAccessibleEvent.EVENT_SHOW; 87 88 return 0; 89 }; 90 91 this.getEventSeq = function mutateA11yTree_getEventSeq() { 92 return this.doNotExpectEvents ? this.unexpectedEventSeq : this.eventSeq; 93 }; 94 95 if (aEventTypes & kHideEvent) { 96 let checker = new invokerChecker(this.getA11yEventType(kHideEvent), 97 this.DOMNode); 98 this.getEventSeq().push(checker); 99 } 100 101 if (aEventTypes & kShowEvent) { 102 let checker = new invokerChecker(this.getA11yEventType(kShowEvent), 103 this.DOMNode); 104 this.getEventSeq().push(checker); 105 } 106 107 if (aEventTypes & kReorderEvent) { 108 let checker = new invokerChecker(this.getA11yEventType(kReorderEvent), 109 this.DOMNode.parentNode); 110 this.getEventSeq().push(checker); 111 } 112 } 113 114 /** 115 * Change CSS style for the given node. 116 */ 117 function changeStyle(aNodeOrID, aProp, aValue, aEventTypes) { 118 this.__proto__ = new mutateA11yTree(aNodeOrID, aEventTypes, false); 119 120 this.invoke = function changeStyle_invoke() { 121 this.DOMNode.style[aProp] = aValue; 122 }; 123 124 this.getID = function changeStyle_getID() { 125 return aNodeOrID + " change style " + aProp + " on value " + aValue; 126 }; 127 } 128 129 /** 130 * Change class name for the given node. 131 */ 132 function changeClass(aParentNodeOrID, aNodeOrID, aClassName, aEventTypes) { 133 this.__proto__ = new mutateA11yTree(aNodeOrID, aEventTypes, false); 134 135 this.invoke = function changeClass_invoke() { 136 this.parentDOMNode.className = aClassName; 137 }; 138 139 this.getID = function changeClass_getID() { 140 return aNodeOrID + " change class " + aClassName; 141 }; 142 143 this.parentDOMNode = getNode(aParentNodeOrID); 144 } 145 146 /** 147 * Clone the node and append it to its parent. 148 */ 149 function cloneAndAppendToDOM(aNodeOrID, aEventTypes, 150 aTargetsFunc, aReorderTargetFunc) { 151 var eventTypes = aEventTypes || kShowEvents; 152 var doNotExpectEvents = (aEventTypes == kNoEvents); 153 154 this.__proto__ = new mutateA11yTree(aNodeOrID, eventTypes, 155 doNotExpectEvents); 156 157 this.invoke = function cloneAndAppendToDOM_invoke() { 158 var newElm = this.DOMNode.cloneNode(true); 159 newElm.removeAttribute("id"); 160 161 var targets = aTargetsFunc ? 162 aTargetsFunc(newElm) : [newElm]; 163 this.setTargets(kShowEvent, targets); 164 165 if (aReorderTargetFunc) { 166 var reorderTarget = aReorderTargetFunc(this.DOMNode); 167 this.setTarget(kReorderEvent, reorderTarget); 168 } 169 170 this.DOMNode.parentNode.appendChild(newElm); 171 }; 172 173 this.getID = function cloneAndAppendToDOM_getID() { 174 return aNodeOrID + " clone and append to DOM."; 175 }; 176 } 177 178 /** 179 * Removes the node from DOM. 180 */ 181 function removeFromDOM(aNodeOrID, aEventTypes, 182 aTargetsFunc, aReorderTargetFunc) { 183 var eventTypes = aEventTypes || kHideEvents; 184 var doNotExpectEvents = (aEventTypes == kNoEvents); 185 186 this.__proto__ = new mutateA11yTree(aNodeOrID, eventTypes, 187 doNotExpectEvents); 188 189 this.invoke = function removeFromDOM_invoke() { 190 this.DOMNode.remove(); 191 }; 192 193 this.getID = function removeFromDOM_getID() { 194 return prettyName(aNodeOrID) + " remove from DOM."; 195 }; 196 197 if (aTargetsFunc && (eventTypes & kHideEvent)) 198 this.setTargets(kHideEvent, aTargetsFunc(this.DOMNode)); 199 200 if (aReorderTargetFunc && (eventTypes & kReorderEvent)) 201 this.setTarget(kReorderEvent, aReorderTargetFunc(this.DOMNode)); 202 } 203 204 /** 205 * Clone the node and replace the original node by cloned one. 206 */ 207 function cloneAndReplaceInDOM(aNodeOrID) { 208 this.__proto__ = new mutateA11yTree(aNodeOrID, kHideAndShowEvents, 209 false); 210 211 this.invoke = function cloneAndReplaceInDOM_invoke() { 212 this.DOMNode.parentNode.replaceChild(this.newElm, this.DOMNode); 213 }; 214 215 this.getID = function cloneAndReplaceInDOM_getID() { 216 return aNodeOrID + " clone and replace in DOM."; 217 }; 218 219 this.newElm = this.DOMNode.cloneNode(true); 220 this.newElm.removeAttribute("id"); 221 this.setTarget(kShowEvent, this.newElm); 222 } 223 224 /** 225 * Trigger content insertion (flush layout), removal and insertion of 226 * the same element for the same parent. 227 */ 228 function test1(aContainerID) { 229 this.divNode = document.createElement("div"); 230 this.divNode.setAttribute("id", "div-test1"); 231 this.containerNode = getNode(aContainerID); 232 233 this.eventSeq = [ 234 new invokerChecker(EVENT_SHOW, this.divNode), 235 new invokerChecker(EVENT_REORDER, this.containerNode), 236 ]; 237 238 this.invoke = function test1_invoke() { 239 this.containerNode.appendChild(this.divNode); 240 getComputedStyle(this.divNode, "").color; 241 this.containerNode.removeChild(this.divNode); 242 this.containerNode.appendChild(this.divNode); 243 }; 244 245 this.getID = function test1_getID() { 246 return "fuzzy test #1: content insertion (flush layout), removal and" + 247 "reinsertion"; 248 }; 249 } 250 251 /** 252 * Trigger content insertion (flush layout), removal and insertion of 253 * the same element for the different parents. 254 */ 255 function test2(aContainerID, aTmpContainerID) { 256 this.divNode = document.createElement("div"); 257 this.divNode.setAttribute("id", "div-test2"); 258 this.containerNode = getNode(aContainerID); 259 this.tmpContainerNode = getNode(aTmpContainerID); 260 this.container = getAccessible(this.containerNode); 261 this.tmpContainer = getAccessible(this.tmpContainerNode); 262 263 this.eventSeq = [ 264 new invokerChecker(EVENT_SHOW, this.divNode), 265 new invokerChecker(EVENT_REORDER, this.containerNode), 266 ]; 267 268 this.unexpectedEventSeq = [ 269 new invokerChecker(EVENT_REORDER, this.tmpContainerNode), 270 ]; 271 272 this.invoke = function test2_invoke() { 273 this.tmpContainerNode.appendChild(this.divNode); 274 getComputedStyle(this.divNode, "").color; 275 this.tmpContainerNode.removeChild(this.divNode); 276 this.containerNode.appendChild(this.divNode); 277 }; 278 279 this.getID = function test2_getID() { 280 return "fuzzy test #2: content insertion (flush layout), removal and" + 281 "reinsertion under another container"; 282 }; 283 } 284 285 /** 286 * Content insertion (flush layout) and then removal (nothing was changed). 287 */ 288 function test3(aContainerID) { 289 this.divNode = document.createElement("div"); 290 this.divNode.setAttribute("id", "div-test3"); 291 this.containerNode = getNode(aContainerID); 292 293 this.unexpectedEventSeq = [ 294 new invokerChecker(EVENT_SHOW, this.divNode), 295 new invokerChecker(EVENT_HIDE, this.divNode), 296 new invokerChecker(EVENT_REORDER, this.containerNode), 297 ]; 298 299 this.invoke = function test3_invoke() { 300 this.containerNode.appendChild(this.divNode); 301 getComputedStyle(this.divNode, "").color; 302 this.containerNode.removeChild(this.divNode); 303 }; 304 305 this.getID = function test3_getID() { 306 return "fuzzy test #3: content insertion (flush layout) and removal"; 307 }; 308 } 309 310 function insertReferredElm(aContainerID) { 311 this.containerNode = getNode(aContainerID); 312 313 this.eventSeq = [ 314 new invokerChecker(EVENT_SHOW, function(aNode) { return aNode.firstChild; }, this.containerNode), 315 new invokerChecker(EVENT_SHOW, function(aNode) { return aNode.lastChild; }, this.containerNode), 316 new invokerChecker(EVENT_REORDER, this.containerNode), 317 ]; 318 319 this.invoke = function insertReferredElm_invoke() { 320 let span = document.createElement("span"); 321 span.setAttribute("id", "insertReferredElms_span"); 322 let input = document.createElement("input"); 323 input.setAttribute("aria-labelledby", "insertReferredElms_span"); 324 this.containerNode.appendChild(span); 325 this.containerNode.appendChild(input); 326 }; 327 328 this.getID = function insertReferredElm_getID() { 329 return "insert inaccessible element and then insert referring element to make it accessible"; 330 }; 331 } 332 333 function showHiddenParentOfVisibleChild() { 334 this.eventSeq = [ 335 new invokerChecker(EVENT_HIDE, getNode("c4_child")), 336 new invokerChecker(EVENT_SHOW, getNode("c4_middle")), 337 new invokerChecker(EVENT_REORDER, getNode("c4")), 338 ]; 339 340 this.invoke = function showHiddenParentOfVisibleChild_invoke() { 341 getNode("c4_middle").style.visibility = "visible"; 342 }; 343 344 this.getID = function showHiddenParentOfVisibleChild_getID() { 345 return "show hidden parent of visible child"; 346 }; 347 } 348 349 function hideNDestroyDoc() { 350 this.txt = null; 351 this.eventSeq = [ 352 new invokerChecker(EVENT_HIDE, () => { return this.txt; }), 353 ]; 354 355 this.invoke = function hideNDestroyDoc_invoke() { 356 this.txt = getAccessible("c5").firstChild.firstChild; 357 this.txt.DOMNode.remove(); 358 }; 359 360 this.check = function hideNDestroyDoc_check() { 361 getNode("c5").remove(); 362 }; 363 364 this.getID = function hideNDestroyDoc_getID() { 365 return "remove text node and destroy a document on hide event"; 366 }; 367 } 368 369 function hideHideNDestroyDoc() { 370 this.target = null; 371 this.eventSeq = [ 372 new invokerChecker(EVENT_HIDE, () => { return this.target; }), 373 ]; 374 375 this.invoke = function hideHideNDestroyDoc_invoke() { 376 var doc = getAccessible("c6").firstChild; 377 var l1 = doc.firstChild; 378 this.target = l1.firstChild; 379 var l2 = doc.lastChild; 380 l1.DOMNode.firstChild.remove(); 381 l2.DOMNode.firstChild.remove(); 382 }; 383 384 this.check = function hideHideNDestroyDoc_check() { 385 getNode("c6").remove(); 386 }; 387 388 this.getID = function hideHideNDestroyDoc_getID() { 389 return "remove text nodes (2 events in the queue) and destroy a document on first hide event"; 390 }; 391 } 392 393 /** 394 * Target getters. 395 */ 396 function getFirstChild(aNode) { 397 return [aNode.firstChild]; 398 } 399 function getLastChild(aNode) { 400 return [aNode.lastChild]; 401 } 402 403 function getNEnsureFirstChild(aNode) { 404 var node = aNode.firstChild; 405 getAccessible(node); 406 return [node]; 407 } 408 409 function getNEnsureChildren(aNode) { 410 var children = []; 411 var node = aNode.firstChild; 412 do { 413 children.push(node); 414 getAccessible(node); 415 node = node.nextSibling; 416 } while (node); 417 418 return children; 419 } 420 421 function getParent(aNode) { 422 return aNode.parentNode; 423 } 424 425 // gA11yEventDumpToConsole = true; // debug stuff 426 // enableLogging("events,verbose"); 427 428 /** 429 * Do tests. 430 */ 431 var gQueue = null; 432 433 function doTests() { 434 gQueue = new eventQueue(); 435 436 // Show/hide events by changing of display style of accessible DOM node 437 // from 'inline' to 'none', 'none' to 'inline'. 438 let id = "link1"; 439 getAccessible(id); // ensure accessible is created 440 gQueue.push(new changeStyle(id, "display", "none", kHideEvents)); 441 gQueue.push(new changeStyle(id, "display", "inline", kShowEvents)); 442 443 // Show/hide events by changing of visibility style of accessible DOM node 444 // from 'visible' to 'hidden', 'hidden' to 'visible'. 445 id = "link2"; 446 getAccessible(id); 447 gQueue.push(new changeStyle(id, "visibility", "hidden", kHideEvents)); 448 gQueue.push(new changeStyle(id, "visibility", "visible", kShowEvents)); 449 450 // Show/hide events by changing of visibility style of accessible DOM node 451 // from 'collapse' to 'visible', 'visible' to 'collapse'. 452 id = "link4"; 453 gQueue.push(new changeStyle(id, "visibility", "visible", kShowEvents)); 454 gQueue.push(new changeStyle(id, "visibility", "collapse", kHideEvents)); 455 456 // Show/hide events by adding new accessible DOM node and removing old one. 457 id = "link5"; 458 gQueue.push(new cloneAndAppendToDOM(id)); 459 gQueue.push(new removeFromDOM(id)); 460 461 // No show/hide events by adding new not accessible DOM node and removing 462 // old one, no reorder event for their parent. 463 id = "child1"; 464 gQueue.push(new cloneAndAppendToDOM(id, kNoEvents)); 465 gQueue.push(new removeFromDOM(id, kNoEvents)); 466 467 // Show/hide events by adding new accessible DOM node and removing 468 // old one, there is reorder event for their parent. 469 id = "child2"; 470 gQueue.push(new cloneAndAppendToDOM(id)); 471 gQueue.push(new removeFromDOM(id)); 472 473 // Show/hide events by adding new DOM node containing accessible DOM and 474 // removing old one, there is reorder event for their parent. 475 id = "child3"; 476 gQueue.push(new cloneAndAppendToDOM(id, kShowEvents, getFirstChild, 477 getParent)); 478 479 // Hide event for accessible child of unaccessible removed DOM node and 480 // reorder event for its parent. 481 gQueue.push(new removeFromDOM(id, kHideEvents, 482 getNEnsureFirstChild, getParent)); 483 484 // Hide events for accessible children of unaccessible removed DOM node 485 // and reorder event for its parent. 486 gQueue.push(new removeFromDOM("child4", kHideEvents, 487 getNEnsureChildren, getParent)); 488 489 // Show/hide events by creating new accessible DOM node and replacing 490 // old one. 491 getAccessible("link6"); // ensure accessible is created 492 gQueue.push(new cloneAndReplaceInDOM("link6")); 493 494 // Show/hide events by changing class name on the parent node. 495 gQueue.push(new changeClass("container2", "link7", "", kShowEvents)); 496 gQueue.push(new changeClass("container2", "link7", "displayNone", 497 kHideEvents)); 498 499 gQueue.push(new changeClass("container3", "link8", "", kShowEvents)); 500 gQueue.push(new changeClass("container3", "link8", "visibilityHidden", 501 kHideEvents)); 502 503 gQueue.push(new test1("testContainer")); 504 gQueue.push(new test2("testContainer", "testContainer2")); 505 gQueue.push(new test2("testContainer", "testNestedContainer")); 506 gQueue.push(new test3("testContainer")); 507 gQueue.push(new insertReferredElm("testContainer3")); 508 gQueue.push(new showHiddenParentOfVisibleChild()); 509 510 gQueue.push(new hideNDestroyDoc()); 511 gQueue.push(new hideHideNDestroyDoc()); 512 gQueue.invoke(); // Will call SimpleTest.finish(); 513 } 514 515 SimpleTest.waitForExplicitFinish(); 516 addA11yLoadEvent(doTests); 517 </script> 518 </head> 519 520 <body> 521 522 <a target="_blank" 523 href="https://bugzilla.mozilla.org/show_bug.cgi?id=469985" 524 title=" turn the test from bug 354745 into mochitest"> 525 Mozilla Bug 469985</a> 526 <a target="_blank" 527 href="https://bugzilla.mozilla.org/show_bug.cgi?id=472662" 528 title="no reorder event when html:link display property is changed from 'none' to 'inline'"> 529 Mozilla Bug 472662</a> 530 <a target="_blank" 531 title="Rework accessible tree update code" 532 href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275"> 533 Mozilla Bug 570275</a> 534 <a target="_blank" 535 title="Develop a way to handle visibility style" 536 href="https://bugzilla.mozilla.org/show_bug.cgi?id=606125"> 537 Mozilla Bug 606125</a> 538 <a target="_blank" 539 title="Update accessible tree on content insertion after layout" 540 href="https://bugzilla.mozilla.org/show_bug.cgi?id=498015"> 541 Mozilla Bug 498015</a> 542 543 <p id="display"></p> 544 <div id="content" style="display: none"></div> 545 <pre id="test"> 546 </pre> 547 <div id="eventdump"></div> 548 549 <div id="testContainer"> 550 <a id="link1" href="http://www.google.com">Link #1</a> 551 <a id="link2" href="http://www.google.com">Link #2</a> 552 <a id="link3" href="http://www.google.com">Link #3</a> 553 <a id="link4" href="http://www.google.com" style="visibility:collapse">Link #4</a> 554 <a id="link5" href="http://www.google.com">Link #5</a> 555 556 <div id="container" role="list"> 557 <span id="child1"></span> 558 <span id="child2" role="listitem"></span> 559 <span id="child3"><span role="listitem"></span></span> 560 <span id="child4"><span id="child4_1" role="listitem"></span><span id="child4_2" role="listitem"></span></span> 561 </div> 562 563 <a id="link6" href="http://www.google.com">Link #6</a> 564 565 <div id="container2" class="displayNone"><a id="link7">Link #7</a></div> 566 <div id="container3" class="visibilityHidden"><a id="link8">Link #8</a></div> 567 <div id="testNestedContainer"></div> 568 </div> 569 <div id="testContainer2"></div> 570 <div id="testContainer3"></div> 571 572 <div id="c4"> 573 <div style="visibility:hidden" id="c4_middle"> 574 <div style="visibility:visible" id="c4_child"></div> 575 </div> 576 577 <iframe id="c5" src="data:text/html,hey"></iframe> 578 <iframe id="c6" src="data:text/html,<label>l</label><label>l</label>"></iframe> 579 </body> 580 </html>