editor/libeditor/html/tests/test_htmleditor_keyevent_handling.html
author Masayuki Nakano <masayuki@d-toybox.com>
Thu, 05 Jun 2014 11:57:54 +0900
changeset 205962 6d883625709965e5b5441986d2a9948b3170404f
parent 186087 e947cee33a9206310dc5ea57492a2f3fac4807ab
permissions -rw-r--r--
Bug 1008772 part.3 Fix new orages caused by tabbrowser consuming some key events at keydown r=smaug

<html>
<head>
  <title>Test for key event handler of HTML editor</title>
  <script type="text/javascript"
          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
  <script type="text/javascript"
          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
  <link rel="stylesheet" type="text/css"
          href="chrome://mochikit/content/tests/SimpleTest/test.css" />
</head>
<body>
<div id="display">
  <div id="htmlEditor" contenteditable="true"><br></div>
</div>
<div id="content" style="display: none">
  
</div>
<pre id="test">
</pre>

<script class="testbody" type="application/javascript">

SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(runTests, window);

var htmlEditor = document.getElementById("htmlEditor");

const kIsMac = navigator.platform.indexOf("Mac") == 0;
const kIsWin = navigator.platform.indexOf("Win") == 0;
const kIsLinux = navigator.platform.indexOf("Linux") == 0 || navigator.platform.indexOf("SunOS") == 0 ;

function runTests()
{
  document.execCommand("stylewithcss", false, "true");

  var fm = SpecialPowers.Cc["@mozilla.org/focus-manager;1"].
    getService(SpecialPowers.Ci.nsIFocusManager);

  var capturingPhase = { fired: false, prevented: false };
  var bubblingPhase = { fired: false, prevented: false };

  var listener = {
    handleEvent: function _hv(aEvent)
    {
      is(aEvent.type, "keypress", "unexpected event is handled");
      switch (aEvent.eventPhase) {
      case aEvent.CAPTURING_PHASE:
        capturingPhase.fired = true;
        capturingPhase.prevented = aEvent.defaultPrevented;
        break;
      case aEvent.BUBBLING_PHASE:
        bubblingPhase.fired = true;
        bubblingPhase.prevented = aEvent.defaultPrevented;
        aEvent.preventDefault(); // prevent the browser default behavior
        break;
      default:
        ok(false, "event is handled in unexpected phase");
      }
    }
  };

  function check(aDescription,
                 aFiredOnCapture, aFiredOnBubbling, aPreventedOnBubbling)
  {
    function getDesciption(aExpected)
    {
      return aDescription + (aExpected ? " wasn't " : " was ");
    }
    is(capturingPhase.fired, aFiredOnCapture,
       getDesciption(aFiredOnCapture) + "fired on capture phase");
    is(bubblingPhase.fired, aFiredOnBubbling,
       getDesciption(aFiredOnBubbling) + "fired on bubbling phase");

    // If the event is fired on bubbling phase and it was already prevented
    // on capture phase, it must be prevented on bubbling phase too.
    if (capturingPhase.prevented) {
      todo(false, aDescription +
           " was consumed already, so, we cannot test the editor behavior actually");
      aPreventedOnBubbling = true;
    }

    is(bubblingPhase.prevented, aPreventedOnBubbling,
       getDesciption(aPreventedOnBubbling) + "prevented on bubbling phase");
  }

  SpecialPowers.addSystemEventListener(window, "keypress", listener, true);
  SpecialPowers.addSystemEventListener(window, "keypress", listener, false);

  function doTest(aElement, aDescription,
                  aIsReadonly, aIsTabbable, aIsPlaintext)
  {
    function reset(aText)
    {
      capturingPhase.fired = false;
      capturingPhase.prevented = false;
      bubblingPhase.fired = false;
      bubblingPhase.prevented = false;
      aElement.innerHTML = aText;
      var sel = window.getSelection();
      var range = document.createRange();
      range.setStart(aElement, aElement.childNodes.length);
      sel.removeAllRanges();
      sel.addRange(range);
    }

    function resetForIndent(aText)
    {
      capturingPhase.fired = false;
      capturingPhase.prevented = false;
      bubblingPhase.fired = false;
      bubblingPhase.prevented = false;
      aElement.innerHTML = aText;
      var sel = window.getSelection();
      var range = document.createRange();
      var target = document.getElementById("target").firstChild;
      range.setStart(target, target.length);
      sel.removeAllRanges();
      sel.addRange(range);
    }

    if (document.activeElement) {
      document.activeElement.blur();
    }

    aDescription += ": "

    aElement.focus();
    is(SpecialPowers.unwrap(fm.focusedElement), aElement, aDescription + "failed to move focus");

    // Modifier keys:
    //   Basically, modifier keys shouldn't cause keypress event.  However,
    //   even if it were dispatched by widget's bug, editor should consume
    //   it when editor is editable.
    reset("");
    synthesizeKey("VK_META", { type: "keypress" });
    check(aDescription + "Meta", true, true, !aIsReadonly);

    reset("");
    synthesizeKey("VK_WIN", { type: "keypress" });
    check(aDescription + "OS", true, true, !aIsReadonly);

    reset("");
    synthesizeKey("VK_SHIFT", { type: "keypress" });
    check(aDescription + "Shift", true, true, !aIsReadonly);

    reset("");
    synthesizeKey("VK_CONTROL", { type: "keypress" });
    check(aDescription + "Control", true, true, !aIsReadonly);

    // Alt key press event installs menubar key event listener, so,
    // we should pass Alt key testing on Windows and Linux.
    if (!kIsWin && !kIsLinux) {
      reset("");
      synthesizeKey("VK_ALT", { type: "keypress" });
      check(aDescription + "Alt", true, true, !aIsReadonly);
    }

    // Backspace key:
    //   If editor is readonly, it doesn't consume.
    //   If editor is editable, it consumes backspace and shift+backspace.
    //   Otherwise, editor doesn't consume the event.
    reset("");
    synthesizeKey("VK_BACK_SPACE", { });
    check(aDescription + "Backspace", true, true, true);

    reset("");
    synthesizeKey("VK_BACK_SPACE", { shiftKey: true });
    check(aDescription + "Shift+Backspace", true, true, true);

    reset("");
    synthesizeKey("VK_BACK_SPACE", { ctrlKey: true });
    check(aDescription + "Ctrl+Backspace", true, true, aIsReadonly);

    reset("");
    synthesizeKey("VK_BACK_SPACE", { altKey: true });
    check(aDescription + "Alt+Backspace", true, true, aIsReadonly || kIsMac);

    reset("");
    synthesizeKey("VK_BACK_SPACE", { metaKey: true });
    check(aDescription + "Meta+Backspace", true, true, aIsReadonly);

    reset("");
    synthesizeKey("VK_BACK_SPACE", { osKey: true });
    check(aDescription + "OS+Backspace", true, true, aIsReadonly);

    // Delete key:
    //   If editor is readonly, it doesn't consume.
    //   If editor is editable, delete is consumed.
    //   Otherwise, editor doesn't consume the event.
    reset("");
    synthesizeKey("VK_DELETE", { });
    check(aDescription + "Delete", true, true, !aIsReadonly || kIsMac);

    reset("");
    synthesizeKey("VK_DELETE", { shiftKey: true });
    check(aDescription + "Shift+Delete", true, true, kIsMac);

    reset("");
    synthesizeKey("VK_DELETE", { ctrlKey: true });
    check(aDescription + "Ctrl+Delete", true, true, false);

    reset("");
    synthesizeKey("VK_DELETE", { altKey: true });
    check(aDescription + "Alt+Delete", true, true, kIsMac);

    reset("");
    synthesizeKey("VK_DELETE", { metaKey: true });
    check(aDescription + "Meta+Delete", true, true, false);

    reset("");
    synthesizeKey("VK_DELETE", { osKey: true });
    check(aDescription + "OS+Delete", true, true, false);

    // Return key:
    //   If editor is readonly, it doesn't consume.
    //   If editor is editable and not single line editor, it consumes Return
    //   and Shift+Return.
    //   Otherwise, editor doesn't consume the event.
    reset("a");
    synthesizeKey("VK_RETURN", { });
    check(aDescription + "Return",
          true, true, !aIsReadonly);
    is(aElement.innerHTML, aIsReadonly ? "a" : "a<br><br>",
       aDescription + "Return");

    reset("a");
    synthesizeKey("VK_RETURN", { shiftKey: true });
    check(aDescription + "Shift+Return",
          true, true, !aIsReadonly);
    is(aElement.innerHTML, aIsReadonly ? "a" : "a<br><br>",
       aDescription + "Shift+Return");

    reset("a");
    synthesizeKey("VK_RETURN", { ctrlKey: true });
    check(aDescription + "Ctrl+Return", true, true, false);
    is(aElement.innerHTML, "a", aDescription + "Ctrl+Return");

    reset("a");
    synthesizeKey("VK_RETURN", { altKey: true });
    check(aDescription + "Alt+Return", true, true, false);
    is(aElement.innerHTML, "a", aDescription + "Alt+Return");

    reset("a");
    synthesizeKey("VK_RETURN", { metaKey: true });
    check(aDescription + "Meta+Return", true, true, false);
    is(aElement.innerHTML, "a", aDescription + "Meta+Return");

    reset("a");
    synthesizeKey("VK_RETURN", { osKey: true });
    check(aDescription + "OS+Return", true, true, false);
    is(aElement.innerHTML, "a", aDescription + "OS+Return");

    // Tab key:
    //   If editor is tabbable, editor doesn't consume all tab key events.
    //   Otherwise, editor consumes tab key event without any modifier keys.
    reset("a");
    synthesizeKey("VK_TAB", { });
    check(aDescription + "Tab",
          true, true, !aIsTabbable && !aIsReadonly);
    is(aElement.innerHTML,
       aIsTabbable || aIsReadonly ? "a" :
         aIsPlaintext ? "a\t" : "a&nbsp;&nbsp;&nbsp; <br>",
       aDescription + "Tab");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (Tab)");

    reset("a");
    synthesizeKey("VK_TAB", { shiftKey: true });
    check(aDescription + "Shift+Tab", true, true, false);
    is(aElement.innerHTML, "a", aDescription + "Shift+Tab");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (Shift+Tab)");

    // Ctrl+Tab should be consumed by tabbrowser at keydown, so, keypress
    // event should never be fired.
    reset("a");
    synthesizeKey("VK_TAB", { ctrlKey: true });
    check(aDescription + "Ctrl+Tab", false, false, false);
    is(aElement.innerHTML, "a", aDescription + "Ctrl+Tab");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (Ctrl+Tab)");

    reset("a");
    synthesizeKey("VK_TAB", { altKey: true });
    check(aDescription + "Alt+Tab", true, true, false);
    is(aElement.innerHTML, "a", aDescription + "Alt+Tab");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (Alt+Tab)");

    reset("a");
    synthesizeKey("VK_TAB", { metaKey: true });
    check(aDescription + "Meta+Tab", true, true, false);
    is(aElement.innerHTML, "a", aDescription + "Meta+Tab");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (Meta+Tab)");

    reset("a");
    synthesizeKey("VK_TAB", { osKey: true });
    check(aDescription + "OS+Tab", true, true, false);
    is(aElement.innerHTML, "a", aDescription + "OS+Tab");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (OS+Tab)");

    // Indent/Outdent tests:
    // UL
    resetForIndent("<ul><li id=\"target\">ul list item</li></ul>");
    synthesizeKey("VK_TAB", { });
    check(aDescription + "Tab on UL",
          true, true, !aIsTabbable && !aIsReadonly);
    is(aElement.innerHTML,
       aIsReadonly || aIsTabbable ?
         "<ul><li id=\"target\">ul list item</li></ul>" :
         aIsPlaintext ? "<ul><li id=\"target\">ul list item\t</li></ul>" :
           "<ul><ul><li id=\"target\">ul list item</li></ul></ul>",
       aDescription + "Tab on UL");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (Tab on UL)");
    synthesizeKey("VK_TAB", { shiftKey: true });
    check(aDescription + "Shift+Tab after Tab on UL",
          true, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext);
    is(aElement.innerHTML,
       aIsReadonly || aIsTabbable || (!aIsPlaintext) ?
         "<ul><li id=\"target\">ul list item</li></ul>" :
         "<ul><li id=\"target\">ul list item\t</li></ul>",
       aDescription + "Shift+Tab after Tab on UL");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (Shift+Tab after Tab on UL)");

    resetForIndent("<ul><li id=\"target\">ul list item</li></ul>");
    synthesizeKey("VK_TAB", { shiftKey: true });
    check(aDescription + "Shift+Tab on UL",
          true, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext);
    is(aElement.innerHTML,
       aIsReadonly || aIsTabbable || aIsPlaintext ?
         "<ul><li id=\"target\">ul list item</li></ul>" : "ul list item",
       aDescription + "Shift+Tab on UL");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (Shift+Tab on UL)");

    // Ctrl+Tab should be consumed by tabbrowser at keydown, so, keypress
    // event should never be fired.
    resetForIndent("<ul><li id=\"target\">ul list item</li></ul>");
    synthesizeKey("VK_TAB", { ctrlKey: true });
    check(aDescription + "Ctrl+Tab on UL", false, false, false);
    is(aElement.innerHTML, "<ul><li id=\"target\">ul list item</li></ul>",
       aDescription + "Ctrl+Tab on UL");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (Ctrl+Tab on UL)");

    resetForIndent("<ul><li id=\"target\">ul list item</li></ul>");
    synthesizeKey("VK_TAB", { altKey: true });
    check(aDescription + "Alt+Tab on UL", true, true, false);
    is(aElement.innerHTML, "<ul><li id=\"target\">ul list item</li></ul>",
       aDescription + "Alt+Tab on UL");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (Alt+Tab on UL)");

    resetForIndent("<ul><li id=\"target\">ul list item</li></ul>");
    synthesizeKey("VK_TAB", { metaKey: true });
    check(aDescription + "Meta+Tab on UL", true, true, false);
    is(aElement.innerHTML, "<ul><li id=\"target\">ul list item</li></ul>",
       aDescription + "Meta+Tab on UL");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (Meta+Tab on UL)");

    resetForIndent("<ul><li id=\"target\">ul list item</li></ul>");
    synthesizeKey("VK_TAB", { osKey: true });
    check(aDescription + "OS+Tab on UL", true, true, false);
    is(aElement.innerHTML, "<ul><li id=\"target\">ul list item</li></ul>",
       aDescription + "OS+Tab on UL");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (OS+Tab on UL)");

    // OL
    resetForIndent("<ol><li id=\"target\">ol list item</li></ol>");
    synthesizeKey("VK_TAB", { });
    check(aDescription + "Tab on OL",
          true, true, !aIsTabbable && !aIsReadonly);
    is(aElement.innerHTML,
       aIsReadonly || aIsTabbable ?
         "<ol><li id=\"target\">ol list item</li></ol>" :
         aIsPlaintext ? "<ol><li id=\"target\">ol list item\t</li></ol>" :
           "<ol><ol><li id=\"target\">ol list item</li></ol></ol>",
       aDescription + "Tab on OL");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (Tab on OL)");
    synthesizeKey("VK_TAB", { shiftKey: true });
    check(aDescription + "Shift+Tab after Tab on OL",
          true, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext);
    is(aElement.innerHTML,
       aIsReadonly || aIsTabbable || (!aIsPlaintext) ?
         "<ol><li id=\"target\">ol list item</li></ol>" :
         "<ol><li id=\"target\">ol list item\t</li></ol>",
       aDescription + "Shift+Tab after Tab on OL");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (Shift+Tab after Tab on OL)");

    resetForIndent("<ol><li id=\"target\">ol list item</li></ol>");
    synthesizeKey("VK_TAB", { shiftKey: true });
    check(aDescription + "Shift+Tab on OL",
          true, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext);
    is(aElement.innerHTML,
       aIsReadonly || aIsTabbable || aIsPlaintext ?
         "<ol><li id=\"target\">ol list item</li></ol>" : "ol list item",
       aDescription + "Shfit+Tab on OL");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (Shift+Tab on OL)");

    // Ctrl+Tab should be consumed by tabbrowser at keydown, so, keypress
    // event should never be fired.
    resetForIndent("<ol><li id=\"target\">ol list item</li></ol>");
    synthesizeKey("VK_TAB", { ctrlKey: true });
    check(aDescription + "Ctrl+Tab on OL", false, false, false);
    is(aElement.innerHTML, "<ol><li id=\"target\">ol list item</li></ol>",
       aDescription + "Ctrl+Tab on OL");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (Ctrl+Tab on OL)");

    resetForIndent("<ol><li id=\"target\">ol list item</li></ol>");
    synthesizeKey("VK_TAB", { altKey: true });
    check(aDescription + "Alt+Tab on OL", true, true, false);
    is(aElement.innerHTML, "<ol><li id=\"target\">ol list item</li></ol>",
       aDescription + "Alt+Tab on OL");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (Alt+Tab on OL)");

    resetForIndent("<ol><li id=\"target\">ol list item</li></ol>");
    synthesizeKey("VK_TAB", { metaKey: true });
    check(aDescription + "Meta+Tab on OL", true, true, false);
    is(aElement.innerHTML, "<ol><li id=\"target\">ol list item</li></ol>",
       aDescription + "Meta+Tab on OL");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (Meta+Tab on OL)");

    resetForIndent("<ol><li id=\"target\">ol list item</li></ol>");
    synthesizeKey("VK_TAB", { osKey: true });
    check(aDescription + "OS+Tab on OL", true, true, false);
    is(aElement.innerHTML, "<ol><li id=\"target\">ol list item</li></ol>",
       aDescription + "OS+Tab on OL");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (OS+Tab on OL)");

    // TD
    resetForIndent("<table><tr><td id=\"target\">td</td></tr></table>");
    synthesizeKey("VK_TAB", { });
    check(aDescription + "Tab on TD",
          true, true, !aIsTabbable && !aIsReadonly);
    is(aElement.innerHTML,
       aIsTabbable || aIsReadonly ?
         "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>" :
         aIsPlaintext ? "<table><tbody><tr><td id=\"target\">td\t</td></tr></tbody></table>" :
           "<table><tbody><tr><td id=\"target\">td</td></tr><tr><td style=\"vertical-align: top;\"><br></td></tr></tbody></table>",
       aDescription + "Tab on TD");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (Tab on TD)");
    synthesizeKey("VK_TAB", { shiftKey: true });
    check(aDescription + "Shift+Tab after Tab on TD",
          true, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext);
    is(aElement.innerHTML,
       aIsTabbable || aIsReadonly ?
         "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>" :
         aIsPlaintext ? "<table><tbody><tr><td id=\"target\">td\t</td></tr></tbody></table>" :
           "<table><tbody><tr><td id=\"target\">td</td></tr><tr><td style=\"vertical-align: top;\"><br></td></tr></tbody></table>",
       aDescription + "Shift+Tab after Tab on TD");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (Shift+Tab after Tab on TD)");

    resetForIndent("<table><tr><td id=\"target\">td</td></tr></table>");
    synthesizeKey("VK_TAB", { shiftKey: true });
    check(aDescription + "Shift+Tab on TD", true, true, false);
    is(aElement.innerHTML,
       "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>",
       aDescription + "Shift+Tab on TD");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (Shift+Tab on TD)");

    // Ctrl+Tab should be consumed by tabbrowser at keydown, so, keypress
    // event should never be fired.
    resetForIndent("<table><tr><td id=\"target\">td</td></tr></table>");
    synthesizeKey("VK_TAB", { ctrlKey: true });
    check(aDescription + "Ctrl+Tab on TD", false, false, false);
    is(aElement.innerHTML,
       "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>",
       aDescription + "Ctrl+Tab on TD");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (Ctrl+Tab on TD)");

    resetForIndent("<table><tr><td id=\"target\">td</td></tr></table>");
    synthesizeKey("VK_TAB", { altKey: true });
    check(aDescription + "Alt+Tab on TD", true, true, false);
    is(aElement.innerHTML,
       "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>",
       aDescription + "Alt+Tab on TD");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (Alt+Tab on TD)");

    resetForIndent("<table><tr><td id=\"target\">td</td></tr></table>");
    synthesizeKey("VK_TAB", { metaKey: true });
    check(aDescription + "Meta+Tab on TD", true, true, false);
    is(aElement.innerHTML,
       "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>",
       aDescription + "Meta+Tab on TD");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (Meta+Tab on TD)");

    resetForIndent("<table><tr><td id=\"target\">td</td></tr></table>");
    synthesizeKey("VK_TAB", { osKey: true });
    check(aDescription + "OS+Tab on TD", true, true, false);
    is(aElement.innerHTML,
       "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>",
       aDescription + "OS+Tab on TD");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (OS+Tab on TD)");

    // TH
    resetForIndent("<table><tr><th id=\"target\">th</th></tr></table>");
    synthesizeKey("VK_TAB", { });
    check(aDescription + "Tab on TH",
          true, true, !aIsTabbable && !aIsReadonly);
    is(aElement.innerHTML,
       aIsTabbable || aIsReadonly ?
         "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>" :
         aIsPlaintext ? "<table><tbody><tr><th id=\"target\">th\t</th></tr></tbody></table>" :
           "<table><tbody><tr><th id=\"target\">th</th></tr><tr><td style=\"vertical-align: top;\"><br></td></tr></tbody></table>",
       aDescription + "Tab on TH");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (Tab on TH)");
    synthesizeKey("VK_TAB", { shiftKey: true });
    check(aDescription + "Shift+Tab after Tab on TH",
          true, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext);
    is(aElement.innerHTML,
       aIsTabbable || aIsReadonly ?
         "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>" :
         aIsPlaintext ? "<table><tbody><tr><th id=\"target\">th\t</th></tr></tbody></table>" :
           "<table><tbody><tr><th id=\"target\">th</th></tr><tr><td style=\"vertical-align: top;\"><br></td></tr></tbody></table>",
       aDescription + "Shift+Tab after Tab on TH");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (Shift+Tab after Tab on TH)");

    resetForIndent("<table><tr><th id=\"target\">th</th></tr></table>");
    synthesizeKey("VK_TAB", { shiftKey: true });
    check(aDescription + "Shift+Tab on TH", true, true, false);
    is(aElement.innerHTML,
       "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>",
       aDescription + "Shift+Tab on TH");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (Shift+Tab on TH)");

    // Ctrl+Tab should be consumed by tabbrowser at keydown, so, keypress
    // event should never be fired.
    resetForIndent("<table><tr><th id=\"target\">th</th></tr></table>");
    synthesizeKey("VK_TAB", { ctrlKey: true });
    check(aDescription + "Ctrl+Tab on TH", false, false, false);
    is(aElement.innerHTML,
       "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>",
       aDescription + "Ctrl+Tab on TH");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (Ctrl+Tab on TH)");

    resetForIndent("<table><tr><th id=\"target\">th</th></tr></table>");
    synthesizeKey("VK_TAB", { altKey: true });
    check(aDescription + "Alt+Tab on TH", true, true, false);
    is(aElement.innerHTML,
       "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>",
       aDescription + "Alt+Tab on TH");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (Alt+Tab on TH)");

    resetForIndent("<table><tr><th id=\"target\">th</th></tr></table>");
    synthesizeKey("VK_TAB", { metaKey: true });
    check(aDescription + "Meta+Tab on TH", true, true, false);
    is(aElement.innerHTML,
       "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>",
       aDescription + "Meta+Tab on TH");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (Meta+Tab on TH)");

    resetForIndent("<table><tr><th id=\"target\">th</th></tr></table>");
    synthesizeKey("VK_TAB", { osKey: true });
    check(aDescription + "OS+Tab on TH", true, true, false);
    is(aElement.innerHTML,
       "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>",
       aDescription + "OS+Tab on TH");
    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
       aDescription + "focus moved unexpectedly (OS+Tab on TH)");

    // Esc key:
    //   In all cases, esc key events are not consumed
    reset("abc");
    synthesizeKey("VK_ESCAPE", { });
    check(aDescription + "Esc", true, true, false);

    reset("abc");
    synthesizeKey("VK_ESCAPE", { shiftKey: true });
    check(aDescription + "Shift+Esc", true, true, false);

    reset("abc");
    synthesizeKey("VK_ESCAPE", { ctrlKey: true });
    check(aDescription + "Ctrl+Esc", true, true, false);

    reset("abc");
    synthesizeKey("VK_ESCAPE", { altKey: true });
    check(aDescription + "Alt+Esc", true, true, false);

    reset("abc");
    synthesizeKey("VK_ESCAPE", { metaKey: true });
    check(aDescription + "Meta+Esc", true, true, false);

    reset("abc");
    synthesizeKey("VK_ESCAPE", { osKey: true });
    check(aDescription + "OS+Esc", true, true, false);

    // typical typing tests:
    reset("");
    synthesizeKey("M", { shiftKey: true });
    check(aDescription + "M", true, true, !aIsReadonly);
    synthesizeKey("o", { });
    check(aDescription + "o", true, true, !aIsReadonly);
    synthesizeKey("z", { });
    check(aDescription + "z", true, true, !aIsReadonly);
    synthesizeKey("i", { });
    check(aDescription + "i", true, true, !aIsReadonly);
    synthesizeKey("l", { });
    check(aDescription + "l", true, true, !aIsReadonly);
    synthesizeKey("l", { });
    check(aDescription + "l", true, true, !aIsReadonly);
    synthesizeKey("a", { });
    check(aDescription + "a", true, true, !aIsReadonly);
    synthesizeKey(" ", { });
    check(aDescription + "' '", true, true, !aIsReadonly);
    is(aElement.innerHTML,
       aIsReadonly ? "" : aIsPlaintext ? "Mozilla " : "Mozilla <br>",
       aDescription + "typed \"Mozilla \"");
  }

  doTest(htmlEditor, "contenteditable=\"true\"", false, true, false);

  const nsIPlaintextEditor = SpecialPowers.Ci.nsIPlaintextEditor;
  var editor = SpecialPowers.wrap(window).
      QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor).
      getInterface(SpecialPowers.Ci.nsIWebNavigation).
      QueryInterface(SpecialPowers.Ci.nsIDocShell).editor;
  var flags = editor.flags;
  // readonly
  editor.flags = flags | nsIPlaintextEditor.eEditorReadonlyMask;
  doTest(htmlEditor, "readonly HTML editor", true, true, false);

  // non-tabbable
  editor.flags = flags & ~(nsIPlaintextEditor.eEditorAllowInteraction);
  doTest(htmlEditor, "non-tabbable HTML editor", false, false, false);

  // readonly and non-tabbable
  editor.flags =
    (flags | nsIPlaintextEditor.eEditorReadonlyMask) &
      ~(nsIPlaintextEditor.eEditorAllowInteraction);
  doTest(htmlEditor, "readonly and non-tabbable HTML editor",
         true, false, false);

  // plaintext
  editor.flags = flags | nsIPlaintextEditor.eEditorPlaintextMask;
  doTest(htmlEditor, "HTML editor but plaintext mode", false, true, true);

  // plaintext and non-tabbable
  editor.flags = (flags | nsIPlaintextEditor.eEditorPlaintextMask) &
                 ~(nsIPlaintextEditor.eEditorAllowInteraction);
  doTest(htmlEditor, "non-tabbable HTML editor but plaintext mode",
         false, false, true);


  // readonly and plaintext
  editor.flags = flags | nsIPlaintextEditor.eEditorPlaintextMask |
                         nsIPlaintextEditor.eEditorReadonlyMask;
  doTest(htmlEditor, "readonly HTML editor but plaintext mode",
         true, true, true);

  // readonly, plaintext and non-tabbable
  editor.flags = (flags | nsIPlaintextEditor.eEditorPlaintextMask |
                          nsIPlaintextEditor.eEditorReadonlyMask) &
                 ~(nsIPlaintextEditor.eEditorAllowInteraction);
  doTest(htmlEditor, "readonly and non-tabbable HTML editor but plaintext mode",
         true, false, true);

  SpecialPowers.removeSystemEventListener(window, "keypress", listener, true);
  SpecialPowers.removeSystemEventListener(window, "keypress", listener, false);

  SimpleTest.finish();
}

</script>
</body>

</html>