add_task(async function () {
  await pushPrefs(
    ["test.wait300msAfterTabSwitch", true],
    ["ui.key.contentAccess", 5],
    ["ui.key.chromeAccess", 5]
  );

  const gPageURL1 =
    "data:text/html,<body><p>" +
    "<button id='button' accesskey='y'>Button</button>" +
    "<input id='checkbox' type='checkbox' accesskey='z'>Checkbox" +
    "</p></body>";
  let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL1);

  Services.focus.clearFocus(window);

  // Press an accesskey in the child document while the chrome is focused.
  let focusedId = await performAccessKey(tab1.linkedBrowser, "y");
  is(focusedId, "button", "button accesskey");

  // Press an accesskey in the child document while the content document is focused.
  focusedId = await performAccessKey(tab1.linkedBrowser, "z");
  is(focusedId, "checkbox", "checkbox accesskey");

  // Add an element with an accesskey to the chrome and press its accesskey while the chrome is focused.
  let newButton = document.createXULElement("button");
  newButton.id = "chromebutton";
  newButton.setAttribute("aria-label", "chromebutton");
  newButton.setAttribute("accesskey", "z");
  document.documentElement.appendChild(newButton);
  Services.focus.clearFocus(window);

  newButton.getBoundingClientRect(); // Accesskey registration happens during frame construction.

  focusedId = await performAccessKeyForChrome("z");
  is(focusedId, "chromebutton", "chromebutton accesskey");

  // Add a second tab and ensure that accesskey from the first tab is not used.
  const gPageURL2 =
    "data:text/html,<body>" +
    "<button id='tab2button' accesskey='y'>Button in Tab 2</button>" +
    "</body>";
  let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL2);

  Services.focus.clearFocus(window);

  focusedId = await performAccessKey(tab2.linkedBrowser, "y");
  is(focusedId, "tab2button", "button accesskey in tab2");

  // Press the accesskey for the chrome element while the content document is focused.
  focusedId = await performAccessKeyForChrome("z");
  is(focusedId, "chromebutton", "chromebutton accesskey");

  gBrowser.removeTab(tab1);
  gBrowser.removeTab(tab2);

  // Test whether access key for the newButton isn't available when content
  // consumes the key event.

  // When content in the tab3 consumes all keydown events.
  const gPageURL3 =
    "data:text/html,<body id='tab3body'>" +
    "<button id='tab3button' accesskey='y'>Button in Tab 3</button>" +
    "<script>" +
    "document.body.addEventListener('keydown', (event)=>{ event.preventDefault(); });" +
    "</script></body>";
  let tab3 = await BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL3);

  Services.focus.clearFocus(window);

  focusedId = await performAccessKey(tab3.linkedBrowser, "y");
  is(focusedId, "tab3button", "button accesskey in tab3 should be focused");

  newButton.onfocus = () => {
    ok(false, "chromebutton shouldn't get focus during testing with tab3");
  };

  // Press the accesskey for the chrome element while the content document is focused.
  focusedId = await performAccessKey(tab3.linkedBrowser, "z");
  is(
    focusedId,
    "tab3body",
    "button accesskey in tab3 should keep having focus"
  );

  newButton.onfocus = null;

  gBrowser.removeTab(tab3);

  // When content in the tab4 consumes all keypress events.
  const gPageURL4 =
    "data:text/html,<body id='tab4body'>" +
    "<button id='tab4button' accesskey='y'>Button in Tab 4</button>" +
    "<script>" +
    "document.body.addEventListener('keypress', (event)=>{ event.preventDefault(); });" +
    "</script></body>";
  let tab4 = await BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL4);

  Services.focus.clearFocus(window);

  focusedId = await performAccessKey(tab4.linkedBrowser, "y");
  is(focusedId, "tab4button", "button accesskey in tab4 should be focused");

  newButton.onfocus = () => {
    // EventStateManager handles accesskey before dispatching keypress event
    // into the DOM tree, therefore, chrome accesskey always wins focus from
    // content. However, this is different from shortcut keys.
    todo(false, "chromebutton shouldn't get focus during testing with tab4");
  };

  // Press the accesskey for the chrome element while the content document is focused.
  focusedId = await performAccessKey(tab4.linkedBrowser, "z");
  is(
    focusedId,
    "tab4body",
    "button accesskey in tab4 should keep having focus"
  );

  newButton.onfocus = null;

  gBrowser.removeTab(tab4);

  newButton.remove();

  // Accesskey on input label in shadow host should work
  const gPageURL5 = `data:text/html,
    <body>
      <div id="host">
        <template shadowrootmode="open">
          <label for="tab5Checkbox" accesskey="y">Label accesskey is "y":</label>
          <input id="tab5Checkbox" type="checkbox">
        </template>
      </div>
    </body>`;
  let tab5 = await BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL5);
  Services.focus.clearFocus(window);
  focusedId = await performAccessKey(tab5.linkedBrowser, "y");

  is(focusedId, "host", "the shadow host in tab5 should be focused");

  gBrowser.removeTab(tab5);
});

function performAccessKey(browser, key) {
  return new Promise(resolve => {
    let removeFocus, removeKeyDown, removeKeyUp;
    function callback() {
      removeFocus();
      removeKeyUp();
      removeKeyDown();

      SpecialPowers.spawn(browser, [], () => {
        let oldFocusedElement = content._oldFocusedElement;
        delete content._oldFocusedElement;
        return oldFocusedElement.id;
      }).then(oldFocus => resolve(oldFocus));
    }

    removeFocus = BrowserTestUtils.addContentEventListener(
      browser,
      "focus",
      callback,
      { capture: true },
      event => {
        if (!HTMLElement.isInstance(event.target)) {
          return false; // ignore window and document focus events
        }

        event.target.ownerGlobal._sent = true;
        let focusedElement = event.target.ownerGlobal.document.activeElement;
        event.target.ownerGlobal._oldFocusedElement = focusedElement;
        focusedElement.blur();
        return true;
      }
    );

    removeKeyDown = BrowserTestUtils.addContentEventListener(
      browser,
      "keydown",
      () => {},
      { capture: true },
      event => {
        event.target.ownerGlobal._sent = false;
        return true;
      }
    );

    removeKeyUp = BrowserTestUtils.addContentEventListener(
      browser,
      "keyup",
      callback,
      {},
      event => {
        if (!event.target.ownerGlobal._sent) {
          event.target.ownerGlobal._sent = true;
          let focusedElement = event.target.ownerGlobal.document.activeElement;
          event.target.ownerGlobal._oldFocusedElement = focusedElement;
          focusedElement.blur();
          return true;
        }

        return false;
      }
    );

    // Spawn an no-op content task to better ensure that the messages
    // for adding the event listeners above get handled.
    SpecialPowers.spawn(browser, [], () => {}).then(() => {
      EventUtils.synthesizeKey(key, { altKey: true, shiftKey: true });
    });
  });
}

// This version is used when a chrome element is expected to be found for an accesskey.
async function performAccessKeyForChrome(key) {
  let waitFocusChangePromise = BrowserTestUtils.waitForEvent(
    document,
    "focus",
    true
  );
  EventUtils.synthesizeKey(key, { altKey: true, shiftKey: true });
  await waitFocusChangePromise;
  return document.activeElement.id;
}
