Bug 1419091: Add keybinding tests. r=masayuki
authorDave Townsend <dtownsend@oxymoronical.com>
Mon, 08 Oct 2018 11:07:00 -0700
changeset 442896 d33100811e81d7a87c080475622e3cb54f85617b
parent 442895 32d8d8c0d44e2030e20578daaba75fe0a69d547a
child 442897 0e7a8e6537619430d62cdda189acbd440a682788
push id34927
push userncsoregi@mozilla.com
push dateThu, 25 Oct 2018 04:45:44 +0000
treeherdermozilla-central@76d5b62fb151 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmasayuki
bugs1419091
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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']