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