tor-browser

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

window_nsITextInputProcessor.xhtml (246514B)


      1 <?xml version="1.0"?>
      2 <?xml-stylesheet href="chrome://global/skin" type="text/css"?>
      3 <?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
      4                 type="text/css"?>
      5 <window title="Testing nsITextInputProcessor behavior"
      6  xmlns:html="http://www.w3.org/1999/xhtml"
      7  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
      8  onunload="onunload();">
      9 <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
     10 <body  xmlns="http://www.w3.org/1999/xhtml">
     11 <div id="display">
     12 <input id="input" type="text"/><input id="anotherInput" type="text"/><br/>
     13 <textarea></textarea>
     14 <iframe id="iframe" width="300" height="150"
     15        src="data:text/html,&lt;textarea id='textarea' cols='20' rows='4'&gt;&lt;/textarea&gt;"></iframe><br/>
     16 <div contenteditable=""><br/></div>
     17 </div>
     18 <div id="content" style="display: none">
     19 
     20 </div>
     21 <pre id="test">
     22 </pre>
     23 </body>
     24 
     25 <script class="testbody" type="application/javascript">
     26 <![CDATA[
     27 
     28 var SimpleTest = window.arguments[0].SimpleTest;
     29 
     30 SimpleTest.waitForFocus(runTests, window);
     31 
     32 function getHTMLEditor(aWindow) {
     33  return SpecialPowers.wrap(aWindow).docShell.editingSession?.getEditorForWindow(aWindow);
     34 }
     35 
     36 function ok(aCondition, aMessage)
     37 {
     38  SimpleTest.ok(aCondition, aMessage);
     39 }
     40 
     41 function is(aLeft, aRight, aMessage)
     42 {
     43  SimpleTest.is(aLeft, aRight, aMessage);
     44 }
     45 
     46 function isnot(aLeft, aRight, aMessage)
     47 {
     48  SimpleTest.isnot(aLeft, aRight, aMessage);
     49 }
     50 
     51 function todo_is(aLeft, aRight, aMessage)
     52 {
     53  SimpleTest.todo_is(aLeft, aRight, aMessage);
     54 }
     55 
     56 function info(aMessage) {
     57  SimpleTest.info(aMessage);
     58 }
     59 
     60 function finish()
     61 {
     62  window.close();
     63 }
     64 
     65 function onunload()
     66 {
     67  SimpleTest.finish();
     68 }
     69 
     70 function checkInputEvent(aEvent, aCancelable, aIsComposing, aInputType, aData, aDescription) {
     71  if (aEvent.type !== "input" && aEvent.type !== "beforeinput") {
     72    console.trace();
     73    throw new Error(`${aDescription}"${aEvent.type}" is not InputEvent`);
     74  }
     75  ok(InputEvent.isInstance(aEvent), `${aDescription}"${aEvent.type}" event should be dispatched with InputEvent interface`);
     76  is(aEvent.cancelable, aCancelable, `${aDescription}"${aEvent.type}" event should ${aCancelable ? "be" : "not be"} cancelable`);
     77  is(aEvent.bubbles, true, `${aDescription}"${aEvent.type}" event should always bubble`);
     78  is(aEvent.isComposing, aIsComposing, `${aDescription}isComposing of "${aEvent.type}" event should be ${aIsComposing}`);
     79  is(aEvent.inputType, aInputType, `${aDescription}inputType of "${aEvent.type}" event should be "${aInputType}"`);
     80  is(aEvent.data, aData, `${aDescription}data of "${aEvent.type}" event should be "${aData}"`);
     81  is(aEvent.dataTransfer, null, `${aDescription}dataTransfer of "${aEvent.type}" event should be null`);
     82  is(aEvent.getTargetRanges().length, 0, `${aDescription}getTargetRanges() of "${aEvent.type}" event should return empty array`);
     83 }
     84 
     85 const kIsMac = (navigator.platform.indexOf("Mac") == 0);
     86 
     87 const iframe = document.getElementById("iframe");
     88 let childWindow = iframe.contentWindow;
     89 let textareaInFrame;
     90 let input = document.getElementById("input");
     91 const textarea = document.querySelector("textarea");
     92 const otherWindow = window.arguments[0];
     93 const otherDocument = otherWindow.document;
     94 const inputInChildWindow = otherDocument.getElementById("input");
     95 const contenteditable = document.querySelector("div[contenteditable]");
     96 const { AppConstants } = ChromeUtils.importESModule(
     97  "resource://gre/modules/AppConstants.sys.mjs"
     98 );
     99 const kLF = "\n";
    100 const kExpectInputBeforeCompositionEnd = SpecialPowers.getBoolPref("dom.input_events.dispatch_before_compositionend");
    101 
    102 function getNativeText(aXPText)
    103 {
    104  if (kLF == "\n") {
    105    return aXPText;
    106  }
    107  return aXPText.replace(/\n/g, kLF);
    108 }
    109 
    110 function createTIP()
    111 {
    112  return Cc["@mozilla.org/text-input-processor;1"].
    113           createInstance(Ci.nsITextInputProcessor);
    114 }
    115 
    116 function runBeginInputTransactionMethodTests()
    117 {
    118  var description = "runBeginInputTransactionMethodTests: ";
    119  input.value = "";
    120  input.focus();
    121 
    122  var simpleCallback = function (aTIP, aNotification)
    123  {
    124    switch (aNotification.type) {
    125      case "request-to-commit":
    126        aTIP.commitComposition();
    127        break;
    128      case "request-to-cancel":
    129        aTIP.cancelComposition();
    130        break;
    131    }
    132    return true;
    133  };
    134 
    135  var TIP1 = createTIP();
    136  var TIP2 = createTIP();
    137  isnot(TIP1, TIP2,
    138        description + "TIP instances should be different");
    139 
    140  // beginInputTransaction() and beginInputTransactionForTests() can take ownership if there is no composition.
    141  ok(TIP1.beginInputTransaction(window, simpleCallback),
    142     description + "TIP1.beginInputTransaction(window) should succeed because there is no composition");
    143  ok(TIP1.beginInputTransactionForTests(window),
    144     description + "TIP1.beginInputTransactionForTests(window) should succeed because there is no composition");
    145  ok(TIP2.beginInputTransaction(window, simpleCallback),
    146     description + "TIP2.beginInputTransaction(window) should succeed because there is no composition");
    147  ok(TIP2.beginInputTransactionForTests(window),
    148     description + "TIP2.beginInputTransactionForTests(window) should succeed because there is no composition");
    149 
    150  // Start composition with TIP1, then, other TIPs cannot take ownership during a composition.
    151  ok(TIP1.beginInputTransactionForTests(window),
    152     description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition");
    153  var composingStr = "foo";
    154  TIP1.setPendingCompositionString(composingStr);
    155  TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
    156  ok(TIP1.flushPendingComposition(),
    157     description + "TIP1.flushPendingComposition() should return true becuase it should be valid composition");
    158  is(input.value, composingStr,
    159     description + "The input element should have composing string");
    160 
    161  // Composing nsITextInputProcessor instance shouldn't allow initialize it again.
    162  try {
    163    TIP1.beginInputTransaction(window, simpleCallback);
    164    ok(false,
    165       "TIP1.beginInputTransaction(window) should cause throwing an exception because it's composing with different purpose");
    166  } catch (e) {
    167    ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
    168       description + "TIP1.beginInputTransaction(window) should cause throwing an exception including NS_ERROR_ALREADY_INITIALIZED because it's composing for tests");
    169  }
    170  try {
    171    TIP1.beginInputTransactionForTests(otherWindow);
    172    ok(false,
    173       "TIP1.beginInputTransactionForTests(otherWindow) should cause throwing an exception because it's composing on different window");
    174  } catch (e) {
    175    ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
    176       description + "TIP1.beginInputTransaction(otherWindow) should cause throwing an exception including NS_ERROR_ALREADY_INITIALIZED because it's composing on this window");
    177  }
    178  ok(TIP1.beginInputTransactionForTests(window),
    179     description + "TIP1.beginInputTransactionForTests(window) should succeed because TextEventDispatcher was initialized with same purpose");
    180  ok(TIP1.beginInputTransactionForTests(childWindow),
    181     description + "TIP1.beginInputTransactionForTests(childWindow) should succeed because TextEventDispatcher was initialized with same purpose and is shared by window and childWindow");
    182  ok(!TIP2.beginInputTransaction(window, simpleCallback),
    183     description + "TIP2.beginInputTransaction(window) should not succeed because there is composition synthesized by TIP1");
    184  ok(!TIP2.beginInputTransactionForTests(window),
    185     description + "TIP2.beginInputTransactionForTests(window) should not succeed because there is composition synthesized by TIP1");
    186  ok(!TIP2.beginInputTransaction(childWindow, simpleCallback),
    187     description + "TIP2.beginInputTransaction(childWindow) should not succeed because there is composition synthesized by TIP1");
    188  ok(!TIP2.beginInputTransactionForTests(childWindow),
    189     description + "TIP2.beginInputTransactionForTests(childWindow) should not succeed because there is composition synthesized by TIP1");
    190  ok(TIP2.beginInputTransaction(otherWindow, simpleCallback),
    191     description + "TIP2.beginInputTransaction(otherWindow) should succeed because there is composition synthesized by TIP1 but it's in other window");
    192  ok(TIP2.beginInputTransactionForTests(otherWindow),
    193     description + "TIP2.beginInputTransactionForTests(otherWindow) should succeed because there is composition synthesized by TIP1 but it's in other window");
    194 
    195  // Let's confirm that the composing string is NOT committed by above tests.
    196  TIP1.commitComposition();
    197  is(input.value, composingStr,
    198     description + "TIP1.commitString() without specifying commit string should commit current composition with the last composing string");
    199  ok(!TIP1.hasComposition,
    200     description + "TIP1.commitString() without specifying commit string should've end composition");
    201 
    202  ok(TIP1.beginInputTransaction(window, simpleCallback),
    203     description + "TIP1.beginInputTransaction() should succeed because there is no composition #2");
    204  ok(TIP1.beginInputTransactionForTests(window),
    205     description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition #2");
    206  ok(TIP2.beginInputTransactionForTests(window),
    207     description + "TIP2.beginInputTransactionForTests() should succeed because the composition was already committed #2");
    208 
    209  // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during startComposition().
    210  var events = [];
    211  input.addEventListener("compositionstart", function (aEvent) {
    212    events.push(aEvent);
    213    // eslint-disable-next-line no-caller
    214    input.removeEventListener(aEvent.type, arguments.callee);
    215    ok(!TIP2.beginInputTransaction(window, simpleCallback),
    216       description + "TIP2 shouldn't be able to begin input transaction from compositionstart event handler during TIP1.startComposition();");
    217  });
    218  TIP1.beginInputTransaction(window, simpleCallback);
    219  TIP1.startComposition();
    220  is(events.length, 1,
    221     description + "compositionstart event should be fired by TIP1.startComposition()");
    222  TIP1.cancelComposition();
    223 
    224  // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during flushPendingComposition().
    225  events = [];
    226  input.addEventListener("compositionstart", function (aEvent) {
    227    events.push(aEvent);
    228    ok(!TIP2.beginInputTransaction(window, simpleCallback),
    229       description + "TIP2 shouldn't be able to begin input transaction from compositionstart event handler during a call of TIP1.flushPendingComposition();");
    230  }, {once: true});
    231  input.addEventListener("compositionupdate", function (aEvent) {
    232    events.push(aEvent);
    233    ok(!TIP2.beginInputTransaction(window, simpleCallback),
    234       description + "TIP2 shouldn't be able to begin input transaction from compositionupdate event handler during a call of TIP1.flushPendingComposition();");
    235  }, {once: true});
    236  input.addEventListener("text", function (aEvent) {
    237    events.push(aEvent);
    238    ok(!TIP2.beginInputTransaction(window, simpleCallback),
    239       description + "TIP2 shouldn't be able to begin input transaction from text event handler during a call of TIP1.flushPendingComposition();");
    240  }, {once: true});
    241  input.addEventListener("beforeinput", function (aEvent) {
    242    events.push(aEvent);
    243    ok(!TIP2.beginInputTransaction(window, simpleCallback),
    244       description + "TIP2 shouldn't be able to begin input transaction from beforeinput event handler during a call of TIP1.flushPendingComposition();");
    245  }, {once: true});
    246  input.addEventListener("input", function (aEvent) {
    247    events.push(aEvent);
    248    ok(!TIP2.beginInputTransaction(window, simpleCallback),
    249       description + "TIP2 shouldn't be able to begin input transaction from input event handler during a call of TIP1.flushPendingComposition();");
    250  }, {once: true});
    251  TIP1.beginInputTransaction(window, simpleCallback);
    252  TIP1.setPendingCompositionString(composingStr);
    253  TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
    254  TIP1.flushPendingComposition();
    255  is(events.length, 5,
    256     description + "compositionstart, compositionupdate, text, beforeinput and input events should be fired by TIP1.flushPendingComposition()");
    257  is(events[0].type, "compositionstart",
    258     description + "events[0] should be compositionstart");
    259  is(events[1].type, "compositionupdate",
    260     description + "events[1] should be compositionupdate");
    261  is(events[2].type, "text",
    262     description + "events[2] should be text");
    263  is(events[3].type, "beforeinput",
    264     description + "events[3] should be beforeinput");
    265  checkInputEvent(events[3], false, true, "insertCompositionText", composingStr, description);
    266  is(events[4].type, "input",
    267     description + "events[4] should be input");
    268  checkInputEvent(events[4], false, true, "insertCompositionText", composingStr, description);
    269  TIP1.cancelComposition();
    270 
    271  // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during commitComposition().
    272  (() => {
    273    events = [];
    274    TIP1.beginInputTransaction(window, simpleCallback);
    275    TIP1.setPendingCompositionString(composingStr);
    276    TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
    277    TIP1.flushPendingComposition();
    278    input.addEventListener("text", function (aEvent) {
    279      events.push(aEvent);
    280      ok(!TIP2.beginInputTransaction(window, simpleCallback),
    281        description + "TIP2 shouldn't be able to begin input transaction from text event handler during a call of TIP1.commitComposition();");
    282    }, {once: true});
    283    input.addEventListener("compositionend", function (aEvent) {
    284      events.push(aEvent);
    285      ok(!TIP2.beginInputTransaction(window, simpleCallback),
    286        description + "TIP2 shouldn't be able to begin input transaction from compositionend event handler during a call of TIP1.commitComposition();");
    287    }, {once: true});
    288    input.addEventListener("beforeinput", function (aEvent) {
    289      events.push(aEvent);
    290      ok(!TIP2.beginInputTransaction(window, simpleCallback),
    291        description + "TIP2 shouldn't be able to begin input transaction from beforeinput event handler during a call of TIP1.commitComposition();");
    292    }, {once: true});
    293    function onInput(aEvent) {
    294      events.push(aEvent);
    295      ok(!TIP2.beginInputTransaction(window, simpleCallback),
    296        description + "TIP2 shouldn't be able to begin input transaction from input event handler during a call of TIP1.commitComposition();");
    297    }
    298    input.addEventListener("input", onInput);
    299    TIP1.commitComposition();
    300    is(events.length, kExpectInputBeforeCompositionEnd ? 5 : 4,
    301      description + "text, beforeinput, compositionend and input events should be fired by TIP1.commitComposition()");
    302    let index = -1;
    303    is(events[++index].type, "text",
    304      `${description}events[${index}] should be text`);
    305    is(events[++index].type, "beforeinput",
    306      `${description}events[${index}] should be beforeinput`);
    307    checkInputEvent(events[index], false, true, "insertCompositionText", composingStr, description);
    308    if (kExpectInputBeforeCompositionEnd) {
    309      is(events[++index].type, "input",
    310        `${description}events[${index}] should be input`);
    311      checkInputEvent(events[index], false, true, "insertCompositionText", composingStr, description);
    312    }
    313    is(events[++index].type, "compositionend",
    314      `${description}events[${index}] should be compositionend`);
    315    is(events[++index].type, "input",
    316      `${description}events[${index}] should be input`);
    317    checkInputEvent(events[index], false, false, "insertCompositionText", composingStr, description);
    318    input.removeEventListener("input", onInput);
    319  })();
    320 
    321  // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during commitCompositionWith("bar").
    322  (() => {
    323    events = [];
    324    input.addEventListener("compositionstart", function (aEvent) {
    325      events.push(aEvent);
    326      ok(!TIP2.beginInputTransaction(window, simpleCallback),
    327        description + "TIP2 shouldn't be able to begin input transaction from compositionstart event handler during TIP1.commitCompositionWith(\"bar\");");
    328    }, {once: true});
    329    input.addEventListener("compositionupdate", function (aEvent) {
    330      events.push(aEvent);
    331      ok(!TIP2.beginInputTransaction(window, simpleCallback),
    332        description + "TIP2 shouldn't be able to begin input transaction during compositionupdate event handler TIP1.commitCompositionWith(\"bar\");");
    333    }, {once: true});
    334    input.addEventListener("text", function (aEvent) {
    335      events.push(aEvent);
    336      ok(!TIP2.beginInputTransaction(window, simpleCallback),
    337        description + "TIP2 shouldn't be able to begin input transaction during text event handler TIP1.commitCompositionWith(\"bar\");");
    338    }, {once: true});
    339    input.addEventListener("compositionend", function (aEvent) {
    340      events.push(aEvent);
    341      ok(!TIP2.beginInputTransaction(window, simpleCallback),
    342        description + "TIP2 shouldn't be able to begin input transaction during compositionend event handler TIP1.commitCompositionWith(\"bar\");");
    343    }, {once: true});
    344    input.addEventListener("beforeinput", function (aEvent) {
    345      events.push(aEvent);
    346      ok(!TIP2.beginInputTransaction(window, simpleCallback),
    347        description + "TIP2 shouldn't be able to begin input transaction during beforeinput event handler TIP1.commitCompositionWith(\"bar\");");
    348    }, {once: true});
    349    function onInput(aEvent) {
    350      events.push(aEvent);
    351      ok(!TIP2.beginInputTransaction(window, simpleCallback),
    352        description + "TIP2 shouldn't be able to begin input transaction during input event handler TIP1.commitCompositionWith(\"bar\");");
    353    }
    354    input.addEventListener("input", onInput);
    355    TIP1.beginInputTransaction(window, simpleCallback);
    356    TIP1.commitCompositionWith("bar");
    357    is(events.length, kExpectInputBeforeCompositionEnd ? 7 : 6,
    358      description + "compositionstart, compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.commitCompositionWith(\"bar\")");
    359    let index = -1;
    360    is(events[++index].type, "compositionstart",
    361      `${description}events[${index}] should be compositionstart`);
    362    is(events[++index].type, "compositionupdate",
    363      `${description}events[${index}] should be compositionupdate`);
    364    is(events[++index].type, "text",
    365      `${description}events[${index}] should be text`);
    366    is(events[++index].type, "beforeinput",
    367      `${description}events[${index}] should be beforeinput`);
    368    checkInputEvent(events[3], false, true, "insertCompositionText", "bar", description);
    369    if (kExpectInputBeforeCompositionEnd) {
    370      is(events[++index].type, "input",
    371        `${description}events[${index}] should be input`);
    372      checkInputEvent(events[index], false, true, "insertCompositionText", "bar", description);
    373    }
    374    is(events[++index].type, "compositionend",
    375      `${description}events[${index}] should be compositionend`);
    376    is(events[++index].type, "input",
    377      `${description}events[${index}] should be input`);
    378    checkInputEvent(events[index], false, false, "insertCompositionText", "bar", description);
    379    input.removeEventListener("input", onInput);
    380  })();
    381 
    382  // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during cancelComposition().
    383  (() => {
    384    events = [];
    385    TIP1.beginInputTransaction(window, simpleCallback);
    386    TIP1.setPendingCompositionString(composingStr);
    387    TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
    388    TIP1.flushPendingComposition();
    389    input.addEventListener("compositionupdate", function (aEvent) {
    390      events.push(aEvent);
    391      ok(!TIP2.beginInputTransaction(window, simpleCallback),
    392        description + "TIP2 shouldn't be able to begin input transaction from compositionupdate event handler during a call of TIP1.cancelComposition();");
    393    }, {once: true});
    394    input.addEventListener("text", function (aEvent) {
    395      events.push(aEvent);
    396      ok(!TIP2.beginInputTransaction(window, simpleCallback),
    397        description + "TIP2 shouldn't be able to begin input transaction from text event handler during a call of TIP1.cancelComposition();");
    398    }, {once: true});
    399    input.addEventListener("compositionend", function (aEvent) {
    400      events.push(aEvent);
    401      ok(!TIP2.beginInputTransaction(window, simpleCallback),
    402        description + "TIP2 shouldn't be able to begin input transaction from compositionend event handler during a call of TIP1.cancelComposition();");
    403    }, {once: true});
    404    input.addEventListener("beforeinput", function (aEvent) {
    405      events.push(aEvent);
    406      ok(!TIP2.beginInputTransaction(window, simpleCallback),
    407        description + "TIP2 shouldn't be able to begin input transaction from beforeinput event handler during a call of TIP1.cancelComposition();");
    408    }, {once: true});
    409    function onInput(aEvent) {
    410      events.push(aEvent);
    411      ok(!TIP2.beginInputTransaction(window, simpleCallback),
    412        description + "TIP2 shouldn't be able to begin input transaction from input event handler during a call of TIP1.cancelComposition();");
    413    }
    414    input.addEventListener("input", onInput);
    415    TIP1.cancelComposition();
    416    is(events.length, kExpectInputBeforeCompositionEnd ? 6 : 5,
    417      description + "compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.cancelComposition()");
    418    let index = -1;
    419    is(events[++index].type, "compositionupdate",
    420      `${description}events[${index}] should be compositionupdate`);
    421    is(events[++index].type, "text",
    422      `${description}events[${index}] should be text`);
    423    is(events[++index].type, "beforeinput",
    424      `${description}events[${index}] should be beforeinput`);
    425    checkInputEvent(events[index], false, true, "insertCompositionText", "", description);
    426    if (kExpectInputBeforeCompositionEnd) {
    427      is(events[++index].type, "input",
    428        `${description}events[${index}] should be input`);
    429      checkInputEvent(events[index], false, true, "insertCompositionText", "", description);
    430    }
    431    is(events[++index].type, "compositionend",
    432      `${description}events[${index}] should be compositionend`);
    433    is(events[++index].type, "input",
    434      `${description}events[${index}] should be input`);
    435    checkInputEvent(events[index], false, false, "insertCompositionText", "", description);
    436    input.removeEventListener("input", onInput);
    437  })();
    438 
    439  // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during keydown() and keyup().
    440  events = [];
    441  TIP1.beginInputTransaction(window, simpleCallback);
    442  input.addEventListener("keydown", function (aEvent) {
    443    events.push(aEvent);
    444    ok(!TIP2.beginInputTransaction(window, simpleCallback),
    445       description + "TIP2 shouldn't be able to begin input transaction from keydown event handler during a call of TIP1.keydown();");
    446  }, {once: true});
    447  input.addEventListener("keypress", function (aEvent) {
    448    events.push(aEvent);
    449    ok(!TIP2.beginInputTransaction(window, simpleCallback),
    450       description + "TIP2 shouldn't be able to begin input transaction from keypress event handler during a call of TIP1.keydown();");
    451  }, {once: true});
    452  input.addEventListener("beforeinput", function (aEvent) {
    453    events.push(aEvent);
    454    ok(!TIP2.beginInputTransaction(window, simpleCallback),
    455       description + "TIP2 shouldn't be able to begin input transaction from beforeinput event handler during a call of TIP1.keydown();");
    456  }, {once: true});
    457  input.addEventListener("input", function (aEvent) {
    458    events.push(aEvent);
    459    ok(!TIP2.beginInputTransaction(window, simpleCallback),
    460       description + "TIP2 shouldn't be able to begin input transaction from input event handler during a call of TIP1.keydown();");
    461  }, {once: true});
    462  input.addEventListener("keyup", function (aEvent) {
    463    events.push(aEvent);
    464    ok(!TIP2.beginInputTransaction(window, simpleCallback),
    465       description + "TIP2 shouldn't be able to begin input transaction from keyup event handler during a call of TIP1.keyup();");
    466  }, {once: true});
    467  var keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
    468  TIP1.keydown(keyA);
    469  TIP1.keyup(keyA);
    470  is(events.length, 5,
    471     description + "keydown, keypress, beforeinput, input, keyup events should be fired by TIP1.keydown() and TIP1.keyup()");
    472  is(events[0].type, "keydown",
    473     description + "events[0] should be keydown");
    474  is(events[1].type, "keypress",
    475     description + "events[1] should be keypress");
    476  is(events[2].type, "beforeinput",
    477     description + "events[2] should be beforeinput");
    478  checkInputEvent(events[2], true, false, "insertText", "a", description);
    479  is(events[3].type, "input",
    480     description + "events[3] should be input");
    481  checkInputEvent(events[3], false, false, "insertText", "a", description);
    482  is(events[4].type, "keyup",
    483     description + "events[4] should be keyup");
    484 
    485  // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during startComposition().
    486  events = [];
    487  input.addEventListener("compositionstart", function (aEvent) {
    488    events.push(aEvent);
    489    // eslint-disable-next-line no-caller
    490    input.removeEventListener(aEvent.type, arguments.callee);
    491    ok(!TIP2.beginInputTransactionForTests(window),
    492       description + "TIP2 shouldn't be able to begin input transaction for tests from compositionstart event handler during TIP1.startComposition();");
    493  });
    494  TIP1.beginInputTransactionForTests(window);
    495  TIP1.startComposition();
    496  is(events.length, 1,
    497     description + "compositionstart event should be fired by TIP1.startComposition()");
    498  TIP1.cancelComposition();
    499 
    500  // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during flushPendingComposition().
    501  events = [];
    502  input.addEventListener("compositionstart", function (aEvent) {
    503    events.push(aEvent);
    504    ok(!TIP2.beginInputTransactionForTests(window),
    505       description + "TIP2 shouldn't be able to begin input transaction for tests from compositionstart event handler during a call of TIP1.flushPendingComposition();");
    506  }, {once: true});
    507  input.addEventListener("compositionupdate", function (aEvent) {
    508    events.push(aEvent);
    509    ok(!TIP2.beginInputTransactionForTests(window),
    510       description + "TIP2 shouldn't be able to begin input transaction for tests from compositionupdate event handler during a call of TIP1.flushPendingComposition();");
    511  }, {once: true});
    512  input.addEventListener("text", function (aEvent) {
    513    events.push(aEvent);
    514    ok(!TIP2.beginInputTransactionForTests(window),
    515       description + "TIP2 shouldn't be able to begin input transaction for tests from text event handler during a call of TIP1.flushPendingComposition();");
    516  }, {once: true});
    517  input.addEventListener("beforeinput", function (aEvent) {
    518    events.push(aEvent);
    519    ok(!TIP2.beginInputTransactionForTests(window),
    520       description + "TIP2 shouldn't be able to begin input transaction for tests from beforeinput event handler during a call of TIP1.flushPendingComposition();");
    521  }, {once: true});
    522  input.addEventListener("input", function (aEvent) {
    523    events.push(aEvent);
    524    ok(!TIP2.beginInputTransactionForTests(window),
    525       description + "TIP2 shouldn't be able to begin input transaction for tests from input event handler during a call of TIP1.flushPendingComposition();");
    526  }, {once: true});
    527  TIP1.beginInputTransactionForTests(window);
    528  TIP1.setPendingCompositionString(composingStr);
    529  TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
    530  TIP1.flushPendingComposition();
    531  is(events.length, 5,
    532     description + "compositionstart, compositionupdate, text, beforeinput and input events should be fired by TIP1.flushPendingComposition()");
    533  is(events[0].type, "compositionstart",
    534     description + "events[0] should be compositionstart");
    535  is(events[1].type, "compositionupdate",
    536     description + "events[1] should be compositionupdate");
    537  is(events[2].type, "text",
    538     description + "events[2] should be text");
    539  is(events[3].type, "beforeinput",
    540     description + "events[3] should be beforeinput");
    541  checkInputEvent(events[3], false, true, "insertCompositionText", composingStr, description);
    542  is(events[4].type, "input",
    543     description + "events[4] should be input");
    544  checkInputEvent(events[4], false, true, "insertCompositionText", composingStr, description);
    545  TIP1.cancelComposition();
    546 
    547  // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during commitComposition().
    548  (function () {
    549    events = [];
    550    TIP1.beginInputTransactionForTests(window, simpleCallback);
    551    TIP1.setPendingCompositionString(composingStr);
    552    TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
    553    TIP1.flushPendingComposition();
    554    input.addEventListener("text", function (aEvent) {
    555      events.push(aEvent);
    556      ok(!TIP2.beginInputTransactionForTests(window),
    557        description + "TIP2 shouldn't be able to begin input transaction for tests from text event handler during a call of TIP1.commitComposition();");
    558    }, {once: true});
    559    input.addEventListener("compositionend", function (aEvent) {
    560      events.push(aEvent);
    561      ok(!TIP2.beginInputTransactionForTests(window),
    562        description + "TIP2 shouldn't be able to begin input transaction for tests from compositionend event handler during a call of TIP1.commitComposition();");
    563    }, {once: true});
    564    input.addEventListener("beforeinput", function (aEvent) {
    565      events.push(aEvent);
    566      ok(!TIP2.beginInputTransactionForTests(window),
    567        description + "TIP2 shouldn't be able to begin input transaction for tests from beforeinput event handler during a call of TIP1.commitComposition();");
    568    }, {once: true});
    569    function onInput(aEvent) {
    570      events.push(aEvent);
    571      ok(!TIP2.beginInputTransactionForTests(window),
    572        description + "TIP2 shouldn't be able to begin input transaction for tests from input event handler during a call of TIP1.commitComposition();");
    573    }
    574    input.addEventListener("input", onInput);
    575    TIP1.commitComposition();
    576    is(events.length, kExpectInputBeforeCompositionEnd ? 5 : 4,
    577      description + "text, beforeinput, compositionend and input events should be fired by TIP1.commitComposition()");
    578    let index = -1;
    579    is(events[++index].type, "text",
    580      `${description}events[${index}] should be text`);
    581    is(events[++index].type, "beforeinput",
    582      `${description}events[${index}] should be beforeinput`);
    583    checkInputEvent(events[index], false, true, "insertCompositionText", composingStr, description);
    584    if (kExpectInputBeforeCompositionEnd) {
    585      is(events[++index].type, "input",
    586        `${description}events[${index}] should be input`);
    587      checkInputEvent(events[index], false, true, "insertCompositionText", composingStr, description);
    588    }
    589    is(events[++index].type, "compositionend",
    590      `${description}events[${index}] should be compositionend`);
    591    is(events[++index].type, "input",
    592      `${description}events[${index}] should be input`);
    593    checkInputEvent(events[index], false, false, "insertCompositionText", composingStr, description);
    594    input.removeEventListener("input", onInput);
    595  })();
    596 
    597  // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during commitCompositionWith("bar").
    598  (() => {
    599    events = [];
    600    input.addEventListener("compositionstart", function (aEvent) {
    601      events.push(aEvent);
    602      ok(!TIP2.beginInputTransactionForTests(window),
    603        description + "TIP2 shouldn't be able to begin input transaction for tests from compositionstart event handler during TIP1.commitCompositionWith(\"bar\");");
    604    }, {once: true});
    605    input.addEventListener("compositionupdate", function (aEvent) {
    606      events.push(aEvent);
    607      ok(!TIP2.beginInputTransactionForTests(window),
    608        description + "TIP2 shouldn't be able to begin input transaction for tests during compositionupdate event handler TIP1.commitCompositionWith(\"bar\");");
    609    }, {once: true});
    610    input.addEventListener("text", function (aEvent) {
    611      events.push(aEvent);
    612      ok(!TIP2.beginInputTransactionForTests(window),
    613        description + "TIP2 shouldn't be able to begin input transaction for tests during text event handler TIP1.commitCompositionWith(\"bar\");");
    614    }, {once: true});
    615    input.addEventListener("compositionend", function (aEvent) {
    616      events.push(aEvent);
    617      ok(!TIP2.beginInputTransactionForTests(window),
    618        description + "TIP2 shouldn't be able to begin input transaction for tests during compositionend event handler TIP1.commitCompositionWith(\"bar\");");
    619    }, {once: true});
    620    input.addEventListener("beforeinput", function (aEvent) {
    621      events.push(aEvent);
    622      ok(!TIP2.beginInputTransactionForTests(window),
    623        description + "TIP2 shouldn't be able to begin input transaction for tests during beforeinput event handler TIP1.commitCompositionWith(\"bar\");");
    624    }, {once: true});
    625    function onInput(aEvent) {
    626      events.push(aEvent);
    627      ok(!TIP2.beginInputTransactionForTests(window),
    628        description + "TIP2 shouldn't be able to begin input transaction for tests during input event handler TIP1.commitCompositionWith(\"bar\");");
    629    }
    630    input.addEventListener("input", onInput);
    631    TIP1.beginInputTransactionForTests(window);
    632    TIP1.commitCompositionWith("bar");
    633    is(events.length, kExpectInputBeforeCompositionEnd ? 7 : 6,
    634      description + "compositionstart, compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.commitCompositionWith(\"bar\")");
    635    let index = -1;
    636    is(events[++index].type, "compositionstart",
    637      `${description}events[${index}] should be compositionstart`);
    638    is(events[++index].type, "compositionupdate",
    639      `${description}events[${index}] should be compositionupdate`);
    640    is(events[++index].type, "text",
    641      `${description}events[${index}] should be text`);
    642    is(events[++index].type, "beforeinput",
    643      `${description}events[${index}] should be beforeinput`);
    644    checkInputEvent(events[index], false, true, "insertCompositionText", "bar", description);
    645    if (kExpectInputBeforeCompositionEnd) {
    646      is(events[++index].type, "input",
    647        `${description}events[${index}] should be input`);
    648      checkInputEvent(events[index], false, true, "insertCompositionText", "bar", description);
    649    }
    650    is(events[++index].type, "compositionend",
    651      `${description}events[${index}] should be compositionend`);
    652    is(events[++index].type, "input",
    653      `${description}events[${index}] should be input`);
    654    checkInputEvent(events[index], false, false, "insertCompositionText", "bar", description);
    655    input.removeEventListener("input", onInput);
    656  })();
    657 
    658  // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during cancelComposition().
    659  (() => {
    660    events = [];
    661    TIP1.beginInputTransactionForTests(window, simpleCallback);
    662    TIP1.setPendingCompositionString(composingStr);
    663    TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
    664    TIP1.flushPendingComposition();
    665    input.addEventListener("compositionupdate", function (aEvent) {
    666      events.push(aEvent);
    667      ok(!TIP2.beginInputTransactionForTests(window),
    668        description + "TIP2 shouldn't be able to begin input transaction for tests from compositionupdate event handler during a call of TIP1.cancelComposition();");
    669    }, {once: true});
    670    input.addEventListener("text", function (aEvent) {
    671      events.push(aEvent);
    672      ok(!TIP2.beginInputTransactionForTests(window),
    673        description + "TIP2 shouldn't be able to begin input transaction for tests from text event handler during a call of TIP1.cancelComposition();");
    674    }, {once: true});
    675    input.addEventListener("compositionend", function (aEvent) {
    676      events.push(aEvent);
    677      ok(!TIP2.beginInputTransactionForTests(window),
    678        description + "TIP2 shouldn't be able to begin input transaction for tests from compositionend event handler during a call of TIP1.cancelComposition();");
    679    }, {once: true});
    680    input.addEventListener("beforeinput", function (aEvent) {
    681      events.push(aEvent);
    682      ok(!TIP2.beginInputTransactionForTests(window),
    683        description + "TIP2 shouldn't be able to begin input transaction for tests from beforeinput event handler during a call of TIP1.cancelComposition();");
    684    }, {once: true});
    685    function onInput(aEvent) {
    686      events.push(aEvent);
    687      ok(!TIP2.beginInputTransactionForTests(window),
    688        description + "TIP2 shouldn't be able to begin input transaction for tests from input event handler during a call of TIP1.cancelComposition();");
    689    }
    690    input.addEventListener("input", onInput);
    691    TIP1.cancelComposition();
    692    is(events.length, kExpectInputBeforeCompositionEnd ? 6 : 5,
    693      description + "compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.cancelComposition()");
    694    let index = -1;
    695    is(events[++index].type, "compositionupdate",
    696      `${description}events[${index}] should be compositionupdate`);
    697    is(events[++index].type, "text",
    698      `${description}events[${index}] should be text`);
    699    is(events[++index].type, "beforeinput",
    700      `${description}events[${index}] should be beforeinput`);
    701    checkInputEvent(events[index], false, true, "insertCompositionText", "", description);
    702    if (kExpectInputBeforeCompositionEnd) {
    703      is(events[++index].type, "input",
    704        `${description}events[${index}] should be input`);
    705      checkInputEvent(events[index], false, true, "insertCompositionText", "", description);
    706    }
    707    is(events[++index].type, "compositionend",
    708      `${description}events[${index}] should be compositionend`);
    709    is(events[++index].type, "input",
    710      `${description}events[${index}] should be input`);
    711    checkInputEvent(events[index], false, false, "insertCompositionText", "", description);
    712    input.removeEventListener("input", onInput);
    713  })();
    714 
    715  // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during keydown() and keyup().
    716  events = [];
    717  TIP1.beginInputTransactionForTests(window);
    718  input.addEventListener("keydown", function (aEvent) {
    719    events.push(aEvent);
    720    ok(!TIP2.beginInputTransactionForTests(window),
    721       description + "TIP2 shouldn't be able to begin input transaction for tests for tests from keydown event handler during a call of TIP1.keydown();");
    722  }, {once: true});
    723  input.addEventListener("keypress", function (aEvent) {
    724    events.push(aEvent);
    725    ok(!TIP2.beginInputTransactionForTests(window),
    726       description + "TIP2 shouldn't be able to begin input transaction for tests from keypress event handler during a call of TIP1.keydown();");
    727  }, {once: true});
    728  input.addEventListener("beforeinput", function (aEvent) {
    729    events.push(aEvent);
    730    ok(!TIP2.beginInputTransactionForTests(window),
    731       description + "TIP2 shouldn't be able to begin input transaction for tests from beforeinput event handler during a call of TIP1.keydown();");
    732  }, {once: true});
    733  input.addEventListener("input", function (aEvent) {
    734    events.push(aEvent);
    735    ok(!TIP2.beginInputTransactionForTests(window),
    736       description + "TIP2 shouldn't be able to begin input transaction for tests from input event handler during a call of TIP1.keydown();");
    737  }, {once: true});
    738  input.addEventListener("keyup", function (aEvent) {
    739    events.push(aEvent);
    740    ok(!TIP2.beginInputTransactionForTests(window),
    741       description + "TIP2 shouldn't be able to begin input transaction for tests from keyup event handler during a call of TIP1.keyup();");
    742  }, {once: true});
    743  keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
    744  TIP1.keydown(keyA);
    745  TIP1.keyup(keyA);
    746  is(events.length, 5,
    747     description + "keydown, keypress, beforeinput, input, keyup events should be fired by TIP1.keydown() and TIP1.keyup()");
    748  is(events[0].type, "keydown",
    749     description + "events[0] should be keydown");
    750  is(events[1].type, "keypress",
    751     description + "events[1] should be keypress");
    752  is(events[2].type, "beforeinput",
    753     description + "events[2] should be beforeinput");
    754  checkInputEvent(events[2], true, false, "insertText", "a", description);
    755  is(events[3].type, "input",
    756     description + "events[3] should be input");
    757  checkInputEvent(events[3], false, false, "insertText", "a", description);
    758  is(events[4].type, "keyup",
    759     description + "events[4] should be keyup");
    760 
    761  // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during startComposition().
    762  events = [];
    763  input.addEventListener("compositionstart", function (aEvent) {
    764    events.push(aEvent);
    765    try {
    766      TIP1.beginInputTransaction(otherWindow, simpleCallback);
    767      ok(false,
    768         description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionstart\" should throw an exception during startComposition()");
    769    } catch (e) {
    770      ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
    771         description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionstart\" should cause NS_ERROR_ALREADY_INITIALIZED during startComposition()");
    772    }
    773  }, {once: true});
    774  TIP1.beginInputTransaction(window, simpleCallback);
    775  TIP1.startComposition();
    776  is(events.length, 1,
    777     description + "compositionstart event should be fired by TIP1.startComposition()");
    778  TIP1.cancelComposition();
    779 
    780  // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during flushPendingComposition().
    781  events = [];
    782  input.addEventListener("compositionstart", function (aEvent) {
    783    events.push(aEvent);
    784    try {
    785      TIP1.beginInputTransaction(otherWindow, simpleCallback);
    786      ok(false,
    787         description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionstart\" should throw an exception during flushPendingComposition()");
    788    } catch (e) {
    789      ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
    790         description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionstart\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
    791    }
    792  }, {once: true});
    793  input.addEventListener("compositionupdate", function (aEvent) {
    794    events.push(aEvent);
    795    try {
    796      TIP1.beginInputTransaction(otherWindow, simpleCallback);
    797      ok(false,
    798         description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionupdate\" should throw an exception during flushPendingComposition()");
    799    } catch (e) {
    800      ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
    801         description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionupdate\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
    802    }
    803  }, {once: true});
    804  input.addEventListener("text", function (aEvent) {
    805    events.push(aEvent);
    806    try {
    807      TIP1.beginInputTransaction(otherWindow, simpleCallback);
    808      ok(false,
    809         description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should throw an exception during flushPendingComposition()");
    810    } catch (e) {
    811      ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
    812         description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
    813    }
    814  }, {once: true});
    815  input.addEventListener("beforeinput", function (aEvent) {
    816    events.push(aEvent);
    817    try {
    818      TIP1.beginInputTransaction(otherWindow, simpleCallback);
    819      ok(false,
    820         description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during flushPendingComposition()");
    821    } catch (e) {
    822      ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
    823         description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
    824    }
    825  }, {once: true});
    826  input.addEventListener("input", function (aEvent) {
    827    events.push(aEvent);
    828    try {
    829      TIP1.beginInputTransaction(otherWindow, simpleCallback);
    830      ok(false,
    831         description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should throw an exception during flushPendingComposition()");
    832    } catch (e) {
    833      ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
    834         description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
    835    }
    836  }, {once: true});
    837  TIP1.beginInputTransaction(window, simpleCallback);
    838  TIP1.setPendingCompositionString(composingStr);
    839  TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
    840  TIP1.flushPendingComposition();
    841  is(events.length, 5,
    842     description + "compositionstart, compositionupdate, text, beforeinput and input events should be fired by TIP1.flushPendingComposition()");
    843  is(events[0].type, "compositionstart",
    844     description + "events[0] should be compositionstart");
    845  is(events[1].type, "compositionupdate",
    846     description + "events[1] should be compositionupdate");
    847  is(events[2].type, "text",
    848     description + "events[2] should be text");
    849  is(events[3].type, "beforeinput",
    850     description + "events[3] should be beforeinput");
    851  checkInputEvent(events[3], false, true, "insertCompositionText", composingStr, description);
    852  is(events[4].type, "input",
    853     description + "events[4] should be input");
    854  checkInputEvent(events[4], false, true, "insertCompositionText", composingStr, description);
    855  TIP1.cancelComposition();
    856 
    857  // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during commitComposition().
    858  (function () {
    859    events = [];
    860    TIP1.beginInputTransaction(window, simpleCallback);
    861    TIP1.setPendingCompositionString(composingStr);
    862    TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
    863    TIP1.flushPendingComposition();
    864    input.addEventListener("text", function (aEvent) {
    865      events.push(aEvent);
    866      try {
    867        TIP1.beginInputTransaction(otherWindow, simpleCallback);
    868        ok(false,
    869          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should throw an exception during commitComposition()");
    870      } catch (e) {
    871        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
    872          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()");
    873      }
    874    }, {once: true});
    875    input.addEventListener("compositionend", function (aEvent) {
    876      events.push(aEvent);
    877      try {
    878        TIP1.beginInputTransaction(otherWindow, simpleCallback);
    879        ok(false,
    880          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionend\" should throw an exception during commitComposition()");
    881      } catch (e) {
    882        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
    883          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionend\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()");
    884      }
    885    }, {once: true});
    886    input.addEventListener("beforeinput", function (aEvent) {
    887      events.push(aEvent);
    888      try {
    889        TIP1.beginInputTransaction(otherWindow, simpleCallback);
    890        ok(false,
    891          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during commitComposition()");
    892      } catch (e) {
    893        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
    894          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()");
    895      }
    896    }, {once: true});
    897    function onInput(aEvent) {
    898      events.push(aEvent);
    899      try {
    900        TIP1.beginInputTransaction(otherWindow, simpleCallback);
    901        ok(false,
    902          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should throw an exception during commitComposition()");
    903      } catch (e) {
    904        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
    905          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()");
    906      }
    907    }
    908    input.addEventListener("input", onInput);
    909    TIP1.commitComposition();
    910    is(events.length, kExpectInputBeforeCompositionEnd ? 5 : 4,
    911      description + "text, beforeinput, compositionend and input events should be fired by TIP1.commitComposition()");
    912    let index = -1;
    913    is(events[++index].type, "text",
    914      `${description}events[${index}] should be text`);
    915    is(events[++index].type, "beforeinput",
    916      `${description}events[${index}] should be beforeinput`);
    917    checkInputEvent(events[index], false, true, "insertCompositionText", composingStr, description);
    918    if (kExpectInputBeforeCompositionEnd) {
    919      is(events[++index].type, "input",
    920        `${description}events[${index}] should be input`);
    921      checkInputEvent(events[index], false, true, "insertCompositionText", composingStr, description);
    922    }
    923    is(events[++index].type, "compositionend",
    924      `${description}events[${index}] should be compositionend`);
    925    is(events[++index].type, "input",
    926      `${description}events[${index}] should be input`);
    927    checkInputEvent(events[index], false, false, "insertCompositionText", composingStr, description);
    928    input.removeEventListener("input", onInput);
    929  })();
    930 
    931  // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during commitCompositionWith("bar");.
    932  (() => {
    933    events = [];
    934    input.addEventListener("compositionstart", function (aEvent) {
    935      events.push(aEvent);
    936      try {
    937        TIP1.beginInputTransaction(otherWindow, simpleCallback);
    938        ok(false,
    939          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionstart\" should throw an exception during commitCompositionWith(\"bar\")");
    940      } catch (e) {
    941        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
    942          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionstart\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
    943      }
    944    }, {once: true});
    945    input.addEventListener("compositionupdate", function (aEvent) {
    946      events.push(aEvent);
    947      try {
    948        TIP1.beginInputTransaction(otherWindow, simpleCallback);
    949        ok(false,
    950          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionupdate\" should throw an exception during commitCompositionWith(\"bar\")");
    951      } catch (e) {
    952        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
    953          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionupdate\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
    954      }
    955    }, {once: true});
    956    input.addEventListener("text", function (aEvent) {
    957      events.push(aEvent);
    958      try {
    959        TIP1.beginInputTransaction(otherWindow, simpleCallback);
    960        ok(false,
    961          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should throw an exception during commitCompositionWith(\"bar\")");
    962      } catch (e) {
    963        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
    964          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
    965      }
    966    }, {once: true});
    967    input.addEventListener("compositionend", function (aEvent) {
    968      events.push(aEvent);
    969      try {
    970        TIP1.beginInputTransaction(otherWindow, simpleCallback);
    971        ok(false,
    972          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionend\" should throw an exception during commitCompositionWith(\"bar\")");
    973      } catch (e) {
    974        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
    975          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionend\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
    976      }
    977    }, {once: true});
    978    input.addEventListener("beforeinput", function (aEvent) {
    979      events.push(aEvent);
    980      try {
    981        TIP1.beginInputTransaction(otherWindow, simpleCallback);
    982        ok(false,
    983          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during commitCompositionWith(\"bar\")");
    984      } catch (e) {
    985        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
    986          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
    987      }
    988    }, {once: true});
    989    function onInput(aEvent) {
    990      events.push(aEvent);
    991      try {
    992        TIP1.beginInputTransaction(otherWindow, simpleCallback);
    993        ok(false,
    994          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should throw an exception during commitCompositionWith(\"bar\")");
    995      } catch (e) {
    996        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
    997          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
    998      }
    999    }
   1000    input.addEventListener("input", onInput);
   1001    TIP1.beginInputTransaction(window, simpleCallback);
   1002    TIP1.commitCompositionWith("bar");
   1003    is(events.length, kExpectInputBeforeCompositionEnd ? 7 : 6,
   1004      description + "compositionstart, compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.commitCompositionWith(\"bar\")");
   1005    let index = -1;
   1006    is(events[++index].type, "compositionstart",
   1007      `${description}events[${index}] should be compositionstart`);
   1008    is(events[++index].type, "compositionupdate",
   1009      `${description}events[${index}] should be compositionupdate`);
   1010    is(events[++index].type, "text",
   1011      `${description}events[${index}] should be text`);
   1012    is(events[++index].type, "beforeinput",
   1013      `${description}events[${index}] should be beforeinput`);
   1014    checkInputEvent(events[index], false, true, "insertCompositionText", "bar", description);
   1015    if (kExpectInputBeforeCompositionEnd) {
   1016      is(events[++index].type, "input",
   1017        `${description}events[${index}] should be input`);
   1018      checkInputEvent(events[index], false, true, "insertCompositionText", "bar", description);
   1019    }
   1020    is(events[++index].type, "compositionend",
   1021      `${description}events[${index}] should be compositionend`);
   1022    is(events[++index].type, "input",
   1023      `${description}events[${index}] should be input`);
   1024    checkInputEvent(events[index], false, false, "insertCompositionText", "bar", description);
   1025    input.removeEventListener("input", onInput);
   1026  })();
   1027 
   1028  // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during cancelComposition();.
   1029  (() => {
   1030    events = [];
   1031    TIP1.beginInputTransaction(window, simpleCallback);
   1032    TIP1.setPendingCompositionString(composingStr);
   1033    TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
   1034    TIP1.flushPendingComposition();
   1035    input.addEventListener("compositionupdate", function (aEvent) {
   1036      events.push(aEvent);
   1037      try {
   1038        TIP1.beginInputTransaction(otherWindow, simpleCallback);
   1039        ok(false,
   1040          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionupdate\" should throw an exception during cancelComposition()");
   1041      } catch (e) {
   1042        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1043          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionupdate\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
   1044      }
   1045    }, {once: true});
   1046    input.addEventListener("text", function (aEvent) {
   1047      events.push(aEvent);
   1048      try {
   1049        TIP1.beginInputTransaction(otherWindow, simpleCallback);
   1050        ok(false,
   1051          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should throw an exception during cancelComposition()");
   1052      } catch (e) {
   1053        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1054          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
   1055      }
   1056    }, {once: true});
   1057    input.addEventListener("compositionend", function (aEvent) {
   1058      events.push(aEvent);
   1059      try {
   1060        TIP1.beginInputTransaction(otherWindow, simpleCallback);
   1061        ok(false,
   1062          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionend\" should throw an exception during cancelComposition()");
   1063      } catch (e) {
   1064        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1065          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionend\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
   1066      }
   1067    }, {once: true});
   1068    input.addEventListener("beforeinput", function (aEvent) {
   1069      events.push(aEvent);
   1070      try {
   1071        TIP1.beginInputTransaction(otherWindow, simpleCallback);
   1072        ok(false,
   1073          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during cancelComposition()");
   1074      } catch (e) {
   1075        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1076          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
   1077      }
   1078    }, {once: true});
   1079    function onInput(aEvent) {
   1080      events.push(aEvent);
   1081      try {
   1082        TIP1.beginInputTransaction(otherWindow, simpleCallback);
   1083        ok(false,
   1084          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should throw an exception during cancelComposition()");
   1085      } catch (e) {
   1086        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1087          description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
   1088      }
   1089    }
   1090    input.addEventListener("input", onInput);
   1091    TIP1.cancelComposition();
   1092    is(events.length, kExpectInputBeforeCompositionEnd ? 6 : 5,
   1093      description + "compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.cancelComposition()");
   1094    let index = -1;
   1095    is(events[++index].type, "compositionupdate",
   1096      `${description}events[${index}] should be compositionupdate`);
   1097    is(events[++index].type, "text",
   1098      `${description}events[${index}] should be text`);
   1099    is(events[++index].type, "beforeinput",
   1100      `${description}events[${index}] should be beforeinput`);
   1101    checkInputEvent(events[index], false, true, "insertCompositionText", "", description);
   1102    if (kExpectInputBeforeCompositionEnd) {
   1103      is(events[++index].type, "input",
   1104        `${description}events[${index}] should be input`);
   1105      checkInputEvent(events[index], false, true, "insertCompositionText", "", description);
   1106    }
   1107    is(events[++index].type, "compositionend",
   1108      `${description}events[${index}] should be compositionend`);
   1109    is(events[++index].type, "input",
   1110      `${description}events[${index}] should be input`);
   1111    checkInputEvent(events[index], false, false, "insertCompositionText", "", description);
   1112    input.removeEventListener("input", onInput);
   1113  })();
   1114 
   1115  // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during keydown() and keyup();.
   1116  events = [];
   1117  TIP1.beginInputTransaction(window, simpleCallback);
   1118  input.addEventListener("keydown", function (aEvent) {
   1119    events.push(aEvent);
   1120    try {
   1121      TIP1.beginInputTransaction(otherWindow, simpleCallback);
   1122      ok(false,
   1123         description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"keydown\" should throw an exception during keydown()");
   1124    } catch (e) {
   1125      ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1126         description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"keydown\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()");
   1127    }
   1128  }, {once: true});
   1129  input.addEventListener("keypress", function (aEvent) {
   1130    events.push(aEvent);
   1131    try {
   1132      TIP1.beginInputTransaction(otherWindow, simpleCallback);
   1133      ok(false,
   1134         description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"keypress\" should throw an exception during keydown()");
   1135    } catch (e) {
   1136      ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1137         description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"keypress\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()");
   1138    }
   1139  }, {once: true});
   1140  input.addEventListener("beforeinput", function (aEvent) {
   1141    events.push(aEvent);
   1142    try {
   1143      TIP1.beginInputTransaction(otherWindow, simpleCallback);
   1144      ok(false,
   1145         description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during keydown()");
   1146    } catch (e) {
   1147      ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1148         description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()");
   1149    }
   1150  }, {once: true});
   1151  input.addEventListener("input", function (aEvent) {
   1152    events.push(aEvent);
   1153    try {
   1154      TIP1.beginInputTransaction(otherWindow, simpleCallback);
   1155      ok(false,
   1156         description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should throw an exception during keydown()");
   1157    } catch (e) {
   1158      ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1159         description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()");
   1160    }
   1161  }, {once: true});
   1162  input.addEventListener("keyup", function (aEvent) {
   1163    events.push(aEvent);
   1164    try {
   1165      TIP1.beginInputTransaction(otherWindow, simpleCallback);
   1166      ok(false,
   1167         description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"keyup\" should throw an exception during keyup()");
   1168    } catch (e) {
   1169      ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1170         description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"keyup\" should cause NS_ERROR_ALREADY_INITIALIZED during keyup()");
   1171    }
   1172  }, {once: true});
   1173  keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
   1174  TIP1.keydown(keyA);
   1175  TIP1.keyup(keyA);
   1176  is(events.length, 5,
   1177     description + "keydown, keypress, beforeinput, input, keyup events should be fired by TIP1.keydown() and TIP1.keyup()");
   1178  is(events[0].type, "keydown",
   1179     description + "events[0] should be keydown");
   1180  is(events[1].type, "keypress",
   1181     description + "events[1] should be keypress");
   1182  is(events[2].type, "beforeinput",
   1183     description + "events[2] should be beforeinput");
   1184  checkInputEvent(events[2], true, false, "insertText", "a", description);
   1185  is(events[3].type, "input",
   1186     description + "events[3] should be input");
   1187  checkInputEvent(events[3], false, false, "insertText", "a", description);
   1188  is(events[4].type, "keyup",
   1189     description + "events[4] should be keyup");
   1190 
   1191  // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during startComposition().
   1192  events = [];
   1193  input.addEventListener("compositionstart", function (aEvent) {
   1194    events.push(aEvent);
   1195    try {
   1196      TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
   1197      ok(false,
   1198         description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionstart\" should throw an exception during startComposition()");
   1199    } catch (e) {
   1200      ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1201         description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionstart\" should cause NS_ERROR_ALREADY_INITIALIZED during startComposition()");
   1202    }
   1203  }, {once: true});
   1204  TIP1.beginInputTransactionForTests(window, simpleCallback);
   1205  TIP1.startComposition();
   1206  is(events.length, 1,
   1207     description + "compositionstart event should be fired by TIP1.startComposition()");
   1208  TIP1.cancelComposition();
   1209 
   1210  // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during flushPendingComposition().
   1211  events = [];
   1212  input.addEventListener("compositionstart", function (aEvent) {
   1213    events.push(aEvent);
   1214    try {
   1215      TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
   1216      ok(false,
   1217         description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionstart\" should throw an exception during flushPendingComposition()");
   1218    } catch (e) {
   1219      ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1220         description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionstart\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
   1221    }
   1222  }, {once: true});
   1223  input.addEventListener("compositionupdate", function (aEvent) {
   1224    events.push(aEvent);
   1225    try {
   1226      TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
   1227      ok(false,
   1228         description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionupdate\" should throw an exception during flushPendingComposition()");
   1229    } catch (e) {
   1230      ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1231         description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionupdate\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
   1232    }
   1233  }, {once: true});
   1234  input.addEventListener("text", function (aEvent) {
   1235    events.push(aEvent);
   1236    try {
   1237      TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
   1238      ok(false,
   1239         description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should throw an exception during flushPendingComposition()");
   1240    } catch (e) {
   1241      ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1242         description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
   1243    }
   1244  }, {once: true});
   1245  input.addEventListener("beforeinput", function (aEvent) {
   1246    events.push(aEvent);
   1247    try {
   1248      TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
   1249      ok(false,
   1250         description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during flushPendingComposition()");
   1251    } catch (e) {
   1252      ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1253         description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
   1254    }
   1255  }, {once: true});
   1256  input.addEventListener("input", function (aEvent) {
   1257    events.push(aEvent);
   1258    try {
   1259      TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
   1260      ok(false,
   1261         description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should throw an exception during flushPendingComposition()");
   1262    } catch (e) {
   1263      ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1264         description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
   1265    }
   1266  }, {once: true});
   1267  TIP1.beginInputTransactionForTests(window, simpleCallback);
   1268  TIP1.setPendingCompositionString(composingStr);
   1269  TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
   1270  TIP1.flushPendingComposition();
   1271  is(events.length, 5,
   1272     description + "compositionstart, compositionupdate, text, beforeinput and input events should be fired by TIP1.flushPendingComposition()");
   1273  is(events[0].type, "compositionstart",
   1274     description + "events[0] should be compositionstart");
   1275  is(events[1].type, "compositionupdate",
   1276     description + "events[1] should be compositionupdate");
   1277  is(events[2].type, "text",
   1278     description + "events[2] should be text");
   1279  is(events[3].type, "beforeinput",
   1280     description + "events[3] should be beforeinput");
   1281  checkInputEvent(events[3], false, true, "insertCompositionText", composingStr, description);
   1282  is(events[4].type, "input",
   1283     description + "events[4] should be input");
   1284  checkInputEvent(events[4], false, true, "insertCompositionText", composingStr, description);
   1285  TIP1.cancelComposition();
   1286 
   1287  // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during commitComposition().
   1288  (() => {
   1289    events = [];
   1290    TIP1.beginInputTransactionForTests(window, simpleCallback);
   1291    TIP1.setPendingCompositionString(composingStr);
   1292    TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
   1293    TIP1.flushPendingComposition();
   1294    input.addEventListener("text", function (aEvent) {
   1295      events.push(aEvent);
   1296      try {
   1297        TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
   1298        ok(false,
   1299          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should throw an exception during commitComposition()");
   1300      } catch (e) {
   1301        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1302          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()");
   1303      }
   1304    }, {once: true});
   1305    input.addEventListener("compositionend", function (aEvent) {
   1306      events.push(aEvent);
   1307      try {
   1308        TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
   1309        ok(false,
   1310          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionend\" should throw an exception during commitComposition()");
   1311      } catch (e) {
   1312        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1313          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionend\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()");
   1314      }
   1315    }, {once: true});
   1316    input.addEventListener("beforeinput", function (aEvent) {
   1317      events.push(aEvent);
   1318      try {
   1319        TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
   1320        ok(false,
   1321          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during commitComposition()");
   1322      } catch (e) {
   1323        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1324          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()");
   1325      }
   1326    }, {once: true});
   1327    function onInput(aEvent) {
   1328      events.push(aEvent);
   1329      try {
   1330        TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
   1331        ok(false,
   1332          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should throw an exception during commitComposition()");
   1333      } catch (e) {
   1334        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1335          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()");
   1336      }
   1337    }
   1338    input.addEventListener("input", onInput);
   1339    TIP1.commitComposition();
   1340    is(events.length, kExpectInputBeforeCompositionEnd ? 5 : 4,
   1341      description + "text, beforeinput, compositionend and input events should be fired by TIP1.commitComposition()");
   1342    let index = -1;
   1343    is(events[++index].type, "text",
   1344      `${description}events[${index}] should be text`);
   1345    is(events[++index].type, "beforeinput",
   1346      `${description}events[${index}] should be beforeinput`);
   1347    checkInputEvent(events[index], false, true, "insertCompositionText", composingStr, description);
   1348    if (kExpectInputBeforeCompositionEnd) {
   1349      is(events[++index].type, "input",
   1350        `${description}events[${index}] should be input`);
   1351      checkInputEvent(events[index], false, true, "insertCompositionText", composingStr, description);
   1352    }
   1353    is(events[++index].type, "compositionend",
   1354      `${description}events[${index}] should be compositionend`);
   1355    is(events[++index].type, "input",
   1356      `${description}events[${index}] should be input`);
   1357    checkInputEvent(events[index], false, false, "insertCompositionText", composingStr, description);
   1358    input.removeEventListener("input", onInput);
   1359  })();
   1360 
   1361  // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during commitCompositionWith("bar");.
   1362  (() => {
   1363    events = [];
   1364    input.addEventListener("compositionstart", function (aEvent) {
   1365      events.push(aEvent);
   1366      try {
   1367        TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
   1368        ok(false,
   1369          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionstart\" should throw an exception during commitCompositionWith(\"bar\")");
   1370      } catch (e) {
   1371        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1372          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionstart\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
   1373      }
   1374    }, {once: true});
   1375    input.addEventListener("compositionupdate", function (aEvent) {
   1376      events.push(aEvent);
   1377      try {
   1378        TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
   1379        ok(false,
   1380          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionupdate\" should throw an exception during commitCompositionWith(\"bar\")");
   1381      } catch (e) {
   1382        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1383          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionupdate\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
   1384      }
   1385    }, {once: true});
   1386    input.addEventListener("text", function (aEvent) {
   1387      events.push(aEvent);
   1388      try {
   1389        TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
   1390        ok(false,
   1391          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should throw an exception during commitCompositionWith(\"bar\")");
   1392      } catch (e) {
   1393        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1394          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
   1395      }
   1396    }, {once: true});
   1397    input.addEventListener("compositionend", function (aEvent) {
   1398      events.push(aEvent);
   1399      try {
   1400        TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
   1401        ok(false,
   1402          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionend\" should throw an exception during commitCompositionWith(\"bar\")");
   1403      } catch (e) {
   1404        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1405          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionend\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
   1406      }
   1407    }, {once: true});
   1408    input.addEventListener("beforeinput", function (aEvent) {
   1409      events.push(aEvent);
   1410      try {
   1411        TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
   1412        ok(false,
   1413          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during commitCompositionWith(\"bar\")");
   1414      } catch (e) {
   1415        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1416          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
   1417      }
   1418    }, {once: true});
   1419    function onInput(aEvent) {
   1420      events.push(aEvent);
   1421      try {
   1422        TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
   1423        ok(false,
   1424          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should throw an exception during commitCompositionWith(\"bar\")");
   1425      } catch (e) {
   1426        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1427          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
   1428      }
   1429    }
   1430    input.addEventListener("input", onInput);
   1431    TIP1.beginInputTransactionForTests(window, simpleCallback);
   1432    TIP1.commitCompositionWith("bar");
   1433    is(events.length, kExpectInputBeforeCompositionEnd ? 7 : 6,
   1434      description + "compositionstart, compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.commitCompositionWith(\"bar\")");
   1435    let index = -1;
   1436    is(events[++index].type, "compositionstart",
   1437      `${description}events[${index}] should be compositionstart`);
   1438    is(events[++index].type, "compositionupdate",
   1439      `${description}events[${index}] should be compositionupdate`);
   1440    is(events[++index].type, "text",
   1441      `${description}events[${index}] should be text`);
   1442    is(events[++index].type, "beforeinput",
   1443      `${description}events[${index}] should be beforeinput`);
   1444    checkInputEvent(events[index], false, true, "insertCompositionText", "bar", description);
   1445    if (kExpectInputBeforeCompositionEnd) {
   1446      is(events[++index].type, "input",
   1447        `${description}events[${index}] should be input`);
   1448      checkInputEvent(events[index], false, true, "insertCompositionText", "bar", description);
   1449    }
   1450    is(events[++index].type, "compositionend",
   1451      `${description}events[${index}] should be compositionend`);
   1452    is(events[++index].type, "input",
   1453      `${description}events[${index}] should be input`);
   1454    checkInputEvent(events[index], false, false, "insertCompositionText", "bar", description);
   1455    input.removeEventListener("input", onInput);
   1456  })();
   1457 
   1458  // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during cancelComposition();.
   1459  (() => {
   1460    events = [];
   1461    TIP1.beginInputTransactionForTests(window, simpleCallback);
   1462    TIP1.setPendingCompositionString(composingStr);
   1463    TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
   1464    TIP1.flushPendingComposition();
   1465    input.addEventListener("compositionupdate", function (aEvent) {
   1466      events.push(aEvent);
   1467      try {
   1468        TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
   1469        ok(false,
   1470          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionupdate\" should throw an exception during cancelComposition()");
   1471      } catch (e) {
   1472        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1473          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionupdate\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
   1474      }
   1475    }, {once: true});
   1476    input.addEventListener("text", function (aEvent) {
   1477      events.push(aEvent);
   1478      try {
   1479        TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
   1480        ok(false,
   1481          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should throw an exception during cancelComposition()");
   1482      } catch (e) {
   1483        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1484          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
   1485      }
   1486    }, {once: true});
   1487    input.addEventListener("compositionend", function (aEvent) {
   1488      events.push(aEvent);
   1489      try {
   1490        TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
   1491        ok(false,
   1492          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionend\" should throw an exception during cancelComposition()");
   1493      } catch (e) {
   1494        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1495          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionend\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
   1496      }
   1497    }, {once: true});
   1498    input.addEventListener("beforeinput", function (aEvent) {
   1499      events.push(aEvent);
   1500      try {
   1501        TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
   1502        ok(false,
   1503          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during cancelComposition()");
   1504      } catch (e) {
   1505        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1506          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
   1507      }
   1508    }, {once: true});
   1509    function onInput(aEvent) {
   1510      events.push(aEvent);
   1511      try {
   1512        TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
   1513        ok(false,
   1514          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should throw an exception during cancelComposition()");
   1515      } catch (e) {
   1516        ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1517          description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
   1518      }
   1519    }
   1520    input.addEventListener("input", onInput);
   1521    TIP1.cancelComposition();
   1522    is(events.length, kExpectInputBeforeCompositionEnd ? 6 : 5,
   1523      description + "compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.cancelComposition()");
   1524    let index = -1;
   1525    is(events[++index].type, "compositionupdate",
   1526      `${description}events[${index}] should be compositionupdate`);
   1527    is(events[++index].type, "text",
   1528      `${description}events[${index}] should be text`);
   1529    is(events[++index].type, "beforeinput",
   1530      `${description}events[${index}] should be beforeinput`);
   1531    checkInputEvent(events[index], false, true, "insertCompositionText", "", description);
   1532    if (kExpectInputBeforeCompositionEnd) {
   1533      is(events[++index].type, "input",
   1534        `${description}events[${index}] should be input`);
   1535      checkInputEvent(events[index], false, true, "insertCompositionText", "", description);
   1536    }
   1537    is(events[++index].type, "compositionend",
   1538      `${description}events[${index}] should be compositionend`);
   1539    is(events[++index].type, "input",
   1540      `${description}events[${index}] should be input`);
   1541    checkInputEvent(events[index], false, false, "insertCompositionText", "", description);
   1542    input.removeEventListener("input", onInput);
   1543  })();
   1544 
   1545  // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during keydown() and keyup();.
   1546  events = [];
   1547  TIP1.beginInputTransactionForTests(window, simpleCallback);
   1548  input.addEventListener("keydown", function (aEvent) {
   1549    events.push(aEvent);
   1550    try {
   1551      TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
   1552      ok(false,
   1553         description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"keydown\" should throw an exception during keydown()");
   1554    } catch (e) {
   1555      ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1556         description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"keydown\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()");
   1557    }
   1558  }, {once: true});
   1559  input.addEventListener("keypress", function (aEvent) {
   1560    events.push(aEvent);
   1561    try {
   1562      TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
   1563      ok(false,
   1564         description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"keypress\" should throw an exception during keydown()");
   1565    } catch (e) {
   1566      ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1567         description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"keypress\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()");
   1568    }
   1569  }, {once: true});
   1570  input.addEventListener("beforeinput", function (aEvent) {
   1571    events.push(aEvent);
   1572    try {
   1573      TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
   1574      ok(false,
   1575         description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during keydown()");
   1576    } catch (e) {
   1577      ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1578         description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()");
   1579    }
   1580  }, {once: true});
   1581  input.addEventListener("input", function (aEvent) {
   1582    events.push(aEvent);
   1583    try {
   1584      TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
   1585      ok(false,
   1586         description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should throw an exception during keydown()");
   1587    } catch (e) {
   1588      ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1589         description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()");
   1590    }
   1591  }, {once: true});
   1592  input.addEventListener("keyup", function (aEvent) {
   1593    events.push(aEvent);
   1594    try {
   1595      TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
   1596      ok(false,
   1597         description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"keyup\" should throw an exception during keyup()");
   1598    } catch (e) {
   1599      ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
   1600         description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"keyup\" should cause NS_ERROR_ALREADY_INITIALIZED during keyup()");
   1601    }
   1602  }, {once: true});
   1603  keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
   1604  TIP1.keydown(keyA);
   1605  TIP1.keyup(keyA);
   1606  is(events.length, 5,
   1607     description + "keydown, keypress, beforeinput, input, keyup events should be fired by TIP1.keydown() and TIP1.keyup()");
   1608  is(events[0].type, "keydown",
   1609     description + "events[0] should be keydown");
   1610  is(events[1].type, "keypress",
   1611     description + "events[1] should be keypress");
   1612  is(events[2].type, "beforeinput",
   1613     description + "events[2] should be beforeinput");
   1614  checkInputEvent(events[2], true, false, "insertText", "a", description);
   1615  is(events[3].type, "input",
   1616     description + "events[3] should be input");
   1617  checkInputEvent(events[3], false, false, "insertText", "a", description);
   1618  is(events[4].type, "keyup",
   1619     description + "events[4] should be keyup");
   1620 
   1621  // Let's check if startComposition() throws an exception after ownership is stolen.
   1622  input.value = "";
   1623  ok(TIP1.beginInputTransactionForTests(window),
   1624     description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition");
   1625  ok(TIP2.beginInputTransactionForTests(window),
   1626     description + "TIP2.beginInputTransactionForTests() should succeed because there is no composition");
   1627  try {
   1628    TIP1.startComposition();
   1629    ok(false,
   1630       description + "TIP1.startComposition() should cause throwing an exception because TIP2 took the ownership");
   1631    TIP1.cancelComposition();
   1632  } catch (e) {
   1633    ok(e.message.includes("NS_ERROR_NOT_INITIALIZED"),
   1634       description + "TIP1.startComposition() should cause throwing an exception including NS_ERROR_NOT_INITIALIZED");
   1635  } finally {
   1636    is(input.value, "",
   1637       description + "The input element should not have commit string");
   1638  }
   1639 
   1640  // Let's check if flushPendingComposition() throws an exception after ownership is stolen.
   1641  ok(TIP1.beginInputTransactionForTests(window),
   1642     description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition");
   1643  ok(TIP2.beginInputTransactionForTests(window),
   1644     description + "TIP2.beginInputTransactionForTests() should succeed because there is no composition");
   1645  input.value = "";
   1646  try {
   1647    TIP1.setPendingCompositionString(composingStr);
   1648    TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
   1649    TIP1.flushPendingComposition()
   1650    ok(false,
   1651       description + "TIP1.flushPendingComposition() should cause throwing an exception because TIP2 took the ownership");
   1652    TIP1.cancelComposition();
   1653  } catch (e) {
   1654    ok(e.message.includes("NS_ERROR_NOT_INITIALIZED"),
   1655       description + "TIP1.flushPendingComposition() should cause throwing an exception including NS_ERROR_NOT_INITIALIZED");
   1656  } finally {
   1657    is(input.value, "",
   1658       description + "The input element should not have commit string");
   1659  }
   1660 
   1661  // Let's check if commitCompositionWith("bar") throws an exception after ownership is stolen.
   1662  ok(TIP1.beginInputTransactionForTests(window),
   1663     description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition");
   1664  ok(TIP2.beginInputTransactionForTests(window),
   1665     description + "TIP2.beginInputTransactionForTests() should succeed because there is no composition");
   1666  input.value = "";
   1667  try {
   1668    TIP1.commitCompositionWith("bar");
   1669    ok(false,
   1670       description + "TIP1.commitCompositionWith(\"bar\") should cause throwing an exception because TIP2 took the ownership");
   1671  } catch (e) {
   1672    ok(e.message.includes("NS_ERROR_NOT_INITIALIZED"),
   1673       description + "TIP1.commitCompositionWith(\"bar\") should cause throwing an exception including NS_ERROR_NOT_INITIALIZED");
   1674  } finally {
   1675    is(input.value, "",
   1676       description + "The input element should not have commit string");
   1677  }
   1678 
   1679  // Let's check if keydown() throws an exception after ownership is stolen.
   1680  ok(TIP1.beginInputTransactionForTests(window),
   1681     description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition");
   1682  ok(TIP2.beginInputTransactionForTests(window),
   1683     description + "TIP2.beginInputTransactionForTests() should succeed because there is no composition");
   1684  input.value = "";
   1685  try {
   1686    let keyF = new KeyboardEvent("", { key: "f", code: "KeyF", keyCode: KeyboardEvent.DOM_VK_F });
   1687    TIP1.keydown(keyF);
   1688    ok(false,
   1689       description + "TIP1.keydown(keyF) should cause throwing an exception because TIP2 took the ownership");
   1690  } catch (e) {
   1691    ok(e.message.includes("NS_ERROR_NOT_INITIALIZED"),
   1692       description + "TIP1.keydown(keyF) should cause throwing an exception including NS_ERROR_NOT_INITIALIZED");
   1693  } finally {
   1694    is(input.value, "",
   1695       description + "The input element should not be modified");
   1696  }
   1697 
   1698  // Let's check if keyup() throws an exception after ownership is stolen.
   1699  ok(TIP1.beginInputTransactionForTests(window),
   1700     description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition");
   1701  ok(TIP2.beginInputTransactionForTests(window),
   1702     description + "TIP2.beginInputTransactionForTests() should succeed because there is no composition");
   1703  input.value = "";
   1704  try {
   1705    let keyF = new KeyboardEvent("", { key: "f", code: "KeyF", keyCode: KeyboardEvent.DOM_VK_F });
   1706    TIP1.keyup(keyF);
   1707    ok(false,
   1708       description + "TIP1.keyup(keyF) should cause throwing an exception because TIP2 took the ownership");
   1709  } catch (e) {
   1710    ok(e.message.includes("NS_ERROR_NOT_INITIALIZED"),
   1711       description + "TIP1.keyup(keyF) should cause throwing an exception including NS_ERROR_NOT_INITIALIZED");
   1712  } finally {
   1713    is(input.value, "",
   1714       description + "The input element should not be modified");
   1715  }
   1716 
   1717  // aCallback of nsITextInputProcessor.beginInputTransaction() must not be omitted.
   1718  try {
   1719    TIP1.beginInputTransaction(window);
   1720    ok(false,
   1721       description + "TIP1.beginInputTransaction(window) should be failed since aCallback is omitted");
   1722  } catch (e) {
   1723    ok(e.message.includes("Not enough arguments"),
   1724       description + "TIP1.beginInputTransaction(window) should cause throwing an exception including \"Not enough arguments\" since aCallback is omitted");
   1725  }
   1726 
   1727  // aCallback of nsITextInputProcessor.beginInputTransaction() must not be undefined.
   1728  try {
   1729    TIP1.beginInputTransaction(window, undefined);
   1730    ok(false,
   1731       description + "TIP1.beginInputTransaction(window, undefined) should be failed since aCallback is undefined");
   1732  } catch (e) {
   1733    ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
   1734       description + "TIP1.beginInputTransaction(window, undefined) should cause throwing an exception including NS_ERROR_ILLEGAL_VALUE since aCallback is undefined");
   1735  }
   1736 
   1737  // aCallback of nsITextInputProcessor.beginInputTransaction() must not be null.
   1738  try {
   1739    TIP1.beginInputTransaction(window, null);
   1740    ok(false,
   1741       description + "TIP1.beginInputTransaction(window, null) should be failed since aCallback is null");
   1742  } catch (e) {
   1743    ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
   1744       description + "TIP1.beginInputTransaction(window, null) should cause throwing an exception including NS_ERROR_ILLEGAL_VALUE since aCallback is null");
   1745  }
   1746 }
   1747 
   1748 function runReleaseTests()
   1749 {
   1750  var description = "runReleaseTests(): ";
   1751 
   1752  var TIP = createTIP();
   1753  ok(TIP.beginInputTransactionForTests(window),
   1754     description + "TIP.beginInputTransactionForTests() should succeed");
   1755 
   1756  input.value = "";
   1757  input.focus();
   1758 
   1759  TIP.setPendingCompositionString("foo");
   1760  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   1761  TIP.setCaretInPendingComposition(3);
   1762  TIP.flushPendingComposition();
   1763  is(input.value, "foo",
   1764     description + "the input should have composition string");
   1765 
   1766  // Release the TIP
   1767  TIP = null;
   1768  // Needs to run GC forcibly for testing this.
   1769  Cu.forceGC();
   1770 
   1771  is(input.value, "",
   1772     description + "the input should be empty because the composition should be canceled");
   1773 
   1774  TIP = createTIP();
   1775  ok(TIP.beginInputTransactionForTests(window),
   1776     description + "TIP.beginInputTransactionForTests() should succeed #2");
   1777 }
   1778 
   1779 function runCompositionTests()
   1780 {
   1781  var description = "runCompositionTests(): ";
   1782 
   1783  var TIP = createTIP();
   1784  ok(TIP.beginInputTransactionForTests(window),
   1785     description + "TIP.beginInputTransactionForTests() should succeed");
   1786 
   1787  var events;
   1788 
   1789  function reset()
   1790  {
   1791    events = [];
   1792  }
   1793 
   1794  function handler(aEvent)
   1795  {
   1796    events.push({ "type": aEvent.type, "data": aEvent.data });
   1797  }
   1798 
   1799  window.addEventListener("compositionstart", handler);
   1800  window.addEventListener("compositionupdate", handler);
   1801  window.addEventListener("compositionend", handler);
   1802 
   1803  input.value = "";
   1804  input.focus();
   1805 
   1806  // nsITextInputProcessor.startComposition()
   1807  reset();
   1808  TIP.startComposition();
   1809  is(events.length, 1,
   1810     description + "startComposition() should cause only compositionstart");
   1811  is(events[0].type, "compositionstart",
   1812     description + "startComposition() should cause only compositionstart");
   1813  is(input.value, "",
   1814     description + "startComposition() shouldn't modify the focused editor");
   1815 
   1816  // Setting composition string "foo" as a raw clause
   1817  TIP.setPendingCompositionString("foo");
   1818  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   1819  TIP.setCaretInPendingComposition(3);
   1820 
   1821  reset();
   1822  TIP.flushPendingComposition();
   1823  is(events.length, 1,
   1824     description + "flushPendingComposition() after startComposition() should cause compositionupdate");
   1825  is(events[0].type, "compositionupdate",
   1826     description + "flushPendingComposition() after startComposition() should cause compositionupdate");
   1827  is(events[0].data, "foo",
   1828     description + "compositionupdate caused by flushPendingComposition() should have new composition string in its data");
   1829  is(input.value, "foo",
   1830     description + "modifying composition string should cause modifying the focused editor");
   1831 
   1832  // Changing the raw clause to a selected clause
   1833  TIP.setPendingCompositionString("foo");
   1834  TIP.appendClauseToPendingComposition(3, TIP.ATTR_SELECTED_CLAUSE);
   1835 
   1836  reset();
   1837  TIP.flushPendingComposition();
   1838  is(events.length, 0,
   1839     description + "flushPendingComposition() changing only clause information shouldn't cause compositionupdate");
   1840  is(input.value, "foo",
   1841     description + "modifying composition clause shouldn't cause modifying the focused editor");
   1842 
   1843  // Separating the selected clause to two clauses
   1844  TIP.setPendingCompositionString("foo");
   1845  TIP.appendClauseToPendingComposition(2, TIP.ATTR_SELECTED_CLAUSE);
   1846  TIP.appendClauseToPendingComposition(1, TIP.ATTR_CONVERTED_CLAUSE);
   1847  TIP.setCaretInPendingComposition(2);
   1848 
   1849  reset();
   1850  TIP.flushPendingComposition();
   1851  is(events.length, 0,
   1852     description + "flushPendingComposition() separating a clause information shouldn't cause compositionupdate");
   1853  is(input.value, "foo",
   1854     description + "separating composition clause shouldn't cause modifying the focused editor");
   1855 
   1856  // Modifying the composition string
   1857  TIP.setPendingCompositionString("FOo");
   1858  TIP.appendClauseToPendingComposition(2, TIP.ATTR_SELECTED_CLAUSE);
   1859  TIP.appendClauseToPendingComposition(1, TIP.ATTR_CONVERTED_CLAUSE);
   1860  TIP.setCaretInPendingComposition(2);
   1861 
   1862  reset();
   1863  TIP.flushPendingComposition();
   1864  is(events.length, 1,
   1865     description + "flushPendingComposition() causing modifying composition string should cause compositionupdate");
   1866  is(events[0].type, "compositionupdate",
   1867     description + "flushPendingComposition() causing modifying composition string should cause compositionupdate");
   1868  is(events[0].data, "FOo",
   1869     description + "compositionupdate caused by flushPendingComposition() should have new composition string in its data");
   1870  is(input.value, "FOo",
   1871     description + "modifying composition clause shouldn't cause modifying the focused editor");
   1872 
   1873  // Committing the composition string
   1874  reset();
   1875  TIP.commitComposition();
   1876  is(events.length, 1,
   1877     description + "commitComposition() should cause compositionend but shouldn't cause compositionupdate");
   1878  is(events[0].type, "compositionend",
   1879     description + "commitComposition() should cause compositionend");
   1880  is(events[0].data, "FOo",
   1881     description + "compositionend caused by commitComposition() should have the committed string in its data");
   1882  is(input.value, "FOo",
   1883     description + "commitComposition() shouldn't cause modifying the focused editor");
   1884 
   1885  // Starting new composition without a call of startComposition()
   1886  TIP.setPendingCompositionString("bar");
   1887  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   1888 
   1889  reset();
   1890  TIP.flushPendingComposition();
   1891  is(events.length, 2,
   1892     description + "flushPendingComposition() without a call of startComposition() should cause both compositionstart and compositionupdate");
   1893  is(events[0].type, "compositionstart",
   1894     description + "flushPendingComposition() without a call of startComposition() should cause compositionstart");
   1895  is(events[1].type, "compositionupdate",
   1896     description + "flushPendingComposition() without a call of startComposition() should cause compositionupdate after compositionstart");
   1897  is(events[1].data, "bar",
   1898     description + "compositionupdate caused by flushPendingComposition() without a call of startComposition() should have the composition string in its data");
   1899  is(input.value, "FOobar",
   1900     description + "new composition string should cause appending composition string to the focused editor");
   1901 
   1902  // Canceling the composition
   1903  reset();
   1904  TIP.cancelComposition();
   1905  is(events.length, 2,
   1906     description + "cancelComposition() should cause both compositionupdate and compositionend");
   1907  is(events[0].type, "compositionupdate",
   1908     description + "cancelComposition() should cause compositionupdate");
   1909  is(events[0].data, "",
   1910     description + "compositionupdate caused by cancelComposition() should have empty string in its data");
   1911  is(events[1].type, "compositionend",
   1912     description + "cancelComposition() should cause compositionend after compositionupdate");
   1913  is(events[1].data, "",
   1914     description + "compositionend caused by cancelComposition() should have empty string in its data");
   1915  is(input.value, "FOo",
   1916     description + "canceled composition string should be removed from the focused editor");
   1917 
   1918  // Starting composition explicitly and canceling it
   1919  reset();
   1920  TIP.startComposition();
   1921  TIP.cancelComposition();
   1922  is(events.length, 2,
   1923     description + "canceling composition immediately after startComposition() should cause compositionstart and compositionend");
   1924  is(events[0].type, "compositionstart",
   1925     description + "canceling composition immediately after startComposition() should cause compositionstart first");
   1926  is(events[1].type, "compositionend",
   1927     description + "canceling composition immediately after startComposition() should cause compositionend after compositionstart");
   1928  is(events[1].data, "",
   1929     description + "compositionend caused by canceling composition should have empty string in its data");
   1930  is(input.value, "FOo",
   1931     description + "canceling composition shouldn't modify the focused editor");
   1932 
   1933  // Create composition for next test.
   1934  TIP.setPendingCompositionString("bar");
   1935  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   1936  TIP.flushPendingComposition();
   1937  is(input.value, "FOobar",
   1938     description + "The focused editor should have new composition string \"bar\"");
   1939 
   1940  // Allow to set empty composition string
   1941  reset();
   1942  TIP.flushPendingComposition();
   1943  is(events.length, 1,
   1944     description + "making composition string empty should cause only compositionupdate");
   1945  is(events[0].type, "compositionupdate",
   1946     description + "making composition string empty should cause compositionupdate");
   1947  is(events[0].data, "",
   1948     description + "compositionupdate caused by making composition string empty should have empty string in its data");
   1949 
   1950  // Allow to insert new composition string without compositionend/compositionstart
   1951  TIP.setPendingCompositionString("buzz");
   1952  TIP.appendClauseToPendingComposition(4, TIP.ATTR_RAW_CLAUSE);
   1953 
   1954  reset();
   1955  TIP.flushPendingComposition();
   1956  is(events.length, 1,
   1957     description + "modifying composition string from empty string should cause only compositionupdate");
   1958  is(events[0].type, "compositionupdate",
   1959     description + "modifying composition string from empty string should cause compositionupdate");
   1960  is(events[0].data, "buzz",
   1961     description + "compositionupdate caused by modifying composition string from empty string should have new composition string in its data");
   1962  is(input.value, "FOobuzz",
   1963     description + "new composition string should be appended to the focused editor");
   1964 
   1965  // Committing with different string
   1966  reset();
   1967  TIP.commitCompositionWith("bar");
   1968  is(events.length, 2,
   1969     description + "committing with different string should cause compositionupdate and compositionend");
   1970  is(events[0].type, "compositionupdate",
   1971     description + "committing with different string should cause compositionupdate first");
   1972  is(events[0].data, "bar",
   1973     description + "compositionupdate caused by committing with different string should have the committing string in its data");
   1974  is(events[1].type, "compositionend",
   1975     description + "committing with different string should cause compositionend after compositionupdate");
   1976  is(events[1].data, "bar",
   1977     description + "compositionend caused by committing with different string should have the committing string in its data");
   1978  is(input.value, "FOobar",
   1979     description + "new committed string should be appended to the focused editor");
   1980 
   1981  // Appending new composition string
   1982  TIP.setPendingCompositionString("buzz");
   1983  TIP.appendClauseToPendingComposition(4, TIP.ATTR_RAW_CLAUSE);
   1984  TIP.flushPendingComposition();
   1985  is(input.value, "FOobarbuzz",
   1986     description + "new composition string should be appended to the focused editor");
   1987 
   1988  // Committing with same string
   1989  reset();
   1990  TIP.commitCompositionWith("buzz");
   1991  is(events.length, 1,
   1992     description + "committing with same string should cause only compositionend");
   1993  is(events[0].type, "compositionend",
   1994     description + "committing with same string should cause compositionend");
   1995  is(events[0].data, "buzz",
   1996     description + "compositionend caused by committing with same string should have the committing string in its data");
   1997  is(input.value, "FOobarbuzz",
   1998     description + "new committed string should be appended to the focused editor");
   1999 
   2000  // Inserting commit string directly
   2001  reset();
   2002  TIP.commitCompositionWith("boo!");
   2003  is(events.length, 3,
   2004     description + "committing text directly should cause compositionstart, compositionupdate and compositionend");
   2005  is(events[0].type, "compositionstart",
   2006     description + "committing text directly should cause compositionstart first");
   2007  is(events[1].type, "compositionupdate",
   2008     description + "committing text directly should cause compositionupdate after compositionstart");
   2009  is(events[1].data, "boo!",
   2010     description + "compositionupdate caused by committing text directly should have the committing text in its data");
   2011  is(events[2].type, "compositionend",
   2012     description + "committing text directly should cause compositionend after compositionupdate");
   2013  is(events[2].data, "boo!",
   2014     description + "compositionend caused by committing text directly should have the committing text in its data");
   2015  is(input.value, "FOobarbuzzboo!",
   2016     description + "committing text directly should append the committing text to the focused editor");
   2017 
   2018  window.removeEventListener("compositionstart", handler);
   2019  window.removeEventListener("compositionupdate", handler);
   2020  window.removeEventListener("compositionend", handler);
   2021 }
   2022 
   2023 function runCompositionWithKeyEventTests()
   2024 {
   2025  var description = "runCompositionWithKeyEventTests(): ";
   2026 
   2027  var TIP = createTIP();
   2028  ok(TIP.beginInputTransactionForTests(window),
   2029     description + "TIP.beginInputTransactionForTests() should succeed");
   2030 
   2031  var events;
   2032 
   2033  function reset()
   2034  {
   2035    events = [];
   2036  }
   2037 
   2038  function handler(aEvent)
   2039  {
   2040    events.push(aEvent);
   2041  }
   2042 
   2043  window.addEventListener("compositionstart", handler);
   2044  window.addEventListener("compositionupdate", handler);
   2045  window.addEventListener("compositionend", handler);
   2046  window.addEventListener("keydown", handler);
   2047  window.addEventListener("keypress", handler);
   2048  window.addEventListener("keyup", handler);
   2049 
   2050  input.value = "";
   2051  input.focus();
   2052 
   2053  var printableKeyEvent = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
   2054  var enterKeyEvent = new KeyboardEvent("", { key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
   2055  var escKeyEvent = new KeyboardEvent("", { key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE });
   2056  var convertKeyEvent = new KeyboardEvent("", { key: "Convert", code: "Convert", keyCode: KeyboardEvent.DOM_VK_CONVERT });
   2057  var backspaceKeyEvent = new KeyboardEvent("", { key: "Backspace", code: "Backspace", keyCode: KeyboardEvent.DOM_VK_BACK_SPACE });
   2058 
   2059  // nsITextInputProcessor.startComposition()
   2060  // Keypress event shouldn't be fired during composition
   2061  reset();
   2062  TIP.startComposition(printableKeyEvent);
   2063  is(events.length, 3,
   2064     description + "TIP.startComposition(printableKeyEvent) should cause keydown, compositionstart and keyup (keypress event shouldn't be fired during composition)");
   2065  is(events[0].type, "keydown",
   2066     description + "TIP.startComposition(printableKeyEvent) should cause keydown (keypress event shouldn't be fired during composition)");
   2067  is(events[1].type, "compositionstart",
   2068     description + "TIP.startComposition(printableKeyEvent) should cause compositionstart (keypress event shouldn't be fired during composition)");
   2069  is(events[2].type, "keyup",
   2070     description + "TIP.startComposition(printableKeyEvent) should cause keyup (keypress event shouldn't be fired during composition)");
   2071 
   2072  // TIP.flushPendingComposition(printableKeyEvent) should cause keydown and keyup events
   2073  TIP.setPendingCompositionString("foo");
   2074  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   2075  TIP.setCaretInPendingComposition(3);
   2076 
   2077  reset();
   2078  TIP.flushPendingComposition(printableKeyEvent);
   2079  is(events.length, 3,
   2080     description + "TIP.flushPendingComposition(printableKeyEvent) should cause keydown, compositionupdate and keyup (keypress event shouldn't be fired during composition)");
   2081  is(events[0].type, "keydown",
   2082     description + "TIP.flushPendingComposition(printableKeyEvent) should cause keydown (keypress event shouldn't be fired during composition)");
   2083  is(events[1].type, "compositionupdate",
   2084     description + "TIP.flushPendingComposition(printableKeyEvent) should cause compositionupdate (keypress event shouldn't be fired during composition)");
   2085  is(events[2].type, "keyup",
   2086     description + "TIP.flushPendingComposition(printableKeyEvent) should cause keyup (keypress event shouldn't be fired during composition)");
   2087 
   2088  // TIP.commitComposition(enterKeyEvent) should cause keydown and keyup events
   2089  reset();
   2090  TIP.commitComposition(enterKeyEvent);
   2091  is(events.length, 3,
   2092     description + "TIP.commitComposition(enterKeyEvent) should cause keydown, compositionend and keyup (keypress event shouldn't be fired during composition)");
   2093  is(events[0].type, "keydown",
   2094     description + "TIP.commitComposition(enterKeyEvent) should cause keydown (keypress event shouldn't be fired during composition)");
   2095  is(events[1].type, "compositionend",
   2096     description + "TIP.commitComposition(enterKeyEvent) should cause compositionend (keypress event shouldn't be fired during composition)");
   2097  is(events[2].type, "keyup",
   2098     description + "TIP.commitComposition(enterKeyEvent) should cause keyup (keypress event shouldn't be fired during composition)");
   2099 
   2100  // TIP.cancelComposition(escKeyEvent) should cause keydown and keyup events
   2101  TIP.startComposition();
   2102  reset();
   2103  TIP.cancelComposition(escKeyEvent);
   2104  is(events.length, 3,
   2105     description + "TIP.cancelComposition(escKeyEvent) should cause keydown, compositionend and keyup (keypress event shouldn't be fired during composition)");
   2106  is(events[0].type, "keydown",
   2107     description + "TIP.cancelComposition(escKeyEvent) should cause keydown (keypress event shouldn't be fired during composition)");
   2108  is(events[1].type, "compositionend",
   2109     description + "TIP.cancelComposition(escKeyEvent) should cause compositionend (keypress event shouldn't be fired during composition)");
   2110  is(events[2].type, "keyup",
   2111     description + "TIP.cancelComposition(escKeyEvent) should cause keyup (keypress event shouldn't be fired during composition)");
   2112 
   2113  var printableKeydownEvent = new KeyboardEvent("keydown", { key: "b", code: "KeyB", keyCode: KeyboardEvent.DOM_VK_B });
   2114  var enterKeydownEvent = new KeyboardEvent("keydown", { key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
   2115  var escKeydownEvent = new KeyboardEvent("keydown", { key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE });
   2116 
   2117  // TIP.startComposition(printableKeydownEvent) shouldn't cause keyup event
   2118  reset();
   2119  TIP.startComposition(printableKeydownEvent);
   2120  is(events.length, 2,
   2121     description + "TIP.startComposition(printableKeydownEvent) should cause keydown and compositionstart (keyup event shouldn't be fired)");
   2122  is(events[0].type, "keydown",
   2123     description + "TIP.startComposition(printableKeydownEvent) should cause keydown (keyup event shouldn't be fired)");
   2124  is(events[1].type, "compositionstart",
   2125     description + "TIP.startComposition(printableKeydownEvent) should cause compositionstart (keyup event shouldn't be fired)");
   2126 
   2127  // TIP.flushPendingComposition(printableKeydownEvent) shouldn't cause keyup event
   2128  TIP.setPendingCompositionString("foo");
   2129  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   2130  TIP.setCaretInPendingComposition(3);
   2131 
   2132  reset();
   2133  TIP.flushPendingComposition(printableKeydownEvent);
   2134  is(events.length, 2,
   2135     description + "TIP.flushPendingComposition(printableKeydownEvent) should cause keydown and compositionupdate (keyup event shouldn't be fired)");
   2136  is(events[0].type, "keydown",
   2137     description + "TIP.flushPendingComposition(printableKeydownEvent) should cause keydown (keyup event shouldn't be fired)");
   2138  is(events[1].type, "compositionupdate",
   2139     description + "TIP.flushPendingComposition(printableKeydownEvent) should cause compositionupdate (keyup event shouldn't be fired)");
   2140 
   2141  // TIP.commitComposition(enterKeydownEvent) shouldn't cause keyup event
   2142  reset();
   2143  TIP.commitComposition(enterKeydownEvent);
   2144  is(events.length, 2,
   2145     description + "TIP.commitComposition(enterKeydownEvent) should cause keydown and compositionend (keyup event shouldn't be fired)");
   2146  is(events[0].type, "keydown",
   2147     description + "TIP.commitComposition(enterKeydownEvent) should cause keydown (keyup event shouldn't be fired)");
   2148  is(events[1].type, "compositionend",
   2149     description + "TIP.commitComposition(enterKeydownEvent) should cause compositionend (keyup event shouldn't be fired)");
   2150 
   2151  // TIP.cancelComposition(escKeydownEvent) shouldn't cause keyup event
   2152  TIP.startComposition();
   2153  reset();
   2154  TIP.cancelComposition(escKeydownEvent);
   2155  is(events.length, 2,
   2156     description + "TIP.cancelComposition(escKeydownEvent) should cause keydown and compositionend (keyup event shouldn't be fired)");
   2157  is(events[0].type, "keydown",
   2158     description + "TIP.cancelComposition(escKeydownEvent) should cause keydown (keyup event shouldn't be fired)");
   2159  is(events[1].type, "compositionend",
   2160     description + "TIP.cancelComposition(escKeydownEvent) should cause compositionend (keyup event shouldn't be fired)");
   2161 
   2162  window.removeEventListener("compositionstart", handler);
   2163  window.removeEventListener("compositionupdate", handler);
   2164  window.removeEventListener("compositionend", handler);
   2165  window.removeEventListener("keydown", handler);
   2166  window.removeEventListener("keypress", handler);
   2167  window.removeEventListener("keyup", handler);
   2168 }
   2169 
   2170 function runConsumingKeydownBeforeCompositionTests()
   2171 {
   2172  var description = "runConsumingKeydownBeforeCompositionTests(): ";
   2173 
   2174  var TIP = createTIP();
   2175  ok(TIP.beginInputTransactionForTests(window),
   2176     description + "TIP.beginInputTransactionForTests() should succeed");
   2177 
   2178  var events;
   2179 
   2180  function reset()
   2181  {
   2182    events = [];
   2183  }
   2184 
   2185  function handler(aEvent)
   2186  {
   2187    events.push(aEvent);
   2188    if (aEvent.type == "keydown") {
   2189      aEvent.preventDefault();
   2190    }
   2191  }
   2192 
   2193  window.addEventListener("compositionstart", handler);
   2194  window.addEventListener("compositionupdate", handler);
   2195  window.addEventListener("compositionend", handler);
   2196  window.addEventListener("keydown", handler);
   2197  window.addEventListener("keypress", handler);
   2198  window.addEventListener("keyup", handler);
   2199 
   2200  input.value = "";
   2201  input.focus();
   2202 
   2203  var printableKeyEvent = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
   2204  var enterKeyEvent = new KeyboardEvent("", { key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
   2205  var escKeyEvent = new KeyboardEvent("", { key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE });
   2206 
   2207  // If composition is already started, TIP.flushPendingComposition(printableKeyEvent) shouldn't be canceled.
   2208  TIP.startComposition();
   2209  ok(TIP.hasComposition,
   2210     description + "Before TIP.flushPendingComposition(printableKeyEvent), composition should've been created");
   2211  reset();
   2212  TIP.setPendingCompositionString("foo");
   2213  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   2214  TIP.setCaretInPendingComposition(3);
   2215  ok(TIP.flushPendingComposition(printableKeyEvent),
   2216     description + "TIP.flushPendingComposition(printableKeyEvent) should return true even if preceding keydown is consumed because there was a composition already");
   2217  is(events.length, 3,
   2218     description + "TIP.flushPendingComposition(printableKeyEvent) should cause only keydown and keyup events");
   2219  is(events[0].type, "keydown",
   2220     description + "TIP.flushPendingComposition(printableKeyEvent) should cause keydown event first");
   2221  is(events[1].type, "compositionupdate",
   2222     description + "TIP.flushPendingComposition(printableKeyEvent) should cause compositionupdate event after keydown");
   2223  is(events[2].type, "keyup",
   2224     description + "TIP.flushPendingComposition(printableKeyEvent) should cause keyup event after compositionupdate");
   2225  ok(TIP.hasComposition,
   2226     description + "TIP.flushPendingComposition(printableKeyEvent) shouldn't cause canceling composition");
   2227  is(input.value, "foo",
   2228     description + "TIP.flushPendingComposition(printableKeyEvent) should cause inserting text even if preceding keydown is consumed because there was a composition already");
   2229 
   2230  // If composition is already started, TIP.commitComposition(enterKeyEvent) shouldn't be canceled.
   2231  reset();
   2232  TIP.commitComposition(enterKeyEvent);
   2233  is(events.length, 3,
   2234     description + "TIP.commitComposition(enterKeyEvent) should cause keydown, compositionend and keyup events");
   2235  is(events[0].type, "keydown",
   2236     description + "TIP.commitComposition(enterKeyEvent) should cause keydown event first");
   2237  is(events[1].type, "compositionend",
   2238     description + "TIP.commitComposition(enterKeyEvent) should cause compositionend event after keydown");
   2239  is(events[2].type, "keyup",
   2240     description + "TIP.commitComposition(enterKeyEvent) should cause keyup event after compositionend");
   2241  ok(!TIP.hasComposition,
   2242     description + "TIP.commitComposition(enterKeyEvent) should cause committing composition even if preceding keydown is consumed because there was a composition already");
   2243  is(input.value, "foo",
   2244     description + "TIP.commitComposition(enterKeyEvent) should commit composition even if preceding keydown is consumed because there was a composition already");
   2245 
   2246  // cancelComposition() should work even if preceding keydown event is consumed.
   2247  input.value = "";
   2248  TIP.setPendingCompositionString("foo");
   2249  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   2250  TIP.setCaretInPendingComposition(3);
   2251  TIP.flushPendingComposition();
   2252  ok(TIP.hasComposition,
   2253     description + "Before TIP.cancelComposition(escKeyEvent), composition should've been created");
   2254  is(input.value, "foo",
   2255     description + "Before TIP.cancelComposition(escKeyEvent) should have composition string");
   2256  reset();
   2257  TIP.cancelComposition(escKeyEvent);
   2258  is(events.length, 4,
   2259     description + "TIP.cancelComposition(escKeyEvent) should cause keydown, compositionupdate, compositionend and keyup events even if preceding keydown is consumed because there was a composition already");
   2260  is(events[0].type, "keydown",
   2261     description + "TIP.cancelComposition(escKeyEvent) should cause keydown event first");
   2262  is(events[1].type, "compositionupdate",
   2263     description + "TIP.cancelComposition(escKeyEvent) should cause compositionupdate event after keydown");
   2264  is(events[2].type, "compositionend",
   2265     description + "TIP.cancelComposition(escKeyEvent) should cause compositionend event after compositionupdate");
   2266  is(events[3].type, "keyup",
   2267     description + "TIP.cancelComposition(escKeyEvent) should cause keyup event after compositionend");
   2268  ok(!TIP.hasComposition,
   2269     description + "TIP.cancelComposition(escKeyEvent) should cause canceling composition even if preceding keydown is consumed because there was a composition already");
   2270  is(input.value, "",
   2271     description + "TIP.cancelComposition(escKeyEvent) should cancel composition even if preceding keydown is consumed because there was a composition already");
   2272 
   2273  window.removeEventListener("compositionstart", handler);
   2274  window.removeEventListener("compositionupdate", handler);
   2275  window.removeEventListener("compositionend", handler);
   2276  window.removeEventListener("keydown", handler);
   2277  window.removeEventListener("keypress", handler);
   2278  window.removeEventListener("keyup", handler);
   2279 }
   2280 
   2281 async function runKeyTests()
   2282 {
   2283  var description = "runKeyTests(): ";
   2284  const kModifiers =
   2285    [ "Alt", "AltGraph", "CapsLock", "Control", "Fn", "FnLock", "Meta", "NumLock",
   2286      "ScrollLock", "Shift", "Symbol", "SymbolLock", "OS" ];
   2287 
   2288  var TIP = createTIP();
   2289  ok(TIP.beginInputTransactionForTests(window),
   2290     description + "TIP.beginInputTransactionForTests() should succeed");
   2291 
   2292  var events;
   2293  var doPreventDefaults;
   2294 
   2295  function reset()
   2296  {
   2297    events = [];
   2298    doPreventDefaults = [];
   2299  }
   2300 
   2301  function handler(aEvent)
   2302  {
   2303    events.push(aEvent);
   2304    if (doPreventDefaults.includes(aEvent.type)) {
   2305      aEvent.preventDefault();
   2306    }
   2307  }
   2308 
   2309  function checkKeyAttrs(aMethodDescription, aEvent, aExpectedData)
   2310  {
   2311    var desc = description + aMethodDescription + ", type=\"" + aEvent.type + "\", key=\"" + aEvent.key + "\", code=\"" + aEvent.code + "\": ";
   2312    var defaultValues = {
   2313      key: "Unidentified", code: "", keyCode: 0, charCode: 0,
   2314      location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD, repeat: false, isComposing: false,
   2315      shiftKey: false, ctrlKey: false, altKey: false, metaKey: false,
   2316      defaultPrevented: false
   2317    };
   2318    function expectedValue(aAttr)
   2319    {
   2320      return aExpectedData[aAttr] !== undefined ? aExpectedData[aAttr] : defaultValues[aAttr];
   2321    }
   2322    is(aEvent.type, aExpectedData.type,
   2323       desc + " should cause keydown event");
   2324    if (aEvent.type != aExpectedData.type) {
   2325      return;
   2326    }
   2327    is(aEvent.defaultPrevented, expectedValue("defaultPrevented"),
   2328       desc + ".defaultPrevented is wrong");
   2329    is(aEvent.key, expectedValue("key"),
   2330       desc + ".key is wrong");
   2331    is(aEvent.code, expectedValue("code"),
   2332       desc + ".code is wrong");
   2333    is(aEvent.location, expectedValue("location"),
   2334       desc + ".location is wrong");
   2335    is(aEvent.repeat, expectedValue("repeat"),
   2336       desc + ".repeat is wrong");
   2337    is(aEvent.isComposing, expectedValue("isComposing"),
   2338       desc + ".isComposing is wrong");
   2339    is(aEvent.keyCode, expectedValue("keyCode"),
   2340       desc + ".keyCode is wrong");
   2341    is(aEvent.charCode, expectedValue("charCode"),
   2342       desc + ".charCode is wrong");
   2343    is(aEvent.shiftKey, expectedValue("shiftKey"),
   2344       desc + ".shiftKey is wrong");
   2345    is(aEvent.ctrlKey, expectedValue("ctrlKey"),
   2346       desc + ".ctrlKey is wrong");
   2347    is(aEvent.altKey, expectedValue("altKey"),
   2348       desc + ".altKey is wrong");
   2349    is(aEvent.metaKey, expectedValue("metaKey"),
   2350       desc + ".metaKey is wrong");
   2351    for (var i = 0; i < kModifiers.length; i++) {
   2352      is(aEvent.getModifierState(kModifiers[i]), aExpectedData[kModifiers[i]] !== undefined ? aExpectedData[kModifiers[i]] : false,
   2353         desc + ".getModifierState(\"" + kModifiers[i] + "\") is wrong");
   2354    }
   2355  }
   2356 
   2357  window.addEventListener("keydown", handler);
   2358  window.addEventListener("keypress", handler);
   2359  window.addEventListener("keyup", handler);
   2360 
   2361  input.value = "";
   2362  input.focus();
   2363 
   2364 
   2365  // Printable key test:
   2366  // Emulates pressing 'a' key.
   2367  var keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
   2368 
   2369  reset();
   2370  var doDefaultKeydown = TIP.keydown(keyA);
   2371 
   2372  is(doDefaultKeydown, TIP.KEYPRESS_IS_CONSUMED,
   2373     description + "TIP.keydown(keyA) should return 0x02 because the keypress event should be consumed by the input element");
   2374  is(events.length, 2,
   2375     description + "TIP.keydown(keyA) should cause keydown and keypress event");
   2376  checkKeyAttrs("TIP.keydown(keyA)", events[0],
   2377                { type: "keydown",  key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0 });
   2378  checkKeyAttrs("TIP.keydown(keyA)", events[1],
   2379                { type: "keypress", key: "a", code: "KeyA", keyCode: 0,                      charCode: "a".charCodeAt(0), defaultPrevented: true });
   2380  is(input.value, "a",
   2381     description + "input.value should be \"a\" which is inputted by TIP.keydown(keyA)");
   2382 
   2383  // Emulates releasing 'a' key.
   2384  reset();
   2385  var doDefaultKeyup = TIP.keyup(keyA);
   2386  ok(doDefaultKeyup,
   2387     description + "TIP.keyup(keyA) should return true");
   2388  is(events.length, 1,
   2389     description + "TIP.keyup(keyA) should cause keyup event");
   2390  checkKeyAttrs("TIP.keyup(keyA)", events[0],
   2391                { type: "keyup",      key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0 });
   2392  is(input.value, "a",
   2393     description + "input.value should stay \"a\" which was inputted by TIP.keydown(keyA)");
   2394 
   2395 
   2396  // Non-printable key test:
   2397  // Emulates pressing Enter key.
   2398  var keyEnter = new KeyboardEvent("", { key: "Enter", code: "Enter" });
   2399 
   2400  reset();
   2401  doDefaultKeydown = TIP.keydown(keyEnter);
   2402 
   2403  is(doDefaultKeydown, 0,
   2404     description + "TIP.keydown(keyEnter) should return 0");
   2405  is(events.length, 2,
   2406     description + "TIP.keydown(keyEnter) should cause keydown and keypress event");
   2407  checkKeyAttrs("TIP.keydown(keyEnter)", events[0],
   2408                { type: "keydown",  key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
   2409  checkKeyAttrs("TIP.keydown(keyEnter)", events[1],
   2410                { type: "keypress", key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
   2411  is(input.value, "a",
   2412     description + "input.value should stay \"a\" which was inputted by TIP.keydown(keyA)");
   2413 
   2414  // Emulates releasing Enter key.
   2415  reset();
   2416  doDefaultKeyup = TIP.keyup(keyEnter);
   2417  ok(doDefaultKeyup,
   2418     description + "TIP.keyup(keyEnter) should return true");
   2419  is(events.length, 1,
   2420     description + "TIP.keyup(keyEnter) should cause keyup event");
   2421  checkKeyAttrs("TIP.keyup(keyEnter)", events[0],
   2422                { type: "keyup",      key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
   2423  is(input.value, "a",
   2424     description + "input.value should stay \"a\" which was inputted by TIP.keydown(keyA)");
   2425 
   2426 
   2427  // KEY_DEFAULT_PREVENTED should cause defaultPrevented = true and not cause keypress event
   2428  var keyB = new KeyboardEvent("", { key: "b", code: "KeyB", keyCode: KeyboardEvent.DOM_VK_B });
   2429 
   2430  reset();
   2431  doDefaultKeydown = TIP.keydown(keyB, TIP.KEY_DEFAULT_PREVENTED);
   2432  doDefaultKeyup   = TIP.keyup(keyB, TIP.KEY_DEFAULT_PREVENTED);
   2433 
   2434  is(doDefaultKeydown, TIP.KEYDOWN_IS_CONSUMED,
   2435     description + "TIP.keydown(keyB, TIP.KEY_DEFAULT_PREVENTED) should return 0x01 because it's marked as consumed at dispatching the event");
   2436  ok(!doDefaultKeyup,
   2437     description + "TIP.keyup(keyB, TIP.KEY_DEFAULT_PREVENTED) should return false because it's marked as consumed at dispatching the event");
   2438  is(events.length, 2,
   2439     description + "TIP.keydown(keyB, TIP.KEY_DEFAULT_PREVENTED) and TIP.keyup(keyB, TIP.KEY_DEFAULT_PREVENTED) should cause keydown and keyup event");
   2440  checkKeyAttrs("TIP.keydown(keyB, TIP.KEY_DEFAULT_PREVENTED) and TIP.keyup(keyB, TIP.KEY_DEFAULT_PREVENTED)", events[0],
   2441                { type: "keydown", key: "b", code: "KeyB", keyCode: KeyboardEvent.DOM_VK_B, defaultPrevented: true });
   2442  checkKeyAttrs("TIP.keydown(keyB, TIP.KEY_DEFAULT_PREVENTED) and TIP.keyup(keyB, TIP.KEY_DEFAULT_PREVENTED)", events[1],
   2443                { type: "keyup",   key: "b", code: "KeyB", keyCode: KeyboardEvent.DOM_VK_B, defaultPrevented: true });
   2444  is(input.value, "a",
   2445     description + "input.value shouldn't be modified by default prevented key events");
   2446 
   2447  // Assume that KeyX causes inputting text "abc"
   2448  input.value = "";
   2449  var keyABC = new KeyboardEvent("", { key: "abc", code: "KeyX", keyCode: KeyboardEvent.DOM_VK_A });
   2450 
   2451  reset();
   2452  doDefaultKeydown = TIP.keydown(keyABC);
   2453  doDefaultKeyup   = TIP.keyup(keyABC);
   2454 
   2455  is(doDefaultKeydown, TIP.KEYPRESS_IS_CONSUMED,
   2456     description + "TIP.keydown(keyABC) should return false because the keypress events should be consumed by the input element");
   2457  ok(doDefaultKeyup,
   2458     description + "TIP.keyup(keyABC) should return true");
   2459  is(events.length, 5,
   2460     description + "TIP.keydown(keyABC) and TIP.keyup(keyABC) should cause keydown, keypress, keypress, keypress and keyup event");
   2461  checkKeyAttrs("TIP.keydown(keyABC) and TIP.keyup(keyABC)", events[0],
   2462                { type: "keydown",  key: "abc",           code: "KeyX", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0,                   defaultPrevented: false });
   2463  checkKeyAttrs("TIP.keydown(keyABC) and TIP.keyup(keyABC)", events[1],
   2464                { type: "keypress", key: "abc".charAt(0), code: "KeyX", keyCode: 0,                      charCode: "abc".charCodeAt(0), defaultPrevented: true });
   2465  checkKeyAttrs("TIP.keydown(keyABC) and TIP.keyup(keyABC)", events[2],
   2466                { type: "keypress", key: "abc".charAt(1), code: "KeyX", keyCode: 0,                      charCode: "abc".charCodeAt(1), defaultPrevented: true });
   2467  checkKeyAttrs("TIP.keydown(keyABC) and TIP.keyup(keyABC)", events[3],
   2468                { type: "keypress", key: "abc".charAt(2), code: "KeyX", keyCode: 0,                      charCode: "abc".charCodeAt(2), defaultPrevented: true });
   2469  checkKeyAttrs("TIP.keydown(keyABC) and TIP.keyup(keyABC)", events[4],
   2470                { type: "keyup",    key: "abc",           code: "KeyX", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0,                   defaultPrevented: false });
   2471  is(input.value, "abc",
   2472     description + "input.value should be \"abc\"");
   2473 
   2474  // Emulates pressing and releasing a key which introduces a surrogate pair.
   2475  async function test_press_and_release_surrogate_pair_key(
   2476    aTestPerSurrogateKeyPress,
   2477    aTestIllFormedUTF16KeyValue = false
   2478  ) {
   2479    await SpecialPowers.pushPrefEnv({
   2480      set: [
   2481        ["dom.event.keypress.dispatch_once_per_surrogate_pair", !aTestPerSurrogateKeyPress],
   2482        ["dom.event.keypress.key.allow_lone_surrogate", aTestIllFormedUTF16KeyValue],
   2483      ],
   2484    });
   2485 
   2486    const settingDescription =
   2487      `aTestPerSurrogateKeyPress=${aTestPerSurrogateKeyPress}, aTestIllFormedUTF16KeyValue=${aTestIllFormedUTF16KeyValue}`;
   2488    const allowIllFormedUTF16 = aTestPerSurrogateKeyPress && aTestIllFormedUTF16KeyValue;
   2489    const keySurrogatePair = new KeyboardEvent("", { key: "\uD842\uDFB7", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
   2490 
   2491    input.value = "";
   2492    reset();
   2493    doDefaultKeydown = TIP.keydown(keySurrogatePair);
   2494    doDefaultKeyup = TIP.keyup(keySurrogatePair);
   2495 
   2496    is(
   2497      doDefaultKeydown,
   2498      TIP.KEYPRESS_IS_CONSUMED,
   2499      `${
   2500        description
   2501      }TIP.keydown(keySurrogatePair), ${
   2502        settingDescription
   2503      }, should return 0x02 because the keypress event should be consumed by the input element`
   2504    );
   2505    is(
   2506      doDefaultKeyup,
   2507      true,
   2508      `${description}TIP.keyup(keySurrogatePair) should return true`
   2509    );
   2510    is(
   2511      events.length,
   2512      aTestPerSurrogateKeyPress ? 4 : 3,
   2513      `${description}TIP.keydown(keySurrogatePair), ${
   2514        settingDescription
   2515      }, should cause keydown, keypress${
   2516        aTestPerSurrogateKeyPress ? ", keypress" : ""
   2517      } and keyup event`
   2518    );
   2519    checkKeyAttrs(
   2520      `${description}TIP.keydown(keySurrogatePair), ${settingDescription}`,
   2521      events[0],
   2522      {
   2523        type: "keydown",
   2524        key: "\uD842\uDFB7",
   2525        code: "KeyA",
   2526        keyCode: KeyboardEvent.DOM_VK_A,
   2527        charCode: 0,
   2528      }
   2529    );
   2530    if (aTestPerSurrogateKeyPress) {
   2531      checkKeyAttrs(
   2532        `${description}TIP.keydown(keySurrogatePair), ${settingDescription}`,
   2533        events[1],
   2534        {
   2535          type: "keypress",
   2536          key: allowIllFormedUTF16
   2537            ? "\uD842"
   2538            : "\uD842\uDFB7", // First keypress should have the surrogate pair
   2539          code: "KeyA",
   2540          keyCode: 0,
   2541          charCode: "\uD842\uDFB7".charCodeAt(0),
   2542          defaultPrevented: true,
   2543        }
   2544      );
   2545      checkKeyAttrs(
   2546        `${description}TIP.keydown(keySurrogatePair), ${settingDescription}`,
   2547        events[2],
   2548        {
   2549          type: "keypress",
   2550          key: allowIllFormedUTF16
   2551            ? "\uDFB7"
   2552            : "", // But the following keypress should have empty string, instead
   2553          code: "KeyA",
   2554          keyCode: 0,
   2555          charCode: "\uD842\uDFB7".charCodeAt(1),
   2556          defaultPrevented: true,
   2557        }
   2558      );
   2559    } else {
   2560      checkKeyAttrs(
   2561        `${description}TIP.keydown(keySurrogatePair), ${settingDescription}`,
   2562        events[1],
   2563        {
   2564          type: "keypress",
   2565          key: "\uD842\uDFB7",
   2566          code: "KeyA",
   2567          keyCode: 0,
   2568          charCode: 0x20BB7,
   2569          defaultPrevented: true,
   2570        }
   2571      );
   2572    }
   2573    checkKeyAttrs(
   2574      `${description}TIP.keyup(keySurrogatePair), ${settingDescription}`,
   2575      events[aTestPerSurrogateKeyPress ? 3 : 2],
   2576      {
   2577        type: "keyup",
   2578        key: "\uD842\uDFB7",
   2579        code: "KeyA",
   2580        keyCode: KeyboardEvent.DOM_VK_A,
   2581        charCode: 0,
   2582      }
   2583    );
   2584    is(
   2585      input.value,
   2586      "\uD842\uDFB7",
   2587      `${description}${settingDescription}, input.value should be the surrogate pair`
   2588    );
   2589  };
   2590 
   2591  await test_press_and_release_surrogate_pair_key(true, true);
   2592  await test_press_and_release_surrogate_pair_key(true, false);
   2593  await test_press_and_release_surrogate_pair_key(false);
   2594 
   2595  // If KEY_FORCE_PRINTABLE_KEY is specified, registered key names can be a printable key which inputs the specified value.
   2596  input.value = "";
   2597  var keyEnterPrintable = new KeyboardEvent("", { key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
   2598 
   2599  reset();
   2600  doDefaultKeydown = TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY);
   2601  doDefaultKeyup   = TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY);
   2602 
   2603  is(doDefaultKeydown, TIP.KEYPRESS_IS_CONSUMED,
   2604     description + "TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) should return 0x02 because the keypress events should be consumed by the input element");
   2605  ok(doDefaultKeyup,
   2606     description + "TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) should return true");
   2607  is(events.length, 7,
   2608     description + "TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) should cause keydown, keypress, keypress, keypress, keypress, keypress and keyup event");
   2609  checkKeyAttrs("TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)", events[0],
   2610                { type: "keydown",  key: "Enter",           code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN, charCode: 0,                    defaultPrevented: false });
   2611  checkKeyAttrs("TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)", events[1],
   2612                { type: "keypress", key: "Enter".charAt(0), code: "Enter", keyCode: 0,                           charCode: "Enter".charCodeAt(0), defaultPrevented: true });
   2613  checkKeyAttrs("TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)", events[2],
   2614                { type: "keypress", key: "Enter".charAt(1), code: "Enter", keyCode: 0,                           charCode: "Enter".charCodeAt(1), defaultPrevented: true });
   2615  checkKeyAttrs("TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)", events[3],
   2616                { type: "keypress", key: "Enter".charAt(2), code: "Enter", keyCode: 0,                           charCode: "Enter".charCodeAt(2), defaultPrevented: true });
   2617  checkKeyAttrs("TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)", events[4],
   2618                { type: "keypress", key: "Enter".charAt(3), code: "Enter", keyCode: 0,                           charCode: "Enter".charCodeAt(3), defaultPrevented: true });
   2619  checkKeyAttrs("TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)", events[5],
   2620                { type: "keypress", key: "Enter".charAt(4), code: "Enter", keyCode: 0,                           charCode: "Enter".charCodeAt(4), defaultPrevented: true });
   2621  checkKeyAttrs("TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)", events[6],
   2622                { type: "keyup",    key: "Enter",           code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN, charCode: 0,                     defaultPrevented: false });
   2623  is(input.value, "Enter",
   2624     description + "input.value should be \"Enter\"");
   2625 
   2626  // modifiers should be ignored.
   2627  var keyWithModifiers = new KeyboardEvent("", { key: "Escape", code: "Escape", shiftKey: true, ctrlKey: true, altKey: true, metaKey: true });
   2628 
   2629  reset();
   2630  doDefaultKeydown = TIP.keydown(keyWithModifiers);
   2631  doDefaultKeyup   = TIP.keyup(keyWithModifiers);
   2632 
   2633  is(doDefaultKeydown, 0,
   2634     description + "TIP.keydown(keyWithModifiers) should return 0");
   2635  ok(doDefaultKeyup,
   2636     description + "TIP.keyup(keyWithModifiers) should return true");
   2637  is(events.length, 3,
   2638     description + "TIP.keydown(keyWithModifiers) and TIP.keyup(keyWithModifiers) should cause keydown, keypress and keyup event");
   2639  checkKeyAttrs("TIP.keydown(keyWithModifiers) and TIP.keyup(keyWithModifiers)", events[0],
   2640                { type: "keydown",  key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE });
   2641  checkKeyAttrs("TIP.keydown(keyWithModifiers) and TIP.keyup(keyWithModifiers)", events[1],
   2642                { type: "keypress", key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE });
   2643  checkKeyAttrs("TIP.keydown(keyWithModifiers) and TIP.keyup(keyWithModifiers)", events[2],
   2644                { type: "keyup",    key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE });
   2645  is(input.value, "Enter",
   2646     description + "input.value should stay \"Enter\" which was inputted by TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)");
   2647 
   2648  // Call preventDefault() at keydown
   2649  input.value = "";
   2650  reset();
   2651  doPreventDefaults = [ "keydown" ];
   2652  doDefaultKeydown = TIP.keydown(keyA);
   2653  doDefaultKeyup   = TIP.keyup(keyA);
   2654 
   2655  is(doDefaultKeydown, TIP.KEYDOWN_IS_CONSUMED,
   2656     description + "TIP.keydown(keyA) should return 0x01 because keydown event's preventDefault should be called");
   2657  ok(doDefaultKeyup,
   2658     description + "TIP.keyup(keyA) should return true");
   2659  is(events.length, 2,
   2660     description + "TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keydown should cause keydown and keyup event");
   2661  checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keydown", events[0],
   2662                { type: "keydown",  key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, defaultPrevented: true });
   2663  checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keydown", events[1],
   2664                { type: "keyup",    key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, defaultPrevented: false });
   2665  is(input.value, "",
   2666     description + "input.value shouldn't be modified by TIP.keyup(keyA) if the keydown event is consumed");
   2667 
   2668  // Call preventDefault() at keypress
   2669  reset();
   2670  doPreventDefaults = [ "keypress" ];
   2671  doDefaultKeydown = TIP.keydown(keyA);
   2672  doDefaultKeyup   = TIP.keyup(keyA);
   2673 
   2674  is(doDefaultKeydown, TIP.KEYPRESS_IS_CONSUMED,
   2675     description + "TIP.keydown(keyA) should return 0x02 because keypress event's preventDefault should be called");
   2676  ok(doDefaultKeyup,
   2677     description + "TIP.keyup(keyA) should return true");
   2678  is(events.length, 3,
   2679     description + "TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keypress should cause keydown, keypress and keyup event");
   2680  checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keypress", events[0],
   2681                { type: "keydown",  key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0,                 defaultPrevented: false });
   2682  checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keypress", events[1],
   2683                { type: "keypress", key: "a", code: "KeyA", keyCode: 0,                      charCode: "a".charCodeAt(0), defaultPrevented: true });
   2684  checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keypress", events[2],
   2685                { type: "keyup",    key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0,                 defaultPrevented: false });
   2686  is(input.value, "",
   2687     description + "input.value shouldn't be modified by TIP.keyup(keyA) if the keypress event is consumed");
   2688 
   2689  // Call preventDefault() at keyup
   2690  input.value = "";
   2691  reset();
   2692  doPreventDefaults = [ "keyup" ];
   2693  doDefaultKeydown = TIP.keydown(keyA);
   2694  doDefaultKeyup   = TIP.keyup(keyA);
   2695 
   2696  is(doDefaultKeydown, TIP.KEYPRESS_IS_CONSUMED,
   2697     description + "TIP.keydown(keyA) should return 0x02 because the key event should be consumed by the input element");
   2698  ok(!doDefaultKeyup,
   2699     description + "TIP.keyup(keyA) should return false because keyup event's preventDefault should be called");
   2700  is(events.length, 3,
   2701     description + "TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keyup should cause keydown, keypress and keyup event");
   2702  checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keyup", events[0],
   2703                { type: "keydown",  key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0,                 defaultPrevented: false });
   2704  checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keyup", events[1],
   2705                { type: "keypress", key: "a", code: "KeyA", keyCode: 0,                      charCode: "a".charCodeAt(0), defaultPrevented: true });
   2706  checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keyup", events[2],
   2707                { type: "keyup",    key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0,                 defaultPrevented: true });
   2708  is(input.value, "a",
   2709     description + "input.value should be \"a\" by TIP.keyup(keyA) even if the keyup event is consumed");
   2710 
   2711  // key events during composition
   2712  ok(TIP.startComposition(), "TIP.startComposition() should start composition");
   2713 
   2714  input.value = "";
   2715 
   2716  reset();
   2717  TIP.keydown(keyA);
   2718  is(events.length, 1,
   2719     description + "TIP.keydown(keyA) should cause keydown event even composition if it's enabled by the pref");
   2720  checkKeyAttrs("TIP.keydown(keyA) during composition", events[0],
   2721                { type: "keydown",  key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0, isComposing: true });
   2722  reset();
   2723  TIP.keyup(keyA);
   2724  is(events.length, 1,
   2725     description + "TIP.keyup(keyA) should cause keyup event even composition if it's enabled by the pref");
   2726  checkKeyAttrs("TIP.keyup(keyA) during composition", events[0],
   2727                { type: "keyup",    key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0, isComposing: true });
   2728 
   2729  TIP.cancelComposition();
   2730 
   2731  // Test .location computation
   2732  const kCodeToLocation = [
   2733    { code: "BracketLeft",              location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
   2734    { code: "BracketRight",             location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
   2735    { code: "Comma",                    location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
   2736    { code: "Digit0",                   location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
   2737    { code: "Digit1",                   location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
   2738    { code: "Digit2",                   location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
   2739    { code: "Digit3",                   location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
   2740    { code: "Digit4",                   location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
   2741    { code: "Digit5",                   location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
   2742    { code: "Digit6",                   location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
   2743    { code: "Digit7",                   location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
   2744    { code: "Digit8",                   location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
   2745    { code: "Digit9",                   location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
   2746    { code: "Equal",                    location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
   2747    { code: "Minus",                    location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
   2748    { code: "Period",                   location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
   2749    { code: "Slash",                    location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
   2750    { code: "AltLeft",                  location: KeyboardEvent.DOM_KEY_LOCATION_LEFT },
   2751    { code: "AltRight",                 location: KeyboardEvent.DOM_KEY_LOCATION_RIGHT },
   2752    { code: "CapsLock",                 location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
   2753    { code: "ContextMenu",              location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
   2754    { code: "ControlLeft",              location: KeyboardEvent.DOM_KEY_LOCATION_LEFT },
   2755    { code: "ControlRight",             location: KeyboardEvent.DOM_KEY_LOCATION_RIGHT },
   2756    { code: "Enter",                    location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
   2757    { code: "MetaLeft",                 location: KeyboardEvent.DOM_KEY_LOCATION_LEFT },
   2758    { code: "MetaRight",                location: KeyboardEvent.DOM_KEY_LOCATION_RIGHT },
   2759    { code: "ShiftLeft",                location: KeyboardEvent.DOM_KEY_LOCATION_LEFT },
   2760    { code: "ShiftRight",               location: KeyboardEvent.DOM_KEY_LOCATION_RIGHT },
   2761    { code: "Space",                    location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
   2762    { code: "Tab",                      location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
   2763    { code: "ArrowDown",                location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
   2764    { code: "ArrowLeft",                location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
   2765    { code: "ArrowRight",               location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
   2766    { code: "ArrowUp",                  location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
   2767    { code: "NumLock",                  location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
   2768    { code: "Numpad0",                  location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
   2769    { code: "Numpad1",                  location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
   2770    { code: "Numpad2",                  location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
   2771    { code: "Numpad3",                  location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
   2772    { code: "Numpad4",                  location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
   2773    { code: "Numpad5",                  location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
   2774    { code: "Numpad6",                  location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
   2775    { code: "Numpad7",                  location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
   2776    { code: "Numpad8",                  location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
   2777    { code: "Numpad9",                  location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
   2778    { code: "NumpadAdd",                location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
   2779    { code: "NumpadBackspace",          location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
   2780    { code: "NumpadClear",              location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
   2781    { code: "NumpadClearEntry",         location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
   2782    { code: "NumpadComma",              location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
   2783    { code: "NumpadDecimal",            location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
   2784    { code: "NumpadDivide",             location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
   2785    { code: "NumpadEnter",              location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
   2786    { code: "NumpadEqual",              location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
   2787    { code: "NumpadMemoryAdd",          location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
   2788    { code: "NumpadMemoryClear",        location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
   2789    { code: "NumpadMemoryRecall",       location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
   2790    { code: "NumpadMemoryStore",        location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
   2791    { code: "NumpadMemorySubtract",     location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
   2792    { code: "NumpadMultiply",           location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
   2793    { code: "NumpadParenLeft",          location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
   2794    { code: "NumpadParenRight",         location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
   2795    { code: "NumpadSubtract",           location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
   2796  ];
   2797  for (let i = 0; i < kCodeToLocation.length; i++) {
   2798    let keyEvent = new KeyboardEvent("", { code: kCodeToLocation[i].code });
   2799    reset();
   2800    doPreventDefaults = [ "keypress" ];
   2801    // If the location isn't initialized or initialized with 0, it should be computed from the code value.
   2802    TIP.keydown(keyEvent);
   2803    TIP.keyup(keyEvent);
   2804    let longDesc = description + "testing computation of .location of \"" + kCodeToLocation[i].code + "\", ";
   2805    is(events.length, 3,
   2806       longDesc + "keydown, keypress and keyup events should be fired");
   2807    for (let j = 0; j < events.length; j++) {
   2808      is(events[j].location, kCodeToLocation[i].location,
   2809         longDesc + " type=\"" + events[j].type + "\", location value is wrong");
   2810    }
   2811    // However, if KEY_KEEP_KEY_LOCATION_STANDARD is specified, .location value should be kept as DOM_KEY_LOCATION_STANDARD (0).
   2812    reset();
   2813    doPreventDefaults = [ "keypress" ];
   2814    TIP.keydown(keyEvent, TIP.KEY_KEEP_KEY_LOCATION_STANDARD);
   2815    TIP.keyup(keyEvent, TIP.KEY_KEEP_KEY_LOCATION_STANDARD);
   2816    longDesc = description + "testing if .location is forcibly set to DOM_KEY_LOCATION_STANDARD, ";
   2817    is(events.length, 3,
   2818       longDesc + "keydown, keypress and keyup events should be fired");
   2819    for (let j = 0; j < events.length; j++) {
   2820      is(events[j].location, KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
   2821         longDesc + " type=\"" + events[j].type + "\", location value is not 0");
   2822    }
   2823    // If .location is initialized with non-zero value, the value shouldn't be computed again.
   2824    let keyEventWithLocation = new KeyboardEvent("", { code: kCodeToLocation[i].code, location: 0xFF });
   2825    reset();
   2826    doPreventDefaults = [ "keypress" ];
   2827    TIP.keydown(keyEventWithLocation);
   2828    TIP.keyup(keyEventWithLocation);
   2829    longDesc = description + "testing if .location is not computed for \"" + kCodeToLocation[i].location + "\", ";
   2830    is(events.length, 3,
   2831       longDesc + "keydown, keypress and keyup events should be fired");
   2832    for (let j = 0; j < events.length; j++) {
   2833      is(events[j].location, 0xFF,
   2834         longDesc + " type=\"" + events[j].type + "\", location shouldn't be computed if it's initialized with non-zero value");
   2835    }
   2836  }
   2837 
   2838  // Test .keyCode value computation
   2839  const kKeyToKeyCode = [
   2840    { key: "Cancel",                    keyCode: KeyboardEvent.DOM_VK_CANCEL },
   2841    { key: "Help",                      keyCode: KeyboardEvent.DOM_VK_HELP },
   2842    { key: "Backspace",                 keyCode: KeyboardEvent.DOM_VK_BACK_SPACE },
   2843    { key: "Tab",                       keyCode: KeyboardEvent.DOM_VK_TAB },
   2844    { key: "Clear",                     keyCode: KeyboardEvent.DOM_VK_CLEAR },
   2845    { key: "Enter",                     keyCode: KeyboardEvent.DOM_VK_RETURN },
   2846    { key: "Shift",                     keyCode: KeyboardEvent.DOM_VK_SHIFT,              isModifier: true },
   2847    { key: "Control",                   keyCode: KeyboardEvent.DOM_VK_CONTROL,            isModifier: true },
   2848    { key: "Alt",                       keyCode: KeyboardEvent.DOM_VK_ALT,                isModifier: true },
   2849    { key: "Pause",                     keyCode: KeyboardEvent.DOM_VK_PAUSE },
   2850    { key: "CapsLock",                  keyCode: KeyboardEvent.DOM_VK_CAPS_LOCK,          isModifier: true, isLockableModifier: true },
   2851    { key: "Hiragana",                  keyCode: KeyboardEvent.DOM_VK_KANA },
   2852    { key: "Katakana",                  keyCode: KeyboardEvent.DOM_VK_KANA },
   2853    { key: "HiraganaKatakana",          keyCode: KeyboardEvent.DOM_VK_KANA },
   2854    { key: "KanaMode",                  keyCode: KeyboardEvent.DOM_VK_KANA },
   2855    { key: "HangulMode",                keyCode: KeyboardEvent.DOM_VK_HANGUL },
   2856    { key: "Eisu",                      keyCode: KeyboardEvent.DOM_VK_EISU },
   2857    { key: "JunjaMode",                 keyCode: KeyboardEvent.DOM_VK_JUNJA },
   2858    { key: "FinalMode",                 keyCode: KeyboardEvent.DOM_VK_FINAL },
   2859    { key: "HanjaMode",                 keyCode: KeyboardEvent.DOM_VK_HANJA },
   2860    { key: "KanjiMode",                 keyCode: KeyboardEvent.DOM_VK_KANJI },
   2861    { key: "Escape",                    keyCode: KeyboardEvent.DOM_VK_ESCAPE },
   2862    { key: "Convert",                   keyCode: KeyboardEvent.DOM_VK_CONVERT },
   2863    { key: "NonConvert",                keyCode: KeyboardEvent.DOM_VK_NONCONVERT },
   2864    { key: "Accept",                    keyCode: KeyboardEvent.DOM_VK_ACCEPT },
   2865    { key: "ModeChange",                keyCode: KeyboardEvent.DOM_VK_MODECHANGE },
   2866    { key: "PageUp",                    keyCode: KeyboardEvent.DOM_VK_PAGE_UP },
   2867    { key: "PageDown",                  keyCode: KeyboardEvent.DOM_VK_PAGE_DOWN },
   2868    { key: "End",                       keyCode: KeyboardEvent.DOM_VK_END },
   2869    { key: "Home",                      keyCode: KeyboardEvent.DOM_VK_HOME },
   2870    { key: "ArrowLeft",                 keyCode: KeyboardEvent.DOM_VK_LEFT },
   2871    { key: "ArrowUp",                   keyCode: KeyboardEvent.DOM_VK_UP },
   2872    { key: "ArrowRight",                keyCode: KeyboardEvent.DOM_VK_RIGHT },
   2873    { key: "ArrowDown",                 keyCode: KeyboardEvent.DOM_VK_DOWN },
   2874    { key: "Select",                    keyCode: KeyboardEvent.DOM_VK_SELECT },
   2875    { key: "Print",                     keyCode: KeyboardEvent.DOM_VK_PRINT },
   2876    { key: "Execute",                   keyCode: KeyboardEvent.DOM_VK_EXECUTE },
   2877    { key: "PrintScreen",               keyCode: KeyboardEvent.DOM_VK_PRINTSCREEN },
   2878    { key: "Insert",                    keyCode: KeyboardEvent.DOM_VK_INSERT },
   2879    { key: "Delete",                    keyCode: KeyboardEvent.DOM_VK_DELETE },
   2880    { key: "ContextMenu",               keyCode: KeyboardEvent.DOM_VK_CONTEXT_MENU },
   2881    { key: "F1",                        keyCode: KeyboardEvent.DOM_VK_F1 },
   2882    { key: "F2",                        keyCode: KeyboardEvent.DOM_VK_F2 },
   2883    { key: "F3",                        keyCode: KeyboardEvent.DOM_VK_F3 },
   2884    { key: "F4",                        keyCode: KeyboardEvent.DOM_VK_F4 },
   2885    { key: "F5",                        keyCode: KeyboardEvent.DOM_VK_F5 },
   2886    { key: "F6",                        keyCode: KeyboardEvent.DOM_VK_F6 },
   2887    { key: "F7",                        keyCode: KeyboardEvent.DOM_VK_F7 },
   2888    { key: "F8",                        keyCode: KeyboardEvent.DOM_VK_F8 },
   2889    { key: "F9",                        keyCode: KeyboardEvent.DOM_VK_F9 },
   2890    { key: "F10",                       keyCode: KeyboardEvent.DOM_VK_F10 },
   2891    { key: "F11",                       keyCode: KeyboardEvent.DOM_VK_F11 },
   2892    { key: "F12",                       keyCode: KeyboardEvent.DOM_VK_F12 },
   2893    { key: "F13",                       keyCode: KeyboardEvent.DOM_VK_F13 },
   2894    { key: "F14",                       keyCode: KeyboardEvent.DOM_VK_F14 },
   2895    { key: "F15",                       keyCode: KeyboardEvent.DOM_VK_F15 },
   2896    { key: "F16",                       keyCode: KeyboardEvent.DOM_VK_F16 },
   2897    { key: "F17",                       keyCode: KeyboardEvent.DOM_VK_F17 },
   2898    { key: "F18",                       keyCode: KeyboardEvent.DOM_VK_F18 },
   2899    { key: "F19",                       keyCode: KeyboardEvent.DOM_VK_F19 },
   2900    { key: "F20",                       keyCode: KeyboardEvent.DOM_VK_F20 },
   2901    { key: "F21",                       keyCode: KeyboardEvent.DOM_VK_F21 },
   2902    { key: "F22",                       keyCode: KeyboardEvent.DOM_VK_F22 },
   2903    { key: "F23",                       keyCode: KeyboardEvent.DOM_VK_F23 },
   2904    { key: "F24",                       keyCode: KeyboardEvent.DOM_VK_F24 },
   2905    { key: "NumLock",                   keyCode: KeyboardEvent.DOM_VK_NUM_LOCK,           isModifier: true, isLockableModifier: true },
   2906    { key: "ScrollLock",                keyCode: KeyboardEvent.DOM_VK_SCROLL_LOCK,        isModifier: true, isLockableModifier: true },
   2907    { key: "AudioVolumeMute",           keyCode: KeyboardEvent.DOM_VK_VOLUME_MUTE },
   2908    { key: "AudioVolumeDown",           keyCode: KeyboardEvent.DOM_VK_VOLUME_DOWN },
   2909    { key: "AudioVolumeUp",             keyCode: KeyboardEvent.DOM_VK_VOLUME_UP },
   2910    { key: "Meta",                      keyCode: kIsMac
   2911                                          ? KeyboardEvent.DOM_VK_META
   2912                                          : KeyboardEvent.DOM_VK_WIN,                     isModifier: true },
   2913    { key: "AltGraph",                  keyCode: KeyboardEvent.DOM_VK_ALTGR,              isModifier: true },
   2914    { key: "Attn",                      keyCode: KeyboardEvent.DOM_VK_ATTN },
   2915    { key: "CrSel",                     keyCode: KeyboardEvent.DOM_VK_CRSEL },
   2916    { key: "ExSel",                     keyCode: KeyboardEvent.DOM_VK_EXSEL },
   2917    { key: "EraseEof",                  keyCode: KeyboardEvent.DOM_VK_EREOF },
   2918    { key: "Play",                      keyCode: KeyboardEvent.DOM_VK_PLAY },
   2919    { key: "ZoomToggle",                keyCode: KeyboardEvent.DOM_VK_ZOOM },
   2920    { key: "ZoomIn",                    keyCode: KeyboardEvent.DOM_VK_ZOOM },
   2921    { key: "ZoomOut",                   keyCode: KeyboardEvent.DOM_VK_ZOOM },
   2922    { key: "Unidentified",              keyCode: 0 },
   2923    { key: "a",                         keyCode: 0, isPrintable: true },
   2924    { key: "A",                         keyCode: 0, isPrintable: true },
   2925    { key: " ",                         keyCode: 0, isPrintable: true },
   2926    { key: "",                          keyCode: 0, isPrintable: true },
   2927  ];
   2928 
   2929  for (let i = 0; i < kKeyToKeyCode.length; i++) {
   2930    let keyEvent = new KeyboardEvent("", { key: kKeyToKeyCode[i].key });
   2931    var causeKeypress = !kKeyToKeyCode[i].isModifier;
   2932    var baseFlags = kKeyToKeyCode[i].isPrintable ? 0 : TIP.KEY_NON_PRINTABLE_KEY;
   2933    reset();
   2934    doPreventDefaults = [ "keypress" ];
   2935    // If the keyCode isn't initialized or initialized with 0, it should be computed from the key value only when it's a printable key.
   2936    TIP.keydown(keyEvent, baseFlags);
   2937    TIP.keyup(keyEvent, baseFlags);
   2938    let longDesc = description + "testing computation of .keyCode of \"" + kKeyToKeyCode[i].key + "\", ";
   2939    is(events.length, causeKeypress ? 3 : 2,
   2940       longDesc + "keydown" + (causeKeypress ? ", keypress" : "") + " and keyup events should be fired");
   2941    for (let j = 0; j < events.length; j++) {
   2942      is(events[j].keyCode, events[j].type == "keypress" && kKeyToKeyCode[i].isPrintable ? 0 : kKeyToKeyCode[i].keyCode,
   2943         longDesc + " type=\"" + events[j].type + "\", keyCode value is wrong");
   2944    }
   2945    // However, if KEY_KEEP_KEYCODE_ZERO is specified, .keyCode value should be kept as 0.
   2946    reset();
   2947    doPreventDefaults = [ "keypress" ];
   2948    TIP.keydown(keyEvent, TIP.KEY_KEEP_KEYCODE_ZERO | baseFlags);
   2949    TIP.keyup(keyEvent, TIP.KEY_KEEP_KEYCODE_ZERO | baseFlags);
   2950    longDesc = description + "testing if .keyCode is forcibly set to KEY_KEEP_KEYCODE_ZERO, ";
   2951    is(events.length, causeKeypress ? 3 : 2,
   2952       longDesc + "keydown" + (causeKeypress ? ", keypress" : "") + " and keyup events should be fired");
   2953    for (let j = 0; j < events.length; j++) {
   2954      is(events[j].keyCode, 0,
   2955         longDesc + " type=\"" + events[j].type + "\", keyCode value is not 0");
   2956    }
   2957    // If .keyCode is initialized with non-zero value, the value shouldn't be computed again.
   2958    let keyEventWithLocation = new KeyboardEvent("", { key: kKeyToKeyCode[i].key, keyCode: 0xFF });
   2959    reset();
   2960    doPreventDefaults = [ "keypress" ];
   2961    TIP.keydown(keyEventWithLocation, baseFlags);
   2962    TIP.keyup(keyEventWithLocation, baseFlags);
   2963    longDesc = description + "testing if .keyCode is not computed for \"" + kKeyToKeyCode[i].key + "\", ";
   2964    is(events.length, causeKeypress ? 3 : 2,
   2965       longDesc + "keydown" + (causeKeypress ? ", keypress" : "") + " and keyup events should be fired");
   2966    for (let j = 0; j < events.length; j++) {
   2967      is(events[j].keyCode, events[j].type == "keypress" && kKeyToKeyCode[i].isPrintable ? 0 : 0xFF,
   2968         longDesc + " type=\"" + events[j].type + "\", keyCode shouldn't be computed if it's initialized with non-zero value");
   2969    }
   2970    // Unlock lockable modifier if the key is a lockable modifier key.
   2971    if (kKeyToKeyCode[i].isLockableModifier) {
   2972      TIP.keydown(keyEvent, baseFlags);
   2973      TIP.keyup(keyEvent, baseFlags);
   2974    }
   2975  }
   2976 
   2977  // Modifier state tests
   2978  var sharedTIP = createTIP();
   2979  ok(sharedTIP.beginInputTransactionForTests(otherWindow),
   2980     description + "sharedTIP.beginInputTransactionForTests(otherWindow) should return true");
   2981  TIP.shareModifierStateOf(sharedTIP);
   2982  var independentTIP = createTIP();
   2983  const kModifierKeys = [
   2984    { key: "Alt",        code: "AltLeft",      isLockable: false },
   2985    { key: "Alt",        code: "AltRight",     isLockable: false },
   2986    { key: "AltGraph",   code: "AltRight",     isLockable: false },
   2987    { key: "CapsLock",   code: "CapsLock",     isLockable: true },
   2988    { key: "Control",    code: "ControlLeft",  isLockable: false },
   2989    { key: "Control",    code: "ControlRight", isLockable: false },
   2990    { key: "Fn",         code: "Fn",           isLockable: false },
   2991    { key: "FnLock",     code: "",             isLockable: true },
   2992    { key: "Meta",       code: "MetaLeft",     isLockable: false },
   2993    { key: "Meta",       code: "MetaRight",    isLockable: false },
   2994    { key: "NumLock",    code: "NumLock",      isLockable: true },
   2995    { key: "ScrollLock", code: "ScrollLock",   isLockable: true },
   2996    { key: "Shift",      code: "ShiftLeft",    isLockable: false },
   2997    { key: "Shift",      code: "ShiftRight",   isLockable: false },
   2998    { key: "Symbol",     code: "",             isLockable: false },
   2999    { key: "SymbolLock", code: "",             isLockable: true },
   3000  ];
   3001 
   3002  function checkModifiers(aTestDesc, aEvent, aType, aKey, aCode, aModifiers)
   3003  {
   3004    var desc = description + aTestDesc + ", type=\"" + aEvent.type + "\", key=\"" + aEvent.key + "\", code=\"" + aEvent.code + "\"";
   3005    is(aEvent.type, aType,
   3006       desc + ", .type value is wrong");
   3007    if (aEvent.type != aType) {
   3008      return;
   3009    }
   3010    is(aEvent.key, aKey,
   3011       desc + ", .key value is wrong");
   3012    is(aEvent.code, aCode,
   3013       desc + ", .code value is wrong");
   3014    is(aEvent.altKey, aModifiers.includes("Alt"),
   3015       desc + ", .altKey value is wrong");
   3016    is(aEvent.ctrlKey, aModifiers.includes("Control"),
   3017       desc + ", .ctrlKey value is wrong");
   3018    is(aEvent.metaKey, aModifiers.includes("Meta"),
   3019       desc + ", .metaKey value is wrong");
   3020    is(aEvent.shiftKey, aModifiers.includes("Shift"),
   3021       desc + ", .shiftKey value is wrong");
   3022    /* eslint-disable-next-line no-shadow */
   3023    for (var i = 0; i < kModifiers.length; i++) {
   3024      is(aEvent.getModifierState(kModifiers[i]), aModifiers.includes(kModifiers[i]),
   3025         desc + ", .getModifierState(\"" + kModifiers[i] + "\") returns wrong value");
   3026    }
   3027  }
   3028 
   3029  function checkAllTIPModifiers(aTestDesc, aModifiers)
   3030  {
   3031    /* eslint-disable-next-line no-shadow */
   3032    for (var i = 0; i < kModifiers.length; i++) {
   3033      is(TIP.getModifierState(kModifiers[i]), aModifiers.includes(kModifiers[i]),
   3034         aTestDesc + ", TIP.getModifierState(\"" + kModifiers[i] + "\") returns wrong value");
   3035      is(sharedTIP.getModifierState(kModifiers[i]), TIP.getModifierState(kModifiers[i]),
   3036         aTestDesc + ", sharedTIP.getModifierState(\"" + kModifiers[i] + "\") returns different value from TIP");
   3037      is(independentTIP.getModifierState(kModifiers[i]), false,
   3038         aTestDesc + ", independentTIP.getModifierState(\"" + kModifiers[i] + "\") should return false");
   3039    }
   3040  }
   3041 
   3042  // First, all modifiers must be false.
   3043  reset();
   3044  doPreventDefaults = [ "keypress" ];
   3045  TIP.keydown(keyA);
   3046  TIP.keyup(keyA);
   3047 
   3048  is(events.length, 3,
   3049     description + "TIP.keydown(keyA) and TIP.keyup(keyA) should cause keydown, keypress and keyup");
   3050  checkModifiers("Before dispatching modifier key events", events[0], "keydown",  "a", "KeyA", []);
   3051  checkModifiers("Before dispatching modifier key events", events[1], "keypress", "a", "KeyA", []);
   3052  checkModifiers("Before dispatching modifier key events", events[2], "keyup",    "a", "KeyA", []);
   3053 
   3054  // Test each modifier keydown/keyup causes activating/inactivating the modifier state.
   3055  for (var i = 0; i < kModifierKeys.length; i++) {
   3056    reset();
   3057    doPreventDefaults = [ "keypress" ];
   3058    var modKey = new KeyboardEvent("", { key: kModifierKeys[i].key, code: kModifierKeys[i].code });
   3059    let testDesc = "A modifier key \"" + kModifierKeys[i].key + "\" (\"" + kModifierKeys[i].code + "\") and a printable key";
   3060    if (!kModifierKeys[i].isLockable) {
   3061      TIP.keydown(modKey);
   3062      checkAllTIPModifiers(testDesc + ", \"" + kModifierKeys[i].key + "\" keydown", [ kModifierKeys[i].key ]);
   3063      TIP.keydown(keyA);
   3064      checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ kModifierKeys[i].key ]);
   3065      TIP.keyup(keyA);
   3066      checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ kModifierKeys[i].key ]);
   3067      TIP.keyup(modKey);
   3068      checkAllTIPModifiers(testDesc + ", \"" + kModifierKeys[i].key + "\" keyup", [ ]);
   3069      is(events.length, 5,
   3070         description + testDesc + " should cause 5 events");
   3071      checkModifiers(testDesc, events[0], "keydown",  kModifierKeys[i].key, kModifierKeys[i].code, [ kModifierKeys[i].key ]);
   3072      checkModifiers(testDesc, events[1], "keydown",  "a",                  "KeyA",                [ kModifierKeys[i].key ]);
   3073      checkModifiers(testDesc, events[2], "keypress", "a",                  "KeyA",                [ kModifierKeys[i].key ]);
   3074      checkModifiers(testDesc, events[3], "keyup",    "a",                  "KeyA",                [ kModifierKeys[i].key ]);
   3075      checkModifiers(testDesc, events[4], "keyup",    kModifierKeys[i].key, kModifierKeys[i].code, [ ]);
   3076 
   3077      // KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT shouldn't cause key events of modifier keys, but should modify the modifier state.
   3078      reset();
   3079      doPreventDefaults = [ "keypress" ];
   3080      testDesc = "A modifier key \"" + kModifierKeys[i].key + "\" (\"" + kModifierKeys[i].code + "\") with KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT and a printable key";
   3081      TIP.keydown(modKey, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
   3082      TIP.keydown(keyA);
   3083      TIP.keyup(keyA);
   3084      TIP.keyup(modKey, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
   3085      TIP.keydown(keyA);
   3086      TIP.keyup(keyA);
   3087      is(events.length, 6,
   3088         description + testDesc + " should cause 6 events");
   3089      checkModifiers(testDesc, events[0], "keydown",  "a", "KeyA", [ kModifierKeys[i].key ]);
   3090      checkModifiers(testDesc, events[1], "keypress", "a", "KeyA", [ kModifierKeys[i].key ]);
   3091      checkModifiers(testDesc, events[2], "keyup",    "a", "KeyA", [ kModifierKeys[i].key ]);
   3092      checkModifiers(testDesc, events[3], "keydown",  "a", "KeyA", [ ]);
   3093      checkModifiers(testDesc, events[4], "keypress", "a", "KeyA", [ ]);
   3094      checkModifiers(testDesc, events[5], "keyup",    "a", "KeyA", [ ]);
   3095    } else {
   3096      TIP.keydown(modKey);
   3097      checkAllTIPModifiers(testDesc + ", \"" + kModifierKeys[i].key + "\" first keydown", [ kModifierKeys[i].key ]);
   3098      TIP.keyup(modKey);
   3099      checkAllTIPModifiers(testDesc + ", \"" + kModifierKeys[i].key + "\" first keyup", [ kModifierKeys[i].key ]);
   3100      TIP.keydown(keyA);
   3101      checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ kModifierKeys[i].key ]);
   3102      TIP.keyup(keyA);
   3103      checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ kModifierKeys[i].key ]);
   3104      TIP.keydown(modKey);
   3105      checkAllTIPModifiers(testDesc + ", \"" + kModifierKeys[i].key + "\" second keydown", [ ]);
   3106      TIP.keyup(modKey);
   3107      checkAllTIPModifiers(testDesc + ", \"" + kModifierKeys[i].key + "\" second keyup", [ ]);
   3108      is(events.length, 7,
   3109         description + testDesc + " should cause 7 events");
   3110      checkModifiers(testDesc, events[0], "keydown",  kModifierKeys[i].key, kModifierKeys[i].code, [ kModifierKeys[i].key ]);
   3111      checkModifiers(testDesc, events[1], "keyup",    kModifierKeys[i].key, kModifierKeys[i].code, [ kModifierKeys[i].key ]);
   3112      checkModifiers(testDesc, events[2], "keydown",  "a",                  "KeyA",                [ kModifierKeys[i].key ]);
   3113      checkModifiers(testDesc, events[3], "keypress", "a",                  "KeyA",                [ kModifierKeys[i].key ]);
   3114      checkModifiers(testDesc, events[4], "keyup",    "a",                  "KeyA",                [ kModifierKeys[i].key ]);
   3115      checkModifiers(testDesc, events[5], "keydown",  kModifierKeys[i].key, kModifierKeys[i].code, [ ]);
   3116      checkModifiers(testDesc, events[6], "keyup",    kModifierKeys[i].key, kModifierKeys[i].code, [ ]);
   3117 
   3118      // KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT shouldn't cause key events of modifier keys, but should modify the modifier state.
   3119      reset();
   3120      doPreventDefaults = [ "keypress" ];
   3121      testDesc = "A modifier key \"" + kModifierKeys[i].key + "\" (\"" + kModifierKeys[i].code + "\") with KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT and a printable key";
   3122      TIP.keydown(modKey, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
   3123      TIP.keyup(modKey, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
   3124      TIP.keydown(keyA);
   3125      TIP.keyup(keyA);
   3126      TIP.keydown(modKey, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
   3127      TIP.keyup(modKey, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
   3128      TIP.keydown(keyA);
   3129      TIP.keyup(keyA);
   3130      is(events.length, 6,
   3131         description + testDesc + " should cause 6 events");
   3132      checkModifiers(testDesc, events[0], "keydown",  "a",                  "KeyA",                [ kModifierKeys[i].key ]);
   3133      checkModifiers(testDesc, events[1], "keypress", "a",                  "KeyA",                [ kModifierKeys[i].key ]);
   3134      checkModifiers(testDesc, events[2], "keyup",    "a",                  "KeyA",                [ kModifierKeys[i].key ]);
   3135      checkModifiers(testDesc, events[3], "keydown",  "a",                  "KeyA",                [ ]);
   3136      checkModifiers(testDesc, events[4], "keypress", "a",                  "KeyA",                [ ]);
   3137      checkModifiers(testDesc, events[5], "keyup",    "a",                  "KeyA",                [ ]);
   3138    }
   3139  }
   3140 
   3141  // Modifier state should be inactivated only when all pressed modifiers are released
   3142  var shiftLeft = new KeyboardEvent("", { key: "Shift", code: "ShiftLeft" });
   3143  var shiftRight = new KeyboardEvent("", { key: "Shift", code: "ShiftRight" });
   3144  var shiftVirtual = new KeyboardEvent("", { key: "Shift", code: "" });
   3145  var altGrVirtual = new KeyboardEvent("", { key: "AltGraph", code: "" });
   3146  var ctrlVirtual = new KeyboardEvent("", { key: "Control", code: "" });
   3147 
   3148  let testDesc = "ShiftLeft press -> ShiftRight press -> ShiftRight release -> ShiftLeft release";
   3149  reset();
   3150  doPreventDefaults = [ "keypress" ];
   3151  TIP.keydown(shiftLeft);
   3152  checkAllTIPModifiers(testDesc + ", Left-Shift keydown", [ "Shift" ]);
   3153  TIP.keydown(shiftRight);
   3154  checkAllTIPModifiers(testDesc + ", Right-Shift keydown", [ "Shift" ]);
   3155  TIP.keydown(keyA);
   3156  checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift" ]);
   3157  TIP.keyup(keyA);
   3158  checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift" ]);
   3159  TIP.keyup(shiftRight);
   3160  checkAllTIPModifiers(testDesc + ", Right-Shift keyup", [ "Shift" ]);
   3161  TIP.keydown(keyA);
   3162  checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Right-Shift keyup)", [ "Shift" ]);
   3163  TIP.keyup(keyA);
   3164  checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Right-Shift keyup)", [ "Shift" ]);
   3165  TIP.keyup(shiftLeft);
   3166  checkAllTIPModifiers(testDesc + ", Left-Shift keyup", [ ]);
   3167 
   3168  is(events.length, 10,
   3169     description + testDesc + " should cause 10 events");
   3170  checkModifiers(testDesc, events[0], "keydown",  "Shift", "ShiftLeft",  [ "Shift" ]);
   3171  checkModifiers(testDesc, events[1], "keydown",  "Shift", "ShiftRight", [ "Shift" ]);
   3172  checkModifiers(testDesc, events[2], "keydown",  "a",     "KeyA",       [ "Shift" ]);
   3173  checkModifiers(testDesc, events[3], "keypress", "a",     "KeyA",       [ "Shift" ]);
   3174  checkModifiers(testDesc, events[4], "keyup",    "a",     "KeyA",       [ "Shift" ]);
   3175  checkModifiers(testDesc, events[5], "keyup",    "Shift", "ShiftRight", [ "Shift" ]);
   3176  checkModifiers(testDesc, events[6], "keydown",  "a",     "KeyA",       [ "Shift" ]);
   3177  checkModifiers(testDesc, events[7], "keypress", "a",     "KeyA",       [ "Shift" ]);
   3178  checkModifiers(testDesc, events[8], "keyup",    "a",     "KeyA",       [ "Shift" ]);
   3179  checkModifiers(testDesc, events[9], "keyup",    "Shift", "ShiftLeft",  [ ]);
   3180 
   3181  testDesc = "ShiftLeft press -> ShiftRight press -> ShiftLeft release -> ShiftRight release";
   3182  reset();
   3183  doPreventDefaults = [ "keypress" ];
   3184  TIP.keydown(shiftLeft);
   3185  checkAllTIPModifiers(testDesc + ", Left-Shift keydown", [ "Shift" ]);
   3186  TIP.keydown(shiftRight);
   3187  checkAllTIPModifiers(testDesc + ", Right-Shift keydown", [ "Shift" ]);
   3188  TIP.keydown(keyA);
   3189  checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift" ]);
   3190  TIP.keyup(keyA);
   3191  checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift" ]);
   3192  TIP.keyup(shiftLeft);
   3193  checkAllTIPModifiers(testDesc + ", Left-Shift keyup", [ "Shift" ]);
   3194  TIP.keydown(keyA);
   3195  checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Left-Shift keyup)", [ "Shift" ]);
   3196  TIP.keyup(keyA);
   3197  checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Left-Shift keyup)", [ "Shift" ]);
   3198  TIP.keyup(shiftRight);
   3199  checkAllTIPModifiers(testDesc + ", Right-Shift keyup", [ ]);
   3200 
   3201  is(events.length, 10,
   3202     description + testDesc + " should cause 10 events");
   3203  checkModifiers(testDesc, events[0], "keydown",  "Shift", "ShiftLeft",  [ "Shift" ]);
   3204  checkModifiers(testDesc, events[1], "keydown",  "Shift", "ShiftRight", [ "Shift" ]);
   3205  checkModifiers(testDesc, events[2], "keydown",  "a",     "KeyA",       [ "Shift" ]);
   3206  checkModifiers(testDesc, events[3], "keypress", "a",     "KeyA",       [ "Shift" ]);
   3207  checkModifiers(testDesc, events[4], "keyup",    "a",     "KeyA",       [ "Shift" ]);
   3208  checkModifiers(testDesc, events[5], "keyup",    "Shift", "ShiftLeft",  [ "Shift" ]);
   3209  checkModifiers(testDesc, events[6], "keydown",  "a",     "KeyA",       [ "Shift" ]);
   3210  checkModifiers(testDesc, events[7], "keypress", "a",     "KeyA",       [ "Shift" ]);
   3211  checkModifiers(testDesc, events[8], "keyup",    "a",     "KeyA",       [ "Shift" ]);
   3212  checkModifiers(testDesc, events[9], "keyup",    "Shift", "ShiftRight",  [ ]);
   3213 
   3214  testDesc = "ShiftLeft press -> virtual Shift press -> virtual Shift release -> ShiftLeft release";
   3215  reset();
   3216  doPreventDefaults = [ "keypress" ];
   3217  TIP.keydown(shiftLeft);
   3218  checkAllTIPModifiers(testDesc + ", Left-Shift keydown", [ "Shift" ]);
   3219  TIP.keydown(shiftVirtual);
   3220  checkAllTIPModifiers(testDesc + ", Virtual-Shift keydown", [ "Shift" ]);
   3221  TIP.keydown(keyA);
   3222  checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift" ]);
   3223  TIP.keyup(keyA);
   3224  checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift" ]);
   3225  TIP.keyup(shiftVirtual);
   3226  checkAllTIPModifiers(testDesc + ", Virtual-Shift keyup", [ "Shift" ]);
   3227  TIP.keydown(keyA);
   3228  checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Virtual-Shift keyup)", [ "Shift" ]);
   3229  TIP.keyup(keyA);
   3230  checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Virtual-Shift keyup)", [ "Shift" ]);
   3231  TIP.keyup(shiftLeft);
   3232  checkAllTIPModifiers(testDesc + ", Left-Shift keyup", [ ]);
   3233 
   3234  is(events.length, 10,
   3235     description + testDesc + " should cause 10 events");
   3236  checkModifiers(testDesc, events[0], "keydown",  "Shift", "ShiftLeft",  [ "Shift" ]);
   3237  checkModifiers(testDesc, events[1], "keydown",  "Shift", "",           [ "Shift" ]);
   3238  checkModifiers(testDesc, events[2], "keydown",  "a",     "KeyA",       [ "Shift" ]);
   3239  checkModifiers(testDesc, events[3], "keypress", "a",     "KeyA",       [ "Shift" ]);
   3240  checkModifiers(testDesc, events[4], "keyup",    "a",     "KeyA",       [ "Shift" ]);
   3241  checkModifiers(testDesc, events[5], "keyup",    "Shift", "",           [ "Shift" ]);
   3242  checkModifiers(testDesc, events[6], "keydown",  "a",     "KeyA",       [ "Shift" ]);
   3243  checkModifiers(testDesc, events[7], "keypress", "a",     "KeyA",       [ "Shift" ]);
   3244  checkModifiers(testDesc, events[8], "keyup",    "a",     "KeyA",       [ "Shift" ]);
   3245  checkModifiers(testDesc, events[9], "keyup",    "Shift", "ShiftLeft",  [ ]);
   3246 
   3247  testDesc = "virtual Shift press -> ShiftRight press -> ShiftRight release -> virtual Shift release";
   3248  reset();
   3249  doPreventDefaults = [ "keypress" ];
   3250  TIP.keydown(shiftVirtual);
   3251  checkAllTIPModifiers(testDesc + ", Virtual-Shift keydown", [ "Shift" ]);
   3252  TIP.keydown(shiftRight);
   3253  checkAllTIPModifiers(testDesc + ", Right-Shift keydown", [ "Shift" ]);
   3254  TIP.keydown(keyA);
   3255  checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift" ]);
   3256  TIP.keyup(keyA);
   3257  checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift" ]);
   3258  TIP.keyup(shiftRight);
   3259  checkAllTIPModifiers(testDesc + ", Right-Shift keyup", [ "Shift" ]);
   3260  TIP.keydown(keyA);
   3261  checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Right-Shift keyup)", [ "Shift" ]);
   3262  TIP.keyup(keyA);
   3263  checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Right-Shift keyup)", [ "Shift" ]);
   3264  TIP.keyup(shiftVirtual);
   3265  checkAllTIPModifiers(testDesc + ", Virtual-Shift keyup", [ ]);
   3266 
   3267  is(events.length, 10,
   3268     description + testDesc + " should cause 10 events");
   3269  checkModifiers(testDesc, events[0], "keydown",  "Shift", "",           [ "Shift" ]);
   3270  checkModifiers(testDesc, events[1], "keydown",  "Shift", "ShiftRight", [ "Shift" ]);
   3271  checkModifiers(testDesc, events[2], "keydown",  "a",     "KeyA",       [ "Shift" ]);
   3272  checkModifiers(testDesc, events[3], "keypress", "a",     "KeyA",       [ "Shift" ]);
   3273  checkModifiers(testDesc, events[4], "keyup",    "a",     "KeyA",       [ "Shift" ]);
   3274  checkModifiers(testDesc, events[5], "keyup",    "Shift", "ShiftRight", [ "Shift" ]);
   3275  checkModifiers(testDesc, events[6], "keydown",  "a",     "KeyA",       [ "Shift" ]);
   3276  checkModifiers(testDesc, events[7], "keypress", "a",     "KeyA",       [ "Shift" ]);
   3277  checkModifiers(testDesc, events[8], "keyup",    "a",     "KeyA",       [ "Shift" ]);
   3278  checkModifiers(testDesc, events[9], "keyup",    "Shift", "",           [ ]);
   3279 
   3280  testDesc = "ShiftLeft press -> ShiftRight press -> ShiftRight release -> ShiftRight release -> ShiftLeft release";
   3281  reset();
   3282  doPreventDefaults = [ "keypress" ];
   3283  TIP.keydown(shiftLeft);
   3284  checkAllTIPModifiers(testDesc + ", Left-Shift keydown", [ "Shift" ]);
   3285  TIP.keydown(shiftRight);
   3286  checkAllTIPModifiers(testDesc + ", Right-Shift keydown", [ "Shift" ]);
   3287  TIP.keydown(keyA);
   3288  checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift" ]);
   3289  TIP.keyup(keyA);
   3290  checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift" ]);
   3291  TIP.keyup(shiftRight);
   3292  checkAllTIPModifiers(testDesc + ", Right-Shift keyup", [ "Shift" ]);
   3293  TIP.keydown(keyA);
   3294  checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Virtual-Shift keyup)", [ "Shift" ]);
   3295  TIP.keyup(keyA);
   3296  checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Virtual-Shift keyup)", [ "Shift" ]);
   3297  TIP.keyup(shiftRight);
   3298  checkAllTIPModifiers(testDesc + ", Right-Shift keyup again", [ "Shift" ]);
   3299  TIP.keydown(keyA);
   3300  checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Virtual-Shift keyup again)", [ "Shift" ]);
   3301  TIP.keyup(keyA);
   3302  checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Virtual-Shift keyup again)", [ "Shift" ]);
   3303  TIP.keyup(shiftLeft);
   3304  checkAllTIPModifiers(testDesc + ", Left-Shift keyup", [ ]);
   3305 
   3306  is(events.length, 14,
   3307     description + testDesc + " should cause 14 events");
   3308  checkModifiers(testDesc, events[0],  "keydown",  "Shift", "ShiftLeft",  [ "Shift" ]);
   3309  checkModifiers(testDesc, events[1],  "keydown",  "Shift", "ShiftRight", [ "Shift" ]);
   3310  checkModifiers(testDesc, events[2],  "keydown",  "a",     "KeyA",       [ "Shift" ]);
   3311  checkModifiers(testDesc, events[3],  "keypress", "a",     "KeyA",       [ "Shift" ]);
   3312  checkModifiers(testDesc, events[4],  "keyup",    "a",     "KeyA",       [ "Shift" ]);
   3313  checkModifiers(testDesc, events[5],  "keyup",    "Shift", "ShiftRight", [ "Shift" ]);
   3314  checkModifiers(testDesc, events[6],  "keydown",  "a",     "KeyA",       [ "Shift" ]);
   3315  checkModifiers(testDesc, events[7],  "keypress", "a",     "KeyA",       [ "Shift" ]);
   3316  checkModifiers(testDesc, events[8],  "keyup",    "a",     "KeyA",       [ "Shift" ]);
   3317  checkModifiers(testDesc, events[9],  "keyup",    "Shift", "ShiftRight", [ "Shift" ]);
   3318  checkModifiers(testDesc, events[10], "keydown",  "a",     "KeyA",       [ "Shift" ]);
   3319  checkModifiers(testDesc, events[11], "keypress", "a",     "KeyA",       [ "Shift" ]);
   3320  checkModifiers(testDesc, events[12], "keyup",    "a",     "KeyA",       [ "Shift" ]);
   3321  checkModifiers(testDesc, events[13], "keyup",    "Shift", "ShiftLeft",  [ ]);
   3322 
   3323  testDesc = "ShiftLeft press -> ShiftLeft press -> ShiftLeft release -> ShiftLeft release";
   3324  reset();
   3325  doPreventDefaults = [ "keypress" ];
   3326  TIP.keydown(shiftLeft);
   3327  checkAllTIPModifiers(testDesc + ", Left-Shift keydown", [ "Shift" ]);
   3328  TIP.keydown(shiftLeft);
   3329  checkAllTIPModifiers(testDesc + ", Left-Shift keydown again", [ "Shift" ]);
   3330  TIP.keydown(keyA);
   3331  checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift" ]);
   3332  TIP.keyup(keyA);
   3333  checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift" ]);
   3334  TIP.keyup(shiftLeft);
   3335  checkAllTIPModifiers(testDesc + ", Left-Shift keyup", [ ]);
   3336  TIP.keydown(keyA);
   3337  checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Left-Shift keyup)", [ ]);
   3338  TIP.keyup(keyA);
   3339  checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Left-Shift keyup)", [ ]);
   3340  TIP.keyup(shiftLeft);
   3341  checkAllTIPModifiers(testDesc + ", Left-Shift keyup again", [ ]);
   3342  TIP.keydown(keyA);
   3343  checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Left-Shift keyup again)", [ ]);
   3344  TIP.keyup(keyA);
   3345  checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Left-Shift keyup again)", [ ]);
   3346 
   3347  is(events.length, 13,
   3348     description + testDesc + " should cause 13 events");
   3349  checkModifiers(testDesc, events[0],  "keydown",  "Shift", "ShiftLeft",  [ "Shift" ]);
   3350  checkModifiers(testDesc, events[1],  "keydown",  "Shift", "ShiftLeft",  [ "Shift" ]);
   3351  checkModifiers(testDesc, events[2],  "keydown",  "a",     "KeyA",       [ "Shift" ]);
   3352  checkModifiers(testDesc, events[3],  "keypress", "a",     "KeyA",       [ "Shift" ]);
   3353  checkModifiers(testDesc, events[4],  "keyup",    "a",     "KeyA",       [ "Shift" ]);
   3354  checkModifiers(testDesc, events[5],  "keyup",    "Shift", "ShiftLeft",  [ ]);
   3355  checkModifiers(testDesc, events[6],  "keydown",  "a",     "KeyA",       [ ]);
   3356  checkModifiers(testDesc, events[7],  "keypress", "a",     "KeyA",       [ ]);
   3357  checkModifiers(testDesc, events[8],  "keyup",    "a",     "KeyA",       [ ]);
   3358  checkModifiers(testDesc, events[9],  "keyup",    "Shift", "ShiftLeft",  [ ]);
   3359  checkModifiers(testDesc, events[10], "keydown",  "a",     "KeyA",       [ ]);
   3360  checkModifiers(testDesc, events[11], "keypress", "a",     "KeyA",       [ ]);
   3361  checkModifiers(testDesc, events[12], "keyup",    "a",     "KeyA",       [ ]);
   3362 
   3363  testDesc = "virtual Shift press -> virtual AltGraph press -> virtual AltGraph release -> virtual Shift release";
   3364  reset();
   3365  doPreventDefaults = [ "keypress" ];
   3366  TIP.keydown(shiftVirtual);
   3367  checkAllTIPModifiers(testDesc + ", Virtual-Shift keydown", [ "Shift" ]);
   3368  TIP.keydown(altGrVirtual);
   3369  checkAllTIPModifiers(testDesc + ", Virtual-AltGraph keydown", [ "Shift", "AltGraph" ]);
   3370  TIP.keydown(keyA);
   3371  checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift", "AltGraph" ]);
   3372  TIP.keyup(keyA);
   3373  checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift", "AltGraph" ]);
   3374  TIP.keyup(altGrVirtual);
   3375  checkAllTIPModifiers(testDesc + ", Virtual-AltGraph keyup", [ "Shift" ]);
   3376  TIP.keydown(keyA);
   3377  checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Virtual-AltGraph keyup)", [ "Shift" ]);
   3378  TIP.keyup(keyA);
   3379  checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Virtual-AltGraph keyup)", [ "Shift" ]);
   3380  TIP.keyup(shiftVirtual);
   3381  checkAllTIPModifiers(testDesc + ", Virtual-Shift keyup", [ ]);
   3382 
   3383  is(events.length, 10,
   3384     description + testDesc + " should cause 10 events");
   3385  checkModifiers(testDesc, events[0], "keydown",  "Shift",    "",     [ "Shift" ]);
   3386  checkModifiers(testDesc, events[1], "keydown",  "AltGraph", "",     [ "Shift", "AltGraph" ]);
   3387  checkModifiers(testDesc, events[2], "keydown",  "a",        "KeyA", [ "Shift", "AltGraph" ]);
   3388  checkModifiers(testDesc, events[3], "keypress", "a",        "KeyA", [ "Shift", "AltGraph" ]);
   3389  checkModifiers(testDesc, events[4], "keyup",    "a",        "KeyA", [ "Shift", "AltGraph" ]);
   3390  checkModifiers(testDesc, events[5], "keyup",    "AltGraph", "",     [ "Shift" ]);
   3391  checkModifiers(testDesc, events[6], "keydown",  "a",        "KeyA", [ "Shift" ]);
   3392  checkModifiers(testDesc, events[7], "keypress", "a",        "KeyA", [ "Shift" ]);
   3393  checkModifiers(testDesc, events[8], "keyup",    "a",        "KeyA", [ "Shift" ]);
   3394  checkModifiers(testDesc, events[9], "keyup",    "Shift",    "",     [ ]);
   3395 
   3396  testDesc = "virtual Shift press -> virtual AltGraph press -> virtual Shift release -> virtual AltGr release";
   3397  reset();
   3398  doPreventDefaults = [ "keypress" ];
   3399  TIP.keydown(shiftVirtual);
   3400  checkAllTIPModifiers(testDesc + ", Virtual-Shift keydown", [ "Shift" ]);
   3401  TIP.keydown(altGrVirtual);
   3402  checkAllTIPModifiers(testDesc + ", Virtual-AltGraph keydown", [ "Shift", "AltGraph" ]);
   3403  TIP.keydown(keyA);
   3404  checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift", "AltGraph" ]);
   3405  TIP.keyup(keyA);
   3406  checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift", "AltGraph" ]);
   3407  TIP.keyup(shiftVirtual);
   3408  checkAllTIPModifiers(testDesc + ", Virtual-Shift keyup", [ "AltGraph" ]);
   3409  TIP.keydown(keyA);
   3410  checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Virtual-Shift keyup)", [ "AltGraph" ]);
   3411  TIP.keyup(keyA);
   3412  checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Virtual-Shift keyup)", [ "AltGraph" ]);
   3413  TIP.keyup(altGrVirtual);
   3414  checkAllTIPModifiers(testDesc + ", Virtual-AltGraph keyup", [ ]);
   3415 
   3416  is(events.length, 10,
   3417     description + testDesc + " should cause 10 events");
   3418  checkModifiers(testDesc, events[0], "keydown",  "Shift",    "",     [ "Shift" ]);
   3419  checkModifiers(testDesc, events[1], "keydown",  "AltGraph", "",     [ "Shift", "AltGraph" ]);
   3420  checkModifiers(testDesc, events[2], "keydown",  "a",        "KeyA", [ "Shift", "AltGraph" ]);
   3421  checkModifiers(testDesc, events[3], "keypress", "a",        "KeyA", [ "Shift", "AltGraph" ]);
   3422  checkModifiers(testDesc, events[4], "keyup",    "a",        "KeyA", [ "Shift", "AltGraph" ]);
   3423  checkModifiers(testDesc, events[5], "keyup",    "Shift",    "",     [ "AltGraph" ]);
   3424  checkModifiers(testDesc, events[6], "keydown",  "a",        "KeyA", [ "AltGraph" ]);
   3425  checkModifiers(testDesc, events[7], "keypress", "a",        "KeyA", [ "AltGraph" ]);
   3426  checkModifiers(testDesc, events[8], "keyup",    "a",        "KeyA", [ "AltGraph" ]);
   3427  checkModifiers(testDesc, events[9], "keyup",    "AltGraph", "",     [ ]);
   3428 
   3429  // shareModifierStateOf(null) should cause resetting the modifier state
   3430  function checkTIPModifiers(aTestDesc, aTIP, aModifiers)
   3431  {
   3432    /* eslint-disable-next-line no-shadow */
   3433    for (var i = 0; i < kModifiers.length; i++) {
   3434      is(aTIP.getModifierState(kModifiers[i]), aModifiers.includes(kModifiers[i]),
   3435         description + aTestDesc + ", aTIP.getModifierState(\"" + kModifiers[i] + "\") returns wrong value");
   3436    }
   3437  }
   3438  TIP.keydown(shiftVirtual);
   3439  TIP.keydown(altGrVirtual);
   3440  sharedTIP.shareModifierStateOf(null);
   3441  checkTIPModifiers("sharedTIP.sharedModifierStateOf(null) shouldn't cause TIP's modifiers reset", TIP, [ "Shift", "AltGraph" ]);
   3442  checkTIPModifiers("sharedTIP.sharedModifierStateOf(null) should cause sharedTIP modifiers reset", sharedTIP, [ ]);
   3443 
   3444  // sharedTIP.shareModifierStateOf(null) should be unlinked from TIP.
   3445  TIP.keydown(ctrlVirtual);
   3446  checkTIPModifiers("TIP.keydown(ctrlVirtual) should cause TIP's modifiers set", TIP, [ "Shift", "AltGraph", "Control" ]);
   3447  checkTIPModifiers("TIP.keydown(ctrlVirtual) shouldn't cause sharedTIP modifiers set", sharedTIP, [ ]);
   3448 
   3449  // beginInputTransactionForTests() shouldn't cause modifier state reset.
   3450  ok(TIP.beginInputTransactionForTests(otherWindow),
   3451     description + "TIP.beginInputTransactionForTests(otherWindow) should return true");
   3452  checkTIPModifiers("TIP.beginInputTransactionForTests(otherWindow) shouldn't cause TIP's modifiers set", TIP, [ "Shift", "AltGraph", "Control" ]);
   3453  TIP.keyup(shiftLeft);
   3454  TIP.keyup(altGrVirtual);
   3455  TIP.keyup(ctrlVirtual);
   3456  checkTIPModifiers("TIP should keep modifier's physical key state", TIP, [ "Shift" ]);
   3457  ok(TIP.beginInputTransactionForTests(window),
   3458     description + "TIP.beginInputTransactionForTests(window) should return true");
   3459  checkTIPModifiers("TIP.beginInputTransactionForTests(window) shouldn't cause TIP's modifiers set", TIP, [ "Shift" ]);
   3460  TIP.keyup(shiftVirtual);
   3461  checkTIPModifiers("TIP should keep modifier's physical key state", TIP, [ ]);
   3462 
   3463  window.removeEventListener("keydown", handler);
   3464  window.removeEventListener("keypress", handler);
   3465  window.removeEventListener("keyup", handler);
   3466 }
   3467 
   3468 function runInsertTextWithKeyPressTests() {
   3469  const description = "runInsertTextWithKeyPressTests(): ";
   3470 
   3471  var TIP = createTIP();
   3472  ok(TIP.beginInputTransactionForTests(window),
   3473     description + "TIP.beginInputTransactionForTests() should succeed");
   3474 
   3475  input.value = "";
   3476  input.focus();
   3477 
   3478  let events = [];
   3479  function handler(aEvent) {
   3480    events.push({
   3481      type: aEvent.type,
   3482      key: aEvent.key,
   3483      code: aEvent.code,
   3484      shiftKey: aEvent.shiftKey,
   3485      altKey: aEvent.altKey,
   3486      ctrlKey: aEvent.ctrlKey,
   3487      metaKey: aEvent.metaKey,
   3488    });
   3489  }
   3490  function stringifyEvents(aEvents) {
   3491    if (!aEvents.length) {
   3492      return "[]";
   3493    }
   3494    function stringifyEvent(aEvent) {
   3495      return `{ type: "${aEvent.type}", key: "${aEvent.key}", code: ${aEvent.code}, shiftKey: ${
   3496          aEvent.shiftKey
   3497        }, altKey: ${aEvent.altKey}, ctrlKey: ${aEvent.ctrlKey}, metaKey: ${aEvent.metaKey}}`;
   3498    }
   3499    let result = "";
   3500    for (const event of aEvents) {
   3501      if (result == "") {
   3502        result = "[ ";
   3503      } else {
   3504        result += ", ";
   3505      }
   3506      result += stringifyEvent(event);
   3507    }
   3508    return result + " ]";
   3509  }
   3510  window.addEventListener("keydown", handler);
   3511  window.addEventListener("keypress", handler);
   3512  window.addEventListener("keyup", handler);
   3513 
   3514  events = [];
   3515  input.value = "";
   3516  TIP.insertTextWithKeyPress("X");
   3517  is(
   3518    input.value,
   3519    "X",
   3520    `${description}insertTextWithKeyPress without optional args should cause inserting the string`
   3521  );
   3522  is(
   3523    stringifyEvents(events),
   3524    stringifyEvents([
   3525      { type: "keypress", key: "X", code: "", shiftKey: false, altKey: false, ctrlKey: false, metaKey: false },
   3526    ]),
   3527    `${description}insertTextWithPress without optional args should cause only a "keypress" event`
   3528  );
   3529 
   3530  events = [];
   3531  input.value = "";
   3532  TIP.insertTextWithKeyPress("Y", new KeyboardEvent("keydown", {key: "Alt", code: "AltLeft"}));
   3533  is(
   3534    input.value,
   3535    "Y",
   3536    `${description}insertTextWithKeyPress with Alt keydown event should cause inserting the string`
   3537  );
   3538  // The key value should be the inserted string and the code value should be same as the source event's.
   3539  is(
   3540    stringifyEvents(events),
   3541    stringifyEvents([
   3542      { type: "keypress", key: "Y", code: "AltLeft", shiftKey: false, altKey: false, ctrlKey: false, metaKey: false },
   3543    ]),
   3544    `${description}insertTextWithKeyPress with Alt keydown event should cause only a "keypress" event whose code is "AltLeft"`
   3545  );
   3546 
   3547  events = [];
   3548  input.value = "";
   3549  TIP.insertTextWithKeyPress("Z", new KeyboardEvent("keydown", {key: "Alt", code: "AltLeft", altKey: true}));
   3550  is(
   3551    input.value,
   3552    "Z",
   3553    `${description}insertTextWithKeyPress with Alt keydown whose altKey is true should cause inserting the string`
   3554  );
   3555  // TIP should use its internal modifier state instead of specified modifier state.
   3556  is(
   3557    stringifyEvents(events),
   3558    stringifyEvents([
   3559      { type: "keypress", key: "Z", code: "AltLeft", shiftKey: false, altKey: false, ctrlKey: false, metaKey: false },
   3560    ]),
   3561    `${description}insertTextWithKeyPress with Alt keydown whose altKey is true should cause only a "keypress" event whose altKey is false"`
   3562  );
   3563 
   3564  TIP.keydown(new KeyboardEvent("keydown", { key: "Alt" }));
   3565  events = [];
   3566  input.value = "";
   3567  TIP.insertTextWithKeyPress("X", new KeyboardEvent("keydown", {key: "Shift", code: "ShiftLeft", shiftKey: true}));
   3568  is(
   3569    input.value,
   3570    kIsMac ? "X" : "",
   3571    `${description}insertTextWithKeyPress after Alt keydown should${kIsMac ? "" : " not"} cause inserting the string`
   3572  );
   3573  is(
   3574    stringifyEvents(events),
   3575    stringifyEvents([
   3576      { type: "keypress", key: "X", code: "ShiftLeft", shiftKey: false, altKey: true, ctrlKey: false, metaKey: false },
   3577    ]),
   3578    `${description}insertTextWithPress after Alt keydown should cause only a "keypress" event whose altKey is true"`
   3579  );
   3580  TIP.keyup(new KeyboardEvent("keyup ", { key: "Alt" }));
   3581 
   3582  window.removeEventListener("keydown", handler);
   3583  window.removeEventListener("keypress", handler);
   3584  window.removeEventListener("keyup", handler);
   3585 }
   3586 
   3587 function runErrorTests()
   3588 {
   3589  var description = "runErrorTests(): ";
   3590 
   3591  var TIP = createTIP();
   3592  ok(TIP.beginInputTransactionForTests(window),
   3593     description + "TIP.beginInputTransactionForTests() should succeed");
   3594 
   3595  input.value = "";
   3596  input.focus();
   3597 
   3598  // startComposition() should throw an exception if there is already a composition
   3599  TIP.startComposition();
   3600  try {
   3601    TIP.startComposition();
   3602    ok(false,
   3603       description + "startComposition() should fail if it was already called");
   3604  } catch (e) {
   3605    ok(e.message.includes("NS_ERROR_FAILURE"),
   3606       description + "startComposition() should cause NS_ERROR_FAILURE if there is already composition");
   3607  } finally {
   3608    TIP.cancelComposition();
   3609  }
   3610 
   3611  // cancelComposition() should throw an exception if there is no composition
   3612  try {
   3613    TIP.cancelComposition();
   3614    ok(false,
   3615       description + "cancelComposition() should fail if there is no composition");
   3616  } catch (e) {
   3617    ok(e.message.includes("NS_ERROR_FAILURE"),
   3618       description + "cancelComposition() should cause NS_ERROR_FAILURE if there is no composition");
   3619  }
   3620 
   3621  // commitComposition() without commit string should throw an exception if there is no composition
   3622  try {
   3623    TIP.commitComposition();
   3624    ok(false,
   3625       description + "commitComposition() should fail if there is no composition");
   3626  } catch (e) {
   3627    ok(e.message.includes("NS_ERROR_FAILURE"),
   3628       description + "commitComposition() should cause NS_ERROR_FAILURE if there is no composition");
   3629  }
   3630 
   3631  // commitCompositionWith("") should throw an exception if there is no composition
   3632  try {
   3633    TIP.commitCompositionWith("");
   3634    ok(false,
   3635       description + "commitCompositionWith(\"\") should fail if there is no composition");
   3636  } catch (e) {
   3637    ok(e.message.includes("NS_ERROR_FAILURE"),
   3638       description + "commitCompositionWith(\"\") should cause NS_ERROR_FAILURE if there is no composition");
   3639  }
   3640 
   3641  // Pending composition string should allow to flush without clause information (for compatibility)
   3642  try {
   3643    TIP.setPendingCompositionString("foo");
   3644    TIP.flushPendingComposition();
   3645    ok(true,
   3646       description + "flushPendingComposition() should succeed even if appendClauseToPendingComposition() has never been called");
   3647    TIP.cancelComposition();
   3648  } catch (e) {
   3649    ok(false,
   3650       description + "flushPendingComposition() shouldn't cause an exception even if appendClauseToPendingComposition() has never been called");
   3651  }
   3652 
   3653  // Pending composition string must be filled by clause information
   3654  try {
   3655    TIP.setPendingCompositionString("foo");
   3656    TIP.appendClauseToPendingComposition(2, TIP.ATTR_RAW_CLAUSE);
   3657    TIP.flushPendingComposition();
   3658    ok(false,
   3659       description + "flushPendingComposition() should fail if appendClauseToPendingComposition() doesn't fill all composition string");
   3660    TIP.cancelComposition();
   3661  } catch (e) {
   3662    ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
   3663       description + "flushPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if appendClauseToPendingComposition() doesn't fill all composition string");
   3664  }
   3665 
   3666  // Pending composition string must not be shorter than appended clause length
   3667  try {
   3668    TIP.setPendingCompositionString("foo");
   3669    TIP.appendClauseToPendingComposition(4, TIP.ATTR_RAW_CLAUSE);
   3670    TIP.flushPendingComposition();
   3671    ok(false,
   3672       description + "flushPendingComposition() should fail if appendClauseToPendingComposition() appends longer clause information");
   3673    TIP.cancelComposition();
   3674  } catch (e) {
   3675    ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
   3676       description + "flushPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if appendClauseToPendingComposition() appends longer clause information");
   3677  }
   3678 
   3679  // Pending composition must not have clause information with empty string
   3680  try {
   3681    TIP.appendClauseToPendingComposition(1, TIP.ATTR_RAW_CLAUSE);
   3682    TIP.flushPendingComposition();
   3683    ok(false,
   3684       description + "flushPendingComposition() should fail if there is a clause with empty string");
   3685    TIP.cancelComposition();
   3686  } catch (e) {
   3687    ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
   3688       description + "flushPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if there is a clause with empty string");
   3689  }
   3690 
   3691  // Appending a clause whose length is 0 should cause an exception
   3692  try {
   3693    TIP.appendClauseToPendingComposition(0, TIP.ATTR_RAW_CLAUSE);
   3694    ok(false,
   3695       description + "appendClauseToPendingComposition() should fail if the length is 0");
   3696    TIP.flushPendingComposition();
   3697    TIP.cancelComposition();
   3698  } catch (e) {
   3699    ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
   3700       description + "appendClauseToPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if the length is 0");
   3701  }
   3702 
   3703  // Appending a clause whose attribute is invalid should cause an exception
   3704  try {
   3705    TIP.setPendingCompositionString("foo");
   3706    TIP.appendClauseToPendingComposition(3, 0);
   3707    ok(false,
   3708       description + "appendClauseToPendingComposition() should fail if the attribute is invalid");
   3709    TIP.flushPendingComposition();
   3710    TIP.cancelComposition();
   3711  } catch (e) {
   3712    ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
   3713       description + "appendClauseToPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if the attribute is invalid");
   3714  }
   3715 
   3716  // Setting caret position outside of composition string should cause an exception
   3717  try {
   3718    TIP.setPendingCompositionString("foo");
   3719    TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   3720    TIP.setCaretInPendingComposition(4);
   3721    TIP.flushPendingComposition();
   3722    ok(false,
   3723       description + "flushPendingComposition() should fail if caret position is out of composition string");
   3724    TIP.cancelComposition();
   3725  } catch (e) {
   3726    ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
   3727       description + "flushPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if caret position is out of composition string");
   3728  }
   3729 
   3730  // Calling keydown() with a KeyboardEvent initialized with invalid code value should cause an exception.
   3731  input.value = "";
   3732  try {
   3733    let keyInvalidCode = new KeyboardEvent("", { key: "f", code: "InvalidCodeValue", keyCode: KeyboardEvent.DOM_VK_F });
   3734    TIP.keydown(keyInvalidCode);
   3735    ok(false,
   3736       description + "TIP.keydown(keyInvalidCode) should cause throwing an exception because its code value is not registered");
   3737  } catch (e) {
   3738    ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
   3739       description + "TIP.keydown(keyInvalidCode) should cause throwing an exception including NS_ERROR_ILLEGAL_VALUE");
   3740  } finally {
   3741    is(input.value, "",
   3742       description + "The input element should not be modified");
   3743  }
   3744 
   3745  // Calling keyup() with a KeyboardEvent initialized with invalid code value should cause an exception.
   3746  input.value = "";
   3747  try {
   3748    let keyInvalidCode = new KeyboardEvent("", { key: "f", code: "InvalidCodeValue", keyCode: KeyboardEvent.DOM_VK_F });
   3749    TIP.keyup(keyInvalidCode);
   3750    ok(false,
   3751       description + "TIP.keyup(keyInvalidCode) should cause throwing an exception because its code value is not registered");
   3752  } catch (e) {
   3753    ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
   3754       description + "TIP.keyup(keyInvalidCode) should cause throwing an exception including NS_ERROR_ILLEGAL_VALUE");
   3755  } finally {
   3756    is(input.value, "",
   3757       description + "The input element should not be modified");
   3758  }
   3759 
   3760  // Calling keydown(KEY_NON_PRINTABLE_KEY) with a KeyboardEvent initialized with non-key name should cause an exception.
   3761  input.value = "";
   3762  try {
   3763    let keyInvalidKey = new KeyboardEvent("", { key: "ESCAPE", code: "Escape", keyCode: KeyboardEvent.DOM_VK_Escape});
   3764    TIP.keydown(keyInvalidKey, TIP.KEY_NON_PRINTABLE_KEY);
   3765    ok(false,
   3766       description + "TIP.keydown(keyInvalidKey, TIP.KEY_NON_PRINTABLE_KEY) should cause throwing an exception because its key value is not registered");
   3767  } catch (e) {
   3768    ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
   3769       description + "keydown(keyInvalidKey, TIP.KEY_NON_PRINTABLE_KEY) should cause throwing an exception including NS_ERROR_ILLEGAL_VALUE");
   3770  } finally {
   3771    is(input.value, "",
   3772       description + "The input element should not be modified");
   3773  }
   3774 
   3775  // Calling keyup(KEY_NON_PRINTABLE_KEY) with a KeyboardEvent initialized with non-key name should cause an exception.
   3776  input.value = "";
   3777  try {
   3778    let keyInvalidKey = new KeyboardEvent("", { key: "ESCAPE", code: "Escape", keyCode: KeyboardEvent.DOM_VK_Escape});
   3779    TIP.keydown(keyInvalidKey, TIP.KEY_NON_PRINTABLE_KEY);
   3780    ok(false,
   3781       description + "TIP.keyup(keyInvalidKey, TIP.KEY_NON_PRINTABLE_KEY) should cause throwing an exception because its key value is not registered");
   3782  } catch (e) {
   3783    ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
   3784       description + "keyup(keyInvalidKey, TIP.KEY_NON_PRINTABLE_KEY) should cause throwing an exception including NS_ERROR_ILLEGAL_VALUE");
   3785  } finally {
   3786    is(input.value, "",
   3787       description + "The input element should not be modified");
   3788  }
   3789 
   3790  // KEY_KEEP_KEY_LOCATION_STANDARD flag should be used only when .location is not initialized with non-zero value.
   3791  try {
   3792    let keyEvent = new KeyboardEvent("", { code: "Enter", location: KeyboardEvent.DOM_KEY_LOCATION_LEFT });
   3793    TIP.keydown(keyEvent, TIP.KEY_KEEP_KEY_LOCATION_STANDARD);
   3794    ok(false,
   3795       description + "keydown(KEY_KEEP_KEY_LOCATION_STANDARD) should fail if the .location of the key event is initialized with non-zero value");
   3796  } catch (e) {
   3797    ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
   3798       description + "keydown(KEY_KEEP_KEY_LOCATION_STANDARD) should cause NS_ERROR_ILLEGAL_VALUE if the .location of the key event is initialized with nonzero value");
   3799  }
   3800  try {
   3801    let keyEvent = new KeyboardEvent("", { code: "Enter", location: KeyboardEvent.DOM_KEY_LOCATION_LEFT });
   3802    TIP.keyup(keyEvent, TIP.KEY_KEEP_KEY_LOCATION_STANDARD);
   3803    ok(false,
   3804       description + "keyup(KEY_KEEP_KEY_LOCATION_STANDARD) should fail if the .location of the key event is initialized with non-zero value");
   3805  } catch (e) {
   3806    ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
   3807       description + "keyup(KEY_KEEP_KEY_LOCATION_STANDARD) should cause NS_ERROR_ILLEGAL_VALUE if the .location of the key event is initialized with nonzero value");
   3808  }
   3809 
   3810  // KEY_KEEP_KEYCODE_ZERO flag should be used only when .keyCode is not initialized with non-zero value.
   3811  try {
   3812    let keyEvent = new KeyboardEvent("", { key: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
   3813    TIP.keydown(keyEvent, TIP.KEY_KEEP_KEYCODE_ZERO);
   3814    ok(false,
   3815       description + "keydown(KEY_KEEP_KEYCODE_ZERO) should fail if the .keyCode of the key event is initialized with non-zero value");
   3816  } catch (e) {
   3817    ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
   3818       description + "keydown(KEY_KEEP_KEYCODE_ZERO) should cause NS_ERROR_ILLEGAL_VALUE if the .keyCode of the key event is initialized with nonzero value");
   3819  }
   3820  try {
   3821    let keyEvent = new KeyboardEvent("", { key: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
   3822    TIP.keyup(keyEvent, TIP.KEY_KEEP_KEYCODE_ZERO);
   3823    ok(false,
   3824       description + "keyup(KEY_KEEP_KEYCODE_ZERO) should fail if the .keyCode of the key event is initialized with non-zero value");
   3825  } catch (e) {
   3826    ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
   3827       description + "keyup(KEY_KEEP_KEYCODE_ZERO) should cause NS_ERROR_ILLEGAL_VALUE if the .keyCode of the key event is initialized with nonzero value");
   3828  }
   3829 
   3830  // Specifying KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT with non-modifier key, it should cause an exception.
   3831  try {
   3832    let keyEvent = new KeyboardEvent("", { key: "a", code: "ShiftLeft" });
   3833    TIP.keyup(keyEvent, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
   3834    ok(false,
   3835       description + "keydown(KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) should fail if the .key value isn't a modifier key");
   3836  } catch (e) {
   3837    ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
   3838       description + "keydown(KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) should cause NS_ERROR_ILLEGAL_VALUE if the .key value isn't a modifier key");
   3839  }
   3840  try {
   3841    let keyEvent = new KeyboardEvent("", { key: "Enter", code: "ShiftLeft" });
   3842    TIP.keyup(keyEvent, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
   3843    ok(false,
   3844       description + "keydown(KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) should fail if the .key value isn't a modifier key");
   3845  } catch (e) {
   3846    ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
   3847       description + "keydown(KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) should cause NS_ERROR_ILLEGAL_VALUE if the .key value isn't a modifier key");
   3848  }
   3849 
   3850  // The type of key events specified to composition methods should be "" or "keydown".
   3851  var kKeyEventTypes = [
   3852    { type: "keydown",   valid: true },
   3853    { type: "keypress",  valid: false },
   3854    { type: "keyup",     valid: false },
   3855    { type: "",          valid: true },
   3856    { type: "mousedown", valid: false },
   3857    { type: "foo",       valid: false },
   3858  ];
   3859  for (var i = 0; i < kKeyEventTypes[i].length; i++) {
   3860    var keyEvent =
   3861      new KeyboardEvent(kKeyEventTypes[i].type, { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
   3862    var testDescription = description + "type=\"" + kKeyEventTypes[i].type + "\", ";
   3863    try {
   3864      TIP.startComposition(keyEvent);
   3865      ok(kKeyEventTypes[i].valid,
   3866         testDescription + "TIP.startComposition(keyEvent) should not accept the event type");
   3867      TIP.cancelComposition();
   3868    } catch (e) {
   3869      ok(!kKeyEventTypes[i].valid,
   3870         testDescription + "TIP.startComposition(keyEvent) should not throw an exception for the event type");
   3871      ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
   3872         testDescription + "TIP.startComposition(keyEvent) should cause NS_ERROR_ILLEGAL_VALUE if the key event type isn't valid");
   3873    }
   3874    try {
   3875      TIP.setPendingCompositionString("foo");
   3876      TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   3877      TIP.setCaretInPendingComposition(3);
   3878      TIP.flushPendingComposition(keyEvent);
   3879      ok(kKeyEventTypes[i].valid,
   3880         testDescription + "TIP.flushPendingComposition(keyEvent) should not accept the event type");
   3881      TIP.cancelComposition();
   3882    } catch (e) {
   3883      ok(!kKeyEventTypes[i].valid,
   3884         testDescription + "TIP.flushPendingComposition(keyEvent) should not throw an exception for the event type");
   3885      ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
   3886         testDescription + "TIP.flushPendingComposition(keyEvent) should cause NS_ERROR_ILLEGAL_VALUE if the key event type isn't valid");
   3887    }
   3888    try {
   3889      TIP.startComposition();
   3890      TIP.commitComposition(keyEvent);
   3891      ok(kKeyEventTypes[i].valid,
   3892         testDescription + "TIP.commitComposition(keyEvent) should not accept the event type");
   3893    } catch (e) {
   3894      ok(!kKeyEventTypes[i].valid,
   3895         testDescription + "TIP.commitComposition(keyEvent) should not throw an exception for the event type");
   3896      ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
   3897         testDescription + "TIP.commitComposition(keyEvent) should cause NS_ERROR_ILLEGAL_VALUE if the key event type isn't valid");
   3898      TIP.cancelComposition();
   3899    }
   3900    try {
   3901      TIP.commitCompositionWith("foo", keyEvent);
   3902      ok(kKeyEventTypes[i].valid,
   3903         testDescription + "TIP.commitCompositionWith(\"foo\", keyEvent) should not accept the event type");
   3904    } catch (e) {
   3905      ok(!kKeyEventTypes[i].valid,
   3906         testDescription + "TIP.commitCompositionWith(\"foo\", keyEvent) should not throw an exception for the event type");
   3907      ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
   3908         testDescription + "TIP.commitCompositionWith(\"foo\", keyEvent) should cause NS_ERROR_ILLEGAL_VALUE if the key event type isn't valid");
   3909    }
   3910    try {
   3911      TIP.startComposition();
   3912      TIP.cancelComposition(keyEvent);
   3913      ok(kKeyEventTypes[i].valid,
   3914         testDescription + "TIP.cancelComposition(keyEvent) should not accept the event type");
   3915    } catch (e) {
   3916      ok(!kKeyEventTypes[i].valid,
   3917         testDescription + "TIP.cancelComposition(keyEvent) should not throw an exception for the event type");
   3918      ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
   3919         testDescription + "TIP.cancelComposition(keyEvent) should cause NS_ERROR_ILLEGAL_VALUE if the key event type isn't valid");
   3920      TIP.cancelComposition();
   3921    }
   3922    input.value = "";
   3923  }
   3924 }
   3925 
   3926 function runCommitCompositionTests()
   3927 {
   3928  var description = "runCommitCompositionTests(): ";
   3929 
   3930  var TIP = createTIP();
   3931  ok(TIP.beginInputTransactionForTests(window),
   3932     description + "TIP.beginInputTransactionForTests() should succeed");
   3933 
   3934  input.focus();
   3935 
   3936  // commitComposition() should commit the composition with the last data.
   3937  input.value = "";
   3938  TIP.setPendingCompositionString("foo");
   3939  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   3940  TIP.setCaretInPendingComposition(3);
   3941  TIP.flushPendingComposition();
   3942  TIP.commitComposition();
   3943  is(input.value, "foo",
   3944     description + "commitComposition() should commit the composition with the last data");
   3945 
   3946  // commitCompositionWith("") should commit the composition with empty string.
   3947  input.value = "";
   3948  TIP.setPendingCompositionString("foo");
   3949  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   3950  TIP.setCaretInPendingComposition(3);
   3951  TIP.flushPendingComposition();
   3952  TIP.commitCompositionWith("");
   3953  is(input.value, "",
   3954     description + "commitCompositionWith(\"\") should commit the composition with empty string");
   3955 
   3956  function doCommit(aText)
   3957  {
   3958    TIP.commitCompositionWith(aText);
   3959  }
   3960 
   3961  // doCommit() should commit the composition with the last data.
   3962  input.value = "";
   3963  TIP.setPendingCompositionString("foo");
   3964  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   3965  TIP.setCaretInPendingComposition(3);
   3966  TIP.flushPendingComposition();
   3967  doCommit();
   3968  todo_is(input.value, "foo",
   3969          description + "doCommit() should commit the composition with the last data");
   3970 
   3971  // doCommit("") should commit the composition with empty string.
   3972  input.value = "";
   3973  TIP.setPendingCompositionString("foo");
   3974  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   3975  TIP.setCaretInPendingComposition(3);
   3976  TIP.flushPendingComposition();
   3977  doCommit("");
   3978  is(input.value, "",
   3979     description + "doCommit(\"\") should commit the composition with empty string");
   3980 
   3981  // doCommit(null) should commit the composition with empty string.
   3982  input.value = "";
   3983  TIP.setPendingCompositionString("foo");
   3984  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   3985  TIP.setCaretInPendingComposition(3);
   3986  TIP.flushPendingComposition();
   3987  doCommit(null);
   3988  is(input.value, "",
   3989     description + "doCommit(null) should commit the composition with empty string");
   3990 
   3991  // doCommit(undefined) should commit the composition with the last data.
   3992  input.value = "";
   3993  TIP.setPendingCompositionString("foo");
   3994  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   3995  TIP.setCaretInPendingComposition(3);
   3996  TIP.flushPendingComposition();
   3997  doCommit(undefined);
   3998  todo_is(input.value, "foo",
   3999          description + "doCommit(undefined) should commit the composition with the last data");
   4000 
   4001  function doCommitWithNullCheck(aText)
   4002  {
   4003    TIP.commitCompositionWith(aText ? aText : "");
   4004  }
   4005 
   4006  // doCommitWithNullCheck() should commit the composition with the last data.
   4007  input.value = "";
   4008  TIP.setPendingCompositionString("foo");
   4009  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   4010  TIP.setCaretInPendingComposition(3);
   4011  TIP.flushPendingComposition();
   4012  doCommitWithNullCheck();
   4013  is(input.value, "",
   4014     description + "doCommitWithNullCheck() should commit the composition with empty string");
   4015 
   4016  // doCommitWithNullCheck("") should commit the composition with empty string.
   4017  input.value = "";
   4018  TIP.setPendingCompositionString("foo");
   4019  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   4020  TIP.setCaretInPendingComposition(3);
   4021  TIP.flushPendingComposition();
   4022  doCommitWithNullCheck("");
   4023  is(input.value, "",
   4024     description + "doCommitWithNullCheck(\"\") should commit the composition with empty string");
   4025 
   4026  // doCommitWithNullCheck(null) should commit the composition with empty string.
   4027  input.value = "";
   4028  TIP.setPendingCompositionString("foo");
   4029  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   4030  TIP.setCaretInPendingComposition(3);
   4031  TIP.flushPendingComposition();
   4032  doCommitWithNullCheck(null);
   4033  is(input.value, "",
   4034     description + "doCommitWithNullCheck(null) should commit the composition with empty string");
   4035 
   4036  // doCommitWithNullCheck(undefined) should commit the composition with the last data.
   4037  input.value = "";
   4038  TIP.setPendingCompositionString("foo");
   4039  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   4040  TIP.setCaretInPendingComposition(3);
   4041  TIP.flushPendingComposition();
   4042  doCommitWithNullCheck(undefined);
   4043  is(input.value, "",
   4044     description + "doCommitWithNullCheck(undefined) should commit the composition with empty string");
   4045 }
   4046 
   4047 function runUnloadTests1()
   4048 {
   4049  return new Promise(resolve => {
   4050    let description = "runUnloadTests1(): ";
   4051 
   4052    let TIP1 = createTIP();
   4053    ok(TIP1.beginInputTransactionForTests(childWindow),
   4054       description + "TIP1.beginInputTransactionForTests() should succeed");
   4055 
   4056    let oldSrc = iframe.src;
   4057    let parentWindow = window;
   4058 
   4059    iframe.addEventListener("load", function () {
   4060      ok(true, description + "dummy page is loaded");
   4061      childWindow = iframe.contentWindow;
   4062      textareaInFrame = null;
   4063      iframe.addEventListener("load", function () {
   4064        ok(true, description + "old iframe is restored");
   4065        // And also restore the iframe information with restored contents.
   4066        childWindow = iframe.contentWindow;
   4067        textareaInFrame = iframe.contentDocument.getElementById("textarea");
   4068        SimpleTest.executeSoon(resolve);
   4069      }, {capture: true, once: true});
   4070 
   4071      // The composition should be committed internally.  So, another TIP should
   4072      // be able to steal the rights to using TextEventDispatcher.
   4073      let TIP2 = createTIP();
   4074      ok(TIP2.beginInputTransactionForTests(parentWindow),
   4075         description + "TIP2.beginInputTransactionForTests() should succeed");
   4076 
   4077      input.focus();
   4078      input.value = "";
   4079 
   4080      TIP2.setPendingCompositionString("foo");
   4081      TIP2.appendClauseToPendingComposition(3, TIP2.ATTR_RAW_CLAUSE);
   4082      TIP2.setCaretInPendingComposition(3);
   4083      TIP2.flushPendingComposition();
   4084      is(input.value, "foo",
   4085         description + "the input in the parent document should have composition string");
   4086 
   4087      TIP2.cancelComposition();
   4088 
   4089      // Restore the old iframe content.
   4090      iframe.src = oldSrc;
   4091    }, {capture: true, once: true});
   4092 
   4093    // Start composition in the iframe.
   4094    textareaInFrame.value = "";
   4095    textareaInFrame.focus();
   4096 
   4097    TIP1.setPendingCompositionString("foo");
   4098    TIP1.appendClauseToPendingComposition(3, TIP1.ATTR_RAW_CLAUSE);
   4099    TIP1.setCaretInPendingComposition(3);
   4100    TIP1.flushPendingComposition();
   4101    is(textareaInFrame.value, "foo",
   4102       description + "the textarea in the iframe should have composition string");
   4103 
   4104    // Load different web page on the frame.
   4105    iframe.src = "data:text/html,<body>dummy page</body>";
   4106  });
   4107 }
   4108 
   4109 function runUnloadTests2()
   4110 {
   4111  return new Promise(resolve => {
   4112    let description = "runUnloadTests2(): ";
   4113 
   4114    let TIP = createTIP();
   4115    ok(TIP.beginInputTransactionForTests(childWindow),
   4116       description + "TIP.beginInputTransactionForTests() should succeed");
   4117 
   4118    let oldSrc = iframe.src;
   4119 
   4120    iframe.addEventListener("load", function () {
   4121      ok(true, description + "dummy page is loaded");
   4122      childWindow = iframe.contentWindow;
   4123      textareaInFrame = null;
   4124      iframe.addEventListener("load", function () {
   4125        ok(true, description + "old iframe is restored");
   4126        // And also restore the iframe information with restored contents.
   4127        childWindow = iframe.contentWindow;
   4128        textareaInFrame = iframe.contentDocument.getElementById("textarea");
   4129        SimpleTest.executeSoon(resolve);
   4130      }, {capture: true, once: true});
   4131 
   4132      input.focus();
   4133      input.value = "";
   4134 
   4135      // TIP should be still available in the same top level widget.
   4136      TIP.setPendingCompositionString("bar");
   4137      TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   4138      TIP.setCaretInPendingComposition(3);
   4139      TIP.flushPendingComposition();
   4140      if (input.value == "") {
   4141        // XXX TextInputProcessor or TextEventDispatcher may have a bug.
   4142        todo_is(input.value, "bar",
   4143                description + "the input in the parent document should have composition string");
   4144      } else {
   4145        is(input.value, "bar",
   4146           description + "the input in the parent document should have composition string");
   4147      }
   4148 
   4149      TIP.cancelComposition();
   4150 
   4151      // Restore the old iframe content.
   4152      iframe.src = oldSrc;
   4153    }, {capture: true, once: true});
   4154 
   4155    // Start composition in the iframe.
   4156    textareaInFrame.value = "";
   4157    textareaInFrame.focus();
   4158 
   4159    TIP.setPendingCompositionString("foo");
   4160    TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   4161    TIP.setCaretInPendingComposition(3);
   4162    TIP.flushPendingComposition();
   4163    is(textareaInFrame.value, "foo",
   4164       description + "the textarea in the iframe should have composition string");
   4165 
   4166    // Load different web page on the frame.
   4167    iframe.src = "data:text/html,<body>dummy page</body>";
   4168  });
   4169 }
   4170 
   4171 async function runCallbackTests(aForTests)
   4172 {
   4173  let description = "runCallbackTests(aForTests=" + aForTests + "): ";
   4174 
   4175  input.value = "";
   4176  input.focus();
   4177  input.blur();
   4178 
   4179  let TIP = createTIP();
   4180  let notifications = [];
   4181  let waitingNextNotification;
   4182  function callback(aTIP, aNotification)
   4183  {
   4184    if (aTIP == TIP) {
   4185      notifications.push(aNotification);
   4186    }
   4187    switch (aNotification.type) {
   4188      case "request-to-commit":
   4189        aTIP.commitComposition();
   4190        break;
   4191      case "request-to-cancel":
   4192        aTIP.cancelComposition();
   4193        break;
   4194    }
   4195    if (waitingNextNotification) {
   4196      SimpleTest.executeSoon(waitingNextNotification);
   4197      waitingNextNotification = undefined;
   4198    }
   4199    return true;
   4200  }
   4201 
   4202  function dumpUnexpectedNotifications(aExpectedCount)
   4203  {
   4204    if (notifications.length <= aExpectedCount) {
   4205      return;
   4206    }
   4207    for (let i = aExpectedCount; i < notifications.length; i++) {
   4208      ok(false,
   4209         description + "Unexpected notification: " + notifications[i].type);
   4210    }
   4211  }
   4212 
   4213  function waitUntilNotificationsReceived()
   4214  {
   4215    return new Promise(resolve => {
   4216      if (notifications.length) {
   4217        SimpleTest.executeSoon(resolve);
   4218      } else {
   4219        waitingNextNotification = resolve;
   4220      }
   4221    });
   4222  }
   4223 
   4224  function checkPositionChangeNotification(aNotification, aDescription)
   4225  {
   4226    is(!aNotification || aNotification.type, "notify-position-change",
   4227       aDescription + " should cause position change notification");
   4228  }
   4229 
   4230  function checkSelectionChangeNotification(aNotification, aDescription, aExpected)
   4231  {
   4232    is(aNotification.type, "notify-selection-change",
   4233       aDescription + " should cause selection change notification");
   4234    if (aNotification.type != "notify-selection-change") {
   4235      return;
   4236    }
   4237    is(aNotification.hasRange, aExpected.hasRange !== false,
   4238      `${aDescription} should cause selection change notification whose hasRange is ${aExpected.hasRange}`);
   4239    if (aNotification.hasRange) {
   4240      is(aNotification.offset, aExpected.offset,
   4241        `${aDescription} should cause selection change notification whose offset is ${aExpected.offset}`);
   4242      is(aNotification.text, aExpected.text,
   4243        `${aDescription} should cause selection change notification whose text is "${aExpected.text}"`);
   4244      is(aNotification.length, aExpected.text.length,
   4245        `${aDescription} should cause selection change notification whose length is ${aExpected.text.length}`);
   4246      is(aNotification.reversed, aExpected.reversed || false,
   4247        `${aDescription} should cause selection change notification whose reversed is ${aExpected.reversed || false}`);
   4248    }
   4249    is(aNotification.collapsed, aExpected.hasRange === false || !aExpected.text.length,
   4250      `${aDescription} should cause selection change notification whose collapsed is ${aExpected.hasRange === false || !aExpected.text.length}`);
   4251    is(aNotification.writingMode, aExpected.writingMode || "horizontal-tb",
   4252      `${aDescription} should cause selection change notification whose writingMode is ${aExpected.writingMode || "horizontal-tb"}`);
   4253    is(aNotification.causedByComposition, aExpected.causedByComposition || false,
   4254      `${aDescription} should cause selection change notification whose causedByComposition is ${aExpected.causedByComposition || false}`);
   4255    is(aNotification.causedBySelectionEvent, aExpected.causedBySelectionEvent || false,
   4256      `${aDescription} should cause selection change notification whose causedBySelectionEvent is ${aExpected.causedBySelectionEvent || false}`);
   4257    is(aNotification.occurredDuringComposition, aExpected.occurredDuringComposition || false,
   4258      `${aDescription} should cause cause selection change notification whose occurredDuringComposition is ${aExpected.occurredDuringComposition || false}`);
   4259  }
   4260 
   4261  function checkTextChangeNotification(aNotification, aDescription, aExpected)
   4262  {
   4263    is(aNotification.type, "notify-text-change",
   4264       aDescription + " should cause text change notification");
   4265    if (aNotification.type != "notify-text-change") {
   4266      return;
   4267    }
   4268    is(aNotification.offset, aExpected.offset,
   4269       aDescription + " should cause text change notification whose offset is " + aExpected.offset);
   4270    is(aNotification.removedLength, aExpected.removedLength,
   4271       aDescription + " should cause text change notification whose removedLength is " + aExpected.removedLength);
   4272    is(aNotification.addedLength, aExpected.addedLength,
   4273       aDescription + " should cause text change notification whose addedLength is " + aExpected.addedLength);
   4274    is(aNotification.causedOnlyByComposition, aExpected.causedOnlyByComposition || false,
   4275       aDescription + " should cause text change notification whose causedOnlyByComposition is " + (aExpected.causedOnlyByComposition || false));
   4276    is(aNotification.includingChangesDuringComposition, aExpected.includingChangesDuringComposition || false,
   4277       aDescription + " should cause text change notification whose includingChangesDuringComposition is " + (aExpected.includingChangesDuringComposition || false));
   4278    is(aNotification.includingChangesWithoutComposition, typeof aExpected.includingChangesWithoutComposition === "boolean" ? aExpected.includingChangesWithoutComposition : true,
   4279       aDescription + " should cause text change notification whose includingChangesWithoutComposition is " + (typeof aExpected.includingChangesWithoutComposition === "boolean" ? aExpected.includingChangesWithoutComposition : true));
   4280  }
   4281 
   4282  if (aForTests) {
   4283    TIP.beginInputTransactionForTests(window, callback);
   4284  } else {
   4285    TIP.beginInputTransaction(window, callback);
   4286  }
   4287 
   4288  notifications = [];
   4289  input.focus();
   4290  is(notifications.length, 1,
   4291     description + "input.focus() should cause a notification");
   4292  is(notifications[0].type, "notify-focus",
   4293     description + "input.focus() should cause \"notify-focus\"");
   4294  dumpUnexpectedNotifications(1);
   4295 
   4296  notifications = [];
   4297  input.blur();
   4298  is(notifications.length, 1,
   4299     description + "input.blur() should cause a notification");
   4300  is(notifications[0].type, "notify-blur",
   4301     description + "input.blur() should cause \"notify-focus\"");
   4302  dumpUnexpectedNotifications(1);
   4303 
   4304  input.focus();
   4305  await waitUntilNotificationsReceived();
   4306  notifications = [];
   4307  TIP.setPendingCompositionString("foo");
   4308  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   4309  TIP.flushPendingComposition();
   4310  is(notifications.length, 3,
   4311     description + "creating composition string 'foo' should cause 3 notifications");
   4312  checkTextChangeNotification(notifications[0], description + "creating composition string 'foo'",
   4313                              { offset: 0, removedLength: 0, addedLength: 3,
   4314                                causedOnlyByComposition: true, includingChangesDuringComposition: false, includingChangesWithoutComposition: false});
   4315  checkSelectionChangeNotification(notifications[1], description + "creating composition string 'foo'",
   4316                                   { offset: 3, text: "", causedByComposition: true, occurredDuringComposition: true });
   4317  checkPositionChangeNotification(notifications[2], description + "creating composition string 'foo'");
   4318  dumpUnexpectedNotifications(3);
   4319 
   4320  notifications = [];
   4321  synthesizeMouseAtCenter(input, {});
   4322  is(notifications.length, 3,
   4323     description + "synthesizeMouseAtCenter(input, {}) during composition should cause 3 notifications");
   4324  is(notifications[0].type, "request-to-commit",
   4325     description + "synthesizeMouseAtCenter(input, {}) during composition should cause \"request-to-commit\"");
   4326  checkTextChangeNotification(notifications[1], description + "synthesizeMouseAtCenter(input, {}) during composition",
   4327                              { offset: 0, removedLength: 3, addedLength: 3,
   4328                                causedOnlyByComposition: true, includingChangesDuringComposition: false, includingChangesWithoutComposition: false});
   4329  checkPositionChangeNotification(notifications[2], description + "synthesizeMouseAtCenter(input, {}) during composition");
   4330  dumpUnexpectedNotifications(3);
   4331 
   4332  input.focus();
   4333  await waitUntilNotificationsReceived();
   4334  notifications = [];
   4335  // XXX On macOS, window.moveBy() doesn't cause notify-position-change.
   4336  //     Investigate this later (although, we cannot notify position change to
   4337  //     native IME on macOS).
   4338  // Wayland also does not support it.
   4339  var isWayland = Services.prefs.getBoolPref("widget.wayland.test-workarounds.enabled", false);
   4340  if (!kIsMac && !isWayland) {
   4341    window.moveBy(0, 10);
   4342    await waitUntilNotificationsReceived();
   4343    is(notifications.length, 1,
   4344       description + "window.moveBy(0, 10) should cause a notification");
   4345    checkPositionChangeNotification(notifications[0], description + "window.moveBy(0, 10)");
   4346    dumpUnexpectedNotifications(1);
   4347 
   4348    notifications = [];
   4349    window.moveBy(10, 0);
   4350    await waitUntilNotificationsReceived();
   4351    is(notifications.length, 1,
   4352       description + "window.moveBy(10, 0) should cause a notification");
   4353    checkPositionChangeNotification(notifications[0], description + "window.moveBy(10, 0)");
   4354    dumpUnexpectedNotifications(1);
   4355  }
   4356 
   4357  input.focus();
   4358  input.value = "abc"
   4359  notifications = [];
   4360  input.selectionStart = input.selectionEnd = 0;
   4361  await waitUntilNotificationsReceived();
   4362  notifications = [];
   4363  let rightArrowKeyEvent =
   4364    new KeyboardEvent("", { key: "ArrowRight", code: "ArrowRight", keyCode: KeyboardEvent.DOM_VK_RIGHT });
   4365  TIP.keydown(rightArrowKeyEvent);
   4366  TIP.keyup(rightArrowKeyEvent);
   4367  is(notifications.length, 1,
   4368     description + "ArrowRight key press should cause a notification");
   4369  checkSelectionChangeNotification(notifications[0], description + "ArrowRight key press", { offset: 1, text: "" });
   4370  dumpUnexpectedNotifications(1);
   4371 
   4372  notifications = [];
   4373  let shiftKeyEvent =
   4374    new KeyboardEvent("", { key: "Shift", code: "ShiftLeft", keyCode: KeyboardEvent.DOM_VK_SHIFT });
   4375  let leftArrowKeyEvent =
   4376    new KeyboardEvent("", { key: "ArrowLeft", code: "ArrowLeft", keyCode: KeyboardEvent.DOM_VK_LEFT });
   4377  TIP.keydown(shiftKeyEvent);
   4378  TIP.keydown(leftArrowKeyEvent);
   4379  TIP.keyup(leftArrowKeyEvent);
   4380  TIP.keyup(shiftKeyEvent);
   4381  is(notifications.length, 1,
   4382     description + "ArrowLeft key press with Shift should cause a notification");
   4383  checkSelectionChangeNotification(notifications[0], description + "ArrowLeft key press with Shift", { offset: 0, text: "a", reversed: true });
   4384  dumpUnexpectedNotifications(1);
   4385 
   4386  TIP.keydown(rightArrowKeyEvent);
   4387  TIP.keyup(rightArrowKeyEvent);
   4388  notifications = [];
   4389  TIP.keydown(shiftKeyEvent);
   4390  TIP.keydown(rightArrowKeyEvent);
   4391  TIP.keyup(rightArrowKeyEvent);
   4392  TIP.keyup(shiftKeyEvent);
   4393  is(notifications.length, 1,
   4394     description + "ArrowRight key press with Shift should cause a notification");
   4395  checkSelectionChangeNotification(notifications[0], description + "ArrowRight key press with Shift", { offset: 1, text: "b" });
   4396  dumpUnexpectedNotifications(1);
   4397 
   4398  notifications = [];
   4399  input.editor.selection.removeAllRanges();
   4400  await waitUntilNotificationsReceived();
   4401  is(notifications.length, 1,
   4402    `${description}Removing all selection ranges should cause a selection change notification`);
   4403  checkSelectionChangeNotification(
   4404    notifications[0],
   4405    `${description}Removing all selection ranges in editor`,
   4406    { hasRange: false }
   4407  );
   4408  dumpUnexpectedNotifications(1);
   4409 
   4410  notifications = [];
   4411  let TIP2 = createTIP();
   4412  if (aForTests) {
   4413    TIP2.beginInputTransactionForTests(window, callback);
   4414  } else {
   4415    TIP2.beginInputTransaction(window, callback);
   4416  }
   4417  is(notifications.length, 1,
   4418     description + "Initializing another TIP should cause a notification");
   4419  is(notifications[0].type, "notify-end-input-transaction",
   4420     description + "Initializing another TIP should cause \"notify-detached\"");
   4421  dumpUnexpectedNotifications(1);
   4422 }
   4423 
   4424 async function runFocusNotificationTestAfterDrop() {
   4425  const inputs = document.querySelectorAll("input[type=text]");
   4426  inputs[0].value = "abc";
   4427  inputs[1].value = "";
   4428 
   4429  const TIP = createTIP();
   4430  let notifications = [];
   4431  function callback(aTIP, aNotification)
   4432  {
   4433    if (aTIP != TIP) {
   4434      return true;
   4435    }
   4436    switch (aNotification.type) {
   4437      case "request-to-commit":
   4438        aTIP.commitComposition();
   4439        break;
   4440      case "request-to-cancel":
   4441        aTIP.cancelComposition();
   4442        break;
   4443      case "notify-focus":
   4444      case "notify-blur":
   4445        notifications.push(aNotification.type);
   4446        break;
   4447    }
   4448    return true;
   4449  }
   4450 
   4451  inputs[0].focus();
   4452  TIP.beginInputTransactionForTests(window, callback);
   4453  inputs[0].select();
   4454  try {
   4455    notifications = [];
   4456    await synthesizePlainDragAndDrop({
   4457      srcSelection: SpecialPowers.wrap(inputs[0]).editor.selection,
   4458      destElement: inputs[1],
   4459    });
   4460  } catch (ex) {
   4461    ok(false, `runFocusNotificationTestAfterDrop: unexpected error during DnD (${ex.message})`);
   4462    return;
   4463  }
   4464  is(
   4465    document.activeElement,
   4466    inputs[1],
   4467    "runFocusNotificationTestAfterDrop: Dropping to the second <input> should make it focused"
   4468  );
   4469  ok(
   4470    notifications.length > 1,
   4471    "runFocusNotificationTestAfterDrop: At least two notifications should be fired"
   4472  );
   4473  if (notifications.length) {
   4474    is(
   4475      notifications[notifications.length - 1],
   4476      "notify-focus",
   4477      "runFocusNotificationTestAfterDrop: focus notification should've been fired at last"
   4478    );
   4479  }
   4480 }
   4481 
   4482 async function runQuerySelectionEventTestAtTextChangeNotification() {
   4483  contenteditable.innerHTML = "<p>abc</p><p>def</p>";
   4484  contenteditable.focus();
   4485  // Ensure to send notify-focus from IMEContentObserver
   4486  await new Promise(
   4487    resolve => requestAnimationFrame(
   4488      () => requestAnimationFrame(resolve)
   4489    )
   4490  );
   4491  document.execCommand("selectall");
   4492  // Ensure to send notify-selection-change from IMEContentObserver
   4493  await new Promise(
   4494    resolve => requestAnimationFrame(
   4495      () => requestAnimationFrame(resolve)
   4496    )
   4497  );
   4498 
   4499  const kTestName = "runQuerySelectionEventTestAtTextChangeNotification";
   4500  await new Promise(resolve => {
   4501    const TIP = createTIP();
   4502    TIP.beginInputTransactionForTests(window, (aTIP, aNotification) => {
   4503      if (aTIP != TIP) {
   4504        return true;
   4505      }
   4506      switch (aNotification.type) {
   4507        case "request-to-commit":
   4508          aTIP.commitComposition();
   4509          break;
   4510        case "request-to-cancel":
   4511          aTIP.cancelComposition();
   4512          break;
   4513        case "notify-text-change": {
   4514          const textContent = synthesizeQueryTextContent(0, 100);
   4515          if (textContent?.text.includes("abc")) {
   4516            break;  // Different notification which we want to test, wait next one.
   4517          }
   4518          ok(
   4519            textContent?.succeeded,
   4520            `${kTestName}: query text content should succeed from notify-text-change handler`
   4521          );
   4522          const selectedText = synthesizeQuerySelectedText();
   4523          ok(
   4524            selectedText?.succeeded,
   4525            `${kTestName}: query selected text should succeed from notify-text-change handler`
   4526          );
   4527          if (textContent?.succeeded && selectedText?.succeeded) {
   4528            is(
   4529              selectedText.text,
   4530              textContent.text,
   4531              `${kTestName}: selected text should be same as all text`
   4532            );
   4533          }
   4534          resolve();
   4535          break;
   4536        }
   4537      }
   4538      return true;
   4539    });
   4540    // TODO: We want to do this  while selection is batched but can flush
   4541    //       pending notifications, however, I have no idea how to do it.
   4542    contenteditable.firstChild.remove();
   4543  });
   4544 }
   4545 
   4546 async function runIMEStateUpdateTests() {
   4547  const TIP = createTIP();
   4548  let notifications = [];
   4549  function callback(aTIP, aNotification)
   4550  {
   4551    if (aTIP != TIP) {
   4552      return true;
   4553    }
   4554    switch (aNotification.type) {
   4555      case "request-to-commit":
   4556        aTIP.commitComposition();
   4557        break;
   4558      case "request-to-cancel":
   4559        aTIP.cancelComposition();
   4560        break;
   4561      case "notify-focus":
   4562      case "notify-blur":
   4563        notifications.push(aNotification.type);
   4564        break;
   4565    }
   4566    return true;
   4567  }
   4568 
   4569  contenteditable.focus();
   4570  TIP.beginInputTransactionForTests(window, callback);
   4571  await new Promise(resolve => requestAnimationFrame(() =>
   4572    requestAnimationFrame(resolve)
   4573  )); // wait for flushing pending notifications if there is.
   4574 
   4575  // run IMEStateManager::UpdateIMEState to disable IME
   4576  notifications = [];
   4577  const editor = getHTMLEditor(window);
   4578  editor.flags |= Ci.nsIEditor.eEditorReadonlyMask;
   4579  await new Promise(resolve => requestAnimationFrame(() =>
   4580    requestAnimationFrame(resolve)
   4581  )); // wait for flush pending notification even if handled asynchronously.
   4582  is(
   4583    notifications.length ? notifications[0] : undefined,
   4584    "notify-blur",
   4585    "runIMEStateUpdateTests: Making the HTMLEditor readonly should cause a blur notification"
   4586  );
   4587  is(
   4588    notifications.length,
   4589    1,
   4590    `runIMEStateUpdateTests: Making the HTMLEditor readonly should not cause any other notifications, but got ${
   4591      notifications.length > 1 ? notifications[1] : ""
   4592    } notification`
   4593  );
   4594  is(
   4595    SpecialPowers.getDOMWindowUtils(window)?.IMEStatus,
   4596    Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
   4597    `runIMEStateUpdateTests: Making the HTMLEditor readonly should make IME disabled`
   4598  );
   4599 
   4600  // run IMEStateManager::UpdateIMEState to enable IME
   4601  notifications = [];
   4602  editor.flags &= ~Ci.nsIEditor.eEditorReadonlyMask;
   4603  await new Promise(resolve => requestAnimationFrame(() =>
   4604    requestAnimationFrame(resolve)
   4605  )); // wait for flush pending notification even if handled asynchronously.
   4606  is(
   4607    notifications.length ? notifications[0] : undefined,
   4608    "notify-focus",
   4609    "runIMEStateUpdateTests: Making the HTMLEditor editable should cause a focus notification without blur notification"
   4610  );
   4611  is(
   4612    notifications.length,
   4613    1,
   4614    `runIMEStateUpdateTests: Making the HTMLEditor editable should not cause any other notifications, but got ${
   4615      notifications.length > 1 ? notifications[1] : ""
   4616    } notification`
   4617  );
   4618  is(
   4619    SpecialPowers.getDOMWindowUtils(window)?.IMEStatus,
   4620    Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
   4621    `runIMEStateUpdateTests: Making the HTMLEditor readonly should make IME disabled`
   4622  );
   4623 }
   4624 
   4625 async function runTextNotificationChangesDuringNoFrame() {
   4626  const TIP = createTIP();
   4627  let onTextChange;
   4628  function callback(aTIP, aNotification)
   4629  {
   4630    if (aTIP != TIP) {
   4631      return true;
   4632    }
   4633    switch (aNotification.type) {
   4634      case "request-to-commit":
   4635        aTIP.commitComposition();
   4636        break;
   4637      case "request-to-cancel":
   4638        aTIP.cancelComposition();
   4639        break;
   4640      case "notify-text-change":
   4641        if (onTextChange) {
   4642          onTextChange(aNotification);
   4643        }
   4644        break;
   4645    }
   4646    return true;
   4647  }
   4648 
   4649  function promiseTextChangeNotification() {
   4650    return new Promise(resolve => onTextChange = resolve);
   4651  }
   4652 
   4653  function waitForTick() {
   4654    return new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
   4655  }
   4656 
   4657  input = document.querySelector("input[type=text]");
   4658  input.focus();
   4659  TIP.beginInputTransactionForTests(window, callback);
   4660 
   4661  await (async function test_text_change_notification_for_value_set_during_no_frame() {
   4662    const description = "runTextNotificationChangesDuringNoFrame: test_text_change_notification_for_value_set_during_no_frame";
   4663    input.value = "Start";
   4664    input.style.display = "inline";
   4665    input.getBoundingClientRect();
   4666    await waitForTick();
   4667    const waitNotifications = promiseTextChangeNotification();
   4668    input.style.display = "block";
   4669    input.value = "Changed";
   4670    info(`${description}: waiting for notifications...`);
   4671    const notification = await waitNotifications;
   4672    is(
   4673      notification?.offset,
   4674      0,
   4675      `${description}: offset should be 0`
   4676    );
   4677    is(
   4678      notification?.removedLength,
   4679      "Start".length,
   4680      `${description}: removedLength should be the length of the old value`
   4681    );
   4682    is(
   4683      notification?.addedLength,
   4684      "Changed".length,
   4685      `${description}: addedLength should be the length of the new value`
   4686    );
   4687  })();
   4688 
   4689  await (async function test_text_change_notification_for_multiple_value_set_during_no_frame() {
   4690    const description = "runTextNotificationChangesDuringNoFrame: test_text_change_notification_for_multiple_value_set_during_no_frame";
   4691    input.value = "Start";
   4692    input.style.display = "inline";
   4693    input.getBoundingClientRect();
   4694    await waitForTick();
   4695    const waitNotifications = promiseTextChangeNotification();
   4696    input.style.display = "block";
   4697    input.value = "Changed";
   4698    input.value = "Again!";
   4699    info(`${description}: waiting for notifications...`);
   4700    const notification = await waitNotifications;
   4701    is(
   4702      notification?.offset,
   4703      0,
   4704      `${description}: offset should be 0`
   4705    );
   4706    is(
   4707      notification?.removedLength,
   4708      "Start".length,
   4709      `${description}: removedLength should be the length of the old value`
   4710    );
   4711    is(
   4712      notification?.addedLength,
   4713      "Again!".length,
   4714      `${description}: addedLength should be the length of the new value`
   4715    );
   4716  })();
   4717 
   4718  await (async function test_text_change_notification_for_value_set_and_typing_character_during_no_frame() {
   4719    const description = "runTextNotificationChangesDuringNoFrame: test_text_change_notification_for_value_set_and_typing_character_during_no_frame";
   4720    input.value = "Start";
   4721    input.style.display = "inline";
   4722    input.getBoundingClientRect();
   4723    await waitForTick();
   4724    const waitNotifications = promiseTextChangeNotification();
   4725    input.style.display = "block";
   4726    input.value = "Change";
   4727    const dKey = new KeyboardEvent("", { code: "KeyD", key: "d", keyCode: KeyboardEvent.DOM_VK_D });
   4728    TIP.keydown(dKey);
   4729    TIP.keyup(dKey);
   4730    info(`${description}: waiting for notifications...`);
   4731    const notification = await waitNotifications;
   4732    is(
   4733      notification?.offset,
   4734      0,
   4735      `${description}: offset should be 0`
   4736    );
   4737    is(
   4738      notification?.removedLength,
   4739      "Start".length,
   4740      `${description}: removedLength should be the length of the old value`
   4741    );
   4742    is(
   4743      notification?.addedLength,
   4744      "Change".length,
   4745      `${description}: addedLength should be the length of the new (set) value`
   4746    );
   4747  })();
   4748 
   4749  input.style.display = "";
   4750 
   4751  textarea.focus();
   4752  TIP.beginInputTransaction(window, callback);
   4753 
   4754  await (async function test_text_change_notification_for_multi_line_value_set_during_no_frame() {
   4755    const description = "runTextNotificationChangesDuringNoFrame: test_text_change_notification_for_multi_line_value_set_during_no_frame";
   4756    textarea.value = "Start\n2nd Line";
   4757    textarea.style.display = "inline";
   4758    textarea.getBoundingClientRect();
   4759    await waitForTick();
   4760    const waitNotifications = promiseTextChangeNotification();
   4761    textarea.style.display = "block";
   4762    textarea.value = "Changed\n2nd Line";
   4763    info(`${description}: waiting for notifications...`);
   4764    const notification = await waitNotifications;
   4765    is(
   4766      notification?.offset,
   4767      0,
   4768      `${description}: offset should be 0`
   4769    );
   4770    is(
   4771      notification?.removedLength,
   4772      getNativeText("Start\n2nd Line").length,
   4773      `${description}: removedLength should be the length of the old value`
   4774    );
   4775    is(
   4776      notification?.addedLength,
   4777      getNativeText("Changed\n2nd Line").length,
   4778      `${description}: addedLength should be the length of the new value`
   4779    );
   4780  })();
   4781 
   4782  textarea.style.display = "";
   4783 }
   4784 
   4785 async function runTests()
   4786 {
   4787  await SpecialPowers.pushPrefEnv({
   4788    set: [["test.ime_content_observer.assert_invalid_cache", true]],
   4789  });
   4790 
   4791  textareaInFrame = iframe.contentDocument.getElementById("textarea");
   4792  runBeginInputTransactionMethodTests();
   4793  runReleaseTests();
   4794  runCompositionTests();
   4795  runCompositionWithKeyEventTests();
   4796  runConsumingKeydownBeforeCompositionTests();
   4797  await runKeyTests();
   4798  runInsertTextWithKeyPressTests();
   4799  runErrorTests();
   4800  runCommitCompositionTests();
   4801  await runCallbackTests(false);
   4802  await runCallbackTests(true);
   4803  await runTextNotificationChangesDuringNoFrame();
   4804  await runFocusNotificationTestAfterDrop();
   4805  await runUnloadTests1();
   4806  await runUnloadTests2();
   4807  await runQuerySelectionEventTestAtTextChangeNotification();
   4808  await runIMEStateUpdateTests();
   4809 
   4810  finish();
   4811 }
   4812 
   4813 ]]>
   4814 </script>
   4815 
   4816 </window>