tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 );