browser_test_general.js (17384B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 add_setup(async function () { 8 await SpecialPowers.pushPrefEnv({ 9 set: [["test.wait300msAfterTabSwitch", true]], 10 }); 11 }); 12 13 async function runTests(browser, accDoc) { 14 const dpr = await getContentDPR(browser); 15 16 await testChildAtPoint( 17 dpr, 18 3, 19 3, 20 findAccessibleChildByID(accDoc, "list"), 21 findAccessibleChildByID(accDoc, "listitem"), 22 findAccessibleChildByID(accDoc, "inner").firstChild 23 ); 24 todo( 25 false, 26 "Bug 746974 - children must match on all platforms. On Windows, " + 27 "ChildAtPoint with eDeepestChild is incorrectly ignoring MustPrune " + 28 "for the graphic." 29 ); 30 31 const txt = findAccessibleChildByID(accDoc, "txt"); 32 await testChildAtPoint(dpr, 1, 1, txt, txt, txt); 33 34 info( 35 "::MustPrune case, point is outside of textbox accessible but is in document." 36 ); 37 await testChildAtPoint(dpr, -1, -1, txt, null, null); 38 39 info("::MustPrune case, point is outside of root accessible."); 40 await testChildAtPoint(dpr, -10000, -10000, txt, null, null); 41 42 info("Not specific case, point is inside of btn accessible."); 43 const btn = findAccessibleChildByID(accDoc, "btn"); 44 await testChildAtPoint(dpr, 1, 1, btn, btn, btn); 45 46 info("Not specific case, point is outside of btn accessible."); 47 await testChildAtPoint(dpr, -1, -1, btn, null, null); 48 49 info( 50 "Out of flow accessible testing, do not return out of flow accessible " + 51 "because it's not a child of the accessible even though visually it is." 52 ); 53 await invokeContentTask(browser, [], () => { 54 const { CommonUtils } = ChromeUtils.importESModule( 55 "chrome://mochitests/content/browser/accessible/tests/browser/Common.sys.mjs" 56 ); 57 const doc = content.document; 58 const rectArea = CommonUtils.getNode("area", doc).getBoundingClientRect(); 59 const outOfFlow = CommonUtils.getNode("outofflow", doc); 60 outOfFlow.style.left = rectArea.left + "px"; 61 outOfFlow.style.top = rectArea.top + "px"; 62 }); 63 64 const area = findAccessibleChildByID(accDoc, "area"); 65 await testChildAtPoint(dpr, 1, 1, area, area, area); 66 67 info("Test image maps. Their children are not in the layout tree."); 68 await waitForImageMap(browser, accDoc); 69 const imgmap = findAccessibleChildByID(accDoc, "imgmap"); 70 ok(imgmap, "Image map exists"); 71 const theLetterA = imgmap.firstChild; 72 await hitTest(browser, imgmap, theLetterA, theLetterA); 73 await hitTest( 74 browser, 75 findAccessibleChildByID(accDoc, "container"), 76 imgmap, 77 theLetterA 78 ); 79 80 info("hit testing for element contained by zero-width element"); 81 const container2Input = findAccessibleChildByID(accDoc, "container2_input"); 82 await hitTest( 83 browser, 84 findAccessibleChildByID(accDoc, "container2"), 85 container2Input, 86 container2Input 87 ); 88 89 info("hittesting table, row, cells -- rows are not in the layout tree"); 90 const table = findAccessibleChildByID(accDoc, "table"); 91 const row = findAccessibleChildByID(accDoc, "row"); 92 const cell1 = findAccessibleChildByID(accDoc, "cell1"); 93 94 await hitTest(browser, table, row, cell1); 95 96 info("Testing that an inaccessible child doesn't break hit testing"); 97 const containerWithInaccessibleChild = findAccessibleChildByID( 98 accDoc, 99 "containerWithInaccessibleChild" 100 ); 101 const containerWithInaccessibleChildP2 = findAccessibleChildByID( 102 accDoc, 103 "containerWithInaccessibleChild_p2" 104 ); 105 await hitTest( 106 browser, 107 containerWithInaccessibleChild, 108 containerWithInaccessibleChildP2, 109 containerWithInaccessibleChildP2.firstChild 110 ); 111 112 info("Testing wrapped text"); 113 const wrappedTextLinkFirstP = findAccessibleChildByID( 114 accDoc, 115 "wrappedTextLinkFirstP" 116 ); 117 const wrappedTextLinkFirstA = findAccessibleChildByID( 118 accDoc, 119 "wrappedTextLinkFirstA" 120 ); 121 await hitTest( 122 browser, 123 wrappedTextLinkFirstP, 124 wrappedTextLinkFirstA, 125 wrappedTextLinkFirstA.firstChild 126 ); 127 const wrappedTextLeafFirstP = findAccessibleChildByID( 128 accDoc, 129 "wrappedTextLeafFirstP" 130 ); 131 const wrappedTextLeafFirstMark = findAccessibleChildByID( 132 accDoc, 133 "wrappedTextLeafFirstMark" 134 ); 135 await hitTest( 136 browser, 137 wrappedTextLeafFirstP, 138 wrappedTextLeafFirstMark, 139 wrappedTextLeafFirstMark.firstChild 140 ); 141 const wrappedTextNestedInlineP = findAccessibleChildByID( 142 accDoc, 143 "wrappedTextNestedInlineP" 144 ); 145 const wrappedTextNestedInlineEm = findAccessibleChildByID( 146 accDoc, 147 "wrappedTextNestedInlineEm" 148 ); 149 const wrappedTextNestedInlineStrong = findAccessibleChildByID( 150 accDoc, 151 "wrappedTextNestedInlineStrong" 152 ); 153 await hitTest( 154 browser, 155 wrappedTextNestedInlineP, 156 wrappedTextNestedInlineEm, 157 wrappedTextNestedInlineStrong.firstChild 158 ); 159 const wrappedTextPre = findAccessibleChildByID(accDoc, "wrappedTextPre"); 160 const wrappedTextPreCode = findAccessibleChildByID( 161 accDoc, 162 "wrappedTextPreCode" 163 ); 164 await hitTest( 165 browser, 166 wrappedTextPre, 167 wrappedTextPreCode, 168 wrappedTextPreCode.firstChild 169 ); 170 // hitTest() can only test the first character. We need to test a subsequent 171 // character for this case. 172 let [x, y, w] = await getContentBoundsForDOMElm( 173 browser, 174 "wrappedTextPreCode" 175 ); 176 // Use the top center of the element. 177 x = x + w / 2; 178 await untilCacheIs( 179 () => getChildAtPoint(wrappedTextPre, x, y, true), 180 wrappedTextPreCode.firstChild, 181 `Wrong deepest child accessible at the point (${x}, ${y}) of wrappedTextPre, sought wrappedTextPreCode leaf` 182 ); 183 184 info("Testing image"); 185 const imageP = findAccessibleChildByID(accDoc, "imageP"); 186 const image = findAccessibleChildByID(accDoc, "image"); 187 await hitTest(browser, imageP, image, image); 188 189 info("Testing image map with 0-sized area"); 190 const mapWith0AreaP = findAccessibleChildByID(accDoc, "mapWith0AreaP"); 191 const mapWith0Area = findAccessibleChildByID(accDoc, "mapWith0Area"); 192 await hitTest(browser, mapWith0AreaP, mapWith0Area, mapWith0Area); 193 } 194 195 addAccessibleTask( 196 ` 197 <div role="list" id="list"> 198 <div role="listitem" id="listitem"><span title="foo" id="inner">inner</span>item</div> 199 </div> 200 201 <span role="button">button1</span><span role="button" id="btn">button2</span> 202 203 <span role="textbox">textbox1</span><span role="textbox" id="txt">textbox2</span> 204 205 <div id="outofflow" style="width: 10px; height: 10px; position: absolute; left: 0px; top: 0px; background-color: yellow;"> 206 </div> 207 <div id="area" style="width: 100px; height: 100px; background-color: blue;"></div> 208 209 <map name="atoz_map"> 210 <area id="thelettera" href="http://www.bbc.co.uk/radio4/atoz/index.shtml#a" 211 coords="0,0,15,15" alt="thelettera" shape="rect"/> 212 </map> 213 214 <div id="container"> 215 <img id="imgmap" width="447" height="15" usemap="#atoz_map" src="http://example.com/a11y/accessible/tests/mochitest/letters.gif"/> 216 </div> 217 218 <div id="container2" style="width: 0px"> 219 <input id="container2_input"> 220 </div> 221 222 <table id="table" border> 223 <tr id="row"> 224 <td id="cell1">hello</td> 225 <td id="cell2">world</td> 226 </tr> 227 </table> 228 229 <div id="containerWithInaccessibleChild"> 230 <p>hi</p> 231 <p aria-hidden="true">hi</p> 232 <p id="containerWithInaccessibleChild_p2">bye</p> 233 </div> 234 235 <p id="wrappedTextLinkFirstP" style="width: 3ch; font-family: monospace;"> 236 <a id="wrappedTextLinkFirstA" href="https://example.com/">a</a>b cd 237 </p> 238 239 <p id="wrappedTextLeafFirstP" style="width: 3ch; font-family: monospace;"> 240 <mark id="wrappedTextLeafFirstMark">a</mark><a href="https://example.com/">b cd</a> 241 </p> 242 243 <p id="wrappedTextNestedInlineP" style="width: 1ch; font-family: monospace;"> 244 <em id="wrappedTextNestedInlineEm"><strong id="wrappedTextNestedInlineStrong">y </strong>z</em> 245 </p> 246 247 <pre id="wrappedTextPre"><code id="wrappedTextPreCode">ab cd 248 e</pre> 249 250 <p id="imageP"> 251 <img id="image" src="http://example.com/a11y/accessible/tests/mochitest/letters.gif"> 252 </p> 253 254 <map id="0Area"> 255 <area shape="rect"> 256 </map> 257 <p id="mapWith0AreaP"> 258 <img id="mapWith0Area" src="http://example.com/a11y/accessible/tests/mochitest/letters.gif" usemap="#0Area"> 259 </p> 260 `, 261 runTests, 262 { 263 iframe: true, 264 remoteIframe: true, 265 // Ensure that all hittest elements are in view. 266 iframeAttrs: { style: "width: 600px; height: 600px; padding: 10px;" }, 267 } 268 ); 269 270 addAccessibleTask( 271 ` 272 <div id="container"> 273 <h1 id="a">A</h1><h1 id="b">B</h1> 274 </div> 275 `, 276 async function (browser, accDoc) { 277 const a = findAccessibleChildByID(accDoc, "a"); 278 const b = findAccessibleChildByID(accDoc, "b"); 279 const dpr = await getContentDPR(browser); 280 // eslint-disable-next-line no-unused-vars 281 const [x, y, w, h] = Layout.getBounds(a, dpr); 282 // The point passed below will be made relative to `b`, but 283 // we'd like to test a point within `a`. Pass `a`s negative 284 // width for an x offset. Pass zero as a y offset, 285 // assuming the headings are on the same line. 286 await testChildAtPoint(dpr, -w, 0, b, null, null); 287 }, 288 { 289 iframe: true, 290 remoteIframe: true, 291 // Ensure that all hittest elements are in view. 292 iframeAttrs: { style: "width: 600px; height: 600px; padding: 10px;" }, 293 } 294 ); 295 296 addAccessibleTask( 297 ` 298 <style> 299 div { 300 width: 50px; 301 height: 50px; 302 position: relative; 303 } 304 305 div > div { 306 width: 30px; 307 height: 30px; 308 position: absolute; 309 opacity: 0.9; 310 } 311 </style> 312 <div id="a" style="background-color: orange;"> 313 <div id="aa" style="background-color: purple;"></div> 314 </div> 315 <div id="b" style="background-color: yellowgreen;"> 316 <div id="bb" style="top: -30px; background-color: turquoise"></div> 317 </div>`, 318 async function (browser, accDoc) { 319 const a = findAccessibleChildByID(accDoc, "a"); 320 const aa = findAccessibleChildByID(accDoc, "aa"); 321 const dpr = await getContentDPR(browser); 322 const [, , w, h] = Layout.getBounds(a, dpr); 323 // test upper left of `a` 324 await testChildAtPoint(dpr, 1, 1, a, aa, aa); 325 // test upper right of `a` 326 await testChildAtPoint(dpr, w - 1, 1, a, a, a); 327 // test just outside upper left of `a` 328 await testChildAtPoint(dpr, 1, -1, a, null, null); 329 // test halfway down/left of `a` 330 await testChildAtPoint(dpr, 1, Math.round(h / 2), a, a, a); 331 }, 332 { 333 chrome: true, 334 topLevel: true, 335 iframe: false, 336 remoteIframe: false, 337 // Ensure that all hittest elements are in view. 338 iframeAttrs: { style: "width: 600px; height: 600px; padding: 10px;" }, 339 } 340 ); 341 342 /** 343 * Verify that hit testing returns the proper accessible when one acc content 344 * is partially hidden due to overflow:hidden; 345 */ 346 addAccessibleTask( 347 ` 348 <style> 349 div div { 350 overflow: hidden; 351 font-family: monospace; 352 width: 2ch; 353 } 354 </style> 355 <div id="container" style="display: flex; flex-direction: row-reverse;"> 356 <div id="aNode">abcde</div><div id="fNode">fghij</div> 357 </div>`, 358 async function (browser, docAcc) { 359 const container = findAccessibleChildByID(docAcc, "container"); 360 const aNode = findAccessibleChildByID(docAcc, "aNode"); 361 const fNode = findAccessibleChildByID(docAcc, "fNode"); 362 const dpr = await getContentDPR(browser); 363 const [, , containerWidth] = Layout.getBounds(container, dpr); 364 const [, , aNodeWidth] = Layout.getBounds(aNode, dpr); 365 366 await testChildAtPoint( 367 dpr, 368 containerWidth - 1, 369 1, 370 container, 371 aNode, 372 aNode.firstChild 373 ); 374 await testChildAtPoint( 375 dpr, 376 containerWidth - aNodeWidth - 1, 377 1, 378 container, 379 fNode, 380 fNode.firstChild 381 ); 382 }, 383 { chrome: true, iframe: true, remoteIframe: true } 384 ); 385 386 /** 387 * Verify that hit testing is appropriately fuzzy when working with generics. 388 * If we match on a generic which contains additional generics and a single text 389 * leaf, we should return the text leaf as the deepest match instead of the 390 * generic itself. 391 */ 392 addAccessibleTask( 393 ` 394 <a href="example.com" id="link"> 395 <span style="overflow:hidden;" id="generic"><span aria-hidden="true" id="visible">I am some visible text</span><span id="invisible" style="overflow:hidden; height: 1px; width: 1px; position:absolute; clip: rect(0 0 0 0); display:block;">I am some invisible text</span></span> 396 </a>`, 397 async function (browser, docAcc) { 398 const link = findAccessibleChildByID(docAcc, "link"); 399 const generic = findAccessibleChildByID(docAcc, "generic"); 400 const invisible = findAccessibleChildByID(docAcc, "invisible"); 401 const dpr = await getContentDPR(browser); 402 403 await testChildAtPoint( 404 dpr, 405 1, 406 1, 407 link, 408 generic, // Direct Child 409 invisible.firstChild // Deepest Child 410 ); 411 412 await testOffsetAtPoint( 413 findAccessibleChildByID(docAcc, "invisible", [Ci.nsIAccessibleText]), 414 1, 415 1, 416 COORDTYPE_PARENT_RELATIVE, 417 0 418 ); 419 }, 420 { chrome: false, iframe: true, remoteIframe: true } 421 ); 422 423 /** 424 * Verify that hit testing is appropriately fuzzy when working with generics with siblings. 425 * We should return the deepest text leaf as the deepest match instead of the generic itself. 426 */ 427 addAccessibleTask( 428 ` 429 <div id="generic"><span aria-hidden="true" id="visible">Mozilla</span><span id="invisible" style="display: block !important;border: 0 !important;clip: rect(0 0 0 0) !important;height: 1px !important;margin: -1px !important;overflow: hidden !important;padding: 0 !important;position: absolute !important;white-space: nowrap !important;width: 1px !important;">hello world<br><div id="extraContainer">Mozilla</div></span><br>I am some other text</div>`, 430 async function (browser, docAcc) { 431 const generic = findAccessibleChildByID(docAcc, "generic"); 432 const invisible = findAccessibleChildByID(docAcc, "invisible"); 433 const dpr = await getContentDPR(browser); 434 435 await testChildAtPoint( 436 dpr, 437 1, 438 1, 439 generic, 440 invisible, // Direct Child 441 invisible.firstChild // Deepest Child 442 ); 443 }, 444 { chrome: false, iframe: true, remoteIframe: true } 445 ); 446 447 /** 448 * Verify that hit testing correctly ignores 449 * elements with pointer-events: none; 450 */ 451 addAccessibleTask( 452 `<div id="container" style="position:relative;"><button id="obscured">click me</button><div id="overlay" style="pointer-events:none; top:0; bottom:0; left:0; right:0; position: absolute;"></div></div><button id="clickable">I am clickable</button>`, 453 async function (browser, docAcc) { 454 const container = findAccessibleChildByID(docAcc, "container"); 455 const obscured = findAccessibleChildByID(docAcc, "obscured"); 456 const clickable = findAccessibleChildByID(docAcc, "clickable"); 457 const dpr = await getContentDPR(browser); 458 let [targetX, targetY, targetW, targetH] = Layout.getBounds(obscured, dpr); 459 const [x, y] = Layout.getBounds(docAcc, dpr); 460 await testChildAtPoint( 461 dpr, 462 targetX - x + targetW / 2, 463 targetY - y + targetH / 2, 464 docAcc, 465 container, // Direct Child 466 obscured // Deepest Child 467 ); 468 469 [targetX, targetY, targetW, targetH] = Layout.getBounds(clickable, dpr); 470 await testChildAtPoint( 471 dpr, 472 targetX - x + targetW / 2, 473 targetY - y + targetH / 2, 474 docAcc, 475 clickable, // Direct Child 476 clickable // Deepest Child 477 ); 478 }, 479 { chrome: false, iframe: true, remoteIframe: true } 480 ); 481 482 /** 483 * Test hit testing on elements which change to display: contents. 484 */ 485 addAccessibleTask( 486 ` 487 <style> 488 p { 489 font-family: monospace; 490 } 491 </style> 492 <details id="detailsOpen" open> 493 <summary>summary</summary> 494 <p id="detailsOpenP">detailsOpenP</p> 495 </details> 496 <details id="details"> 497 <summary>summary</summary> 498 <p id="detailsP">detailsP</p> 499 </details> 500 <div id="outer"> 501 <p>before</p> 502 <div id="inner" role="group"> 503 <p id="innerP" hidden>innerP</p> 504 </div> 505 </div> 506 `, 507 async function testChangeDisplayContents(browser, docAcc) { 508 const detailsOpen = findAccessibleChildByID(docAcc, "detailsOpen"); 509 const detailsOpenP = findAccessibleChildByID(docAcc, "detailsOpenP"); 510 // Between the details element and its <p> exists 511 // a pseudoelement, ::details-content. Use this as 512 // our expected target for direct-child hittesting. 513 await hitTest( 514 browser, 515 detailsOpen, 516 detailsOpenP.parent, 517 detailsOpenP.firstChild 518 ); 519 520 info("Opening details"); 521 let shown = waitForEvent(EVENT_SHOW, "detailsP"); 522 await invokeContentTask(browser, [], () => { 523 content.document.getElementById("details").open = true; 524 }); 525 const detailsP = (await shown).accessible; 526 // There is a <slot> between details and detailsP which has display: 527 // contents. 528 await hitTest(browser, detailsP.parent, detailsP, detailsP.firstChild); 529 530 info("Setting display: contents on inner, showing innerP"); 531 shown = waitForEvent(EVENT_SHOW, "innerP"); 532 await invokeContentTask(browser, [], () => { 533 content.document.getElementById("inner").style.display = "contents"; 534 content.document.getElementById("innerP").hidden = false; 535 }); 536 const innerP = (await shown).accessible; 537 const inner = findAccessibleChildByID(docAcc, "inner"); 538 await hitTest(browser, inner, innerP, innerP.firstChild); 539 } 540 );