tor-browser

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

browser_simplePatterns.js (18141B)


      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 /* import-globals-from ../../../mochitest/role.js */
      8 /* import-globals-from ../../../mochitest/states.js */
      9 loadScripts(
     10  { name: "role.js", dir: MOCHITESTS_DIR },
     11  { name: "states.js", dir: MOCHITESTS_DIR }
     12 );
     13 
     14 /* eslint-disable camelcase */
     15 const ExpandCollapseState_Collapsed = 0;
     16 const ExpandCollapseState_Expanded = 1;
     17 const ToggleState_Off = 0;
     18 const ToggleState_On = 1;
     19 const ToggleState_Indeterminate = 2;
     20 /* eslint-enable camelcase */
     21 
     22 /**
     23 * Test the Invoke pattern.
     24 */
     25 addUiaTask(
     26  `
     27 <button id="button">button</button>
     28 <p id="p">p</p>
     29 <input id="checkbox" type="checkbox">
     30 <input id="radio" type="radio">
     31  `,
     32  async function testInvoke() {
     33    await definePyVar("doc", `getDocUia()`);
     34    await assignPyVarToUiaWithId("button");
     35    await definePyVar("pattern", `getUiaPattern(button, "Invoke")`);
     36    ok(await runPython(`bool(pattern)`), "button has Invoke pattern");
     37    info("Calling Invoke on button");
     38    // The button will get focus when it is clicked.
     39    let focused = waitForEvent(EVENT_FOCUS, "button");
     40    // The UIA -> IA2 proxy doesn't fire the Invoked event.
     41    if (gIsUiaEnabled) {
     42      await setUpWaitForUiaEvent("Invoke_Invoked", "button");
     43    }
     44    await runPython(`pattern.Invoke()`);
     45    await focused;
     46    ok(true, "button got focus");
     47    if (gIsUiaEnabled) {
     48      await waitForUiaEvent();
     49      ok(true, "button got Invoked event");
     50    }
     51 
     52    await testPatternAbsent("p", "Invoke");
     53    // The Microsoft IA2 -> UIA proxy doesn't follow Microsoft's own rules.
     54    if (gIsUiaEnabled) {
     55      // Check boxes expose the Toggle pattern, so they should not expose the
     56      // Invoke pattern.
     57      await testPatternAbsent("checkbox", "Invoke");
     58      // Ditto for radio buttons.
     59      await testPatternAbsent("radio", "Invoke");
     60    }
     61  }
     62 );
     63 
     64 /**
     65 * Test the Toggle pattern.
     66 */
     67 addUiaTask(
     68  `
     69 <input id="checkbox" type="checkbox" checked>
     70 <button id="toggleButton" aria-pressed="false">toggle</button>
     71 <button id="button">button</button>
     72 <p id="p">p</p>
     73 
     74 <script>
     75  // When checkbox is clicked and it is not checked, make it indeterminate.
     76  document.getElementById("checkbox").addEventListener("click", evt => {
     77    // Within the event listener, .checked is reversed and you can't set
     78    // .indeterminate. Work around this by deferring and handling the changes
     79    // ourselves.
     80    evt.preventDefault();
     81    const target = evt.target;
     82    setTimeout(() => {
     83      if (target.checked) {
     84        target.checked = false;
     85      } else {
     86        target.indeterminate = true;
     87      }
     88    }, 0);
     89  });
     90 
     91  // When toggleButton is clicked, set aria-pressed to true.
     92  document.getElementById("toggleButton").addEventListener("click", evt => {
     93    evt.target.ariaPressed = "true";
     94  });
     95 </script>
     96  `,
     97  async function testToggle() {
     98    await definePyVar("doc", `getDocUia()`);
     99    await assignPyVarToUiaWithId("checkbox");
    100    await definePyVar("pattern", `getUiaPattern(checkbox, "Toggle")`);
    101    ok(await runPython(`bool(pattern)`), "checkbox has Toggle pattern");
    102    is(
    103      await runPython(`pattern.CurrentToggleState`),
    104      ToggleState_On,
    105      "checkbox has ToggleState_On"
    106    );
    107    // The IA2 -> UIA proxy doesn't fire ToggleState prop change events.
    108    if (gIsUiaEnabled) {
    109      info("Calling Toggle on checkbox");
    110      await setUpWaitForUiaPropEvent("ToggleToggleState", "checkbox");
    111      await runPython(`pattern.Toggle()`);
    112      await waitForUiaEvent();
    113      ok(true, "Got ToggleState prop change event on checkbox");
    114      is(
    115        await runPython(`pattern.CurrentToggleState`),
    116        ToggleState_Off,
    117        "checkbox has ToggleState_Off"
    118      );
    119      info("Calling Toggle on checkbox");
    120      await setUpWaitForUiaPropEvent("ToggleToggleState", "checkbox");
    121      await runPython(`pattern.Toggle()`);
    122      await waitForUiaEvent();
    123      ok(true, "Got ToggleState prop change event on checkbox");
    124      is(
    125        await runPython(`pattern.CurrentToggleState`),
    126        ToggleState_Indeterminate,
    127        "checkbox has ToggleState_Indeterminate"
    128      );
    129    }
    130 
    131    await assignPyVarToUiaWithId("toggleButton");
    132    await definePyVar("pattern", `getUiaPattern(toggleButton, "Toggle")`);
    133    ok(await runPython(`bool(pattern)`), "toggleButton has Toggle pattern");
    134    is(
    135      await runPython(`pattern.CurrentToggleState`),
    136      ToggleState_Off,
    137      "toggleButton has ToggleState_Off"
    138    );
    139    if (gIsUiaEnabled) {
    140      info("Calling Toggle on toggleButton");
    141      await setUpWaitForUiaPropEvent("ToggleToggleState", "toggleButton");
    142      await runPython(`pattern.Toggle()`);
    143      await waitForUiaEvent();
    144      ok(true, "Got ToggleState prop change event on toggleButton");
    145      is(
    146        await runPython(`pattern.CurrentToggleState`),
    147        ToggleState_On,
    148        "toggleButton has ToggleState_Off"
    149      );
    150    }
    151 
    152    await testPatternAbsent("button", "Toggle");
    153    await testPatternAbsent("p", "Toggle");
    154  }
    155 );
    156 
    157 /**
    158 * Test the ExpandCollapse pattern.
    159 */
    160 addUiaTask(
    161  `
    162 <details>
    163  <summary id="summary">summary</summary>
    164  details
    165 </details>
    166 <button id="popup" aria-haspopup="true">popup</button>
    167 <button id="button">button</button>
    168 <script>
    169  // When popup is clicked, set aria-expanded to true.
    170  document.getElementById("popup").addEventListener("click", evt => {
    171    evt.target.ariaExpanded = "true";
    172  });
    173 </script>
    174  `,
    175  async function testExpandCollapse() {
    176    await definePyVar("doc", `getDocUia()`);
    177    await assignPyVarToUiaWithId("summary");
    178    await definePyVar("pattern", `getUiaPattern(summary, "ExpandCollapse")`);
    179    ok(await runPython(`bool(pattern)`), "summary has ExpandCollapse pattern");
    180    is(
    181      await runPython(`pattern.CurrentExpandCollapseState`),
    182      ExpandCollapseState_Collapsed,
    183      "summary has ExpandCollapseState_Collapsed"
    184    );
    185    // The IA2 -> UIA proxy doesn't fire ToggleState prop change events, nor
    186    // does it fail when Expand/Collapse is called on a control which is
    187    // already in the desired state.
    188    if (gIsUiaEnabled) {
    189      info("Calling Expand on summary");
    190      await setUpWaitForUiaPropEvent(
    191        "ExpandCollapseExpandCollapseState",
    192        "summary"
    193      );
    194      await runPython(`pattern.Expand()`);
    195      await waitForUiaEvent();
    196      ok(
    197        true,
    198        "Got ExpandCollapseExpandCollapseState prop change event on summary"
    199      );
    200      is(
    201        await runPython(`pattern.CurrentExpandCollapseState`),
    202        ExpandCollapseState_Expanded,
    203        "summary has ExpandCollapseState_Expanded"
    204      );
    205      info("Calling Expand on summary");
    206      await testPythonRaises(`pattern.Expand()`, "Expand on summary failed");
    207      info("Calling Collapse on summary");
    208      await setUpWaitForUiaPropEvent(
    209        "ExpandCollapseExpandCollapseState",
    210        "summary"
    211      );
    212      await runPython(`pattern.Collapse()`);
    213      await waitForUiaEvent();
    214      ok(
    215        true,
    216        "Got ExpandCollapseExpandCollapseState prop change event on summary"
    217      );
    218      is(
    219        await runPython(`pattern.CurrentExpandCollapseState`),
    220        ExpandCollapseState_Collapsed,
    221        "summary has ExpandCollapseState_Collapsed"
    222      );
    223      info("Calling Collapse on summary");
    224      await testPythonRaises(
    225        `pattern.Collapse()`,
    226        "Collapse on summary failed"
    227      );
    228    }
    229 
    230    await assignPyVarToUiaWithId("popup");
    231    // Initially, popup has aria-haspopup but not aria-expanded. That should
    232    // be exposed as collapsed.
    233    await definePyVar("pattern", `getUiaPattern(popup, "ExpandCollapse")`);
    234    ok(await runPython(`bool(pattern)`), "popup has ExpandCollapse pattern");
    235    // The IA2 -> UIA proxy doesn't expose ExpandCollapseState_Collapsed for
    236    // aria-haspopup without aria-expanded.
    237    if (gIsUiaEnabled) {
    238      is(
    239        await runPython(`pattern.CurrentExpandCollapseState`),
    240        ExpandCollapseState_Collapsed,
    241        "popup has ExpandCollapseState_Collapsed"
    242      );
    243      info("Calling Expand on popup");
    244      await setUpWaitForUiaPropEvent(
    245        "ExpandCollapseExpandCollapseState",
    246        "popup"
    247      );
    248      await runPython(`pattern.Expand()`);
    249      await waitForUiaEvent();
    250      ok(
    251        true,
    252        "Got ExpandCollapseExpandCollapseState prop change event on popup"
    253      );
    254      is(
    255        await runPython(`pattern.CurrentExpandCollapseState`),
    256        ExpandCollapseState_Expanded,
    257        "popup has ExpandCollapseState_Expanded"
    258      );
    259    }
    260 
    261    await testPatternAbsent("button", "ExpandCollapse");
    262  }
    263 );
    264 
    265 /**
    266 * Test the ScrollItem pattern.
    267 */
    268 addUiaTask(
    269  `
    270 <hr style="height: 100vh;">
    271 <button id="button">button</button>
    272  `,
    273  async function testScrollItem(browser, docAcc) {
    274    await definePyVar("doc", `getDocUia()`);
    275    await assignPyVarToUiaWithId("button");
    276    await definePyVar("pattern", `getUiaPattern(button, "ScrollItem")`);
    277    ok(await runPython(`bool(pattern)`), "button has ScrollItem pattern");
    278    const button = findAccessibleChildByID(docAcc, "button");
    279    testStates(button, STATE_OFFSCREEN);
    280    info("Calling ScrollIntoView on button");
    281    // UIA doesn't have an event for this.
    282    let scrolled = waitForEvent(EVENT_SCROLLING_END, docAcc);
    283    await runPython(`pattern.ScrollIntoView()`);
    284    await scrolled;
    285    ok(true, "Document scrolled");
    286    testStates(button, 0, 0, STATE_OFFSCREEN);
    287  }
    288 );
    289 
    290 /**
    291 * Test the Value pattern.
    292 */
    293 addUiaTask(
    294  `
    295 <input id="text" value="before">
    296 <input id="textRo" readonly value="textRo">
    297 <input id="textDis" disabled value="textDis">
    298 <select id="select"><option selected>a</option><option>b</option></select>
    299 <progress id="progress" value="0.5"></progress>
    300 <input id="range" type="range" aria-valuetext="02:00:00">
    301 <a id="link" href="https://example.com/">Link</a>
    302 <div id="ariaTextbox" contenteditable role="textbox">before</div>
    303 <button id="button">button</button>
    304  `,
    305  async function testValue() {
    306    await definePyVar("doc", `getDocUia()`);
    307    await assignPyVarToUiaWithId("text");
    308    await definePyVar("pattern", `getUiaPattern(text, "Value")`);
    309    ok(await runPython(`bool(pattern)`), "text has Value pattern");
    310    ok(
    311      !(await runPython(`pattern.CurrentIsReadOnly`)),
    312      "text has IsReadOnly false"
    313    );
    314    is(
    315      await runPython(`pattern.CurrentValue`),
    316      "before",
    317      "text has correct Value"
    318    );
    319    info("SetValue on text");
    320    await setUpWaitForUiaPropEvent("ValueValue", "text");
    321    await runPython(`pattern.SetValue("after")`);
    322    await waitForUiaEvent();
    323    ok(true, "Got ValueValue prop change event on text");
    324    is(
    325      await runPython(`pattern.CurrentValue`),
    326      "after",
    327      "text has correct Value"
    328    );
    329 
    330    await assignPyVarToUiaWithId("textRo");
    331    await definePyVar("pattern", `getUiaPattern(textRo, "Value")`);
    332    ok(await runPython(`bool(pattern)`), "textRo has Value pattern");
    333    ok(
    334      await runPython(`pattern.CurrentIsReadOnly`),
    335      "textRo has IsReadOnly true"
    336    );
    337    is(
    338      await runPython(`pattern.CurrentValue`),
    339      "textRo",
    340      "textRo has correct Value"
    341    );
    342    info("SetValue on textRo");
    343    await testPythonRaises(
    344      `pattern.SetValue("after")`,
    345      "SetValue on textRo failed"
    346    );
    347 
    348    await assignPyVarToUiaWithId("textDis");
    349    await definePyVar("pattern", `getUiaPattern(textDis, "Value")`);
    350    ok(await runPython(`bool(pattern)`), "textDis has Value pattern");
    351    ok(
    352      !(await runPython(`pattern.CurrentIsReadOnly`)),
    353      "textDis has IsReadOnly false"
    354    );
    355    is(
    356      await runPython(`pattern.CurrentValue`),
    357      "textDis",
    358      "textDis has correct Value"
    359    );
    360    // The IA2 -> UIA proxy doesn't fail SetValue for a disabled element.
    361    if (gIsUiaEnabled) {
    362      info("SetValue on textDis");
    363      await testPythonRaises(
    364        `pattern.SetValue("after")`,
    365        "SetValue on textDis failed"
    366      );
    367    }
    368 
    369    await assignPyVarToUiaWithId("select");
    370    await definePyVar("pattern", `getUiaPattern(select, "Value")`);
    371    ok(await runPython(`bool(pattern)`), "select has Value pattern");
    372    ok(
    373      !(await runPython(`pattern.CurrentIsReadOnly`)),
    374      "select has IsReadOnly false"
    375    );
    376    is(
    377      await runPython(`pattern.CurrentValue`),
    378      "a",
    379      "select has correct Value"
    380    );
    381    info("SetValue on select");
    382    await testPythonRaises(
    383      `pattern.SetValue("b")`,
    384      "SetValue on select failed"
    385    );
    386 
    387    await assignPyVarToUiaWithId("progress");
    388    await definePyVar("pattern", `getUiaPattern(progress, "Value")`);
    389    ok(await runPython(`bool(pattern)`), "progress has Value pattern");
    390    ok(
    391      await runPython(`pattern.CurrentIsReadOnly`),
    392      "progress has IsReadOnly true"
    393    );
    394    is(
    395      await runPython(`pattern.CurrentValue`),
    396      "50%",
    397      "progress has correct Value"
    398    );
    399    info("SetValue on progress");
    400    await testPythonRaises(
    401      `pattern.SetValue("60%")`,
    402      "SetValue on progress failed"
    403    );
    404 
    405    await assignPyVarToUiaWithId("range");
    406    await definePyVar("pattern", `getUiaPattern(range, "Value")`);
    407    ok(await runPython(`bool(pattern)`), "range has Value pattern");
    408    is(
    409      await runPython(`pattern.CurrentValue`),
    410      "02:00:00",
    411      "range has correct Value"
    412    );
    413 
    414    await assignPyVarToUiaWithId("link");
    415    await definePyVar("pattern", `getUiaPattern(link, "Value")`);
    416    ok(await runPython(`bool(pattern)`), "link has Value pattern");
    417    is(
    418      await runPython(`pattern.CurrentValue`),
    419      "https://example.com/",
    420      "link has correct Value"
    421    );
    422 
    423    await assignPyVarToUiaWithId("ariaTextbox");
    424    await definePyVar("pattern", `getUiaPattern(ariaTextbox, "Value")`);
    425    ok(await runPython(`bool(pattern)`), "ariaTextbox has Value pattern");
    426    ok(
    427      !(await runPython(`pattern.CurrentIsReadOnly`)),
    428      "ariaTextbox has IsReadOnly false"
    429    );
    430    is(
    431      await runPython(`pattern.CurrentValue`),
    432      "before",
    433      "ariaTextbox has correct Value"
    434    );
    435    info("SetValue on ariaTextbox");
    436    await setUpWaitForUiaPropEvent("ValueValue", "ariaTextbox");
    437    await runPython(`pattern.SetValue("after")`);
    438    await waitForUiaEvent();
    439    ok(true, "Got ValueValue prop change event on ariaTextbox");
    440    is(
    441      await runPython(`pattern.CurrentValue`),
    442      "after",
    443      "ariaTextbox has correct Value"
    444    );
    445 
    446    await testPatternAbsent("button", "Value");
    447  }
    448 );
    449 
    450 /**
    451 * Test the Value pattern on a document.
    452 */
    453 addUiaTask(``, async function testValueDoc(browser) {
    454  // A test snippet is a data: URI. The accessibility engine won't return these.
    455  let url = new URL("https://example.net/document-builder.sjs");
    456  url.searchParams.append("html", `<body id=${DEFAULT_CONTENT_DOC_BODY_ID}>`);
    457  let loaded = waitForEvent(
    458    EVENT_DOCUMENT_LOAD_COMPLETE,
    459    DEFAULT_CONTENT_DOC_BODY_ID
    460  );
    461  BrowserTestUtils.startLoadingURIString(browser, url.href);
    462  await loaded;
    463  await definePyVar("doc", `getDocUia()`);
    464  await definePyVar("pattern", `getUiaPattern(doc, "Value")`);
    465  ok(await runPython(`bool(pattern)`), "doc has Value pattern");
    466  is(
    467    await runPython(`pattern.CurrentValue`),
    468    url.href,
    469    "doc has correct Value"
    470  );
    471 });
    472 
    473 async function testRangeValueProps(id, ro, val, min, max, small, large) {
    474  await assignPyVarToUiaWithId(id);
    475  await definePyVar("pattern", `getUiaPattern(${id}, "RangeValue")`);
    476  ok(await runPython(`bool(pattern)`), `${id} has RangeValue pattern`);
    477  is(
    478    !!(await runPython(`pattern.CurrentIsReadOnly`)),
    479    ro,
    480    `${id} has IsReadOnly ${ro}`
    481  );
    482  is(await runPython(`pattern.CurrentValue`), val, `${id} has correct Value`);
    483  is(
    484    await runPython(`pattern.CurrentMinimum`),
    485    min,
    486    `${id} has correct Minimum`
    487  );
    488  is(
    489    await runPython(`pattern.CurrentMaximum`),
    490    max,
    491    `${id} has correct Maximum`
    492  );
    493  // IA2 doesn't support small/large change, so the IA2 -> UIA proxy can't
    494  // either.
    495  if (gIsUiaEnabled) {
    496    is(
    497      await runPython(`pattern.CurrentSmallChange`),
    498      small,
    499      `${id} has correct SmallChange`
    500    );
    501    is(
    502      await runPython(`pattern.CurrentLargeChange`),
    503      large,
    504      `${id} has correct LargeChange`
    505    );
    506  }
    507 }
    508 
    509 /**
    510 * Test the RangeValue pattern.
    511 */
    512 addUiaTask(
    513  `
    514 <input id="range" type="range">
    515 <input id="rangeBig" type="range" max="1000">
    516 <progress id="progress" value="0.5"></progress>
    517 <input id="numberRo" type="number" min="0" max="10" value="5" readonly>
    518 <div id="ariaSlider" role="slider">slider</div>
    519 <button id="button">button</button>
    520  `,
    521  async function testRangeValue(browser) {
    522    await definePyVar("doc", `getDocUia()`);
    523    await testRangeValueProps("range", false, 50, 0, 100, 1, 10);
    524    info("SetValue on range");
    525    await setUpWaitForUiaPropEvent("RangeValueValue", "range");
    526    await runPython(`pattern.SetValue(20)`);
    527    await waitForUiaEvent();
    528    ok(true, "Got RangeValueValue prop change event on range");
    529    is(await runPython(`pattern.CurrentValue`), 20, "range has correct Value");
    530 
    531    await testRangeValueProps("rangeBig", false, 500, 0, 1000, 1, 100);
    532 
    533    await testRangeValueProps("progress", true, 0.5, 0, 1, 0, 0.1);
    534    info("Calling SetValue on progress");
    535    await testPythonRaises(
    536      `pattern.SetValue(0.6)`,
    537      "SetValue on progress failed"
    538    );
    539 
    540    await testRangeValueProps("numberRo", true, 5, 0, 10, 1, 1);
    541    info("Calling SetValue on numberRo");
    542    await testPythonRaises(
    543      `pattern.SetValue(6)`,
    544      "SetValue on numberRo failed"
    545    );
    546 
    547    await testRangeValueProps("ariaSlider", false, 50, 0, 100, null, null);
    548    info("Setting aria-valuenow on ariaSlider");
    549    await setUpWaitForUiaPropEvent("RangeValueValue", "ariaSlider");
    550    await invokeSetAttribute(browser, "ariaSlider", "aria-valuenow", "60");
    551    await waitForUiaEvent();
    552    ok(true, "Got RangeValueValue prop change event on ariaSlider");
    553    is(
    554      await runPython(`pattern.CurrentValue`),
    555      60,
    556      "ariaSlider has correct Value"
    557    );
    558 
    559    await testPatternAbsent("button", "RangeValue");
    560  }
    561 );