tor-browser

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

browser_shadow_dom_and_custom_elements.js (8729B)


      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 /**
      8 * Test relation defaults via element internals
      9 */
     10 addAccessibleTask(
     11  `
     12  <div id="dependant1">label</div>
     13  <custom-checkbox id="host"></custom-checkbox>
     14  <div id="dependant2">label2</div>
     15 
     16 <script>
     17 customElements.define("custom-checkbox",
     18  class extends HTMLElement {
     19    constructor() {
     20      super();
     21      this.tabIndex = "0";
     22      this._internals = this.attachInternals();
     23      this._internals.role = "checkbox";
     24      this._internals.ariaChecked = "true";
     25    }
     26    get internals() {
     27      return this._internals;
     28    }
     29  }
     30 );
     31 </script>`,
     32  async function (browser, accDoc) {
     33    let host = findAccessibleChildByID(accDoc, "host");
     34    let dependant1 = findAccessibleChildByID(accDoc, "dependant1");
     35    let dependant2 = findAccessibleChildByID(accDoc, "dependant2");
     36 
     37    function invokeSetInternals(reflectionAttrName, targetIds) {
     38      if (targetIds) {
     39        Logger.log(
     40          `Setting internals reflected ${reflectionAttrName} attribute to ${targetIds} for host`
     41        );
     42      } else {
     43        Logger.log(
     44          `Removing internals reflected ${reflectionAttrName} attribute from node with host`
     45        );
     46      }
     47 
     48      return invokeContentTask(
     49        browser,
     50        [reflectionAttrName, targetIds],
     51        (contentAttr, contentTargetIds) => {
     52          let internals = content.document.getElementById("host").internals;
     53          if (contentTargetIds) {
     54            internals[contentAttr] = contentTargetIds.map(targetId =>
     55              content.document.getElementById(targetId)
     56            );
     57          } else {
     58            internals[contentAttr] = null;
     59          }
     60        }
     61      );
     62    }
     63 
     64    async function testInternalsRelation(
     65      attrName,
     66      reflectionAttrName,
     67      hostRelation,
     68      dependantRelation
     69    ) {
     70      info(`setting default ${reflectionAttrName}`);
     71      await invokeSetInternals(reflectionAttrName, ["dependant1"]);
     72      await testCachedRelation(host, hostRelation, [dependant1]);
     73      await testCachedRelation(dependant1, dependantRelation, [host]);
     74      await testCachedRelation(dependant2, dependantRelation, []);
     75 
     76      info(`setting override ${attrName}`);
     77      await invokeSetAttribute(browser, "host", attrName, "dependant2");
     78      await testCachedRelation(host, hostRelation, [dependant2]);
     79      await testCachedRelation(dependant2, dependantRelation, [host]);
     80      await testCachedRelation(dependant1, dependantRelation, []);
     81 
     82      info(`unsetting default ${reflectionAttrName} and ${attrName} override`);
     83      await invokeSetInternals(reflectionAttrName, null);
     84      await invokeSetAttribute(browser, "host", attrName, null);
     85      await testCachedRelation(host, hostRelation, []);
     86      await testCachedRelation(dependant2, dependantRelation, []);
     87      await testCachedRelation(dependant1, dependantRelation, []);
     88    }
     89 
     90    await testInternalsRelation(
     91      "aria-labelledby",
     92      "ariaLabelledByElements",
     93      RELATION_LABELLED_BY,
     94      RELATION_LABEL_FOR
     95    );
     96    await testInternalsRelation(
     97      "aria-describedby",
     98      "ariaDescribedByElements",
     99      RELATION_DESCRIBED_BY,
    100      RELATION_DESCRIPTION_FOR
    101    );
    102    await testInternalsRelation(
    103      "aria-controls",
    104      "ariaControlsElements",
    105      RELATION_CONTROLLER_FOR,
    106      RELATION_CONTROLLED_BY
    107    );
    108    await testInternalsRelation(
    109      "aria-flowto",
    110      "ariaFlowToElements",
    111      RELATION_FLOWS_TO,
    112      RELATION_FLOWS_FROM
    113    );
    114    await testInternalsRelation(
    115      "aria-details",
    116      "ariaDetailsElements",
    117      RELATION_DETAILS,
    118      RELATION_DETAILS_FOR
    119    );
    120    await testInternalsRelation(
    121      "aria-errormessage",
    122      "ariaErrorMessageElements",
    123      RELATION_ERRORMSG,
    124      RELATION_ERRORMSG_FOR
    125    );
    126  }
    127 );
    128 
    129 /**
    130 * Moving explicitly set elements across shadow DOM boundaries.
    131 */
    132 addAccessibleTask(
    133  `
    134  <div id="describedButtonContainer">
    135    <div id="buttonDescription1">Delicious</div>
    136    <div id="buttonDescription2">Nutritious</div>
    137    <div id="outerShadowHost"></div>
    138    <button id="describedElement">Button</button>
    139  </div>
    140 
    141 <script>
    142    const buttonDescription1 = document.getElementById("buttonDescription1");
    143    const buttonDescription2 = document.getElementById("buttonDescription2");
    144    const outerShadowRoot = outerShadowHost.attachShadow({mode: "open"});
    145    const innerShadowHost = document.createElement("div");
    146    outerShadowRoot.appendChild(innerShadowHost);
    147    const innerShadowRoot = innerShadowHost.attachShadow({mode: "open"});
    148 
    149    const describedElement = document.getElementById("describedElement");
    150    // Add some attr associated light DOM elements.
    151    describedElement.ariaDescribedByElements = [buttonDescription1, buttonDescription2];
    152 </script>`,
    153  async function (browser, accDoc) {
    154    const waitAndReturnRecreated = acc => {
    155      const id = getAccessibleDOMNodeID(acc);
    156      return waitForEvents([
    157        [EVENT_HIDE, acc],
    158        [EVENT_SHOW, id],
    159      ]).then(evts => evts[1].accessible);
    160    };
    161 
    162    let describedAcc = findAccessibleChildByID(accDoc, "describedElement");
    163    let accDescription1 = findAccessibleChildByID(accDoc, "buttonDescription1");
    164    let accDescription2 = findAccessibleChildByID(accDoc, "buttonDescription2");
    165 
    166    // All elements were in the same scope, so relations are intact.
    167    await testCachedRelation(describedAcc, RELATION_DESCRIBED_BY, [
    168      accDescription1,
    169      accDescription2,
    170    ]);
    171    await testCachedRelation(accDescription1, RELATION_DESCRIPTION_FOR, [
    172      describedAcc,
    173    ]);
    174    await testCachedRelation(accDescription2, RELATION_DESCRIPTION_FOR, [
    175      describedAcc,
    176    ]);
    177 
    178    let onRecreated = waitAndReturnRecreated(describedAcc);
    179    await invokeContentTask(browser, [], () => {
    180      const outerShadowRoot =
    181        content.document.getElementById("outerShadowHost").shadowRoot;
    182      const describedElement =
    183        content.document.getElementById("describedElement");
    184      outerShadowRoot.appendChild(describedElement);
    185    });
    186 
    187    info("Waiting for described accessible to be recreated");
    188    describedAcc = await onRecreated;
    189    // Relations should still be intact, we are referencing elements in a lighter scope.
    190    await testCachedRelation(describedAcc, RELATION_DESCRIBED_BY, [
    191      accDescription1,
    192      accDescription2,
    193    ]);
    194    await testCachedRelation(accDescription1, RELATION_DESCRIPTION_FOR, [
    195      describedAcc,
    196    ]);
    197    await testCachedRelation(accDescription2, RELATION_DESCRIPTION_FOR, [
    198      describedAcc,
    199    ]);
    200 
    201    // Move the explicitly set elements into a deeper shadow DOM.
    202    onRecreated = Promise.all([
    203      waitAndReturnRecreated(accDescription1),
    204      waitAndReturnRecreated(accDescription2),
    205    ]);
    206    await invokeContentTask(browser, [], () => {
    207      const buttonDescription1 =
    208        content.document.getElementById("buttonDescription1");
    209      const buttonDescription2 =
    210        content.document.getElementById("buttonDescription2");
    211      const innerShadowRoot =
    212        content.document.getElementById("outerShadowHost").shadowRoot
    213          .firstElementChild.shadowRoot;
    214      innerShadowRoot.appendChild(buttonDescription1);
    215      innerShadowRoot.appendChild(buttonDescription2);
    216    });
    217 
    218    [accDescription1, accDescription2] = await onRecreated;
    219 
    220    // Relation is severed, because relation dependants are no longer in a valid scope.
    221    await testCachedRelation(describedAcc, RELATION_DESCRIBED_BY, []);
    222    await testCachedRelation(accDescription1, RELATION_DESCRIPTION_FOR, []);
    223    await testCachedRelation(accDescription2, RELATION_DESCRIPTION_FOR, []);
    224 
    225    // Move into the same shadow scope as the explicitly set elements.
    226    onRecreated = waitAndReturnRecreated(describedAcc);
    227    await invokeContentTask(browser, [], () => {
    228      const outerShadowRoot =
    229        content.document.getElementById("outerShadowHost").shadowRoot;
    230      const describedElement =
    231        outerShadowRoot.getElementById("describedElement");
    232      const innerShadowRoot = outerShadowRoot.firstElementChild.shadowRoot;
    233      innerShadowRoot.appendChild(describedElement);
    234    });
    235 
    236    describedAcc = await onRecreated;
    237    // Relation is restored, because target is now in same shadow scope.
    238    await testCachedRelation(describedAcc, RELATION_DESCRIBED_BY, [
    239      accDescription1,
    240      accDescription2,
    241    ]);
    242    await testCachedRelation(accDescription1, RELATION_DESCRIPTION_FOR, [
    243      describedAcc,
    244    ]);
    245    await testCachedRelation(accDescription2, RELATION_DESCRIPTION_FOR, [
    246      describedAcc,
    247    ]);
    248  }
    249 );