Bug 1419091: Add keybinding tests. r=masayuki
☠☠ backed out by 1bd4af34fe10 ☠ ☠
authorDave Townsend <dtownsend@oxymoronical.com>
Mon, 08 Oct 2018 11:07:00 -0700
changeset 490655 58d5a882493d491780115ebfe6c561866fc07dda
parent 490654 105be1518d2ead905311d1ae9d47f4633e0a3539
child 490656 72ccc94449163738199ec04886f2f5513bce5122
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersmasayuki
bugs1419091
milestone64.0a1
Bug 1419091: Add keybinding tests. r=masayuki Differential Revision: https://phabricator.services.mozilla.com/D3030
dom/tests/mochitest/keyhandling/browsertest.html
dom/tests/mochitest/keyhandling/chrome.ini
dom/tests/mochitest/keyhandling/mochitest.ini
dom/tests/mochitest/keyhandling/test_browser.xul
dom/tests/mochitest/keyhandling/test_editor.xul
dom/tests/mochitest/keyhandling/test_input.html
dom/tests/mochitest/keyhandling/test_textarea.html
dom/tests/mochitest/keyhandling/test_windowed.xul
dom/tests/moz.build
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/keyhandling/browsertest.html
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+
+<html>
+<body>
+<p style="white-space: nowrap">
+  A long paragraph to make a horizontal scrollbar.
+  A long paragraph to make a horizontal scrollbar.
+  A long paragraph to make a horizontal scrollbar.
+  A long paragraph to make a horizontal scrollbar.
+  A long paragraph to make a horizontal scrollbar.
+  A long paragraph to make a horizontal scrollbar.
+  A long paragraph to make a horizontal scrollbar.
+  A long paragraph to make a horizontal scrollbar.
+  A long paragraph to make a horizontal scrollbar.
+  A long paragraph to make a horizontal scrollbar.
+</p>
+<p id="paragraph">Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/keyhandling/chrome.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+# nsIWidget::SynthesizeNativeKeyEvent() required (Bug 1410525 for headless)
+skip-if = os == 'linux' || os == 'android' || headless
+
+[test_browser.xul]
+support-files =
+  browsertest.html
+[test_editor.xul]
+[test_windowed.xul]
+support-files =
+  test_input.html
+  test_textarea.html
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/keyhandling/mochitest.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+# nsIWidget::SynthesizeNativeKeyEvent() required (Bug 1410525 for headless)
+skip-if = os == 'linux' || os == 'android' || headless
+
+[test_input.html]
+[test_textarea.html]
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/keyhandling/test_browser.xul
@@ -0,0 +1,255 @@
+<?xml version="1.0"?>
+
+<window title="Browser element keyhandling tests"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        onload="test();">
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/NativeKeyCodes.js"/>
+
+  <script type="application/javascript">
+  <![CDATA[
+    SimpleTest.waitForExplicitFinish();
+
+    const IS_MAC = navigator.platform.indexOf("Mac") === 0;
+    const VK = {};
+    const CHARS = {};
+
+    // Copied values from NativeKeyCodes.js and EventUtils.js
+    if (IS_MAC) {
+      VK.LEFT = MAC_VK_LeftArrow;
+      CHARS.LEFT = "\uF702";
+      VK.RIGHT = MAC_VK_RightArrow;
+      CHARS.RIGHT = "\uF703";
+      VK.UP = MAC_VK_UpArrow;
+      CHARS.UP = "\uF700";
+      VK.DOWN = MAC_VK_DownArrow;
+      CHARS.DOWN = "\uF701";
+      VK.SPACE = MAC_VK_Space;
+      VK.PGDOWN = MAC_VK_PageDown;
+      CHARS.PGDOWN = "\uF72D";
+      VK.PGUP = MAC_VK_PageUp;
+      CHARS.PGUP = "\uF72C";
+      VK.C = MAC_VK_ANSI_C;
+      VK.HOME = MAC_VK_Home;
+      CHARS.HOME = "\uF729";
+      VK.END = MAC_VK_End;
+      CHARS.END = "\uF72B";
+    } else {
+      VK.LEFT = WIN_VK_LEFT;
+      CHARS.LEFT = "";
+      VK.RIGHT = WIN_VK_RIGHT;
+      CHARS.RIGHT = "";
+      VK.UP = WIN_VK_UP;
+      CHARS.UP = "";
+      VK.DOWN = WIN_VK_DOWN;
+      CHARS.DOWN = "";
+      VK.SPACE = WIN_VK_SPACE;
+      VK.PGDOWN = WIN_VK_NEXT;
+      CHARS.PGDOWN = "";
+      VK.PGUP = WIN_VK_PRIOR;
+      CHARS.PGUP = "";
+      VK.C = WIN_VK_C;
+      VK.HOME = WIN_VK_HOME;
+      CHARS.HOME = "";
+      VK.END = WIN_VK_END;
+      CHARS.END = "";
+    }
+
+    function waitForEvent(target, event) {
+      info(`Waiting for ${event} event.`);
+      return new Promise(resolve => {
+        browser.addEventListener(event, resolve, { once: true });
+      });
+    }
+
+    function synthesizeKey(keyCode, modifiers, chars) {
+      return new Promise((resolve, reject) => {
+        if (!synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, keyCode, modifiers, chars, chars, resolve)) {
+          reject();
+        }
+      });
+    }
+
+    function getWindowProperties(browser, properties) {
+      let results = {};
+      for (let prop of properties) {
+        results[prop] = browser.contentWindow[prop];
+      }
+
+      return results;
+    }
+
+    function getScrollPosition(browser) {
+      return getWindowProperties(browser, ["scrollX", "scrollY"]);
+    }
+
+    async function test() {
+      // Smooth scrolling makes scroll events take time and it's difficult to know
+      // when they've ended, so turn it off for this test.
+      await SpecialPowers.pushPrefEnv({"set": [["general.smoothScroll", false]] });
+
+      let browser = document.getElementById("browser");
+      browser.focus();
+      let { scrollX, scrollY } = await getScrollPosition(browser);
+      is(scrollX, 0, "Should not be scrolled");
+      is(scrollY, 0, "Should not be scrolled");
+
+      info("down");
+      await synthesizeKey(VK.DOWN, {}, CHARS.DOWN);
+      await waitForEvent(browser.contentWindow, "scroll");
+      let { scrollX: lineScrollX, scrollY: lineScrollY } = await getScrollPosition(browser);
+      is(lineScrollX, 0, "Should not be scrolled");
+      ok(lineScrollY > 0, "Should be scrolled");
+
+      info("up");
+      await synthesizeKey(VK.UP, {}, CHARS.UP);
+      await waitForEvent(browser.contentWindow, "scroll");
+      {
+        let { scrollX, scrollY } = await getScrollPosition(browser);
+        is(scrollX, 0, "Should not be scrolled");
+        is(scrollY, 0, "Should not be scrolled");
+      }
+
+      info("right");
+      await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+      await waitForEvent(browser.contentWindow, "scroll");
+      let { scrollX: rightScrollX, scrollY: rightScrollY } = await getScrollPosition(browser);
+      ok(rightScrollX > 0, "Should be scrolled");
+      is(rightScrollY, 0, "Should not be scrolled");
+
+      info("left");
+      await synthesizeKey(VK.LEFT, {}, CHARS.LEFT);
+      await waitForEvent(browser.contentWindow, "scroll");
+      {
+        let { scrollX, scrollY } = await getScrollPosition(browser);
+        is(scrollX, 0, "Should not be scrolled");
+        is(scrollY, 0, "Should not be scrolled");
+      }
+
+      info("space");
+      await synthesizeKey(VK.SPACE, {}, " ");
+      await waitForEvent(browser.contentWindow, "scroll");
+      let { scrollX: pageScrollX, scrollY: pageScrollY } = await getScrollPosition(browser);
+      is(pageScrollX, 0, "Should not be scrolled");
+      ok(pageScrollY > lineScrollY, "Should be scrolled more than a single line");
+
+      info("shift+space");
+      await synthesizeKey(VK.SPACE, { shiftKey: true }, " ");
+      await waitForEvent(browser.contentWindow, "scroll");
+      {
+        let { scrollX, scrollY } = await getScrollPosition(browser);
+        is(scrollX, 0, "Should not be scrolled");
+        is(scrollY, 0, "Should not be scrolled");
+      }
+
+      info("page down");
+      await synthesizeKey(VK.PGDOWN, {}, CHARS.PGDOWN);
+      await waitForEvent(browser.contentWindow, "scroll");
+      {
+        let { scrollX, scrollY } = await getScrollPosition(browser);
+        is(scrollX, 0, "Should not be scrolled");
+        is(scrollY, pageScrollY, "Should be scrolled a page");
+      }
+
+      info("page up");
+      await synthesizeKey(VK.PGUP, {}, CHARS.PGUP);
+      await waitForEvent(browser.contentWindow, "scroll");
+      {
+        let { scrollX, scrollY } = await getScrollPosition(browser);
+        is(scrollX, 0, "Should not be scrolled");
+        is(scrollY, 0, "Should not be scrolled");
+      }
+
+      info("accel+down");
+      await synthesizeKey(VK.DOWN, { accelKey: true }, CHARS.DOWN);
+      await waitForEvent(browser.contentWindow, "scroll");
+      {
+        let { scrollX, scrollY, innerHeight } = await getWindowProperties(browser, ["scrollX", "scrollY", "innerHeight"]);
+        is(scrollX, 0, "Should not be scrolled");
+        // We can't know the scrollbar height so check that we're scrolled to within 100px of what we expect.
+        isfuzzy(scrollY, browser.contentDocument.body.clientHeight - innerHeight, 100, "Should be scrolled to the end.");
+      }
+
+      info("accel+up");
+      await synthesizeKey(VK.UP, { accelKey: true }, CHARS.UP);
+      await waitForEvent(browser.contentWindow, "scroll");
+      {
+        let { scrollX, scrollY } = await getScrollPosition(browser);
+        is(scrollX, 0, "Should not be scrolled");
+        is(scrollY, 0, "Should not be scrolled");
+      }
+
+      info("end");
+      await synthesizeKey(VK.END, {}, CHARS.END);
+      await waitForEvent(browser.contentWindow, "scroll");
+      {
+        let { scrollX, scrollY, innerHeight } = await getWindowProperties(browser, ["scrollX", "scrollY", "innerHeight"]);
+        is(scrollX, 0, "Should not be scrolled");
+        // We can't know the scrollbar height so check that we're scrolled to within 100px of what we expect.
+        isfuzzy(scrollY, browser.contentDocument.body.clientHeight - innerHeight, 100, "Should be scrolled to the end.");
+      }
+
+      info("home");
+      await synthesizeKey(VK.HOME, {}, CHARS.HOME);
+      await waitForEvent(browser.contentWindow, "scroll");
+      {
+        let { scrollX, scrollY } = await getScrollPosition(browser);
+        is(scrollX, 0, "Should not be scrolled");
+        is(scrollY, 0, "Should not be scrolled");
+      }
+
+      // Select the start of the first paragraph
+      let paragraph = browser.contentDocument.getElementById("paragraph");
+      let selection = browser.contentWindow.getSelection();
+      selection.setBaseAndExtent(paragraph.firstChild, 0, paragraph.firstChild, "Lots of".length);
+
+      info("copy");
+      await SimpleTest.promiseClipboardChange("Lots of", () => {
+        synthesizeKey(VK.C, { accelKey: true }, "c");
+      });
+
+      for (let i = 0; i < " paragraphs".length; i++) {
+        info("select right");
+        await synthesizeKey(VK.RIGHT, { shiftKey: true }, CHARS.RIGHT);
+      }
+
+      info("copy");
+      await SimpleTest.promiseClipboardChange("Lots of paragraphs", () => {
+        synthesizeKey(VK.C, { accelKey: true }, "c");
+      });
+
+      for (let i = 0; i < " paragraphs".length; i++) {
+        info("select left");
+        await synthesizeKey(VK.LEFT, { shiftKey: true }, CHARS.LEFT);
+      }
+
+      info("copy");
+      await SimpleTest.promiseClipboardChange("Lots of", () => {
+        synthesizeKey(VK.C, { accelKey: true }, "c");
+      });
+
+      info("select down");
+      await synthesizeKey(VK.DOWN, { shiftKey: true }, CHARS.DOWN);
+
+      info("copy");
+      await SimpleTest.promiseClipboardChange("Lots of paragraphs to make a vertical scrollbar.\n\nLots of", () => {
+        synthesizeKey(VK.C, { accelKey: true }, "c");
+      });
+
+      SimpleTest.finish();
+    }
+  ]]>
+  </script>
+
+  <body xmlns="http://www.w3.org/1999/xhtml">
+    <p id="display"></p>
+    <div id="content" style="display:none;"></div>
+    <pre id="test"></pre>
+  </body>
+  <browser id="browser" src="browsertest.html" style="height: 500px"/>
+</window>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/keyhandling/test_editor.xul
@@ -0,0 +1,271 @@
+<?xml version="1.0"?>
+
+<window title="Browser element keyhandling tests"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        onload="test();">
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/NativeKeyCodes.js"/>
+
+  <script type="application/javascript">
+  <![CDATA[
+    SimpleTest.waitForExplicitFinish();
+
+    const IS_MAC = navigator.platform.indexOf("Mac") === 0;
+    const VK = {};
+    const CHARS = {};
+
+    // Copied values from NativeKeyCodes.js and EventUtils.js
+    if (IS_MAC) {
+      VK.LEFT = MAC_VK_LeftArrow;
+      CHARS.LEFT = "\uF702";
+      VK.RIGHT = MAC_VK_RightArrow;
+      CHARS.RIGHT = "\uF703";
+      VK.UP = MAC_VK_UpArrow;
+      CHARS.UP = "\uF700";
+      VK.DOWN = MAC_VK_DownArrow;
+      CHARS.DOWN = "\uF701";
+      VK.SPACE = MAC_VK_Space;
+      VK.X = MAC_VK_ANSI_X;
+      VK.V = MAC_VK_ANSI_V;
+      VK.A = MAC_VK_ANSI_A;
+      VK.Z = MAC_VK_ANSI_Z;
+      VK.F = MAC_VK_ANSI_F;
+      VK.O = MAC_VK_ANSI_O;
+      VK.BACKSPACE = MAC_VK_PC_Backspace;
+      CHARS.BACKSPACE = "\u007F";
+    } else {
+      VK.LEFT = WIN_VK_LEFT;
+      CHARS.LEFT = "";
+      VK.RIGHT = WIN_VK_RIGHT;
+      CHARS.RIGHT = "";
+      VK.HOME = WIN_VK_HOME;
+      CHARS.HOME = "";
+      VK.END = WIN_VK_END;
+      CHARS.END = "";
+      VK.SPACE = WIN_VK_SPACE;
+      VK.X = WIN_VK_X;
+      VK.V = WIN_VK_V;
+      VK.A = WIN_VK_A;
+      VK.Z = WIN_VK_Z;
+      VK.Y = WIN_VK_Y;
+      VK.F = WIN_VK_F;
+      VK.O = WIN_VK_O;
+      VK.BACKSPACE = WIN_VK_BACK;
+      CHARS.BACKSPACE = "";
+    }
+
+    function waitForEvent(target, event) {
+      info(`Waiting for ${event} event.`);
+      return new Promise(resolve => {
+        browser.addEventListener(event, resolve, { once: true });
+      });
+    }
+
+    function synthesizeKey(keyCode, modifiers, chars) {
+      return new Promise((resolve, reject) => {
+        if (!synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, keyCode, modifiers, chars, chars, resolve)) {
+          reject();
+        }
+      });
+    }
+
+    function* nodes(element) {
+      let node = element.firstChild;
+      while (node) {
+        yield node;
+
+        if (node.nodeType === Node.ELEMENT_NODE) {
+          yield* nodes(node);
+        }
+
+        node = node.nextSibling;
+      }
+    }
+
+    async function checkElement(element, start, selectedText, content = "Test text") {
+      selectionPosition = (element, range) => {
+        let pos = 0;
+        for (let node of nodes(element)) {
+          if (node.nodeType === Node.TEXT_NODE) {
+            if (node === range.startContainer) {
+              return pos + range.startOffset;
+            } else {
+              pos += node.nodeValue.length;
+            }
+          } else if (node === range.startContainer) {
+            for (let i = 0; i < range.startOffset; i++) {
+              pos += node.childNodes[i].textContent.length;
+            }
+
+            return pos;
+          }
+        }
+
+        throw new Error("startContainer of range never found.");
+      }
+
+      isReady = () => {
+        let selection = element.contentWindow.getSelection();
+        let range = selection.getRangeAt(0);
+        let pos = selectionPosition(element.contentDocument.documentElement, range);
+
+        if (start != pos) {
+          return false;
+        }
+        if (selectedText != selection.toString()) {
+          return false;
+        }
+        if (content != element.contentDocument.documentElement.textContent) {
+          return false;
+        }
+        return true;
+      };
+
+      for (let i = 0; i < 10; i++) {
+        if (isReady()) {
+          return;
+        }
+
+        SimpleTest.requestFlakyTimeout("Polling for changes to apply");
+        await new Promise(resolve => setTimeout(resolve, 50));
+      }
+      ok(false, `Timed out waiting for state ${start} "${selectedText}" "${content}"`);
+      let selection = element.contentWindow.getSelection();
+      let range = selection.getRangeAt(0);
+      info(`${selectionPosition(element.contentDocument.documentElement, range)} "${selection.toString()}" "${element.contentDocument.documentElement.textContent}"`);
+    }
+
+    async function test() {
+      let editor = document.getElementById("editor");
+      editor.contentDocument.designMode = "on";
+      editor.contentWindow.focus();
+      let edit = editor.getEditor(editor.contentWindow);
+      edit.beginningOfDocument();
+
+      await checkElement(editor, 0, "");
+
+      info("right");
+      await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+      await checkElement(editor, 1, "");
+
+      info("shift+right");
+      await synthesizeKey(VK.RIGHT, { shiftKey: true }, CHARS.RIGHT);
+      await checkElement(editor, 1, "e");
+
+      info("shift+right");
+      await synthesizeKey(VK.RIGHT, { shiftKey: true }, CHARS.RIGHT);
+      await checkElement(editor, 1, "es");
+
+      info("shift+left");
+      await synthesizeKey(VK.LEFT, { shiftKey: true }, CHARS.LEFT);
+      await checkElement(editor, 1, "e");
+
+      info("shift+left");
+      await synthesizeKey(VK.LEFT, { shiftKey: true }, CHARS.LEFT);
+      await checkElement(editor, 1, "");
+
+      info("shift+left");
+      await synthesizeKey(VK.LEFT, { shiftKey: true }, CHARS.LEFT);
+      await checkElement(editor, 0, "T");
+
+      info("shift+right");
+      await synthesizeKey(VK.RIGHT, { shiftKey: true }, CHARS.RIGHT);
+      await checkElement(editor, 1, "");
+
+      info("left");
+      await synthesizeKey(VK.LEFT, {}, CHARS.LEFT);
+      await checkElement(editor, 0, "");
+
+      if (IS_MAC) {
+        info("down");
+        await synthesizeKey(VK.DOWN, { shiftKey: true }, CHARS.DOWN);
+      } else {
+        info("end");
+        await synthesizeKey(VK.END, { shiftKey: true }, CHARS.END);
+      }
+      await checkElement(editor, 0, "Test text");
+
+      info("cut");
+      await synthesizeKey(VK.X, { accelKey: true }, "x");
+      await checkElement(editor, 0, "", "");
+      let text = SpecialPowers.getClipboardData("text/unicode");
+      is(text, "Test text", "Should have cut to the clipboard");
+      SpecialPowers.clipboardCopyString("New text");
+
+      info("paste");
+      await synthesizeKey(VK.V, { accelKey: true }, "v");
+      await checkElement(editor, 8, "", "New text");
+
+      if (IS_MAC) {
+        info("up");
+        await synthesizeKey(VK.UP, {}, CHARS.UP);
+      } else {
+        info("home");
+        await synthesizeKey(VK.HOME, {}, CHARS.HOME);
+      }
+      await checkElement(editor, 0, "", "New text");
+
+      info("select all");
+      await synthesizeKey(VK.A, { accelKey: true}, "a", "select");
+      await checkElement(editor, 0, "New text", "New text");
+
+      info("right");
+      await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+      await checkElement(editor, 8, "", "New text");
+
+      info("word left");
+      if (IS_MAC) {
+        await synthesizeKey(VK.LEFT, { altKey: true }, CHARS.LEFT);
+      } else {
+        await synthesizeKey(VK.LEFT, { ctrlKey: true }, CHARS.LEFT);
+      }
+      await checkElement(editor, 4, "", "New text");
+
+      info("delete word left");
+      if (IS_MAC) {
+        await synthesizeKey(VK.BACKSPACE, { altKey: true }, CHARS.BACKSPACE);
+      } else {
+        await synthesizeKey(VK.BACKSPACE, { ctrlKey: true }, CHARS.BACKSPACE);
+      }
+      await checkElement(editor, 0, "", "text");
+
+      info("undo");
+      await synthesizeKey(VK.Z, { accelKey: true }, "z");
+      await checkElement(editor, 4, "", "New text");
+
+      info("redo");
+      if (IS_MAC) {
+        await synthesizeKey(VK.Z, { accelKey: true, shiftKey: true }, "z");
+      } else {
+        await synthesizeKey(VK.Y, { accelKey: true }, "y");
+      }
+      await checkElement(editor, 0, "", "text");
+
+      info("typing");
+      await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+      await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+      await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+      await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+      await synthesizeKey(VK.SPACE, {}, " ");
+      await synthesizeKey(VK.F, {}, "f");
+      await synthesizeKey(VK.O, {}, "o");
+      await synthesizeKey(VK.O, {}, "o");
+      await checkElement(editor, 8, "", "text foo");
+
+      SimpleTest.finish();
+    }
+  ]]>
+  </script>
+
+  <body xmlns="http://www.w3.org/1999/xhtml">
+    <p id="display"></p>
+    <div id="content" style="display:none;"></div>
+    <pre id="test"></pre>
+  </body>
+  <editor id="editor" editortype="text" src="data:text/plain,Test text" style="height: 500px"/>
+</window>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/keyhandling/test_input.html
@@ -0,0 +1,226 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>input key handling</title>
+
+<script type="text/javascript" src="http://mochi.test:8888/tests/SimpleTest/SimpleTest.js"></script>
+<script type="text/javascript" src="http://mochi.test:8888/tests/SimpleTest/EventUtils.js"></script>
+<script type="text/javascript" src="http://mochi.test:8888/tests/SimpleTest/NativeKeyCodes.js"></script>
+<link rel="stylesheet" type="text/css" href="http://mochi.test:8888/tests/SimpleTest/test.css" />
+
+<script type="text/javascript">
+const IS_MAC = navigator.platform.indexOf("Mac") == 0;
+const VK = {};
+const CHARS = {};
+
+if (IS_MAC) {
+  VK.LEFT = MAC_VK_LeftArrow;
+  CHARS.LEFT = "\uF702";
+  VK.RIGHT = MAC_VK_RightArrow;
+  CHARS.RIGHT = "\uF703";
+  VK.UP = MAC_VK_UpArrow;
+  CHARS.UP = "\uF700";
+  VK.DOWN = MAC_VK_DownArrow;
+  CHARS.DOWN = "\uF701";
+  VK.X = MAC_VK_ANSI_X;
+  VK.V = MAC_VK_ANSI_V;
+  VK.A = MAC_VK_ANSI_A;
+  VK.F = MAC_VK_ANSI_F;
+  VK.O = MAC_VK_ANSI_O;
+  VK.BACKSPACE = MAC_VK_PC_Backspace;
+  CHARS.BACKSPACE = "\u007F";
+  VK.Z = MAC_VK_ANSI_Z;
+  VK.SPACE = MAC_VK_Space;
+} else {
+  VK.LEFT = WIN_VK_LEFT;
+  CHARS.LEFT = "";
+  VK.RIGHT = WIN_VK_RIGHT;
+  CHARS.RIGHT = "";
+  VK.UP = WIN_VK_UP;
+  CHARS.UP = "";
+  VK.DOWN = WIN_VK_DOWN;
+  CHARS.DOWN = "";
+  VK.X = WIN_VK_X;
+  VK.V = WIN_VK_V;
+  VK.A = WIN_VK_A;
+  VK.F = WIN_VK_F;
+  VK.O = WIN_VK_O;
+  VK.END = WIN_VK_END;
+  CHARS.END = "";
+  VK.HOME = WIN_VK_HOME;
+  CHARS.HOME = "";
+  VK.BACKSPACE = WIN_VK_BACK;
+  CHARS.BACKSPACE = "";
+  VK.Z = WIN_VK_Z;
+  VK.SPACE = WIN_VK_SPACE;
+}
+
+if (window.opener) {
+  ok = window.opener.ok;
+  is = window.opener.is;
+}
+
+function synthesizeKey(keyCode, modifiers, chars, event = "keyup") {
+  return new Promise((resolve, reject) => {
+    window.addEventListener(event, resolve, { once: true });
+
+    if (!synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, keyCode, modifiers, chars, chars)) {
+      reject();
+    }
+  });
+}
+
+async function checkElement(element, start, end, content = "Test text") {
+  isReady = () => {
+    if (start != element.selectionStart) {
+      return false;
+    }
+    if (end != element.selectionEnd) {
+      return false;
+    }
+    if (content != element.value) {
+      return false;
+    }
+    return true;
+  };
+
+  for (let i = 0; i < 10; i++) {
+    if (isReady()) {
+      return;
+    }
+
+    SimpleTest.requestFlakyTimeout("Polling for changes to apply");
+    await new Promise(resolve => setTimeout(resolve, 50));
+  }
+  ok(false, "Timed out waiting for state");
+  is(element.selectionStart, start, "Should have the right selectionStart");
+  is(element.selectionEnd, end, "Should have the right selectionEnd");
+  is(element.value, content, "Should have the right value");
+}
+
+async function startTest() {
+  let input = document.getElementById("input");
+  input.focus();
+  await checkElement(input, 0, 0);
+
+  info("right");
+  await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+  await checkElement(input, 1, 1);
+
+  info("shift+right");
+  await synthesizeKey(VK.RIGHT, { shiftKey: true }, CHARS.RIGHT);
+  await checkElement(input, 1, 2);
+
+  info("shift+right");
+  await synthesizeKey(VK.RIGHT, { shiftKey: true }, CHARS.RIGHT);
+  await checkElement(input, 1, 3);
+
+  info("shift+left");
+  await synthesizeKey(VK.LEFT, { shiftKey: true }, CHARS.LEFT);
+  await checkElement(input, 1, 2);
+
+  info("shift+left");
+  await synthesizeKey(VK.LEFT, { shiftKey: true }, CHARS.LEFT);
+  await checkElement(input, 1, 1);
+
+  info("shift+left");
+  await synthesizeKey(VK.LEFT, { shiftKey: true }, CHARS.LEFT);
+  await checkElement(input, 0, 1);
+
+  info("shift+right");
+  await synthesizeKey(VK.RIGHT, { shiftKey: true }, CHARS.RIGHT);
+  await checkElement(input, 1, 1);
+
+  info("left");
+  await synthesizeKey(VK.LEFT, {}, CHARS.LEFT);
+  await checkElement(input, 0, 0);
+
+  if (IS_MAC) {
+    info("down");
+    await synthesizeKey(VK.DOWN, { shiftKey: true }, CHARS.DOWN);
+  } else {
+    info("end");
+    await synthesizeKey(VK.END, { shiftKey: true }, CHARS.END);
+  }
+  await checkElement(input, 0, 9);
+
+  info("cut");
+  await synthesizeKey(VK.X, { accelKey: true }, "x", "input");
+  await checkElement(input, 0, 0, "");
+  let text = SpecialPowers.getClipboardData("text/unicode");
+  is(text, "Test text", "Should have cut to the clipboard");
+  SpecialPowers.clipboardCopyString("New text");
+
+  info("paste");
+  await synthesizeKey(VK.V, { accelKey: true }, "v", "input");
+  await checkElement(input, 8, 8, "New text");
+
+  if (IS_MAC) {
+    info("up");
+    await synthesizeKey(VK.UP, {}, CHARS.UP);
+  } else {
+    info("home");
+    await synthesizeKey(VK.HOME, {}, CHARS.HOME);
+  }
+  await checkElement(input, 0, 0, "New text");
+
+  info("select all");
+  await synthesizeKey(VK.A, { accelKey: true}, "a", "select");
+  await checkElement(input, 0, 8, "New text");
+
+  info("right");
+  await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+  await checkElement(input, 8, 8, "New text");
+
+  info("word left");
+  if (IS_MAC) {
+    await synthesizeKey(VK.LEFT, { altKey: true }, CHARS.LEFT);
+  } else {
+    await synthesizeKey(VK.LEFT, { ctrlKey: true }, CHARS.LEFT);
+  }
+  await checkElement(input, 4, 4, "New text");
+
+  info("delete word left");
+  if (IS_MAC) {
+    await synthesizeKey(VK.BACKSPACE, { altKey: true }, CHARS.BACKSPACE);
+  } else {
+    await synthesizeKey(VK.BACKSPACE, { ctrlKey: true }, CHARS.BACKSPACE);
+  }
+  await checkElement(input, 0, 0, "text");
+
+  info("undo");
+  await synthesizeKey(VK.Z, { accelKey: true }, "", "input");
+  await checkElement(input, 4, 4, "New text");
+
+  info("redo");
+  await synthesizeKey(VK.Z, { accelKey: true, shiftKey: true }, "", "input");
+  await checkElement(input, 0, 0, "text");
+
+  info("typing");
+  await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+  await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+  await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+  await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+  await synthesizeKey(VK.SPACE, {}, " ");
+  await synthesizeKey(VK.F, {}, "f");
+  await synthesizeKey(VK.O, {}, "o");
+  await synthesizeKey(VK.O, {}, "o");
+  await checkElement(input, 8, 8, "text foo");
+}
+
+async function runTest() {
+  // When running in windowed mode the caller will start the test once we have
+  // focus.
+  if (window.opener) {
+    return;
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  await startTest();
+  SimpleTest.finish();
+}
+</script>
+</head>
+<body onload="runTest();">
+<input id=input value="Test text"/>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/keyhandling/test_textarea.html
@@ -0,0 +1,226 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>input key handling</title>
+
+<script type="text/javascript" src="http://mochi.test:8888/tests/SimpleTest/SimpleTest.js"></script>
+<script type="text/javascript" src="http://mochi.test:8888/tests/SimpleTest/EventUtils.js"></script>
+<script type="text/javascript" src="http://mochi.test:8888/tests/SimpleTest/NativeKeyCodes.js"></script>
+<link rel="stylesheet" type="text/css" href="http://mochi.test:8888/tests/SimpleTest/test.css" />
+
+<script type="text/javascript">
+const IS_MAC = navigator.platform.indexOf("Mac") == 0;
+const VK = {};
+const CHARS = {};
+
+if (IS_MAC) {
+  VK.LEFT = MAC_VK_LeftArrow;
+  CHARS.LEFT = "\uF702";
+  VK.RIGHT = MAC_VK_RightArrow;
+  CHARS.RIGHT = "\uF703";
+  VK.UP = MAC_VK_UpArrow;
+  CHARS.UP = "\uF700";
+  VK.DOWN = MAC_VK_DownArrow;
+  CHARS.DOWN = "\uF701";
+  VK.X = MAC_VK_ANSI_X;
+  VK.V = MAC_VK_ANSI_V;
+  VK.A = MAC_VK_ANSI_A;
+  VK.F = MAC_VK_ANSI_F;
+  VK.O = MAC_VK_ANSI_O;
+  VK.BACKSPACE = MAC_VK_PC_Backspace;
+  CHARS.BACKSPACE = "\u007F";
+  VK.Z = MAC_VK_ANSI_Z;
+  VK.SPACE = MAC_VK_Space;
+} else {
+  VK.LEFT = WIN_VK_LEFT;
+  CHARS.LEFT = "";
+  VK.RIGHT = WIN_VK_RIGHT;
+  CHARS.RIGHT = "";
+  VK.UP = WIN_VK_UP;
+  CHARS.UP = "";
+  VK.DOWN = WIN_VK_DOWN;
+  CHARS.DOWN = "";
+  VK.X = WIN_VK_X;
+  VK.V = WIN_VK_V;
+  VK.A = WIN_VK_A;
+  VK.F = WIN_VK_F;
+  VK.O = WIN_VK_O;
+  VK.END = WIN_VK_END;
+  CHARS.END = "";
+  VK.HOME = WIN_VK_HOME;
+  CHARS.HOME = "";
+  VK.BACKSPACE = WIN_VK_BACK;
+  CHARS.BACKSPACE = "";
+  VK.Z = WIN_VK_Z;
+  VK.SPACE = WIN_VK_SPACE;
+}
+
+if (window.opener) {
+  ok = window.opener.ok;
+  is = window.opener.is;
+}
+
+function synthesizeKey(keyCode, modifiers, chars, event = "keyup") {
+  return new Promise((resolve, reject) => {
+    window.addEventListener(event, resolve, { once: true });
+
+    if (!synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, keyCode, modifiers, chars, chars)) {
+      reject();
+    }
+  });
+}
+
+async function checkElement(element, start, end, content = "Test text") {
+  isReady = () => {
+    if (start != element.selectionStart) {
+      return false;
+    }
+    if (end != element.selectionEnd) {
+      return false;
+    }
+    if (content != element.value) {
+      return false;
+    }
+    return true;
+  };
+
+  for (let i = 0; i < 10; i++) {
+    if (isReady()) {
+      return;
+    }
+
+    SimpleTest.requestFlakyTimeout("Polling for changes to apply");
+    await new Promise(resolve => setTimeout(resolve, 50));
+  }
+  ok(false, "Timed out waiting for state");
+  is(element.selectionStart, start, "Should have the right selectionStart");
+  is(element.selectionEnd, end, "Should have the right selectionEnd");
+  is(element.value, content, "Should have the right value");
+}
+
+async function startTest() {
+  let input = document.getElementById("input");
+  input.focus();
+  await checkElement(input, 0, 0);
+
+  info("right");
+  await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+  await checkElement(input, 1, 1);
+
+  info("shift+right");
+  await synthesizeKey(VK.RIGHT, { shiftKey: true }, CHARS.RIGHT);
+  await checkElement(input, 1, 2);
+
+  info("shift+right");
+  await synthesizeKey(VK.RIGHT, { shiftKey: true }, CHARS.RIGHT);
+  await checkElement(input, 1, 3);
+
+  info("shift+left");
+  await synthesizeKey(VK.LEFT, { shiftKey: true }, CHARS.LEFT);
+  await checkElement(input, 1, 2);
+
+  info("shift+left");
+  await synthesizeKey(VK.LEFT, { shiftKey: true }, CHARS.LEFT);
+  await checkElement(input, 1, 1);
+
+  info("shift+left");
+  await synthesizeKey(VK.LEFT, { shiftKey: true }, CHARS.LEFT);
+  await checkElement(input, 0, 1);
+
+  info("shift+right");
+  await synthesizeKey(VK.RIGHT, { shiftKey: true }, CHARS.RIGHT);
+  await checkElement(input, 1, 1);
+
+  info("left");
+  await synthesizeKey(VK.LEFT, {}, CHARS.LEFT);
+  await checkElement(input, 0, 0);
+
+  if (IS_MAC) {
+    info("down");
+    await synthesizeKey(VK.DOWN, { shiftKey: true }, CHARS.DOWN);
+  } else {
+    info("end");
+    await synthesizeKey(VK.END, { shiftKey: true }, CHARS.END);
+  }
+  await checkElement(input, 0, 9);
+
+  info("cut");
+  await synthesizeKey(VK.X, { accelKey: true }, "x", "input");
+  await checkElement(input, 0, 0, "");
+  let text = SpecialPowers.getClipboardData("text/unicode");
+  is(text, "Test text", "Should have cut to the clipboard");
+  SpecialPowers.clipboardCopyString("New text");
+
+  info("paste");
+  await synthesizeKey(VK.V, { accelKey: true }, "v", "input");
+  await checkElement(input, 8, 8, "New text");
+
+  if (IS_MAC) {
+    info("up");
+    await synthesizeKey(VK.UP, {}, CHARS.UP);
+  } else {
+    info("home");
+    await synthesizeKey(VK.HOME, {}, CHARS.HOME);
+  }
+  await checkElement(input, 0, 0, "New text");
+
+  info("select all");
+  await synthesizeKey(VK.A, { accelKey: true}, "a", "select");
+  await checkElement(input, 0, 8, "New text");
+
+  info("right");
+  await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+  await checkElement(input, 8, 8, "New text");
+
+  info("word left");
+  if (IS_MAC) {
+    await synthesizeKey(VK.LEFT, { altKey: true }, CHARS.LEFT);
+  } else {
+    await synthesizeKey(VK.LEFT, { ctrlKey: true }, CHARS.LEFT);
+  }
+  await checkElement(input, 4, 4, "New text");
+
+  info("delete word left");
+  if (IS_MAC) {
+    await synthesizeKey(VK.BACKSPACE, { altKey: true }, CHARS.BACKSPACE);
+  } else {
+    await synthesizeKey(VK.BACKSPACE, { ctrlKey: true }, CHARS.BACKSPACE);
+  }
+  await checkElement(input, 0, 0, "text");
+
+  info("undo");
+  await synthesizeKey(VK.Z, { accelKey: true }, "", "input");
+  await checkElement(input, 4, 4, "New text");
+
+  info("redo");
+  await synthesizeKey(VK.Z, { accelKey: true, shiftKey: true }, "", "input");
+  await checkElement(input, 0, 0, "text");
+
+  info("typing");
+  await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+  await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+  await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+  await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+  await synthesizeKey(VK.SPACE, {}, " ");
+  await synthesizeKey(VK.F, {}, "f");
+  await synthesizeKey(VK.O, {}, "o");
+  await synthesizeKey(VK.O, {}, "o");
+  await checkElement(input, 8, 8, "text foo");
+}
+
+async function runTest() {
+  // When running in windowed mode the caller will start the test once we have
+  // focus.
+  if (window.opener) {
+    return;
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  await startTest();
+  SimpleTest.finish();
+}
+</script>
+</head>
+<body onload="runTest();">
+<textarea id=input>Test text</textarea>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/keyhandling/test_windowed.xul
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+
+<window title="Top-level window keyhandling tests"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        onload="test();">
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+  <script type="application/javascript">
+  <![CDATA[
+    SimpleTest.waitForExplicitFinish();
+
+    async function run_test(path) {
+      let win = window.openDialog(path, "_blank", "width=500,height=500");
+      await SimpleTest.promiseFocus(win);
+      await win.startTest();
+    }
+
+    async function test() {
+      await run_test("test_input.html");
+      await run_test("test_textarea.html");
+      SimpleTest.finish();
+    }
+  ]]>
+  </script>
+
+  <body xmlns="http://www.w3.org/1999/xhtml">
+    <p id="display"></p>
+    <div id="content" style="display:none;"></div>
+    <pre id="test"></pre>
+  </body>
+</window>
--- a/dom/tests/moz.build
+++ b/dom/tests/moz.build
@@ -157,31 +157,33 @@ MOCHITEST_MANIFESTS += [
     'mochitest/dom-level0/mochitest.ini',
     'mochitest/dom-level1-core/mochitest.ini',
     'mochitest/dom-level2-core/mochitest.ini',
     'mochitest/dom-level2-html/mochitest.ini',
     'mochitest/fetch/mochitest.ini',
     'mochitest/gamepad/mochitest.ini',
     'mochitest/general/mochitest.ini',
     'mochitest/geolocation/mochitest.ini',
+    'mochitest/keyhandling/mochitest.ini',
     'mochitest/localstorage/mochitest.ini',
     'mochitest/orientation/mochitest.ini',
     'mochitest/pointerlock/mochitest.ini',
     'mochitest/script/mochitest.ini',
     'mochitest/sessionstorage/mochitest.ini',
     'mochitest/storageevent/mochitest.ini',
     'mochitest/webcomponents/mochitest.ini',
     'mochitest/whatwg/mochitest.ini',
 ]
 
 MOCHITEST_CHROME_MANIFESTS += [
     'mochitest/beacon/chrome.ini',
     'mochitest/chrome/chrome.ini',
     'mochitest/general/chrome.ini',
     'mochitest/geolocation/chrome.ini',
+    'mochitest/keyhandling/chrome.ini',
     'mochitest/localstorage/chrome.ini',
     'mochitest/sessionstorage/chrome.ini',
     'mochitest/webcomponents/chrome.ini',
     'mochitest/webcomponents/chrome_disabled.ini',
     'mochitest/whatwg/chrome.ini',
 ]
 
 XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini']