tor-browser

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

test_htmleditor_keyevent_handling.html (32521B)


      1 <html>
      2 <head>
      3  <title>Test for key event handler of HTML editor</title>
      4  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
      5  <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
      6  <link rel="stylesheet" type="text/css"
      7          href="chrome://mochikit/content/tests/SimpleTest/test.css" />
      8 </head>
      9 <body>
     10 <div id="display">
     11  <div id="htmlEditor" contenteditable="true"><br></div>
     12 </div>
     13 <div id="content" style="display: none">
     14 
     15 </div>
     16 <pre id="test">
     17 </pre>
     18 
     19 <script class="testbody" type="application/javascript">
     20 
     21 /* eslint-disable no-nested-ternary */
     22 
     23 SimpleTest.waitForExplicitFinish();
     24 SimpleTest.waitForFocus(runTests, window);
     25 
     26 var htmlEditor = document.getElementById("htmlEditor");
     27 
     28 const kIsMac = navigator.platform.includes("Mac");
     29 const kIsWin = navigator.platform.includes("Win");
     30 const kIsLinux = navigator.platform.includes("Linux") || navigator.platform.includes("SunOS");
     31 
     32 async function runTests() {
     33  document.execCommand("stylewithcss", false, "true");
     34  document.execCommand("defaultParagraphSeparator", false, "div");
     35 
     36  var fm = SpecialPowers.Services.focus;
     37 
     38  var capturingPhase = { fired: false, prevented: false };
     39  var bubblingPhase = { fired: false, prevented: false };
     40 
     41  var listener = {
     42    handleEvent: function _hv(aEvent) {
     43      is(aEvent.type, "keypress", "unexpected event is handled");
     44      switch (aEvent.eventPhase) {
     45      case aEvent.CAPTURING_PHASE:
     46        capturingPhase.fired = true;
     47        capturingPhase.prevented = aEvent.defaultPrevented;
     48        break;
     49      case aEvent.BUBBLING_PHASE:
     50        bubblingPhase.fired = true;
     51        bubblingPhase.prevented = aEvent.defaultPrevented;
     52        aEvent.preventDefault(); // prevent the browser default behavior
     53        break;
     54      default:
     55        ok(false, "event is handled in unexpected phase");
     56      }
     57    },
     58  };
     59 
     60  function check(aDescription,
     61                 aFiredOnCapture, aFiredOnBubbling, aPreventedOnBubbling) {
     62    function getDesciption(aExpected) {
     63      return aDescription + (aExpected ? " wasn't " : " was ");
     64    }
     65    is(capturingPhase.fired, aFiredOnCapture,
     66       getDesciption(aFiredOnCapture) + "fired on capture phase");
     67    is(bubblingPhase.fired, aFiredOnBubbling,
     68       getDesciption(aFiredOnBubbling) + "fired on bubbling phase");
     69 
     70    // If the event is fired on bubbling phase and it was already prevented
     71    // on capture phase, it must be prevented on bubbling phase too.
     72    if (capturingPhase.prevented) {
     73      todo(false, aDescription +
     74           " was consumed already, so, we cannot test the editor behavior actually");
     75      aPreventedOnBubbling = true;
     76    }
     77 
     78    is(bubblingPhase.prevented, aPreventedOnBubbling,
     79       getDesciption(aPreventedOnBubbling) + "prevented on bubbling phase");
     80  }
     81 
     82  SpecialPowers.wrap(window).addEventListener("keypress", listener, { capture: true, mozSystemGroup: true });
     83  SpecialPowers.wrap(window).addEventListener("keypress", listener, { capture: false, mozSystemGroup: true });
     84 
     85  // eslint-disable-next-line complexity
     86  async function doTest(
     87    aElement,
     88    aDescription,
     89    aIsReadonly,
     90    aIsTabbable,
     91    aIsPlaintext
     92  ) {
     93    function reset(aText) {
     94      capturingPhase.fired = false;
     95      capturingPhase.prevented = false;
     96      bubblingPhase.fired = false;
     97      bubblingPhase.prevented = false;
     98      aElement.innerHTML = aText;
     99      var sel = window.getSelection();
    100      var range = document.createRange();
    101      range.setStart(aElement, aElement.childNodes.length);
    102      sel.removeAllRanges();
    103      sel.addRange(range);
    104    }
    105 
    106    function resetForIndent(aText) {
    107      capturingPhase.fired = false;
    108      capturingPhase.prevented = false;
    109      bubblingPhase.fired = false;
    110      bubblingPhase.prevented = false;
    111      aElement.innerHTML = aText;
    112      var sel = window.getSelection();
    113      var range = document.createRange();
    114      var target = document.getElementById("target").firstChild;
    115      range.setStart(target, target.length);
    116      sel.removeAllRanges();
    117      sel.addRange(range);
    118    }
    119 
    120    if (document.activeElement) {
    121      document.activeElement.blur();
    122    }
    123 
    124    aDescription += ": ";
    125 
    126    aElement.focus();
    127    is(SpecialPowers.unwrap(fm.focusedElement), aElement, aDescription + "failed to move focus");
    128 
    129    // Backspace key:
    130    //   If native key bindings map the key combination to something, it's consumed.
    131    //   If editor is readonly, it doesn't consume.
    132    //   If editor is editable, it consumes backspace and shift+backspace.
    133    //   Otherwise, editor doesn't consume the event.
    134    reset("");
    135    synthesizeKey("KEY_Backspace");
    136    check(aDescription + "Backspace", true, true, true);
    137 
    138    reset("");
    139    synthesizeKey("KEY_Backspace", {shiftKey: true});
    140    check(aDescription + "Shift+Backspace", true, true, true);
    141 
    142    reset("");
    143    synthesizeKey("KEY_Backspace", {ctrlKey: true});
    144    check(aDescription + "Ctrl+Backspace", true, true, aIsReadonly || kIsLinux);
    145 
    146    reset("");
    147    synthesizeKey("KEY_Backspace", {altKey: true});
    148    check(aDescription + "Alt+Backspace", true, true, aIsReadonly || kIsMac);
    149 
    150    reset("");
    151    synthesizeKey("KEY_Backspace", {metaKey: true});
    152    check(aDescription + "Meta+Backspace", true, true, aIsReadonly || kIsMac);
    153 
    154    // Delete key:
    155    //   If native key bindings map the key combination to something, it's consumed.
    156    //   If editor is readonly, it doesn't consume.
    157    //   If editor is editable, delete is consumed.
    158    //   Otherwise, editor doesn't consume the event.
    159    reset("");
    160    synthesizeKey("KEY_Delete");
    161    check(aDescription + "Delete", true, true, !aIsReadonly || kIsMac || kIsLinux);
    162 
    163    reset("");
    164    synthesizeKey("KEY_Delete", {shiftKey: true});
    165    check(aDescription + "Shift+Delete", true, true, kIsMac || kIsLinux);
    166 
    167    reset("");
    168    synthesizeKey("KEY_Delete", {ctrlKey: true});
    169    check(aDescription + "Ctrl+Delete", true, true, kIsLinux);
    170 
    171    reset("");
    172    synthesizeKey("KEY_Delete", {altKey: true});
    173    check(aDescription + "Alt+Delete", true, true, kIsMac);
    174 
    175    reset("");
    176    synthesizeKey("KEY_Delete", {metaKey: true});
    177    check(aDescription + "Meta+Delete", true, true, false);
    178 
    179    // Return key:
    180    //   If editor is readonly, it doesn't consume.
    181    //   If editor is editable and not single line editor, it consumes Return
    182    //   and Shift+Return.
    183    //   Otherwise, editor doesn't consume the event.
    184    reset("a");
    185    synthesizeKey("KEY_Enter");
    186    check(aDescription + "Return",
    187          true, true, !aIsReadonly);
    188    is(aElement.innerHTML, aIsReadonly ? "a" : "<div>a</div><br>",
    189       aDescription + "Return");
    190 
    191    reset("a");
    192    synthesizeKey("KEY_Enter", {shiftKey: true});
    193    check(aDescription + "Shift+Return",
    194          true, true, !aIsReadonly);
    195    is(aElement.innerHTML, aIsReadonly ? "a" : "a<br><br>",
    196       aDescription + "Shift+Return");
    197 
    198    reset("a");
    199    synthesizeKey("KEY_Enter", {ctrlKey: true});
    200    check(aDescription + "Ctrl+Return", true, true, false);
    201    is(aElement.innerHTML, "a", aDescription + "Ctrl+Return");
    202 
    203    reset("a");
    204    synthesizeKey("KEY_Enter", {altKey: true});
    205    check(aDescription + "Alt+Return", true, true, false);
    206    is(aElement.innerHTML, "a", aDescription + "Alt+Return");
    207 
    208    reset("a");
    209    synthesizeKey("KEY_Enter", {metaKey: true});
    210    check(aDescription + "Meta+Return", true, true, false);
    211    is(aElement.innerHTML, "a", aDescription + "Meta+Return");
    212 
    213    // Tab key:
    214    //   If editor is tabbable, editor doesn't consume all tab key events.
    215    //   Otherwise, editor consumes tab key event without any modifier keys.
    216    reset("a");
    217    synthesizeKey("KEY_Tab");
    218    check(aDescription + "Tab",
    219          true, true, !aIsTabbable && !aIsReadonly);
    220    is(aElement.innerHTML,
    221       (() => {
    222         if (aIsTabbable || aIsReadonly) {
    223           return "a";
    224         }
    225         if (aIsPlaintext) {
    226           return "a\t";
    227         }
    228         return"a&nbsp; &nbsp;&nbsp;";
    229       })(),
    230       aDescription + "Tab");
    231    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    232       aDescription + "focus moved unexpectedly (Tab)");
    233 
    234    reset("a");
    235    synthesizeKey("KEY_Tab", {shiftKey: true});
    236    check(aDescription + "Shift+Tab", true, true, false);
    237    is(aElement.innerHTML, "a", aDescription + "Shift+Tab");
    238    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    239       aDescription + "focus moved unexpectedly (Shift+Tab)");
    240 
    241    // Ctrl+Tab should be consumed by tabbrowser at keydown, so, keypress
    242    // event should never be fired.
    243    reset("a");
    244    synthesizeKey("KEY_Tab", {ctrlKey: true});
    245    check(aDescription + "Ctrl+Tab", false, false, false);
    246    is(aElement.innerHTML, "a", aDescription + "Ctrl+Tab");
    247    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    248       aDescription + "focus moved unexpectedly (Ctrl+Tab)");
    249 
    250    reset("a");
    251    synthesizeKey("KEY_Tab", {altKey: true});
    252    check(aDescription + "Alt+Tab", true, true, false);
    253    is(aElement.innerHTML, "a", aDescription + "Alt+Tab");
    254    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    255       aDescription + "focus moved unexpectedly (Alt+Tab)");
    256 
    257    reset("a");
    258    synthesizeKey("KEY_Tab", {metaKey: true});
    259    check(aDescription + "Meta+Tab", true, true, false);
    260    is(aElement.innerHTML, "a", aDescription + "Meta+Tab");
    261    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    262       aDescription + "focus moved unexpectedly (Meta+Tab)");
    263 
    264    // Indent/Outdent tests:
    265    // UL
    266    resetForIndent("<ul><li id=\"target\">ul list item</li></ul>");
    267    synthesizeKey("KEY_Tab");
    268    check(aDescription + "Tab on UL",
    269          true, true, !aIsTabbable && !aIsReadonly);
    270    is(aElement.innerHTML,
    271       aIsReadonly || aIsTabbable ?
    272         "<ul><li id=\"target\">ul list item</li></ul>" :
    273         aIsPlaintext ? "<ul><li id=\"target\">ul list item\t</li></ul>" :
    274           "<ul><ul><li id=\"target\">ul list item</li></ul></ul>",
    275       aDescription + "Tab on UL");
    276    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    277       aDescription + "focus moved unexpectedly (Tab on UL)");
    278    synthesizeKey("KEY_Tab", {shiftKey: true});
    279    check(aDescription + "Shift+Tab after Tab on UL",
    280          true, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext);
    281    is(aElement.innerHTML,
    282       aIsReadonly || aIsTabbable || (!aIsPlaintext) ?
    283         "<ul><li id=\"target\">ul list item</li></ul>" :
    284         "<ul><li id=\"target\">ul list item\t</li></ul>",
    285       aDescription + "Shift+Tab after Tab on UL");
    286    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    287       aDescription + "focus moved unexpectedly (Shift+Tab after Tab on UL)");
    288 
    289    resetForIndent("<ul><li id=\"target\">ul list item</li></ul>");
    290    synthesizeKey("KEY_Tab", {shiftKey: true});
    291    check(aDescription + "Shift+Tab on UL",
    292          true, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext);
    293    is(aElement.innerHTML,
    294       aIsReadonly || aIsTabbable || aIsPlaintext ?
    295         "<ul><li id=\"target\">ul list item</li></ul>" : "ul list item",
    296       aDescription + "Shift+Tab on UL");
    297    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    298       aDescription + "focus moved unexpectedly (Shift+Tab on UL)");
    299 
    300    // Ctrl+Tab should be consumed by tabbrowser at keydown, so, keypress
    301    // event should never be fired.
    302    resetForIndent("<ul><li id=\"target\">ul list item</li></ul>");
    303    synthesizeKey("KEY_Tab", {ctrlKey: true});
    304    check(aDescription + "Ctrl+Tab on UL", false, false, false);
    305    is(aElement.innerHTML, "<ul><li id=\"target\">ul list item</li></ul>",
    306       aDescription + "Ctrl+Tab on UL");
    307    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    308       aDescription + "focus moved unexpectedly (Ctrl+Tab on UL)");
    309 
    310    resetForIndent("<ul><li id=\"target\">ul list item</li></ul>");
    311    synthesizeKey("KEY_Tab", {altKey: true});
    312    check(aDescription + "Alt+Tab on UL", true, true, false);
    313    is(aElement.innerHTML, "<ul><li id=\"target\">ul list item</li></ul>",
    314       aDescription + "Alt+Tab on UL");
    315    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    316       aDescription + "focus moved unexpectedly (Alt+Tab on UL)");
    317 
    318    resetForIndent("<ul><li id=\"target\">ul list item</li></ul>");
    319    synthesizeKey("KEY_Tab", {metaKey: true});
    320    check(aDescription + "Meta+Tab on UL", true, true, false);
    321    is(aElement.innerHTML, "<ul><li id=\"target\">ul list item</li></ul>",
    322       aDescription + "Meta+Tab on UL");
    323    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    324       aDescription + "focus moved unexpectedly (Meta+Tab on UL)");
    325 
    326    // OL
    327    resetForIndent("<ol><li id=\"target\">ol list item</li></ol>");
    328    synthesizeKey("KEY_Tab");
    329    check(aDescription + "Tab on OL",
    330          true, true, !aIsTabbable && !aIsReadonly);
    331    is(aElement.innerHTML,
    332       aIsReadonly || aIsTabbable ?
    333         "<ol><li id=\"target\">ol list item</li></ol>" :
    334         aIsPlaintext ? "<ol><li id=\"target\">ol list item\t</li></ol>" :
    335           "<ol><ol><li id=\"target\">ol list item</li></ol></ol>",
    336       aDescription + "Tab on OL");
    337    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    338       aDescription + "focus moved unexpectedly (Tab on OL)");
    339    synthesizeKey("KEY_Tab", {shiftKey: true});
    340    check(aDescription + "Shift+Tab after Tab on OL",
    341          true, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext);
    342    is(aElement.innerHTML,
    343       aIsReadonly || aIsTabbable || (!aIsPlaintext) ?
    344         "<ol><li id=\"target\">ol list item</li></ol>" :
    345         "<ol><li id=\"target\">ol list item\t</li></ol>",
    346       aDescription + "Shift+Tab after Tab on OL");
    347    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    348       aDescription + "focus moved unexpectedly (Shift+Tab after Tab on OL)");
    349 
    350    resetForIndent("<ol><li id=\"target\">ol list item</li></ol>");
    351    synthesizeKey("KEY_Tab", {shiftKey: true});
    352    check(aDescription + "Shift+Tab on OL",
    353          true, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext);
    354    is(aElement.innerHTML,
    355       aIsReadonly || aIsTabbable || aIsPlaintext ?
    356         "<ol><li id=\"target\">ol list item</li></ol>" : "ol list item",
    357       aDescription + "Shfit+Tab on OL");
    358    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    359       aDescription + "focus moved unexpectedly (Shift+Tab on OL)");
    360 
    361    // Ctrl+Tab should be consumed by tabbrowser at keydown, so, keypress
    362    // event should never be fired.
    363    resetForIndent("<ol><li id=\"target\">ol list item</li></ol>");
    364    synthesizeKey("KEY_Tab", {ctrlKey: true});
    365    check(aDescription + "Ctrl+Tab on OL", false, false, false);
    366    is(aElement.innerHTML, "<ol><li id=\"target\">ol list item</li></ol>",
    367       aDescription + "Ctrl+Tab on OL");
    368    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    369       aDescription + "focus moved unexpectedly (Ctrl+Tab on OL)");
    370 
    371    resetForIndent("<ol><li id=\"target\">ol list item</li></ol>");
    372    synthesizeKey("KEY_Tab", {altKey: true});
    373    check(aDescription + "Alt+Tab on OL", true, true, false);
    374    is(aElement.innerHTML, "<ol><li id=\"target\">ol list item</li></ol>",
    375       aDescription + "Alt+Tab on OL");
    376    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    377       aDescription + "focus moved unexpectedly (Alt+Tab on OL)");
    378 
    379    resetForIndent("<ol><li id=\"target\">ol list item</li></ol>");
    380    synthesizeKey("KEY_Tab", {metaKey: true});
    381    check(aDescription + "Meta+Tab on OL", true, true, false);
    382    is(aElement.innerHTML, "<ol><li id=\"target\">ol list item</li></ol>",
    383       aDescription + "Meta+Tab on OL");
    384    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    385       aDescription + "focus moved unexpectedly (Meta+Tab on OL)");
    386 
    387    // TD
    388    resetForIndent("<table><tr><td id=\"target\">td</td></tr></table>");
    389    synthesizeKey("KEY_Tab");
    390    check(aDescription + "Tab on TD",
    391          true, true, !aIsTabbable && !aIsReadonly);
    392    is(aElement.innerHTML,
    393       aIsTabbable || aIsReadonly ?
    394         "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>" :
    395         aIsPlaintext ? "<table><tbody><tr><td id=\"target\">td\t</td></tr></tbody></table>" :
    396           "<table><tbody><tr><td id=\"target\">td</td></tr><tr><td style=\"vertical-align: top;\"><br></td></tr></tbody></table>",
    397       aDescription + "Tab on TD");
    398    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    399       aDescription + "focus moved unexpectedly (Tab on TD)");
    400    synthesizeKey("KEY_Tab", {shiftKey: true});
    401    check(aDescription + "Shift+Tab after Tab on TD",
    402          true, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext);
    403    is(aElement.innerHTML,
    404       aIsTabbable || aIsReadonly ?
    405         "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>" :
    406         aIsPlaintext ? "<table><tbody><tr><td id=\"target\">td\t</td></tr></tbody></table>" :
    407           "<table><tbody><tr><td id=\"target\">td</td></tr><tr><td style=\"vertical-align: top;\"><br></td></tr></tbody></table>",
    408       aDescription + "Shift+Tab after Tab on TD");
    409    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    410       aDescription + "focus moved unexpectedly (Shift+Tab after Tab on TD)");
    411 
    412    resetForIndent("<table><tr><td id=\"target\">td</td></tr></table>");
    413    synthesizeKey("KEY_Tab", {shiftKey: true});
    414    check(aDescription + "Shift+Tab on TD", true, true, false);
    415    is(aElement.innerHTML,
    416       "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>",
    417       aDescription + "Shift+Tab on TD");
    418    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    419       aDescription + "focus moved unexpectedly (Shift+Tab on TD)");
    420 
    421    // Ctrl+Tab should be consumed by tabbrowser at keydown, so, keypress
    422    // event should never be fired.
    423    resetForIndent("<table><tr><td id=\"target\">td</td></tr></table>");
    424    synthesizeKey("KEY_Tab", {ctrlKey: true});
    425    check(aDescription + "Ctrl+Tab on TD", false, false, false);
    426    is(aElement.innerHTML,
    427       "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>",
    428       aDescription + "Ctrl+Tab on TD");
    429    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    430       aDescription + "focus moved unexpectedly (Ctrl+Tab on TD)");
    431 
    432    resetForIndent("<table><tr><td id=\"target\">td</td></tr></table>");
    433    synthesizeKey("KEY_Tab", {altKey: true});
    434    check(aDescription + "Alt+Tab on TD", true, true, false);
    435    is(aElement.innerHTML,
    436       "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>",
    437       aDescription + "Alt+Tab on TD");
    438    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    439       aDescription + "focus moved unexpectedly (Alt+Tab on TD)");
    440 
    441    resetForIndent("<table><tr><td id=\"target\">td</td></tr></table>");
    442    synthesizeKey("KEY_Tab", {metaKey: true});
    443    check(aDescription + "Meta+Tab on TD", true, true, false);
    444    is(aElement.innerHTML,
    445       "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>",
    446       aDescription + "Meta+Tab on TD");
    447    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    448       aDescription + "focus moved unexpectedly (Meta+Tab on TD)");
    449 
    450    // TH
    451    resetForIndent("<table><tr><th id=\"target\">th</th></tr></table>");
    452    synthesizeKey("KEY_Tab");
    453    check(aDescription + "Tab on TH",
    454          true, true, !aIsTabbable && !aIsReadonly);
    455    is(aElement.innerHTML,
    456       aIsTabbable || aIsReadonly ?
    457         "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>" :
    458         aIsPlaintext ? "<table><tbody><tr><th id=\"target\">th\t</th></tr></tbody></table>" :
    459           "<table><tbody><tr><th id=\"target\">th</th></tr><tr><td style=\"vertical-align: top;\"><br></td></tr></tbody></table>",
    460       aDescription + "Tab on TH");
    461    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    462       aDescription + "focus moved unexpectedly (Tab on TH)");
    463    synthesizeKey("KEY_Tab", {shiftKey: true});
    464    check(aDescription + "Shift+Tab after Tab on TH",
    465          true, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext);
    466    is(aElement.innerHTML,
    467       aIsTabbable || aIsReadonly ?
    468         "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>" :
    469         aIsPlaintext ? "<table><tbody><tr><th id=\"target\">th\t</th></tr></tbody></table>" :
    470           "<table><tbody><tr><th id=\"target\">th</th></tr><tr><td style=\"vertical-align: top;\"><br></td></tr></tbody></table>",
    471       aDescription + "Shift+Tab after Tab on TH");
    472    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    473       aDescription + "focus moved unexpectedly (Shift+Tab after Tab on TH)");
    474 
    475    resetForIndent("<table><tr><th id=\"target\">th</th></tr></table>");
    476    synthesizeKey("KEY_Tab", {shiftKey: true});
    477    check(aDescription + "Shift+Tab on TH", true, true, false);
    478    is(aElement.innerHTML,
    479       "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>",
    480       aDescription + "Shift+Tab on TH");
    481    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    482       aDescription + "focus moved unexpectedly (Shift+Tab on TH)");
    483 
    484    // Ctrl+Tab should be consumed by tabbrowser at keydown, so, keypress
    485    // event should never be fired.
    486    resetForIndent("<table><tr><th id=\"target\">th</th></tr></table>");
    487    synthesizeKey("KEY_Tab", {ctrlKey: true});
    488    check(aDescription + "Ctrl+Tab on TH", false, false, false);
    489    is(aElement.innerHTML,
    490       "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>",
    491       aDescription + "Ctrl+Tab on TH");
    492    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    493       aDescription + "focus moved unexpectedly (Ctrl+Tab on TH)");
    494 
    495    resetForIndent("<table><tr><th id=\"target\">th</th></tr></table>");
    496    synthesizeKey("KEY_Tab", {altKey: true});
    497    check(aDescription + "Alt+Tab on TH", true, true, false);
    498    is(aElement.innerHTML,
    499       "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>",
    500       aDescription + "Alt+Tab on TH");
    501    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    502       aDescription + "focus moved unexpectedly (Alt+Tab on TH)");
    503 
    504    resetForIndent("<table><tr><th id=\"target\">th</th></tr></table>");
    505    synthesizeKey("KEY_Tab", {metaKey: true});
    506    check(aDescription + "Meta+Tab on TH", true, true, false);
    507    is(aElement.innerHTML,
    508       "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>",
    509       aDescription + "Meta+Tab on TH");
    510    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    511       aDescription + "focus moved unexpectedly (Meta+Tab on TH)");
    512 
    513    // Esc key:
    514    //   In all cases, esc key events are not consumed
    515    reset("abc");
    516    synthesizeKey("KEY_Escape");
    517    check(aDescription + "Esc", true, true, false);
    518 
    519    reset("abc");
    520    synthesizeKey("KEY_Escape", {shiftKey: true});
    521    check(aDescription + "Shift+Esc", true, true, false);
    522 
    523    reset("abc");
    524    synthesizeKey("KEY_Escape", {ctrlKey: true});
    525    check(aDescription + "Ctrl+Esc", true, true, false);
    526 
    527    reset("abc");
    528    synthesizeKey("KEY_Escape", {altKey: true});
    529    check(aDescription + "Alt+Esc", true, true, false);
    530 
    531    reset("abc");
    532    synthesizeKey("KEY_Escape", {metaKey: true});
    533    check(aDescription + "Meta+Esc", true, true, false);
    534 
    535    // typical typing tests:
    536    reset("");
    537    sendString("M");
    538    check(aDescription + "M", true, true, !aIsReadonly);
    539    sendString("o");
    540    check(aDescription + "o", true, true, !aIsReadonly);
    541    sendString("z");
    542    check(aDescription + "z", true, true, !aIsReadonly);
    543    sendString("i");
    544    check(aDescription + "i", true, true, !aIsReadonly);
    545    sendString("l");
    546    check(aDescription + "l", true, true, !aIsReadonly);
    547    sendString("l");
    548    check(aDescription + "l", true, true, !aIsReadonly);
    549    sendString("a");
    550    check(aDescription + "a", true, true, !aIsReadonly);
    551    sendString(" ");
    552    check(aDescription + "' '", true, true, !aIsReadonly);
    553    is(aElement.innerHTML,
    554       (() => {
    555         if (aIsReadonly) {
    556           return "";
    557         }
    558         if (aIsPlaintext) {
    559           return "Mozilla ";
    560         }
    561         return "Mozilla&nbsp;";
    562       })(),
    563       aDescription + "typed \"Mozilla \"");
    564 
    565    // typing non-BMP character:
    566    async function test_typing_surrogate_pair(
    567      aTestPerSurrogateKeyPress,
    568      aTestIllFormedUTF16KeyValue = false
    569    ) {
    570      await SpecialPowers.pushPrefEnv({
    571        set: [
    572          ["dom.event.keypress.dispatch_once_per_surrogate_pair", !aTestPerSurrogateKeyPress],
    573          ["dom.event.keypress.key.allow_lone_surrogate", aTestIllFormedUTF16KeyValue],
    574        ],
    575      });
    576      reset("");
    577      let events = [];
    578      function pushIntoEvents(aEvent) {
    579        events.push(aEvent);
    580      }
    581      function getEventData(aKeyboardEventOrInputEvent) {
    582        if (!aKeyboardEventOrInputEvent) {
    583          return "{}";
    584        }
    585        switch (aKeyboardEventOrInputEvent.type) {
    586          case "keydown":
    587          case "keypress":
    588          case "keyup":
    589            return `{ type: "${aKeyboardEventOrInputEvent.type}", key="${
    590              aKeyboardEventOrInputEvent.key
    591            }", charCode=0x${
    592              aKeyboardEventOrInputEvent.charCode.toString(16).toUpperCase()
    593            } }`;
    594          default:
    595            return `{ type: "${aKeyboardEventOrInputEvent.type}", inputType="${
    596              aKeyboardEventOrInputEvent.inputType
    597            }", data="${aKeyboardEventOrInputEvent.data}" }`;
    598        }
    599      }
    600      function getEventArrayData(aEvents) {
    601        if (!aEvents.length) {
    602          return "[]";
    603        }
    604        let result = "[\n";
    605        for (const e of aEvents) {
    606          result += `  ${getEventData(e)}\n`;
    607        }
    608        return result + "]";
    609      }
    610      aElement.addEventListener("keydown", pushIntoEvents);
    611      aElement.addEventListener("keypress", pushIntoEvents);
    612      aElement.addEventListener("keyup", pushIntoEvents);
    613      aElement.addEventListener("beforeinput", pushIntoEvents);
    614      aElement.addEventListener("input", pushIntoEvents);
    615      synthesizeKey("\uD842\uDFB7");
    616      aElement.removeEventListener("keydown", pushIntoEvents);
    617      aElement.removeEventListener("keypress", pushIntoEvents);
    618      aElement.removeEventListener("keyup", pushIntoEvents);
    619      aElement.removeEventListener("beforeinput", pushIntoEvents);
    620      aElement.removeEventListener("input", pushIntoEvents);
    621      const settingDescription =
    622        `aTestPerSurrogateKeyPress=${
    623          aTestPerSurrogateKeyPress
    624        }, aTestIllFormedUTF16KeyValue=${aTestIllFormedUTF16KeyValue}`;
    625      const allowIllFormedUTF16 =
    626        aTestPerSurrogateKeyPress && aTestIllFormedUTF16KeyValue;
    627 
    628      check(`${aDescription}, ${settingDescription}a surrogate pair`, true, true, !aIsReadonly);
    629      is(
    630        aElement.textContent,
    631        !aIsReadonly ? "\uD842\uDFB7" : "",
    632        `${aDescription}, ${settingDescription}, The typed surrogate pair should've been inserted`
    633      );
    634      if (aIsReadonly) {
    635        is(
    636          getEventArrayData(events),
    637          getEventArrayData(
    638            aTestPerSurrogateKeyPress
    639              ? (
    640                allowIllFormedUTF16
    641                ? [
    642                    { type: "keydown",     key:  "\uD842\uDFB7", charCode: 0 },
    643                    { type: "keypress",    key:  "\uD842",       charCode: 0xD842 },
    644                    { type: "keypress",    key:  "\uDFB7",       charCode: 0xDFB7 },
    645                    { type: "keyup",       key:  "\uD842\uDFB7", charCode: 0 },
    646                  ]
    647                : [
    648                    { type: "keydown",     key:  "\uD842\uDFB7", charCode: 0 },
    649                    { type: "keypress",    key:  "\uD842\uDFB7", charCode: 0xD842 },
    650                    { type: "keypress",    key:  "",             charCode: 0xDFB7 },
    651                    { type: "keyup",       key:  "\uD842\uDFB7", charCode: 0 },
    652                  ]
    653                )
    654              : [
    655                  { type: "keydown",     key:  "\uD842\uDFB7", charCode: 0 },
    656                  { type: "keypress",    key:  "\uD842\uDFB7", charCode: 0x20BB7 },
    657                  { type: "keyup",       key:  "\uD842\uDFB7", charCode: 0 },
    658                ]
    659          ),
    660          `${aDescription}, ${
    661            settingDescription
    662          }, Typing a surrogate pair in readonly editor should not cause input events`
    663        );
    664      } else {
    665        is(
    666          getEventArrayData(events),
    667          getEventArrayData(
    668            aTestPerSurrogateKeyPress
    669              ? (
    670                allowIllFormedUTF16
    671                ? [
    672                    { type: "keydown",     key:  "\uD842\uDFB7", charCode: 0 },
    673                    { type: "keypress",    key:  "\uD842",       charCode: 0xD842 },
    674                    { type: "beforeinput", data: "\uD842",       inputType: "insertText" },
    675                    { type: "input",       data: "\uD842",       inputType: "insertText" },
    676                    { type: "keypress",    key:  "\uDFB7",       charCode: 0xDFB7 },
    677                    { type: "beforeinput", data: "\uDFB7",       inputType: "insertText" },
    678                    { type: "input",       data: "\uDFB7",       inputType: "insertText" },
    679                    { type: "keyup",       key:  "\uD842\uDFB7", charCode: 0 },
    680                  ]
    681                : [
    682                    { type: "keydown",     key:  "\uD842\uDFB7", charCode: 0 },
    683                    { type: "keypress",    key:  "\uD842\uDFB7", charCode: 0xD842 },
    684                    { type: "beforeinput", data: "\uD842\uDFB7", inputType: "insertText" },
    685                    { type: "input",       data: "\uD842\uDFB7", inputType: "insertText" },
    686                    { type: "keypress",    key:  "",             charCode: 0xDFB7 },
    687                    { type: "keyup",       key:  "\uD842\uDFB7", charCode: 0 },
    688                  ]
    689                )
    690              : [
    691                  { type: "keydown",     key:  "\uD842\uDFB7", charCode: 0 },
    692                  { type: "keypress",    key:  "\uD842\uDFB7", charCode: 0x20BB7 },
    693                  { type: "beforeinput", data: "\uD842\uDFB7", inputType: "insertText" },
    694                  { type: "input",       data: "\uD842\uDFB7", inputType: "insertText" },
    695                  { type: "keyup",       key:  "\uD842\uDFB7", charCode: 0 },
    696                ]
    697          ),
    698          `${aDescription}, ${
    699            settingDescription
    700          }, Typing a surrogate pair in editor should cause input events`
    701        );
    702      }
    703    }
    704    await test_typing_surrogate_pair(true, true);
    705    await test_typing_surrogate_pair(true, false);
    706    await test_typing_surrogate_pair(false);
    707  }
    708 
    709  await doTest(htmlEditor, "contenteditable=\"true\"", false, true, false);
    710 
    711  const nsIEditor = SpecialPowers.Ci.nsIEditor;
    712  var editor = SpecialPowers.wrap(window).docShell.editor;
    713  var flags = editor.flags;
    714  // readonly
    715  editor.flags = flags | nsIEditor.eEditorReadonlyMask;
    716  await doTest(htmlEditor, "readonly HTML editor", true, true, false);
    717 
    718  // non-tabbable
    719  editor.flags = flags & ~(nsIEditor.eEditorAllowInteraction);
    720  await doTest(htmlEditor, "non-tabbable HTML editor", false, false, false);
    721 
    722  // readonly and non-tabbable
    723  editor.flags =
    724    (flags | nsIEditor.eEditorReadonlyMask) &
    725      ~(nsIEditor.eEditorAllowInteraction);
    726  await doTest(htmlEditor, "readonly and non-tabbable HTML editor",
    727         true, false, false);
    728 
    729  // plaintext
    730  editor.flags = flags | nsIEditor.eEditorPlaintextMask;
    731  await doTest(htmlEditor, "HTML editor but plaintext mode", false, true, true);
    732 
    733  // plaintext and non-tabbable
    734  editor.flags = (flags | nsIEditor.eEditorPlaintextMask) &
    735                 ~(nsIEditor.eEditorAllowInteraction);
    736  await doTest(htmlEditor, "non-tabbable HTML editor but plaintext mode",
    737         false, false, true);
    738 
    739 
    740  // readonly and plaintext
    741  editor.flags = flags | nsIEditor.eEditorPlaintextMask |
    742                         nsIEditor.eEditorReadonlyMask;
    743  await doTest(htmlEditor, "readonly HTML editor but plaintext mode",
    744         true, true, true);
    745 
    746  // readonly, plaintext and non-tabbable
    747  editor.flags = (flags | nsIEditor.eEditorPlaintextMask |
    748                          nsIEditor.eEditorReadonlyMask) &
    749                 ~(nsIEditor.eEditorAllowInteraction);
    750  await doTest(htmlEditor, "readonly and non-tabbable HTML editor but plaintext mode",
    751         true, false, true);
    752 
    753  SpecialPowers.wrap(window).removeEventListener("keypress", listener, { capture: true, mozSystemGroup: true });
    754  SpecialPowers.wrap(window).removeEventListener("keypress", listener, { capture: false, mozSystemGroup: true });
    755 
    756  SimpleTest.finish();
    757 }
    758 
    759 </script>
    760 </body>
    761 
    762 </html>