tor-browser

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

browser_accesskeys.js (7534B)


      1 add_task(async function () {
      2  await pushPrefs(
      3    ["test.wait300msAfterTabSwitch", true],
      4    ["ui.key.contentAccess", 5],
      5    ["ui.key.chromeAccess", 5]
      6  );
      7 
      8  const gPageURL1 =
      9    "data:text/html,<body><p>" +
     10    "<button id='button' accesskey='y'>Button</button>" +
     11    "<input id='checkbox' type='checkbox' accesskey='z'>Checkbox" +
     12    "</p></body>";
     13  let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL1);
     14 
     15  Services.focus.clearFocus(window);
     16 
     17  // Press an accesskey in the child document while the chrome is focused.
     18  let focusedId = await performAccessKey(tab1.linkedBrowser, "y");
     19  is(focusedId, "button", "button accesskey");
     20 
     21  // Press an accesskey in the child document while the content document is focused.
     22  focusedId = await performAccessKey(tab1.linkedBrowser, "z");
     23  is(focusedId, "checkbox", "checkbox accesskey");
     24 
     25  // Add an element with an accesskey to the chrome and press its accesskey while the chrome is focused.
     26  let newButton = document.createXULElement("button");
     27  newButton.id = "chromebutton";
     28  newButton.setAttribute("aria-label", "chromebutton");
     29  newButton.setAttribute("accesskey", "z");
     30  document.documentElement.appendChild(newButton);
     31  Services.focus.clearFocus(window);
     32 
     33  newButton.getBoundingClientRect(); // Accesskey registration happens during frame construction.
     34 
     35  focusedId = await performAccessKeyForChrome("z");
     36  is(focusedId, "chromebutton", "chromebutton accesskey");
     37 
     38  // Add a second tab and ensure that accesskey from the first tab is not used.
     39  const gPageURL2 =
     40    "data:text/html,<body>" +
     41    "<button id='tab2button' accesskey='y'>Button in Tab 2</button>" +
     42    "</body>";
     43  let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL2);
     44 
     45  Services.focus.clearFocus(window);
     46 
     47  focusedId = await performAccessKey(tab2.linkedBrowser, "y");
     48  is(focusedId, "tab2button", "button accesskey in tab2");
     49 
     50  // Press the accesskey for the chrome element while the content document is focused.
     51  focusedId = await performAccessKeyForChrome("z");
     52  is(focusedId, "chromebutton", "chromebutton accesskey");
     53 
     54  gBrowser.removeTab(tab1);
     55  gBrowser.removeTab(tab2);
     56 
     57  // Test whether access key for the newButton isn't available when content
     58  // consumes the key event.
     59 
     60  // When content in the tab3 consumes all keydown events.
     61  const gPageURL3 =
     62    "data:text/html,<body id='tab3body'>" +
     63    "<button id='tab3button' accesskey='y'>Button in Tab 3</button>" +
     64    "<script>" +
     65    "document.body.addEventListener('keydown', (event)=>{ event.preventDefault(); });" +
     66    "</script></body>";
     67  let tab3 = await BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL3);
     68 
     69  Services.focus.clearFocus(window);
     70 
     71  focusedId = await performAccessKey(tab3.linkedBrowser, "y");
     72  is(focusedId, "tab3button", "button accesskey in tab3 should be focused");
     73 
     74  newButton.onfocus = () => {
     75    ok(false, "chromebutton shouldn't get focus during testing with tab3");
     76  };
     77 
     78  // Press the accesskey for the chrome element while the content document is focused.
     79  focusedId = await performAccessKey(tab3.linkedBrowser, "z");
     80  is(
     81    focusedId,
     82    "tab3body",
     83    "button accesskey in tab3 should keep having focus"
     84  );
     85 
     86  newButton.onfocus = null;
     87 
     88  gBrowser.removeTab(tab3);
     89 
     90  // When content in the tab4 consumes all keypress events.
     91  const gPageURL4 =
     92    "data:text/html,<body id='tab4body'>" +
     93    "<button id='tab4button' accesskey='y'>Button in Tab 4</button>" +
     94    "<script>" +
     95    "document.body.addEventListener('keypress', (event)=>{ event.preventDefault(); });" +
     96    "</script></body>";
     97  let tab4 = await BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL4);
     98 
     99  Services.focus.clearFocus(window);
    100 
    101  focusedId = await performAccessKey(tab4.linkedBrowser, "y");
    102  is(focusedId, "tab4button", "button accesskey in tab4 should be focused");
    103 
    104  newButton.onfocus = () => {
    105    // EventStateManager handles accesskey before dispatching keypress event
    106    // into the DOM tree, therefore, chrome accesskey always wins focus from
    107    // content. However, this is different from shortcut keys.
    108    todo(false, "chromebutton shouldn't get focus during testing with tab4");
    109  };
    110 
    111  // Press the accesskey for the chrome element while the content document is focused.
    112  focusedId = await performAccessKey(tab4.linkedBrowser, "z");
    113  is(
    114    focusedId,
    115    "tab4body",
    116    "button accesskey in tab4 should keep having focus"
    117  );
    118 
    119  newButton.onfocus = null;
    120 
    121  gBrowser.removeTab(tab4);
    122 
    123  newButton.remove();
    124 
    125  // Accesskey on input label in shadow host should work
    126  const gPageURL5 = `data:text/html,
    127    <body>
    128      <div id="host">
    129        <template shadowrootmode="open">
    130          <label for="tab5Checkbox" accesskey="y">Label accesskey is "y":</label>
    131          <input id="tab5Checkbox" type="checkbox">
    132        </template>
    133      </div>
    134    </body>`;
    135  let tab5 = await BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL5);
    136  Services.focus.clearFocus(window);
    137  focusedId = await performAccessKey(tab5.linkedBrowser, "y");
    138 
    139  is(focusedId, "host", "the shadow host in tab5 should be focused");
    140 
    141  gBrowser.removeTab(tab5);
    142 });
    143 
    144 function performAccessKey(browser, key) {
    145  return new Promise(resolve => {
    146    let removeFocus, removeKeyDown, removeKeyUp;
    147    function callback() {
    148      removeFocus();
    149      removeKeyUp();
    150      removeKeyDown();
    151 
    152      SpecialPowers.spawn(browser, [], () => {
    153        let oldFocusedElement = content._oldFocusedElement;
    154        delete content._oldFocusedElement;
    155        return oldFocusedElement.id;
    156      }).then(oldFocus => resolve(oldFocus));
    157    }
    158 
    159    removeFocus = BrowserTestUtils.addContentEventListener(
    160      browser,
    161      "focus",
    162      callback,
    163      { capture: true },
    164      event => {
    165        if (!HTMLElement.isInstance(event.target)) {
    166          return false; // ignore window and document focus events
    167        }
    168 
    169        event.target.ownerGlobal._sent = true;
    170        let focusedElement = event.target.ownerGlobal.document.activeElement;
    171        event.target.ownerGlobal._oldFocusedElement = focusedElement;
    172        focusedElement.blur();
    173        return true;
    174      }
    175    );
    176 
    177    removeKeyDown = BrowserTestUtils.addContentEventListener(
    178      browser,
    179      "keydown",
    180      () => {},
    181      { capture: true },
    182      event => {
    183        event.target.ownerGlobal._sent = false;
    184        return true;
    185      }
    186    );
    187 
    188    removeKeyUp = BrowserTestUtils.addContentEventListener(
    189      browser,
    190      "keyup",
    191      callback,
    192      {},
    193      event => {
    194        if (!event.target.ownerGlobal._sent) {
    195          event.target.ownerGlobal._sent = true;
    196          let focusedElement = event.target.ownerGlobal.document.activeElement;
    197          event.target.ownerGlobal._oldFocusedElement = focusedElement;
    198          focusedElement.blur();
    199          return true;
    200        }
    201 
    202        return false;
    203      }
    204    );
    205 
    206    // Spawn an no-op content task to better ensure that the messages
    207    // for adding the event listeners above get handled.
    208    SpecialPowers.spawn(browser, [], () => {}).then(() => {
    209      EventUtils.synthesizeKey(key, { altKey: true, shiftKey: true });
    210    });
    211  });
    212 }
    213 
    214 // This version is used when a chrome element is expected to be found for an accesskey.
    215 async function performAccessKeyForChrome(key) {
    216  let waitFocusChangePromise = BrowserTestUtils.waitForEvent(
    217    document,
    218    "focus",
    219    true
    220  );
    221  EventUtils.synthesizeKey(key, { altKey: true, shiftKey: true });
    222  await waitFocusChangePromise;
    223  return document.activeElement.id;
    224 }