browser_anchor_positioning.js (19231B)
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 async function testDetailsRelations(anchor, target) { 8 await testCachedRelation(anchor, RELATION_DETAILS, target); 9 await testCachedRelation(target, RELATION_DETAILS_FOR, anchor); 10 } 11 12 async function testNoDetailsRelations(anchor, target) { 13 await testCachedRelation(anchor, RELATION_DETAILS, []); 14 await testCachedRelation(target, RELATION_DETAILS_FOR, []); 15 } 16 17 async function invokeContentTaskAndTick(browser, args, task) { 18 await invokeContentTask(browser, args, task); 19 20 await invokeContentTask(browser, [], () => { 21 content.windowUtils.advanceTimeAndRefresh(100); 22 content.windowUtils.restoreNormalRefresh(); 23 }); 24 } 25 26 async function invokeSetAttributeAndTick(browser, id, attr, attrValue) { 27 await invokeSetAttribute(browser, id, attr, attrValue); 28 await invokeContentTask(browser, [], () => { 29 content.windowUtils.advanceTimeAndRefresh(100); 30 content.windowUtils.restoreNormalRefresh(); 31 }); 32 } 33 34 /** 35 * Test details relations for CSS explicit and implicit Anchor Positioning 36 */ 37 addAccessibleTask( 38 ` 39 <style> 40 #btn1 { 41 anchor-name: --btn1; 42 } 43 44 #target1 { 45 position: absolute; 46 position-anchor: --btn1; 47 left: anchor(right); 48 bottom: anchor(top); 49 } 50 51 #btn2 { 52 anchor-name: --btn2; 53 } 54 55 #target2 { 56 position: absolute; 57 left: anchor(--btn2 right); 58 } 59 60 #btn3 { 61 anchor-name: --btn3; 62 } 63 </style> 64 65 <div id="target1">World</div> 66 <button id="btn1">Hello</button> 67 68 <div id="target2">World</div> 69 <button id="btn2">Hello</button> 70 71 <button id="btn3">No Target</button> 72 `, 73 async function testSimplePositionAnchors(browser, docAcc) { 74 info("Implicit anchor"); 75 const btn1 = findAccessibleChildByID(docAcc, "btn1"); 76 const target1 = findAccessibleChildByID(docAcc, "target1"); 77 await testDetailsRelations(btn1, target1); 78 79 is( 80 btn1.attributes.getStringProperty("details-from"), 81 "css-anchor", 82 "Correct details-from attribute" 83 ); 84 info("Make anchor invalid"); 85 await invokeContentTaskAndTick(browser, [], () => { 86 Object.assign(content.document.getElementById("btn1").style, { 87 "anchor-name": "--invalid", 88 }); 89 }); 90 91 await testNoDetailsRelations(btn1, target1); 92 93 info("Make anchor valid again"); 94 await invokeContentTaskAndTick(browser, [], () => { 95 Object.assign(content.document.getElementById("btn1").style, { 96 "anchor-name": "--btn1", 97 }); 98 }); 99 100 await testDetailsRelations(btn1, target1); 101 102 info("Assign target to different anchor"); 103 await invokeContentTaskAndTick(browser, [], () => { 104 Object.assign(content.document.getElementById("target1").style, { 105 "position-anchor": "--btn3", 106 }); 107 }); 108 109 const btn3 = findAccessibleChildByID(docAcc, "btn3"); 110 await testDetailsRelations(btn3, target1); 111 await testCachedRelation(btn1, RELATION_DETAILS, []); 112 113 info("Assign target to invalid anchor"); 114 await invokeContentTaskAndTick(browser, [], () => { 115 Object.assign(content.document.getElementById("target1").style, { 116 "position-anchor": "--invalid", 117 }); 118 }); 119 120 await testNoDetailsRelations(btn3, target1); 121 122 info("Explicit anchor"); 123 const btn2 = findAccessibleChildByID(docAcc, "btn2"); 124 const target2 = findAccessibleChildByID(docAcc, "target2"); 125 await testDetailsRelations(btn2, target2); 126 127 await invokeContentTaskAndTick(browser, [], () => { 128 Object.assign(content.document.getElementById("target2").style, { 129 left: "0px", 130 }); 131 }); 132 133 await testNoDetailsRelations(btn2, target2); 134 }, 135 { chrome: true, topLevel: true } 136 ); 137 138 /** 139 * Test no details relations for sibling target 140 */ 141 addAccessibleTask( 142 ` 143 <style> 144 #sibling-btn { 145 anchor-name: --sibling-btn; 146 } 147 148 #sibling-target { 149 position: absolute; 150 position-anchor: --sibling-btn; 151 left: anchor(right); 152 bottom: anchor(top); 153 } 154 </style> 155 156 <button id="sibling-btn">Hello</button> 157 <button id="intermediate-button" hidden>Cruel</button> 158 <div id="sibling-target">World</div> 159 `, 160 async function testSiblingPositionAnchor(browser, docAcc) { 161 info("Target is sibling after anchor, no relation"); 162 const siblingBtn = findAccessibleChildByID(docAcc, "sibling-btn"); 163 const siblingTarget = findAccessibleChildByID(docAcc, "sibling-target"); 164 await testNoDetailsRelations(siblingBtn, siblingTarget); 165 166 await invokeSetAttributeAndTick(browser, "intermediate-button", "hidden"); 167 168 await testDetailsRelations(siblingBtn, siblingTarget); 169 }, 170 { chrome: true, topLevel: true } 171 ); 172 173 /** 174 * Test no details relations parent anchor with child target 175 */ 176 addAccessibleTask( 177 ` 178 <style> 179 #parent-btn { 180 anchor-name: --parent-btn; 181 } 182 183 #child-target { 184 position: absolute; 185 position-anchor: --parent-btn; 186 left: anchor(right); 187 bottom: anchor(top); 188 } 189 190 #owner-btn { 191 anchor-name: --owner-btn; 192 } 193 194 #owned-target { 195 position: absolute; 196 position-anchor: --owner-btn; 197 left: anchor(right); 198 bottom: anchor(top); 199 } 200 </style> 201 202 <button id="parent-btn">Hello <div role="group"><div id="child-target">World</div></div></button> 203 204 <div id="owned-target">World</div> 205 <button id="owner-btn" aria-owns="owned-target">Hello</button> 206 `, 207 async function testSiblingPositionAnchor(browser, docAcc) { 208 info("Target is child of anchor, no relation"); 209 const parentBtn = findAccessibleChildByID(docAcc, "parent-btn"); 210 const childTarget = findAccessibleChildByID(docAcc, "child-target"); 211 await testNoDetailsRelations(parentBtn, childTarget); 212 213 if (!browser.isRemoteBrowser) { 214 // Bug 1989629: This doesn't work in e10s yet. 215 216 info("Target is owned by anchor, no relation"); 217 const ownerBtn = findAccessibleChildByID(docAcc, "owner-btn"); 218 const ownedTarget = findAccessibleChildByID(docAcc, "owned-target"); 219 await testNoDetailsRelations(ownerBtn, ownedTarget); 220 221 info("Remove aria owns, relation should be restored"); 222 await invokeSetAttributeAndTick(browser, "owner-btn", "aria-owns"); 223 await testDetailsRelations(ownerBtn, ownedTarget); 224 } 225 }, 226 { chrome: true, topLevel: true } 227 ); 228 229 /** 230 * Test no details relations for CSS anchor with multiple targets or targets with multiple anchors 231 */ 232 addAccessibleTask( 233 ` 234 <style> 235 #multiTarget-btn { 236 anchor-name: --multiTarget-btn; 237 } 238 239 #multiTarget-target1 { 240 position: absolute; 241 position-anchor: --multiTarget-btn; 242 right: anchor(left); 243 bottom: anchor(top); 244 } 245 246 #multiTarget-target2 { 247 position: absolute; 248 position-anchor: --multiTarget-btn; 249 left: anchor(right); 250 bottom: anchor(top); 251 } 252 253 #multiTarget-target2.unanchored { 254 position-anchor: --invalid; 255 } 256 257 #multiAnchor-btn1 { 258 anchor-name: --multiAnchor-btn1; 259 } 260 261 #multiAnchor-btn2 { 262 anchor-name: --multiAnchor-btn2; 263 } 264 265 #multiAnchor-target { 266 position: absolute; 267 left: anchor(--multiAnchor-btn1 right); 268 bottom: anchor(--multiAnchor-btn2 top); 269 right: anchor(--multiAnchor-btn2 left); 270 } 271 272 #multiAnchor-target.unanchored { 273 left: 0px; 274 } 275 </style> 276 277 <div id="multiTarget-target1">Cruel</div> 278 <div id="multiTarget-target2">World</div> 279 <button id="multiTarget-btn">Hello</button> 280 281 <div id="multiAnchor-target">Hello</div> 282 <button id="multiAnchor-btn1">Cruel</button> 283 <button id="multiAnchor-btn2">World</button> 284 `, 285 async function testMultiplePositionAnchors(browser, docAcc) { 286 info("Multiple targets for one anchor"); 287 const multiTargetBtn = findAccessibleChildByID(docAcc, "multiTarget-btn"); 288 const multiTargetTarget1 = findAccessibleChildByID( 289 docAcc, 290 "multiTarget-target1" 291 ); 292 const multiTargetTarget2 = findAccessibleChildByID( 293 docAcc, 294 "multiTarget-target2" 295 ); 296 await testNoDetailsRelations(multiTargetBtn, multiTargetTarget1); 297 await testNoDetailsRelations(multiTargetBtn, multiTargetTarget2); 298 299 info("Remove one target from anchor via styling"); 300 await invokeSetAttributeAndTick( 301 browser, 302 "multiTarget-target2", 303 "class", 304 "unanchored" 305 ); 306 307 await testDetailsRelations(multiTargetBtn, multiTargetTarget1); 308 309 info("Restore target styling"); 310 await invokeSetAttributeAndTick(browser, "multiTarget-target2", "class"); 311 312 await testNoDetailsRelations(multiTargetBtn, multiTargetTarget2); 313 314 info("Remove one target node completely"); 315 await invokeSetAttributeAndTick( 316 browser, 317 "multiTarget-target2", 318 "hidden", 319 "true" 320 ); 321 322 await testDetailsRelations(multiTargetBtn, multiTargetTarget1); 323 324 info("Add back target node"); 325 await invokeSetAttributeAndTick(browser, "multiTarget-target2", "hidden"); 326 327 await testNoDetailsRelations(multiTargetBtn, multiTargetTarget1); 328 329 info("Multiple anchors for one target"); 330 const multiAnchorBtn1 = findAccessibleChildByID(docAcc, "multiAnchor-btn1"); 331 const multiAnchorBtn2 = findAccessibleChildByID(docAcc, "multiAnchor-btn2"); 332 const multiAnchorTarget = findAccessibleChildByID( 333 docAcc, 334 "multiAnchor-target" 335 ); 336 await testNoDetailsRelations(multiAnchorBtn1, multiAnchorTarget); 337 await testNoDetailsRelations(multiAnchorBtn2, multiAnchorTarget); 338 339 info("Remove one anchor via styling"); 340 await invokeSetAttributeAndTick( 341 browser, 342 "multiAnchor-target", 343 "class", 344 "unanchored" 345 ); 346 await testDetailsRelations(multiAnchorBtn2, multiAnchorTarget); 347 348 info("Add back one anchor via styling"); 349 await invokeSetAttributeAndTick(browser, "multiAnchor-target", "class"); 350 await testNoDetailsRelations(multiAnchorBtn2, multiAnchorTarget); 351 352 info("Remove one anchor node"); 353 await invokeSetAttributeAndTick( 354 browser, 355 "multiAnchor-btn1", 356 "hidden", 357 "true" 358 ); 359 await testDetailsRelations(multiAnchorBtn2, multiAnchorTarget); 360 361 info("Add back anchor node"); 362 await invokeSetAttributeAndTick(browser, "multiAnchor-btn1", "hidden"); 363 await testNoDetailsRelations(multiAnchorBtn2, multiAnchorTarget); 364 }, 365 { chrome: true, topLevel: true } 366 ); 367 368 /** 369 * Test no details relations for tooltip target 370 */ 371 addAccessibleTask( 372 ` 373 <style> 374 #btn { 375 anchor-name: --btn; 376 } 377 378 #tooltip-target { 379 position: absolute; 380 position-anchor: --btn; 381 left: anchor(right); 382 bottom: anchor(top); 383 } 384 </style> 385 386 <div id="tooltip-target" role="tooltip">World</div> 387 <button id="btn">Hello</button> 388 `, 389 async function testTooltipPositionAnchor(browser, docAcc) { 390 info("Target is tooltip, no relation"); 391 const btn = findAccessibleChildByID(docAcc, "btn"); 392 const tooltipTarget = findAccessibleChildByID(docAcc, "tooltip-target"); 393 await testNoDetailsRelations(btn, tooltipTarget); 394 }, 395 { chrome: true, topLevel: true } 396 ); 397 398 /** 399 * Test no details relations for when explicit relations are set. 400 */ 401 addAccessibleTask( 402 ` 403 <style> 404 .target { 405 position: absolute; 406 left: anchor(right); 407 bottom: anchor(top); 408 } 409 410 #btn-describedby { 411 anchor-name: --btn-describedby; 412 } 413 414 #target-describedby { 415 position-anchor: --btn-describedby; 416 } 417 418 #btn-labelledby { 419 anchor-name: --btn-labelledby; 420 } 421 422 #target-labelledby { 423 position-anchor: --btn-labelledby; 424 } 425 426 #btn-anchorsetdetails { 427 anchor-name: --btn-anchorsetdetails; 428 } 429 430 #target-anchorsetdetails { 431 position-anchor: --btn-anchorsetdetails; 432 } 433 434 #btn-targetsetdetails { 435 anchor-name: --btn-targetsetdetails; 436 } 437 438 #target-targetsetdetails { 439 position-anchor: --btn-targetsetdetails; 440 } 441 442 </style> 443 444 <div id="target-describedby" class="target">World</div> 445 <button id="btn-describedby" aria-describedby="target-describedby">Hello</button> 446 447 <div id="target-labelledby" class="target">World</div> 448 <button id="btn-labelledby" aria-labelledby="target-labelledby">Hello</button> 449 450 <div id="target-anchorsetdetails" class="target">World</div> 451 <button id="btn-anchorsetdetails" aria-details="">Hello</button> 452 453 <div id="target-targetsetdetails" aria-details="" class="target">World</div> 454 <button id="btn-targetsetdetails">Hello</button> 455 `, 456 async function testOtherRelationsWithAnchor(browser, docAcc) { 457 info("Test no details relations when explicit relations are set"); 458 const btnDescribedby = findAccessibleChildByID(docAcc, "btn-describedby"); 459 const targetDescribedby = findAccessibleChildByID( 460 docAcc, 461 "target-describedby" 462 ); 463 const btnLabelledby = findAccessibleChildByID(docAcc, "btn-labelledby"); 464 const targetLabelledby = findAccessibleChildByID( 465 docAcc, 466 "target-labelledby" 467 ); 468 const btnAnchorsetdetails = findAccessibleChildByID( 469 docAcc, 470 "btn-anchorsetdetails" 471 ); 472 const targetAnchorsetdetails = findAccessibleChildByID( 473 docAcc, 474 "target-anchorsetdetails" 475 ); 476 const btnTargetsetdetails = findAccessibleChildByID( 477 docAcc, 478 "btn-targetsetdetails" 479 ); 480 const targetTargetsetdetails = findAccessibleChildByID( 481 docAcc, 482 "target-targetsetdetails" 483 ); 484 485 await testNoDetailsRelations(btnDescribedby, targetDescribedby); 486 await invokeSetAttributeAndTick( 487 browser, 488 "btn-describedby", 489 "aria-describedby" 490 ); 491 await testDetailsRelations(btnDescribedby, targetDescribedby); 492 493 await testNoDetailsRelations(btnLabelledby, targetLabelledby); 494 await invokeSetAttributeAndTick( 495 browser, 496 "btn-labelledby", 497 "aria-labelledby" 498 ); 499 await testDetailsRelations(btnLabelledby, targetLabelledby); 500 501 await testNoDetailsRelations(btnAnchorsetdetails, targetAnchorsetdetails); 502 await invokeSetAttributeAndTick( 503 browser, 504 "btn-anchorsetdetails", 505 "aria-details" 506 ); 507 await testDetailsRelations(btnAnchorsetdetails, targetAnchorsetdetails); 508 509 await testNoDetailsRelations(btnTargetsetdetails, targetTargetsetdetails); 510 await invokeSetAttributeAndTick( 511 browser, 512 "target-targetsetdetails", 513 "aria-details" 514 ); 515 await testDetailsRelations(btnTargetsetdetails, targetTargetsetdetails); 516 }, 517 { chrome: true, topLevel: true } 518 ); 519 520 /** 521 * Test no details when anchor is used for sizing target only 522 */ 523 addAccessibleTask( 524 ` 525 <style> 526 #anchor1 { 527 anchor-name: --anchor1; 528 width: 200px; 529 } 530 531 #anchor2 { 532 anchor-name: --anchor2; 533 height: 150px; 534 } 535 536 #target { 537 position: absolute; 538 width: anchor-size(--anchor1 width); 539 } 540 541 #target.positioned { 542 left: anchor(--anchor1 right); 543 } 544 545 #target.anchor-height { 546 height: anchor-size(--anchor2 height); 547 } 548 </style> 549 550 <div id="target">World</div> 551 <button id="anchor1">Hello</button> 552 <button id="anchor2">Cruel</button> 553 `, 554 async function testTooltipPositionAnchor(browser, docAcc) { 555 info("Target is tooltip, no relation"); 556 const anchor1 = findAccessibleChildByID(docAcc, "anchor1"); 557 const target = findAccessibleChildByID(docAcc, "target"); 558 await testNoDetailsRelations(anchor1, target); 559 560 info("Use anchor for positioning as well"); 561 await invokeSetAttributeAndTick(browser, "target", "class", "positioned"); 562 await testDetailsRelations(anchor1, target); 563 564 info("Use second anchor for sizing"); 565 await invokeSetAttributeAndTick( 566 browser, 567 "target", 568 "class", 569 "positioned anchor-height" 570 ); 571 await testNoDetailsRelations(anchor1, target); 572 }, 573 { chrome: true, topLevel: true } 574 ); 575 576 /** 577 * Test multi columns colspan 578 */ 579 addAccessibleTask( 580 ` 581 <style> 582 .columns { 583 column-count: 2; 584 column-fill: auto; 585 } 586 587 .colspan { 588 column-span: all; 589 } 590 591 .spacer { 592 height: 10px; 593 } 594 595 #anchor { 596 anchor-name: --a1; 597 margin-left: 10px; 598 width: 40px; 599 } 600 601 #target { 602 position: absolute; 603 left: anchor(--a1 left); 604 top: anchor(--a1 top); 605 width: 150px; 606 height: 60px; 607 } 608 </style> 609 610 <div class="columns"> 611 <div id="target" role="group"></div> 612 <div id="anchor" role="group"> 613 <div class="spacer"></div> 614 <div class="colspan" style="height: 20px"></div> 615 </div> 616 </div>`, 617 async function testTooltipPositionAnchor(browser, docAcc) { 618 const anchor = findAccessibleChildByID(docAcc, "anchor"); 619 const target = findAccessibleChildByID(docAcc, "target"); 620 await testDetailsRelations(anchor, target); 621 }, 622 { chrome: true, topLevel: true } 623 ); 624 625 /** 626 * Test details relations when content does not have accessibles. 627 */ 628 addAccessibleTask( 629 ` 630 <style> 631 #btn1 { 632 anchor-name: --btn1; 633 } 634 635 #target1 { 636 position: absolute; 637 position-anchor: --btn1; 638 left: anchor(right); 639 bottom: anchor(top); 640 } 641 642 #btn2 { 643 anchor-name: --btn2; 644 } 645 646 #target2 { 647 position: absolute; 648 position-anchor: --btn2; 649 left: anchor(right); 650 bottom: anchor(top); 651 } 652 </style> 653 654 <div id="target1">World</div> 655 <button id="btn1" aria-hidden="true">Hello</button> 656 657 <div id="target2" aria-hidden="true">World</div> 658 <button id="btn2">Hello</button> 659 `, 660 async function testARIAHiddenAnchorsAndPosition(browser, docAcc) { 661 info("ARIA hidden anchor"); 662 const target1 = findAccessibleChildByID(docAcc, "target1"); 663 await testCachedRelation(target1, RELATION_DETAILS_FOR, []); 664 665 info("ARIA hidden target"); 666 const btn2 = findAccessibleChildByID(docAcc, "btn2"); 667 await testCachedRelation(btn2, RELATION_DETAILS, []); 668 }, 669 { chrome: true, topLevel: true } 670 ); 671 672 addAccessibleTask( 673 ` 674 <style> 675 .anchor { 676 width: 50px; 677 height: 50px; 678 background: pink; 679 } 680 681 .positioned { 682 width: 50px; 683 height: 50px; 684 background: purple; 685 position: absolute; 686 position-anchor: --a; 687 left: anchor(right); 688 bottom: anchor(bottom); 689 position-try-fallbacks: --fb; 690 } 691 692 @position-try --fb { 693 position-anchor: --b; 694 } 695 696 .abs-cb { 697 width: 200px; 698 height: 200px; 699 border: 1px solid; 700 position: relative; 701 } 702 703 .scroller { 704 overflow: scroll; 705 width: 100%; 706 height: 100%; 707 border: 1px solid; 708 box-sizing: border-box; 709 } 710 711 .filler { 712 height: 125px; 713 width: 1px; 714 } 715 716 .dn { 717 display: none; 718 } 719 </style> 720 <div class="abs-cb"> 721 <div class="scroller"> 722 <div class="filler"></div> 723 <div class="anchor" id="anchor1" style="anchor-name: --a;" role="group"></div> 724 <div class="filler"></div> 725 <div class="anchor" id="anchor2" style="anchor-name: --b; background: aqua;" role="group"></div> 726 <div class="filler"></div> 727 </div> 728 <div id="target" class="positioned" role="group"></div> 729 </div>`, 730 async function testScrollWithFallback(browser, docAcc) { 731 const anchor1 = findAccessibleChildByID(docAcc, "anchor1"); 732 const anchor2 = findAccessibleChildByID(docAcc, "anchor2"); 733 const target = findAccessibleChildByID(docAcc, "target"); 734 735 info("Correct details relation before scroll"); 736 await testDetailsRelations(anchor1, target); 737 738 await invokeContentTaskAndTick(browser, [], () => { 739 let scroller = content.document.querySelector(".scroller"); 740 scroller.scrollTo(0, scroller.scrollTopMax); 741 }); 742 743 info("Correct details relation after scroll"); 744 await testDetailsRelations(anchor2, target); 745 }, 746 { chrome: true, topLevel: true } 747 );