test_delayed_removal.html (15196B)
1 <!DOCTYPE html> 2 <html> 3 4 <head> 5 <title>Test accessible delayed removal</title> 6 7 <link rel="stylesheet" type="text/css" 8 href="chrome://mochikit/content/tests/SimpleTest/test.css" /> 9 10 <style> 11 .gentext:before { 12 content: "START" 13 } 14 .gentext:after { 15 content: "END" 16 } 17 </style> 18 19 <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> 20 21 <script type="application/javascript" 22 src="../common.js"></script> 23 <script type="application/javascript" 24 src="../role.js"></script> 25 <script type="application/javascript" 26 src="../promisified-events.js"></script> 27 28 <script type="application/javascript"> 29 30 async function hideDivFromInsideSpan() { 31 let msg = "hideDivFromInsideSpan"; 32 info(msg); 33 let events = waitForOrderedEvents([ 34 [EVENT_HIDE, "div1"], [EVENT_TEXT_REMOVED, "span1"], 35 [EVENT_REORDER, "span1"] 36 ], msg); 37 document.body.offsetTop; // Flush layout. 38 getNode("div1").style.display = "none"; 39 await events; 40 41 testAccessibleTree("c1", { SECTION: [ { REGION: [] }, ] }); 42 } 43 44 async function showDivFromInsideSpan() { 45 let msg = "showDivFromInsideSpan"; 46 info(msg); 47 let events = waitForOrderedEvents( 48 [[EVENT_SHOW, "div2"], [EVENT_REORDER, "span2"]], msg); 49 document.body.offsetTop; // Flush layout. 50 getNode("div2").style.display = "block"; 51 await events; 52 53 testAccessibleTree("c2", 54 { SECTION: [ { REGION: [{ SECTION: [ { TEXT_LEAF: [] } ] }] }, ] }); 55 } 56 57 async function removeDivFromInsideSpan() { 58 let msg = "removeDivFromInsideSpan"; 59 info(msg); 60 let events = waitForOrderedEvents([ 61 [EVENT_HIDE, getNode("div3")], [EVENT_TEXT_REMOVED, "span3"], 62 [EVENT_REORDER, "span3"] 63 ], msg); 64 document.body.offsetTop; // Flush layout. 65 getNode("div3").remove(); 66 await events; 67 68 testAccessibleTree("c3", { SECTION: [ { REGION: [] }, ] }); 69 } 70 71 // Test to see that generated content is inserted 72 async function addCSSGeneratedContent() { 73 let msg = "addCSSGeneratedContent"; 74 let c4_child = getAccessible("c4_child"); 75 info(msg); 76 let events = waitForOrderedEvents([ 77 [EVENT_SHOW, evt => evt.accessible == c4_child.firstChild], 78 [EVENT_SHOW, evt => evt.accessible == c4_child.lastChild], 79 [EVENT_REORDER, c4_child]], msg); 80 document.body.offsetTop; // Flush layout. 81 getNode("c4_child").classList.add('gentext'); 82 await events; 83 84 testAccessibleTree("c4", { SECTION: [ // container 85 { SECTION: [ // inserted node 86 { STATICTEXT: [] }, // :before 87 { TEXT_LEAF: [] }, // primary text 88 { STATICTEXT: [] }, // :after 89 ] }, 90 ] }); 91 } 92 93 // Test to see that generated content gets removed 94 async function removeCSSGeneratedContent() { 95 let msg = "removeCSSGeneratedContent"; 96 let c5_child = getAccessible("c5_child"); 97 info(msg); 98 let events = waitForEvents([ 99 [EVENT_HIDE, c5_child.firstChild], 100 [EVENT_HIDE, c5_child.lastChild], 101 [EVENT_REORDER, c5_child]], msg); 102 document.body.offsetTop; // Flush layout. 103 getNode("c5_child").classList.remove('gentext'); 104 await events; 105 106 testAccessibleTree("c5",{ SECTION: [ // container 107 { SECTION: [ // inserted node 108 { TEXT_LEAF: [] }, // primary text 109 ] }, 110 ] }); 111 } 112 113 // Test to see that a non-accessible intermediate container gets its accessible 114 // descendants removed and inserted correctly. 115 async function intermediateNonAccessibleContainers() { 116 let msg = "intermediateNonAccessibleContainers"; 117 info(msg); 118 119 testAccessibleTree("c6",{ SECTION: [ 120 { SECTION: [ 121 { role: ROLE_PUSHBUTTON, name: "Hello" }, 122 ] }, 123 ] }); 124 125 let events = waitForOrderedEvents( 126 [[EVENT_HIDE, "b1"], [EVENT_SHOW, "b2"], [EVENT_REORDER, "scrollarea"]], msg); 127 document.body.offsetTop; // Flush layout. 128 getNode("scrollarea").style.overflow = "auto"; 129 document.querySelector("#scrollarea > div > div:first-child").style.display = "none"; 130 document.querySelector("#scrollarea > div > div:last-child").style.display = "block"; 131 await events; 132 133 testAccessibleTree("c6",{ SECTION: [ 134 { SECTION: [ 135 { role: ROLE_PUSHBUTTON, name: "Goodbye" }, 136 ] }, 137 ] }); 138 } 139 140 // Test to see that the button gets reparented into the new accessible container. 141 async function intermediateNonAccessibleContainerBecomesAccessible() { 142 let msg = "intermediateNonAccessibleContainerBecomesAccessible"; 143 info(msg); 144 145 testAccessibleTree("c7",{ SECTION: [ 146 { role: ROLE_PUSHBUTTON, name: "Hello" }, 147 ] }); 148 149 let events = waitForOrderedEvents( 150 [[EVENT_HIDE, "b3"], 151 // b3 show event coalesced into its new container 152 [EVENT_SHOW, evt => evt.DOMNode.classList.contains('intermediate')], 153 [EVENT_REORDER, "c7"]], msg); 154 document.body.offsetTop; // Flush layout. 155 document.querySelector("#c7 > div").style.display = "block"; 156 await events; 157 158 testAccessibleTree("c7",{ SECTION: [ 159 { SECTION: [ { role: ROLE_PUSHBUTTON, name: "Hello" } ] } 160 ] }); 161 } 162 163 // Test to ensure that relocated accessibles are removed when a DOM 164 // ancestor is hidden. 165 async function removeRelocatedWhenDomAncestorHidden() { 166 info("removeRelocatedWhenDomAncestorHidden"); 167 168 testAccessibleTree("c8",{ SECTION: [ 169 { EDITCOMBOBOX: [ // c8_owner 170 { COMBOBOX_LIST: [] }, // c8_owned 171 ]}, 172 { SECTION: [] }, // c8_owned_container 173 ] }); 174 175 let events = waitForOrderedEvents([ 176 [EVENT_HIDE, "c8_owned_container"], 177 [EVENT_HIDE, "c8_owned"], 178 [EVENT_REORDER, "c8"], 179 ], "removeRelocatedWhenDomAncestorHidden"); 180 document.body.offsetTop; // Flush layout. 181 getNode("c8_owned_container").hidden = true; 182 await events; 183 184 testAccessibleTree("c8",{ SECTION: [ 185 { EDITCOMBOBOX: [] }, // c8_owner 186 ] }); 187 } 188 189 // Bug 1572829 190 async function removeShadowRootHost() { 191 info("removeShadowRootHost"); 192 document.body.offsetTop; // Flush layout. 193 194 let event = waitForEvent(EVENT_REORDER, "c9", "removeShadowRootHost"); 195 getNode("c9").firstElementChild.attachShadow({mode: "open"}); 196 getNode("c9").firstElementChild.replaceWith(""); 197 198 await event; 199 } 200 201 function listItemReframe() { 202 testAccessibleTree("li",{ LISTITEM: [ 203 { LISTITEM_MARKER: [] }, 204 { TEXT_LEAF: [] }, 205 ] }); 206 207 getNode("li").style.listStylePosition = "inside"; 208 document.body.offsetTop; // Flush layout. 209 window.windowUtils.advanceTimeAndRefresh(100); 210 211 testAccessibleTree("li",{ LISTITEM: [ 212 { LISTITEM_MARKER: [] }, 213 { TEXT_LEAF: [] }, 214 ] }); 215 216 window.windowUtils.restoreNormalRefresh(); 217 } 218 219 // Check to see that a reframed body gets its children pruned correctly. 220 async function bodyReframe() { 221 // Load sub-document in iframe. 222 let event = waitForEvent(EVENT_REORDER, "iframe", "bodyReframe"); 223 getNode("iframe").src = 224 `data:text/html,<div>Hello</div><div style="display: none">World</div>`; 225 await event; 226 227 // Initial tree should have one section leaf. 228 testAccessibleTree("c10",{ SECTION: [ 229 { INTERNAL_FRAME: [ 230 { DOCUMENT: [ 231 { SECTION: [ 232 { role: ROLE_TEXT_LEAF, name: "Hello" } 233 ] } 234 ]} 235 ] } 236 ] }); 237 238 239 let iframeDoc = getNode("iframe").contentWindow.document; 240 241 // Trigger coalesced reframing. Both the body node and its children 242 // will need reframing. 243 event = waitForEvent(EVENT_REORDER, iframeDoc, "bodyReframe"); 244 iframeDoc.body.style.display = "inline-block"; 245 iframeDoc.querySelector("div:first-child").style.display = "none"; 246 iframeDoc.querySelector("div:last-child").style.display = "block"; 247 248 await event; 249 250 // Only the second section should be showing 251 testAccessibleTree("c10",{ SECTION: [ 252 { INTERNAL_FRAME: [ 253 { DOCUMENT: [ 254 { SECTION: [ 255 { role: ROLE_TEXT_LEAF, name: "World" } 256 ] } 257 ]} 258 ] } 259 ] }); 260 } 261 262 // Ensure that embed elements recreate their Accessible if they started 263 // without an src and then an src is set later. 264 async function embedBecomesOuterDoc() { 265 let msg = "embedBecomesOuterDoc"; 266 info(msg); 267 268 testAccessibleTree("c12", { SECTION: [ 269 { TEXT: [] } 270 ] }); 271 272 let events = waitForOrderedEvents([ 273 [EVENT_HIDE, "embed"], 274 [EVENT_SHOW, "embed"], 275 [EVENT_REORDER, "c12"], 276 ], msg); 277 getNode("embed").src = "data:text/html,"; 278 await events; 279 280 testAccessibleTree("c12", { SECTION: [ 281 { INTERNAL_FRAME: [ 282 { DOCUMENT: [] } 283 ] } 284 ] }); 285 } 286 287 // Test that we get a text removed event when removing generated content from a button 288 async function testCSSGeneratedContentRemovedFromButton() { 289 let msg = "testCSSGeneratedContentRemovedFromButton"; 290 info(msg); 291 292 testAccessibleTree("c13", { SECTION: [ 293 { role: ROLE_PUSHBUTTON, name: "beforego", 294 children: [{ STATICTEXT: [] }, { TEXT_LEAF: [] }] } 295 ] }); 296 297 let events = waitForOrderedEvents([ 298 [EVENT_HIDE, evt => evt.accessible.name == "before"], 299 [EVENT_TEXT_REMOVED, evt => evt.accessible.role == ROLE_PUSHBUTTON], 300 [EVENT_SHOW, evt => evt.DOMNode.tagName == "HR"], 301 [EVENT_REORDER, "c13"], 302 ], msg); 303 getNode("b13").click(); 304 await events; 305 306 testAccessibleTree("c13", { SECTION: [ 307 { role: ROLE_PUSHBUTTON, name: "go", 308 children: [{ TEXT_LEAF: [] }] }, 309 { SEPARATOR: [] } 310 ] }); 311 } 312 313 // Slack seems to often restyle containers and change children 314 // simultaneously, this results in an insertion queue filled with 315 // redundant insertions and unparented nodes. 316 // This test duplicates some of this. 317 async function testSlack() { 318 let msg = "testSlack"; 319 info(msg); 320 321 window.windowUtils.advanceTimeAndRefresh(100); 322 let event = waitForEvent(EVENT_REORDER, "c14", "testSlack"); 323 324 let keyContainer = document.querySelector("#c14 .intermediate"); 325 keyContainer.style.display = "inline-block"; 326 document.body.offsetTop; // Flush layout. 327 328 let one = document.querySelector("#c14 [aria-label='one']"); 329 let three = document.querySelector("#c14 [aria-label='three']"); 330 one.remove(); 331 three.remove(); 332 // insert one first 333 keyContainer.firstChild.before(one.cloneNode()); 334 // insert three last 335 keyContainer.lastChild.after(three.cloneNode()); 336 337 keyContainer.style.display = "flex"; 338 document.body.offsetTop; // Flush layout. 339 340 window.windowUtils.restoreNormalRefresh(); 341 342 await event; 343 344 is(getAccessible("c14").name, "one two three", "subtree has correct order"); 345 } 346 347 // Ensure that a node is removed when visibility: hidden is set but the 348 // layout frame is reconstructed; e.g. because of position: fixed. Also 349 // ensure that visible children aren't clobbered. 350 async function visibilityHiddenWithReframe() { 351 let msg = "visibilityHiddenWithReframe"; 352 info(msg); 353 354 testAccessibleTree("c15", { SECTION: [ // c15 355 { SECTION: [ // c15_inner 356 { TEXT_LEAF: [] }, // Text 357 { PARAGRAPH: [ 358 { TEXT_LEAF: [] } // Para 359 ] }, 360 { HEADING: [ // c15_visible 361 { TEXT_LEAF: [] } // Visible 362 ] }, // c15_visible 363 ] } // c15_inner 364 ] }); 365 366 let events = waitForOrderedEvents([ 367 [EVENT_HIDE, "c15_inner"], 368 [EVENT_SHOW, "c15_visible"], 369 [EVENT_REORDER, "c15"], 370 ], msg); 371 getNode("c15_inner").style = "visibility: hidden; position: fixed;"; 372 await events; 373 374 testAccessibleTree("c15", { SECTION: [ // c15 375 { HEADING: [ // c15_visible 376 { TEXT_LEAF: [] } // Visible 377 ] }, // c15_visible 378 ] }); 379 } 380 381 async function doTest() { 382 await hideDivFromInsideSpan(); 383 384 await showDivFromInsideSpan(); 385 386 await removeDivFromInsideSpan(); 387 388 await addCSSGeneratedContent(); 389 390 await removeCSSGeneratedContent(); 391 392 await intermediateNonAccessibleContainers(); 393 394 await intermediateNonAccessibleContainerBecomesAccessible(); 395 396 await removeRelocatedWhenDomAncestorHidden(); 397 398 await removeShadowRootHost(); 399 400 listItemReframe(); 401 402 await bodyReframe(); 403 404 await embedBecomesOuterDoc(); 405 406 await testCSSGeneratedContentRemovedFromButton(); 407 408 await testSlack(); 409 410 await visibilityHiddenWithReframe(); 411 412 SimpleTest.finish(); 413 } 414 415 SimpleTest.waitForExplicitFinish(); 416 addA11yLoadEvent(doTest); 417 </script> 418 </head> 419 <body> 420 421 <p id="display"></p> 422 <div id="content" style="display: none"></div> 423 <pre id="test"> 424 </pre> 425 426 <div id="c1"> 427 <span role="region" id="span1" aria-label="region"><div id="div1">hello</div></span> 428 </div> 429 430 <div id="c2"> 431 <span role="region" id="span2" aria-label="region"><div id="div2" style="display: none">hello</div></span> 432 </div> 433 434 <div id="c3"> 435 <span role="region" id="span3" aria-label="region"><div id="div3">hello</div></span> 436 </div> 437 438 <div id="c4"><div id="c4_child">text</div></div> 439 440 <div id="c5"><div id="c5_child" class="gentext">text</div></div> 441 442 <div id="c6"> 443 <div id="scrollarea" style="overflow:hidden;"> 444 <div><div role="none"><button id="b1">Hello</button></div><div role="none" style="display: none"><button id="b2">Goodbye</button></div></div> 445 </div> 446 </div> 447 448 <div id="c7"> 449 <div style="display: inline;" class="intermediate"> 450 <button id="b3">Hello</button> 451 </div> 452 </div> 453 454 <div id="c8"> 455 <div id="c8_owner" role="combobox" aria-owns="c8_owned"></div> 456 <div id="c8_owned_container"> 457 <div id="c8_owned" role="listbox"></div> 458 </div> 459 </div> 460 461 <div id="c9"> 462 <div><dir>a</dir></div> 463 </div> 464 465 <div id="c11"> 466 <ul> 467 <li id="li">Test</li> 468 </ul> 469 </div> 470 471 <div id="c12"><embed id="embed"></embed></div> 472 473 <div id="c10"> 474 <iframe id="iframe"></iframe> 475 </div> 476 477 <div id="c13"> 478 <style> 479 .before::before { content: 'before' } 480 </style> 481 <button id="b13" class="before" onclick="this.className = ''; this.insertAdjacentElement('afterend', document.createElement('hr'))">go</button> 482 </div> 483 484 <div role="heading" id="c14" data-qa="virtual-list-item"> 485 <div class="intermediate"> 486 <div role="img" aria-label="one"></div> two <div role="img" 487 aria-label="three"></div> 488 </div> 489 </div> 490 491 <div id="c15"><div id="c15_inner"> 492 Text 493 <p>Para</p> 494 <h1 id="c15_visible" style="visibility: visible;">Visible</h1> 495 </div></div> 496 497 <div id="eventdump"></div> 498 </body> 499 </html>