summaryrefslogtreecommitdiffstats
path: root/dom/base/test/chrome/window_nsITextInputProcessor.xhtml
diff options
context:
space:
mode:
Diffstat (limited to 'dom/base/test/chrome/window_nsITextInputProcessor.xhtml')
-rw-r--r--dom/base/test/chrome/window_nsITextInputProcessor.xhtml4750
1 files changed, 4750 insertions, 0 deletions
diff --git a/dom/base/test/chrome/window_nsITextInputProcessor.xhtml b/dom/base/test/chrome/window_nsITextInputProcessor.xhtml
new file mode 100644
index 0000000000..291a9e4324
--- /dev/null
+++ b/dom/base/test/chrome/window_nsITextInputProcessor.xhtml
@@ -0,0 +1,4750 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window title="Testing nsITextInputProcessor behavior"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onunload="onunload();">
+<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+<body xmlns="http://www.w3.org/1999/xhtml">
+<div id="display">
+<input id="input" type="text"/><input id="anotherInput" type="text"/><br/>
+<textarea></textarea>
+<iframe id="iframe" width="300" height="150"
+ src="data:text/html,&lt;textarea id='textarea' cols='20' rows='4'&gt;&lt;/textarea&gt;"></iframe><br/>
+<div contenteditable=""><br/></div>
+</div>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+var SimpleTest = window.arguments[0].SimpleTest;
+
+SimpleTest.waitForFocus(runTests, window);
+
+function getHTMLEditor(aWindow) {
+ return SpecialPowers.wrap(aWindow).docShell.editingSession?.getEditorForWindow(aWindow);
+}
+
+function ok(aCondition, aMessage)
+{
+ SimpleTest.ok(aCondition, aMessage);
+}
+
+function is(aLeft, aRight, aMessage)
+{
+ SimpleTest.is(aLeft, aRight, aMessage);
+}
+
+function isnot(aLeft, aRight, aMessage)
+{
+ SimpleTest.isnot(aLeft, aRight, aMessage);
+}
+
+function todo_is(aLeft, aRight, aMessage)
+{
+ SimpleTest.todo_is(aLeft, aRight, aMessage);
+}
+
+function info(aMessage) {
+ SimpleTest.info(aMessage);
+}
+
+function finish()
+{
+ window.close();
+}
+
+function onunload()
+{
+ SimpleTest.finish();
+}
+
+function checkInputEvent(aEvent, aCancelable, aIsComposing, aInputType, aData, aDescription) {
+ if (aEvent.type !== "input" && aEvent.type !== "beforeinput") {
+ throw new Error(`${aDescription}"${aEvent.type}" is not InputEvent`);
+ }
+ ok(InputEvent.isInstance(aEvent), `${aDescription}"${aEvent.type}" event should be dispatched with InputEvent interface`);
+ is(aEvent.cancelable, aCancelable, `${aDescription}"${aEvent.type}" event should ${aCancelable ? "be" : "not be"} cancelable`);
+ is(aEvent.bubbles, true, `${aDescription}"${aEvent.type}" event should always bubble`);
+ is(aEvent.isComposing, aIsComposing, `${aDescription}isComposing of "${aEvent.type}" event should be ${aIsComposing}`);
+ is(aEvent.inputType, aInputType, `${aDescription}inputType of "${aEvent.type}" event should be "${aInputType}"`);
+ is(aEvent.data, aData, `${aDescription}data of "${aEvent.type}" event should be "${aData}"`);
+ is(aEvent.dataTransfer, null, `${aDescription}dataTransfer of "${aEvent.type}" event should be null`);
+ is(aEvent.getTargetRanges().length, 0, `${aDescription}getTargetRanges() of "${aEvent.type}" event should return empty array`);
+}
+
+const kIsMac = (navigator.platform.indexOf("Mac") == 0);
+
+const iframe = document.getElementById("iframe");
+let childWindow = iframe.contentWindow;
+let textareaInFrame;
+const input = document.getElementById("input");
+const textarea = document.querySelector("textarea");
+const otherWindow = window.arguments[0];
+const otherDocument = otherWindow.document;
+const inputInChildWindow = otherDocument.getElementById("input");
+const contenteditable = document.querySelector("div[contenteditable]");
+
+const kLF = navigator.platform.startsWith("Win") ? "\r\n" : "\n";
+function getNativeText(aXPText)
+{
+ if (kLF == "\n") {
+ return aXPText;
+ }
+ return aXPText.replace(/\n/g, kLF);
+}
+
+function createTIP()
+{
+ return Cc["@mozilla.org/text-input-processor;1"].
+ createInstance(Ci.nsITextInputProcessor);
+}
+
+function runBeginInputTransactionMethodTests()
+{
+ var description = "runBeginInputTransactionMethodTests: ";
+ input.value = "";
+ input.focus();
+
+ var simpleCallback = function (aTIP, aNotification)
+ {
+ switch (aNotification.type) {
+ case "request-to-commit":
+ aTIP.commitComposition();
+ break;
+ case "request-to-cancel":
+ aTIP.cancelComposition();
+ break;
+ }
+ return true;
+ };
+
+ var TIP1 = createTIP();
+ var TIP2 = createTIP();
+ isnot(TIP1, TIP2,
+ description + "TIP instances should be different");
+
+ // beginInputTransaction() and beginInputTransactionForTests() can take ownership if there is no composition.
+ ok(TIP1.beginInputTransaction(window, simpleCallback),
+ description + "TIP1.beginInputTransaction(window) should succeed because there is no composition");
+ ok(TIP1.beginInputTransactionForTests(window),
+ description + "TIP1.beginInputTransactionForTests(window) should succeed because there is no composition");
+ ok(TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2.beginInputTransaction(window) should succeed because there is no composition");
+ ok(TIP2.beginInputTransactionForTests(window),
+ description + "TIP2.beginInputTransactionForTests(window) should succeed because there is no composition");
+
+ // Start composition with TIP1, then, other TIPs cannot take ownership during a composition.
+ ok(TIP1.beginInputTransactionForTests(window),
+ description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition");
+ var composingStr = "foo";
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ ok(TIP1.flushPendingComposition(),
+ description + "TIP1.flushPendingComposition() should return true becuase it should be valid composition");
+ is(input.value, composingStr,
+ description + "The input element should have composing string");
+
+ // Composing nsITextInputProcessor instance shouldn't allow initialize it again.
+ try {
+ TIP1.beginInputTransaction(window, simpleCallback);
+ ok(false,
+ "TIP1.beginInputTransaction(window) should cause throwing an exception because it's composing with different purpose");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(window) should cause throwing an exception including NS_ERROR_ALREADY_INITIALIZED because it's composing for tests");
+ }
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow);
+ ok(false,
+ "TIP1.beginInputTransactionForTests(otherWindow) should cause throwing an exception because it's composing on different window");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow) should cause throwing an exception including NS_ERROR_ALREADY_INITIALIZED because it's composing on this window");
+ }
+ ok(TIP1.beginInputTransactionForTests(window),
+ description + "TIP1.beginInputTransactionForTests(window) should succeed because TextEventDispatcher was initialized with same purpose");
+ ok(TIP1.beginInputTransactionForTests(childWindow),
+ description + "TIP1.beginInputTransactionForTests(childWindow) should succeed because TextEventDispatcher was initialized with same purpose and is shared by window and childWindow");
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2.beginInputTransaction(window) should not succeed because there is composition synthesized by TIP1");
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2.beginInputTransactionForTests(window) should not succeed because there is composition synthesized by TIP1");
+ ok(!TIP2.beginInputTransaction(childWindow, simpleCallback),
+ description + "TIP2.beginInputTransaction(childWindow) should not succeed because there is composition synthesized by TIP1");
+ ok(!TIP2.beginInputTransactionForTests(childWindow),
+ description + "TIP2.beginInputTransactionForTests(childWindow) should not succeed because there is composition synthesized by TIP1");
+ ok(TIP2.beginInputTransaction(otherWindow, simpleCallback),
+ description + "TIP2.beginInputTransaction(otherWindow) should succeed because there is composition synthesized by TIP1 but it's in other window");
+ ok(TIP2.beginInputTransactionForTests(otherWindow),
+ description + "TIP2.beginInputTransactionForTests(otherWindow) should succeed because there is composition synthesized by TIP1 but it's in other window");
+
+ // Let's confirm that the composing string is NOT committed by above tests.
+ TIP1.commitComposition();
+ is(input.value, composingStr,
+ description + "TIP1.commitString() without specifying commit string should commit current composition with the last composing string");
+ ok(!TIP1.hasComposition,
+ description + "TIP1.commitString() without specifying commit string should've end composition");
+
+ ok(TIP1.beginInputTransaction(window, simpleCallback),
+ description + "TIP1.beginInputTransaction() should succeed because there is no composition #2");
+ ok(TIP1.beginInputTransactionForTests(window),
+ description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition #2");
+ ok(TIP2.beginInputTransactionForTests(window),
+ description + "TIP2.beginInputTransactionForTests() should succeed because the composition was already committed #2");
+
+ // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during startComposition().
+ var events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from compositionstart event handler during TIP1.startComposition();");
+ }, false);
+ TIP1.beginInputTransaction(window, simpleCallback);
+ TIP1.startComposition();
+ is(events.length, 1,
+ description + "compositionstart event should be fired by TIP1.startComposition()");
+ TIP1.cancelComposition();
+
+ // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during flushPendingComposition().
+ events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from compositionstart event handler during a call of TIP1.flushPendingComposition();");
+ }, {once: true});
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from compositionupdate event handler during a call of TIP1.flushPendingComposition();");
+ }, {once: true});
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from text event handler during a call of TIP1.flushPendingComposition();");
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from beforeinput event handler during a call of TIP1.flushPendingComposition();");
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from input event handler during a call of TIP1.flushPendingComposition();");
+ }, {once: true});
+ TIP1.beginInputTransaction(window, simpleCallback);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ is(events.length, 5,
+ description + "compositionstart, compositionupdate, text, beforeinput and input events should be fired by TIP1.flushPendingComposition()");
+ is(events[0].type, "compositionstart",
+ description + "events[0] should be compositionstart");
+ is(events[1].type, "compositionupdate",
+ description + "events[1] should be compositionupdate");
+ is(events[2].type, "text",
+ description + "events[2] should be text");
+ is(events[3].type, "beforeinput",
+ description + "events[3] should be beforeinput");
+ checkInputEvent(events[3], false, true, "insertCompositionText", composingStr, description);
+ is(events[4].type, "input",
+ description + "events[4] should be input");
+ checkInputEvent(events[4], false, true, "insertCompositionText", composingStr, description);
+ TIP1.cancelComposition();
+
+ // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during commitComposition().
+ events = [];
+ TIP1.beginInputTransaction(window, simpleCallback);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from text event handler during a call of TIP1.commitComposition();");
+ }, {once: true});
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from compositionend event handler during a call of TIP1.commitComposition();");
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from beforeinput event handler during a call of TIP1.commitComposition();");
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from input event handler during a call of TIP1.commitComposition();");
+ }, {once: true});
+ TIP1.commitComposition();
+ is(events.length, 4,
+ description + "text, beforeinput, compositionend and input events should be fired by TIP1.commitComposition()");
+ is(events[0].type, "text",
+ description + "events[0] should be text");
+ is(events[1].type, "beforeinput",
+ description + "events[1] should be beforeinput");
+ checkInputEvent(events[1], false, true, "insertCompositionText", composingStr, description);
+ is(events[2].type, "compositionend",
+ description + "events[2] should be compositionend");
+ is(events[3].type, "input",
+ description + "events[3] should be input");
+ checkInputEvent(events[3], false, false, "insertCompositionText", composingStr, description);
+
+ // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during commitCompositionWith("bar").
+ events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from compositionstart event handler during TIP1.commitCompositionWith(\"bar\");");
+ }, {once: true});
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction during compositionupdate event handler TIP1.commitCompositionWith(\"bar\");");
+ }, {once: true});
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction during text event handler TIP1.commitCompositionWith(\"bar\");");
+ }, {once: true});
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction during compositionend event handler TIP1.commitCompositionWith(\"bar\");");
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction during beforeinput event handler TIP1.commitCompositionWith(\"bar\");");
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction during input event handler TIP1.commitCompositionWith(\"bar\");");
+ }, {once: true});
+ TIP1.beginInputTransaction(window, simpleCallback);
+ TIP1.commitCompositionWith("bar");
+ is(events.length, 6,
+ description + "compositionstart, compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.commitCompositionWith(\"bar\")");
+ is(events[0].type, "compositionstart",
+ description + "events[0] should be compositionstart");
+ is(events[1].type, "compositionupdate",
+ description + "events[1] should be compositionupdate");
+ is(events[2].type, "text",
+ description + "events[2] should be text");
+ is(events[3].type, "beforeinput",
+ description + "events[3] should be beforeinput");
+ checkInputEvent(events[3], false, true, "insertCompositionText", "bar", description);
+ is(events[4].type, "compositionend",
+ description + "events[4] should be compositionend");
+ is(events[5].type, "input",
+ description + "events[5] should be input");
+ checkInputEvent(events[5], false, false, "insertCompositionText", "bar", description);
+
+ // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during cancelComposition().
+ events = [];
+ TIP1.beginInputTransaction(window, simpleCallback);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from compositionupdate event handler during a call of TIP1.cancelComposition();");
+ }, {once: true});
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from text event handler during a call of TIP1.cancelComposition();");
+ }, {once: true});
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from compositionend event handler during a call of TIP1.cancelComposition();");
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from beforeinput event handler during a call of TIP1.cancelComposition();");
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from input event handler during a call of TIP1.cancelComposition();");
+ }, {once: true});
+ TIP1.cancelComposition();
+ is(events.length, 5,
+ description + "compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.cancelComposition()");
+ is(events[0].type, "compositionupdate",
+ description + "events[0] should be compositionupdate");
+ is(events[1].type, "text",
+ description + "events[1] should be text");
+ is(events[2].type, "beforeinput",
+ description + "events[2] should be beforeinput");
+ checkInputEvent(events[2], false, true, "insertCompositionText", "", description);
+ is(events[3].type, "compositionend",
+ description + "events[3] should be compositionend");
+ is(events[4].type, "input",
+ description + "events[4] should be input");
+ checkInputEvent(events[4], false, false, "insertCompositionText", "", description);
+
+ // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during keydown() and keyup().
+ events = [];
+ TIP1.beginInputTransaction(window, simpleCallback);
+ input.addEventListener("keydown", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from keydown event handler during a call of TIP1.keydown();");
+ }, {once: true});
+ input.addEventListener("keypress", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from keypress event handler during a call of TIP1.keydown();");
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from beforeinput event handler during a call of TIP1.keydown();");
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from input event handler during a call of TIP1.keydown();");
+ }, {once: true});
+ input.addEventListener("keyup", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from keyup event handler during a call of TIP1.keyup();");
+ }, {once: true});
+ var keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
+ TIP1.keydown(keyA);
+ TIP1.keyup(keyA);
+ is(events.length, 5,
+ description + "keydown, keypress, beforeinput, input, keyup events should be fired by TIP1.keydown() and TIP1.keyup()");
+ is(events[0].type, "keydown",
+ description + "events[0] should be keydown");
+ is(events[1].type, "keypress",
+ description + "events[1] should be keypress");
+ is(events[2].type, "beforeinput",
+ description + "events[2] should be beforeinput");
+ checkInputEvent(events[2], true, false, "insertText", "a", description);
+ is(events[3].type, "input",
+ description + "events[3] should be input");
+ checkInputEvent(events[3], false, false, "insertText", "a", description);
+ is(events[4].type, "keyup",
+ description + "events[4] should be keyup");
+
+ // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during startComposition().
+ var events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from compositionstart event handler during TIP1.startComposition();");
+ }, false);
+ TIP1.beginInputTransactionForTests(window);
+ TIP1.startComposition();
+ is(events.length, 1,
+ description + "compositionstart event should be fired by TIP1.startComposition()");
+ TIP1.cancelComposition();
+
+ // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during flushPendingComposition().
+ events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from compositionstart event handler during a call of TIP1.flushPendingComposition();");
+ }, {once: true});
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from compositionupdate event handler during a call of TIP1.flushPendingComposition();");
+ }, {once: true});
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from text event handler during a call of TIP1.flushPendingComposition();");
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from beforeinput event handler during a call of TIP1.flushPendingComposition();");
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from input event handler during a call of TIP1.flushPendingComposition();");
+ }, {once: true});
+ TIP1.beginInputTransactionForTests(window);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ is(events.length, 5,
+ description + "compositionstart, compositionupdate, text, beforeinput and input events should be fired by TIP1.flushPendingComposition()");
+ is(events[0].type, "compositionstart",
+ description + "events[0] should be compositionstart");
+ is(events[1].type, "compositionupdate",
+ description + "events[1] should be compositionupdate");
+ is(events[2].type, "text",
+ description + "events[2] should be text");
+ is(events[3].type, "beforeinput",
+ description + "events[3] should be beforeinput");
+ checkInputEvent(events[3], false, true, "insertCompositionText", composingStr, description);
+ is(events[4].type, "input",
+ description + "events[4] should be input");
+ checkInputEvent(events[4], false, true, "insertCompositionText", composingStr, description);
+ TIP1.cancelComposition();
+
+ // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during commitComposition().
+ events = [];
+ TIP1.beginInputTransactionForTests(window, simpleCallback);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from text event handler during a call of TIP1.commitComposition();");
+ }, {once: true});
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from compositionend event handler during a call of TIP1.commitComposition();");
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from beforeinput event handler during a call of TIP1.commitComposition();");
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from input event handler during a call of TIP1.commitComposition();");
+ }, {once: true});
+ TIP1.commitComposition();
+ is(events.length, 4,
+ description + "text, beforeinput, compositionend and input events should be fired by TIP1.commitComposition()");
+ is(events[0].type, "text",
+ description + "events[0] should be text");
+ is(events[1].type, "beforeinput",
+ description + "events[1] should be beforeinput");
+ checkInputEvent(events[1], false, true, "insertCompositionText", composingStr, description);
+ is(events[2].type, "compositionend",
+ description + "events[2] should be compositionend");
+ is(events[3].type, "input",
+ description + "events[3] should be input");
+ checkInputEvent(events[3], false, false, "insertCompositionText", composingStr, description);
+
+ // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during commitCompositionWith("bar").
+ events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from compositionstart event handler during TIP1.commitCompositionWith(\"bar\");");
+ }, {once: true});
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests during compositionupdate event handler TIP1.commitCompositionWith(\"bar\");");
+ }, {once: true});
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests during text event handler TIP1.commitCompositionWith(\"bar\");");
+ }, {once: true});
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests during compositionend event handler TIP1.commitCompositionWith(\"bar\");");
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests during beforeinput event handler TIP1.commitCompositionWith(\"bar\");");
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests during input event handler TIP1.commitCompositionWith(\"bar\");");
+ }, {once: true});
+ TIP1.beginInputTransactionForTests(window);
+ TIP1.commitCompositionWith("bar");
+ is(events.length, 6,
+ description + "compositionstart, compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.commitCompositionWith(\"bar\")");
+ is(events[0].type, "compositionstart",
+ description + "events[0] should be compositionstart");
+ is(events[1].type, "compositionupdate",
+ description + "events[1] should be compositionupdate");
+ is(events[2].type, "text",
+ description + "events[2] should be text");
+ is(events[3].type, "beforeinput",
+ description + "events[3] should be beforeinput");
+ checkInputEvent(events[3], false, true, "insertCompositionText", "bar", description);
+ is(events[4].type, "compositionend",
+ description + "events[4] should be compositionend");
+ is(events[5].type, "input",
+ description + "events[5] should be input");
+ checkInputEvent(events[5], false, false, "insertCompositionText", "bar", description);
+
+ // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during cancelComposition().
+ events = [];
+ TIP1.beginInputTransactionForTests(window, simpleCallback);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from compositionupdate event handler during a call of TIP1.cancelComposition();");
+ }, {once: true});
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from text event handler during a call of TIP1.cancelComposition();");
+ }, {once: true});
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from compositionend event handler during a call of TIP1.cancelComposition();");
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from beforeinput event handler during a call of TIP1.cancelComposition();");
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from input event handler during a call of TIP1.cancelComposition();");
+ }, {once: true});
+ TIP1.cancelComposition();
+ is(events.length, 5,
+ description + "compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.cancelComposition()");
+ is(events[0].type, "compositionupdate",
+ description + "events[0] should be compositionupdate");
+ is(events[1].type, "text",
+ description + "events[1] should be text");
+ is(events[2].type, "beforeinput",
+ description + "events[2] should be beforeinput");
+ checkInputEvent(events[2], false, true, "insertCompositionText", "", description);
+ is(events[3].type, "compositionend",
+ description + "events[3] should be compositionend");
+ is(events[4].type, "input",
+ description + "events[4] should be input");
+ checkInputEvent(events[4], false, false, "insertCompositionText", "", description);
+
+ // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during keydown() and keyup().
+ events = [];
+ TIP1.beginInputTransactionForTests(window);
+ input.addEventListener("keydown", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests for tests from keydown event handler during a call of TIP1.keydown();");
+ }, {once: true});
+ input.addEventListener("keypress", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from keypress event handler during a call of TIP1.keydown();");
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from beforeinput event handler during a call of TIP1.keydown();");
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from input event handler during a call of TIP1.keydown();");
+ }, {once: true});
+ input.addEventListener("keyup", function (aEvent) {
+ events.push(aEvent);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from keyup event handler during a call of TIP1.keyup();");
+ }, {once: true});
+ var keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
+ TIP1.keydown(keyA);
+ TIP1.keyup(keyA);
+ is(events.length, 5,
+ description + "keydown, keypress, beforeinput, input, keyup events should be fired by TIP1.keydown() and TIP1.keyup()");
+ is(events[0].type, "keydown",
+ description + "events[0] should be keydown");
+ is(events[1].type, "keypress",
+ description + "events[1] should be keypress");
+ is(events[2].type, "beforeinput",
+ description + "events[2] should be beforeinput");
+ checkInputEvent(events[2], true, false, "insertText", "a", description);
+ is(events[3].type, "input",
+ description + "events[3] should be input");
+ checkInputEvent(events[3], false, false, "insertText", "a", description);
+ is(events[4].type, "keyup",
+ description + "events[4] should be keyup");
+
+ // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during startComposition().
+ var events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionstart\" should throw an exception during startComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionstart\" should cause NS_ERROR_ALREADY_INITIALIZED during startComposition()");
+ }
+ }, {once: true});
+ TIP1.beginInputTransaction(window, simpleCallback);
+ TIP1.startComposition();
+ is(events.length, 1,
+ description + "compositionstart event should be fired by TIP1.startComposition()");
+ TIP1.cancelComposition();
+
+ // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during flushPendingComposition().
+ events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionstart\" should throw an exception during flushPendingComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionstart\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionupdate\" should throw an exception during flushPendingComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionupdate\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should throw an exception during flushPendingComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during flushPendingComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should throw an exception during flushPendingComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
+ }
+ }, {once: true});
+ TIP1.beginInputTransaction(window, simpleCallback);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ is(events.length, 5,
+ description + "compositionstart, compositionupdate, text, beforeinput and input events should be fired by TIP1.flushPendingComposition()");
+ is(events[0].type, "compositionstart",
+ description + "events[0] should be compositionstart");
+ is(events[1].type, "compositionupdate",
+ description + "events[1] should be compositionupdate");
+ is(events[2].type, "text",
+ description + "events[2] should be text");
+ is(events[3].type, "beforeinput",
+ description + "events[3] should be beforeinput");
+ checkInputEvent(events[3], false, true, "insertCompositionText", composingStr, description);
+ is(events[4].type, "input",
+ description + "events[4] should be input");
+ checkInputEvent(events[4], false, true, "insertCompositionText", composingStr, description);
+ TIP1.cancelComposition();
+
+ // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during commitComposition().
+ events = [];
+ TIP1.beginInputTransaction(window, simpleCallback);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should throw an exception during commitComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionend\" should throw an exception during commitComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionend\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during commitComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should throw an exception during commitComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()");
+ }
+ }, {once: true});
+ TIP1.commitComposition();
+ is(events.length, 4,
+ description + "text, beforeinput, compositionend and input events should be fired by TIP1.commitComposition()");
+ is(events[0].type, "text",
+ description + "events[0] should be text");
+ is(events[1].type, "beforeinput",
+ description + "events[1] should be beforeinput");
+ checkInputEvent(events[1], false, true, "insertCompositionText", composingStr, description);
+ is(events[2].type, "compositionend",
+ description + "events[2] should be compositionend");
+ is(events[3].type, "input",
+ description + "events[3] should be input");
+ checkInputEvent(events[3], false, false, "insertCompositionText", composingStr, description);
+
+ // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during commitCompositionWith("bar");.
+ events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionstart\" should throw an exception during commitCompositionWith(\"bar\")");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionstart\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
+ }
+ }, {once: true});
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionupdate\" should throw an exception during commitCompositionWith(\"bar\")");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionupdate\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
+ }
+ }, {once: true});
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should throw an exception during commitCompositionWith(\"bar\")");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
+ }
+ }, {once: true});
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionend\" should throw an exception during commitCompositionWith(\"bar\")");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionend\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
+ }
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during commitCompositionWith(\"bar\")");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
+ }
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should throw an exception during commitCompositionWith(\"bar\")");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
+ }
+ }, {once: true});
+ TIP1.beginInputTransaction(window, simpleCallback);
+ TIP1.commitCompositionWith("bar");
+ is(events.length, 6,
+ description + "compositionstart, compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.commitCompositionWith(\"bar\")");
+ is(events[0].type, "compositionstart",
+ description + "events[0] should be compositionstart");
+ is(events[1].type, "compositionupdate",
+ description + "events[1] should be compositionupdate");
+ is(events[2].type, "text",
+ description + "events[2] should be text");
+ is(events[3].type, "beforeinput",
+ description + "events[3] should be beforeinput");
+ checkInputEvent(events[3], false, true, "insertCompositionText", "bar", description);
+ is(events[4].type, "compositionend",
+ description + "events[4] should be compositionend");
+ is(events[5].type, "input",
+ description + "events[5] should be input");
+ checkInputEvent(events[5], false, false, "insertCompositionText", "bar", description);
+
+ // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during cancelComposition();.
+ events = [];
+ TIP1.beginInputTransaction(window, simpleCallback);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionupdate\" should throw an exception during cancelComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionupdate\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should throw an exception during cancelComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionend\" should throw an exception during cancelComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionend\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during cancelComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should throw an exception during cancelComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
+ }
+ }, {once: true});
+ TIP1.cancelComposition();
+ is(events.length, 5,
+ description + "compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.cancelComposition()");
+ is(events[0].type, "compositionupdate",
+ description + "events[0] should be compositionupdate");
+ is(events[1].type, "text",
+ description + "events[1] should be text");
+ is(events[2].type, "beforeinput",
+ description + "events[2] should be beforeinput");
+ checkInputEvent(events[2], false, true, "insertCompositionText", "", description);
+ is(events[3].type, "compositionend",
+ description + "events[3] should be compositionend");
+ is(events[4].type, "input",
+ description + "events[4] should be input");
+ checkInputEvent(events[4], false, false, "insertCompositionText", "", description);
+
+ // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during keydown() and keyup();.
+ events = [];
+ TIP1.beginInputTransaction(window, simpleCallback);
+ input.addEventListener("keydown", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"keydown\" should throw an exception during keydown()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"keydown\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()");
+ }
+ }, {once: true});
+ input.addEventListener("keypress", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"keypress\" should throw an exception during keydown()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"keypress\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()");
+ }
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during keydown()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()");
+ }
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should throw an exception during keydown()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()");
+ }
+ }, {once: true});
+ input.addEventListener("keyup", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransaction(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"keyup\" should throw an exception during keyup()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"keyup\" should cause NS_ERROR_ALREADY_INITIALIZED during keyup()");
+ }
+ }, {once: true});
+ var keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
+ TIP1.keydown(keyA);
+ TIP1.keyup(keyA);
+ is(events.length, 5,
+ description + "keydown, keypress, beforeinput, input, keyup events should be fired by TIP1.keydown() and TIP1.keyup()");
+ is(events[0].type, "keydown",
+ description + "events[0] should be keydown");
+ is(events[1].type, "keypress",
+ description + "events[1] should be keypress");
+ is(events[2].type, "beforeinput",
+ description + "events[2] should be beforeinput");
+ checkInputEvent(events[2], true, false, "insertText", "a", description);
+ is(events[3].type, "input",
+ description + "events[3] should be input");
+ checkInputEvent(events[3], false, false, "insertText", "a", description);
+ is(events[4].type, "keyup",
+ description + "events[4] should be keyup");
+
+ // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during startComposition().
+ var events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionstart\" should throw an exception during startComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionstart\" should cause NS_ERROR_ALREADY_INITIALIZED during startComposition()");
+ }
+ }, {once: true});
+ TIP1.beginInputTransactionForTests(window, simpleCallback);
+ TIP1.startComposition();
+ is(events.length, 1,
+ description + "compositionstart event should be fired by TIP1.startComposition()");
+ TIP1.cancelComposition();
+
+ // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during flushPendingComposition().
+ events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionstart\" should throw an exception during flushPendingComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionstart\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionupdate\" should throw an exception during flushPendingComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionupdate\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should throw an exception during flushPendingComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during flushPendingComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should throw an exception during flushPendingComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()");
+ }
+ }, {once: true});
+ TIP1.beginInputTransactionForTests(window, simpleCallback);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ is(events.length, 5,
+ description + "compositionstart, compositionupdate, text, beforeinput and input events should be fired by TIP1.flushPendingComposition()");
+ is(events[0].type, "compositionstart",
+ description + "events[0] should be compositionstart");
+ is(events[1].type, "compositionupdate",
+ description + "events[1] should be compositionupdate");
+ is(events[2].type, "text",
+ description + "events[2] should be text");
+ is(events[3].type, "beforeinput",
+ description + "events[3] should be beforeinput");
+ checkInputEvent(events[3], false, true, "insertCompositionText", composingStr, description);
+ is(events[4].type, "input",
+ description + "events[4] should be input");
+ checkInputEvent(events[4], false, true, "insertCompositionText", composingStr, description);
+ TIP1.cancelComposition();
+
+ // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during commitComposition().
+ events = [];
+ TIP1.beginInputTransactionForTests(window, simpleCallback);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should throw an exception during commitComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionend\" should throw an exception during commitComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionend\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during commitComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should throw an exception during commitComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()");
+ }
+ }, {once: true});
+ TIP1.commitComposition();
+ is(events.length, 4,
+ description + "text, beforeinput, compositionend and input events should be fired by TIP1.commitComposition()");
+ is(events[0].type, "text",
+ description + "events[0] should be text");
+ is(events[1].type, "beforeinput",
+ description + "events[1] should be beforeinput");
+ checkInputEvent(events[1], false, true, "insertCompositionText", composingStr, description);
+ is(events[2].type, "compositionend",
+ description + "events[2] should be compositionend");
+ is(events[3].type, "input",
+ description + "events[3] should be input");
+ checkInputEvent(events[3], false, false, "insertCompositionText", composingStr, description);
+
+ // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during commitCompositionWith("bar");.
+ events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionstart\" should throw an exception during commitCompositionWith(\"bar\")");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionstart\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
+ }
+ }, {once: true});
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionupdate\" should throw an exception during commitCompositionWith(\"bar\")");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionupdate\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
+ }
+ }, {once: true});
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should throw an exception during commitCompositionWith(\"bar\")");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
+ }
+ }, {once: true});
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionend\" should throw an exception during commitCompositionWith(\"bar\")");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionend\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
+ }
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during commitCompositionWith(\"bar\")");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
+ }
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should throw an exception during commitCompositionWith(\"bar\")");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")");
+ }
+ }, {once: true});
+ TIP1.beginInputTransactionForTests(window, simpleCallback);
+ TIP1.commitCompositionWith("bar");
+ is(events.length, 6,
+ description + "compositionstart, compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.commitCompositionWith(\"bar\")");
+ is(events[0].type, "compositionstart",
+ description + "events[0] should be compositionstart");
+ is(events[1].type, "compositionupdate",
+ description + "events[1] should be compositionupdate");
+ is(events[2].type, "text",
+ description + "events[2] should be text");
+ is(events[3].type, "beforeinput",
+ description + "events[3] should be beforeinput");
+ checkInputEvent(events[3], false, true, "insertCompositionText", "bar", description);
+ is(events[4].type, "compositionend",
+ description + "events[4] should be compositionend");
+ is(events[5].type, "input",
+ description + "events[5] should be input");
+ checkInputEvent(events[5], false, false, "insertCompositionText", "bar", description);
+
+ // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during cancelComposition();.
+ events = [];
+ TIP1.beginInputTransactionForTests(window, simpleCallback);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionupdate\" should throw an exception during cancelComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionupdate\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should throw an exception during cancelComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionend\" should throw an exception during cancelComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionend\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during cancelComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
+ }
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should throw an exception during cancelComposition()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()");
+ }
+ }, {once: true});
+ TIP1.cancelComposition();
+ is(events.length, 5,
+ description + "compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.cancelComposition()");
+ is(events[0].type, "compositionupdate",
+ description + "events[0] should be compositionupdate");
+ is(events[1].type, "text",
+ description + "events[1] should be text");
+ is(events[2].type, "beforeinput",
+ description + "events[2] should be beforeinput");
+ checkInputEvent(events[2], false, true, "insertCompositionText", "", description);
+ is(events[3].type, "compositionend",
+ description + "events[3] should be compositionend");
+ is(events[4].type, "input",
+ description + "events[4] should be input");
+ checkInputEvent(events[4], false, false, "insertCompositionText", "", description);
+
+ // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during keydown() and keyup();.
+ events = [];
+ TIP1.beginInputTransactionForTests(window, simpleCallback);
+ input.addEventListener("keydown", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"keydown\" should throw an exception during keydown()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"keydown\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()");
+ }
+ }, {once: true});
+ input.addEventListener("keypress", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"keypress\" should throw an exception during keydown()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"keypress\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()");
+ }
+ }, {once: true});
+ input.addEventListener("beforeinput", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during keydown()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()");
+ }
+ }, {once: true});
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should throw an exception during keydown()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()");
+ }
+ }, {once: true});
+ input.addEventListener("keyup", function (aEvent) {
+ events.push(aEvent);
+ try {
+ TIP1.beginInputTransactionForTests(otherWindow, simpleCallback);
+ ok(false,
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"keyup\" should throw an exception during keyup()");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"),
+ description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"keyup\" should cause NS_ERROR_ALREADY_INITIALIZED during keyup()");
+ }
+ }, {once: true});
+ var keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
+ TIP1.keydown(keyA);
+ TIP1.keyup(keyA);
+ is(events.length, 5,
+ description + "keydown, keypress, beforeinput, input, keyup events should be fired by TIP1.keydown() and TIP1.keyup()");
+ is(events[0].type, "keydown",
+ description + "events[0] should be keydown");
+ is(events[1].type, "keypress",
+ description + "events[1] should be keypress");
+ is(events[2].type, "beforeinput",
+ description + "events[2] should be beforeinput");
+ checkInputEvent(events[2], true, false, "insertText", "a", description);
+ is(events[3].type, "input",
+ description + "events[3] should be input");
+ checkInputEvent(events[3], false, false, "insertText", "a", description);
+ is(events[4].type, "keyup",
+ description + "events[4] should be keyup");
+
+ // Let's check if startComposition() throws an exception after ownership is stolen.
+ input.value = "";
+ ok(TIP1.beginInputTransactionForTests(window),
+ description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition");
+ ok(TIP2.beginInputTransactionForTests(window),
+ description + "TIP2.beginInputTransactionForTests() should succeed because there is no composition");
+ try {
+ TIP1.startComposition();
+ ok(false,
+ description + "TIP1.startComposition() should cause throwing an exception because TIP2 took the ownership");
+ TIP1.cancelComposition();
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_NOT_INITIALIZED"),
+ description + "TIP1.startComposition() should cause throwing an exception including NS_ERROR_NOT_INITIALIZED");
+ } finally {
+ is(input.value, "",
+ description + "The input element should not have commit string");
+ }
+
+ // Let's check if flushPendingComposition() throws an exception after ownership is stolen.
+ ok(TIP1.beginInputTransactionForTests(window),
+ description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition");
+ ok(TIP2.beginInputTransactionForTests(window),
+ description + "TIP2.beginInputTransactionForTests() should succeed because there is no composition");
+ input.value = "";
+ try {
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition()
+ ok(false,
+ description + "TIP1.flushPendingComposition() should cause throwing an exception because TIP2 took the ownership");
+ TIP1.cancelComposition();
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_NOT_INITIALIZED"),
+ description + "TIP1.flushPendingComposition() should cause throwing an exception including NS_ERROR_NOT_INITIALIZED");
+ } finally {
+ is(input.value, "",
+ description + "The input element should not have commit string");
+ }
+
+ // Let's check if commitCompositionWith("bar") throws an exception after ownership is stolen.
+ ok(TIP1.beginInputTransactionForTests(window),
+ description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition");
+ ok(TIP2.beginInputTransactionForTests(window),
+ description + "TIP2.beginInputTransactionForTests() should succeed because there is no composition");
+ input.value = "";
+ try {
+ TIP1.commitCompositionWith("bar");
+ ok(false,
+ description + "TIP1.commitCompositionWith(\"bar\") should cause throwing an exception because TIP2 took the ownership");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_NOT_INITIALIZED"),
+ description + "TIP1.commitCompositionWith(\"bar\") should cause throwing an exception including NS_ERROR_NOT_INITIALIZED");
+ } finally {
+ is(input.value, "",
+ description + "The input element should not have commit string");
+ }
+
+ // Let's check if keydown() throws an exception after ownership is stolen.
+ ok(TIP1.beginInputTransactionForTests(window),
+ description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition");
+ ok(TIP2.beginInputTransactionForTests(window),
+ description + "TIP2.beginInputTransactionForTests() should succeed because there is no composition");
+ input.value = "";
+ try {
+ var keyF = new KeyboardEvent("", { key: "f", code: "KeyF", keyCode: KeyboardEvent.DOM_VK_F });
+ TIP1.keydown(keyF);
+ ok(false,
+ description + "TIP1.keydown(keyF) should cause throwing an exception because TIP2 took the ownership");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_NOT_INITIALIZED"),
+ description + "TIP1.keydown(keyF) should cause throwing an exception including NS_ERROR_NOT_INITIALIZED");
+ } finally {
+ is(input.value, "",
+ description + "The input element should not be modified");
+ }
+
+ // Let's check if keyup() throws an exception after ownership is stolen.
+ ok(TIP1.beginInputTransactionForTests(window),
+ description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition");
+ ok(TIP2.beginInputTransactionForTests(window),
+ description + "TIP2.beginInputTransactionForTests() should succeed because there is no composition");
+ input.value = "";
+ try {
+ var keyF = new KeyboardEvent("", { key: "f", code: "KeyF", keyCode: KeyboardEvent.DOM_VK_F });
+ TIP1.keyup(keyF);
+ ok(false,
+ description + "TIP1.keyup(keyF) should cause throwing an exception because TIP2 took the ownership");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_NOT_INITIALIZED"),
+ description + "TIP1.keyup(keyF) should cause throwing an exception including NS_ERROR_NOT_INITIALIZED");
+ } finally {
+ is(input.value, "",
+ description + "The input element should not be modified");
+ }
+
+ // aCallback of nsITextInputProcessor.beginInputTransaction() must not be omitted.
+ try {
+ TIP1.beginInputTransaction(window);
+ ok(false,
+ description + "TIP1.beginInputTransaction(window) should be failed since aCallback is omitted");
+ } catch (e) {
+ ok(e.message.includes("Not enough arguments"),
+ description + "TIP1.beginInputTransaction(window) should cause throwing an exception including \"Not enough arguments\" since aCallback is omitted");
+ }
+
+ // aCallback of nsITextInputProcessor.beginInputTransaction() must not be undefined.
+ try {
+ TIP1.beginInputTransaction(window, undefined);
+ ok(false,
+ description + "TIP1.beginInputTransaction(window, undefined) should be failed since aCallback is undefined");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "TIP1.beginInputTransaction(window, undefined) should cause throwing an exception including NS_ERROR_ILLEGAL_VALUE since aCallback is undefined");
+ }
+
+ // aCallback of nsITextInputProcessor.beginInputTransaction() must not be null.
+ try {
+ TIP1.beginInputTransaction(window, null);
+ ok(false,
+ description + "TIP1.beginInputTransaction(window, null) should be failed since aCallback is null");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "TIP1.beginInputTransaction(window, null) should cause throwing an exception including NS_ERROR_ILLEGAL_VALUE since aCallback is null");
+ }
+}
+
+function runReleaseTests()
+{
+ var description = "runReleaseTests(): ";
+
+ var TIP = createTIP();
+ ok(TIP.beginInputTransactionForTests(window),
+ description + "TIP.beginInputTransactionForTests() should succeed");
+
+ input.value = "";
+ input.focus();
+
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition();
+ is(input.value, "foo",
+ description + "the input should have composition string");
+
+ // Release the TIP
+ TIP = null;
+ // Needs to run GC forcibly for testing this.
+ Cu.forceGC();
+
+ is(input.value, "",
+ description + "the input should be empty because the composition should be canceled");
+
+ TIP = createTIP();
+ ok(TIP.beginInputTransactionForTests(window),
+ description + "TIP.beginInputTransactionForTests() should succeed #2");
+}
+
+function runCompositionTests()
+{
+ var description = "runCompositionTests(): ";
+
+ var TIP = createTIP();
+ ok(TIP.beginInputTransactionForTests(window),
+ description + "TIP.beginInputTransactionForTests() should succeed");
+
+ var events;
+
+ function reset()
+ {
+ events = [];
+ }
+
+ function handler(aEvent)
+ {
+ events.push({ "type": aEvent.type, "data": aEvent.data });
+ }
+
+ window.addEventListener("compositionstart", handler, false);
+ window.addEventListener("compositionupdate", handler, false);
+ window.addEventListener("compositionend", handler, false);
+
+ input.value = "";
+ input.focus();
+
+ // nsITextInputProcessor.startComposition()
+ reset();
+ TIP.startComposition();
+ is(events.length, 1,
+ description + "startComposition() should cause only compositionstart");
+ is(events[0].type, "compositionstart",
+ description + "startComposition() should cause only compositionstart");
+ is(input.value, "",
+ description + "startComposition() shouldn't modify the focused editor");
+
+ // Setting composition string "foo" as a raw clause
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+
+ reset();
+ TIP.flushPendingComposition();
+ is(events.length, 1,
+ description + "flushPendingComposition() after startComposition() should cause compositionupdate");
+ is(events[0].type, "compositionupdate",
+ description + "flushPendingComposition() after startComposition() should cause compositionupdate");
+ is(events[0].data, "foo",
+ description + "compositionupdate caused by flushPendingComposition() should have new composition string in its data");
+ is(input.value, "foo",
+ description + "modifying composition string should cause modifying the focused editor");
+
+ // Changing the raw clause to a selected clause
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_SELECTED_CLAUSE);
+
+ reset();
+ TIP.flushPendingComposition();
+ is(events.length, 0,
+ description + "flushPendingComposition() changing only clause information shouldn't cause compositionupdate");
+ is(input.value, "foo",
+ description + "modifying composition clause shouldn't cause modifying the focused editor");
+
+ // Separating the selected clause to two clauses
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(2, TIP.ATTR_SELECTED_CLAUSE);
+ TIP.appendClauseToPendingComposition(1, TIP.ATTR_CONVERTED_CLAUSE);
+ TIP.setCaretInPendingComposition(2);
+
+ reset();
+ TIP.flushPendingComposition();
+ is(events.length, 0,
+ description + "flushPendingComposition() separating a clause information shouldn't cause compositionupdate");
+ is(input.value, "foo",
+ description + "separating composition clause shouldn't cause modifying the focused editor");
+
+ // Modifying the composition string
+ TIP.setPendingCompositionString("FOo");
+ TIP.appendClauseToPendingComposition(2, TIP.ATTR_SELECTED_CLAUSE);
+ TIP.appendClauseToPendingComposition(1, TIP.ATTR_CONVERTED_CLAUSE);
+ TIP.setCaretInPendingComposition(2);
+
+ reset();
+ TIP.flushPendingComposition();
+ is(events.length, 1,
+ description + "flushPendingComposition() causing modifying composition string should cause compositionupdate");
+ is(events[0].type, "compositionupdate",
+ description + "flushPendingComposition() causing modifying composition string should cause compositionupdate");
+ is(events[0].data, "FOo",
+ description + "compositionupdate caused by flushPendingComposition() should have new composition string in its data");
+ is(input.value, "FOo",
+ description + "modifying composition clause shouldn't cause modifying the focused editor");
+
+ // Committing the composition string
+ reset();
+ TIP.commitComposition();
+ is(events.length, 1,
+ description + "commitComposition() should cause compositionend but shouldn't cause compositionupdate");
+ is(events[0].type, "compositionend",
+ description + "commitComposition() should cause compositionend");
+ is(events[0].data, "FOo",
+ description + "compositionend caused by commitComposition() should have the committed string in its data");
+ is(input.value, "FOo",
+ description + "commitComposition() shouldn't cause modifying the focused editor");
+
+ // Starting new composition without a call of startComposition()
+ TIP.setPendingCompositionString("bar");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+
+ reset();
+ TIP.flushPendingComposition();
+ is(events.length, 2,
+ description + "flushPendingComposition() without a call of startComposition() should cause both compositionstart and compositionupdate");
+ is(events[0].type, "compositionstart",
+ description + "flushPendingComposition() without a call of startComposition() should cause compositionstart");
+ is(events[1].type, "compositionupdate",
+ description + "flushPendingComposition() without a call of startComposition() should cause compositionupdate after compositionstart");
+ is(events[1].data, "bar",
+ description + "compositionupdate caused by flushPendingComposition() without a call of startComposition() should have the composition string in its data");
+ is(input.value, "FOobar",
+ description + "new composition string should cause appending composition string to the focused editor");
+
+ // Canceling the composition
+ reset();
+ TIP.cancelComposition();
+ is(events.length, 2,
+ description + "cancelComposition() should cause both compositionupdate and compositionend");
+ is(events[0].type, "compositionupdate",
+ description + "cancelComposition() should cause compositionupdate");
+ is(events[0].data, "",
+ description + "compositionupdate caused by cancelComposition() should have empty string in its data");
+ is(events[1].type, "compositionend",
+ description + "cancelComposition() should cause compositionend after compositionupdate");
+ is(events[1].data, "",
+ description + "compositionend caused by cancelComposition() should have empty string in its data");
+ is(input.value, "FOo",
+ description + "canceled composition string should be removed from the focused editor");
+
+ // Starting composition explicitly and canceling it
+ reset();
+ TIP.startComposition();
+ TIP.cancelComposition();
+ is(events.length, 2,
+ description + "canceling composition immediately after startComposition() should cause compositionstart and compositionend");
+ is(events[0].type, "compositionstart",
+ description + "canceling composition immediately after startComposition() should cause compositionstart first");
+ is(events[1].type, "compositionend",
+ description + "canceling composition immediately after startComposition() should cause compositionend after compositionstart");
+ is(events[1].data, "",
+ description + "compositionend caused by canceling composition should have empty string in its data");
+ is(input.value, "FOo",
+ description + "canceling composition shouldn't modify the focused editor");
+
+ // Create composition for next test.
+ TIP.setPendingCompositionString("bar");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.flushPendingComposition();
+ is(input.value, "FOobar",
+ description + "The focused editor should have new composition string \"bar\"");
+
+ // Allow to set empty composition string
+ reset();
+ TIP.flushPendingComposition();
+ is(events.length, 1,
+ description + "making composition string empty should cause only compositionupdate");
+ is(events[0].type, "compositionupdate",
+ description + "making composition string empty should cause compositionupdate");
+ is(events[0].data, "",
+ description + "compositionupdate caused by making composition string empty should have empty string in its data");
+
+ // Allow to insert new composition string without compositionend/compositionstart
+ TIP.setPendingCompositionString("buzz");
+ TIP.appendClauseToPendingComposition(4, TIP.ATTR_RAW_CLAUSE);
+
+ reset();
+ TIP.flushPendingComposition();
+ is(events.length, 1,
+ description + "modifying composition string from empty string should cause only compositionupdate");
+ is(events[0].type, "compositionupdate",
+ description + "modifying composition string from empty string should cause compositionupdate");
+ is(events[0].data, "buzz",
+ description + "compositionupdate caused by modifying composition string from empty string should have new composition string in its data");
+ is(input.value, "FOobuzz",
+ description + "new composition string should be appended to the focused editor");
+
+ // Committing with different string
+ reset();
+ TIP.commitCompositionWith("bar");
+ is(events.length, 2,
+ description + "committing with different string should cause compositionupdate and compositionend");
+ is(events[0].type, "compositionupdate",
+ description + "committing with different string should cause compositionupdate first");
+ is(events[0].data, "bar",
+ description + "compositionupdate caused by committing with different string should have the committing string in its data");
+ is(events[1].type, "compositionend",
+ description + "committing with different string should cause compositionend after compositionupdate");
+ is(events[1].data, "bar",
+ description + "compositionend caused by committing with different string should have the committing string in its data");
+ is(input.value, "FOobar",
+ description + "new committed string should be appended to the focused editor");
+
+ // Appending new composition string
+ TIP.setPendingCompositionString("buzz");
+ TIP.appendClauseToPendingComposition(4, TIP.ATTR_RAW_CLAUSE);
+ TIP.flushPendingComposition();
+ is(input.value, "FOobarbuzz",
+ description + "new composition string should be appended to the focused editor");
+
+ // Committing with same string
+ reset();
+ TIP.commitCompositionWith("buzz");
+ is(events.length, 1,
+ description + "committing with same string should cause only compositionend");
+ is(events[0].type, "compositionend",
+ description + "committing with same string should cause compositionend");
+ is(events[0].data, "buzz",
+ description + "compositionend caused by committing with same string should have the committing string in its data");
+ is(input.value, "FOobarbuzz",
+ description + "new committed string should be appended to the focused editor");
+
+ // Inserting commit string directly
+ reset();
+ TIP.commitCompositionWith("boo!");
+ is(events.length, 3,
+ description + "committing text directly should cause compositionstart, compositionupdate and compositionend");
+ is(events[0].type, "compositionstart",
+ description + "committing text directly should cause compositionstart first");
+ is(events[1].type, "compositionupdate",
+ description + "committing text directly should cause compositionupdate after compositionstart");
+ is(events[1].data, "boo!",
+ description + "compositionupdate caused by committing text directly should have the committing text in its data");
+ is(events[2].type, "compositionend",
+ description + "committing text directly should cause compositionend after compositionupdate");
+ is(events[2].data, "boo!",
+ description + "compositionend caused by committing text directly should have the committing text in its data");
+ is(input.value, "FOobarbuzzboo!",
+ description + "committing text directly should append the committing text to the focused editor");
+
+ window.removeEventListener("compositionstart", handler, false);
+ window.removeEventListener("compositionupdate", handler, false);
+ window.removeEventListener("compositionend", handler, false);
+}
+
+function runCompositionWithKeyEventTests()
+{
+ var description = "runCompositionWithKeyEventTests(): ";
+
+ var TIP = createTIP();
+ ok(TIP.beginInputTransactionForTests(window),
+ description + "TIP.beginInputTransactionForTests() should succeed");
+
+ var events;
+
+ function reset()
+ {
+ events = [];
+ }
+
+ function handler(aEvent)
+ {
+ events.push(aEvent);
+ }
+
+ window.addEventListener("compositionstart", handler, false);
+ window.addEventListener("compositionupdate", handler, false);
+ window.addEventListener("compositionend", handler, false);
+ window.addEventListener("keydown", handler, false);
+ window.addEventListener("keypress", handler, false);
+ window.addEventListener("keyup", handler, false);
+
+ input.value = "";
+ input.focus();
+
+ var printableKeyEvent = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
+ var enterKeyEvent = new KeyboardEvent("", { key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
+ var escKeyEvent = new KeyboardEvent("", { key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE });
+ var convertKeyEvent = new KeyboardEvent("", { key: "Convert", code: "Convert", keyCode: KeyboardEvent.DOM_VK_CONVERT });
+ var backspaceKeyEvent = new KeyboardEvent("", { key: "Backspace", code: "Backspace", keyCode: KeyboardEvent.DOM_VK_BACK_SPACE });
+
+ Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", false);
+
+ // nsITextInputProcessor.startComposition()
+ reset();
+ TIP.startComposition(printableKeyEvent);
+ is(events.length, 2,
+ description + "startComposition(printableKeyEvent) should cause keydown and compositionstart");
+ is(events[0].type, "keydown",
+ description + "startComposition(printableKeyEvent) should cause keydown");
+ is(events[1].type, "compositionstart",
+ description + "startComposition(printableKeyEvent) should cause compositionstart");
+ is(input.value, "",
+ description + "startComposition(printableKeyEvent) shouldn't modify the focused editor");
+
+ // Setting composition string "foo" as a raw clause
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+
+ reset();
+ TIP.flushPendingComposition(printableKeyEvent);
+ is(events.length, 1,
+ description + "flushPendingComposition(KeyupInternal) after startComposition() should cause compositionupdate");
+ is(events[0].type, "compositionupdate",
+ description + "flushPendingComposition(KeyupInternal) after startComposition() should cause compositionupdate");
+ is(events[0].data, "foo",
+ description + "compositionupdate caused by flushPendingComposition(KeyupInternal) should have new composition string in its data");
+ is(input.value, "foo",
+ description + "modifying composition string should cause modifying the focused editor");
+
+ // Changing the raw clause to a selected clause
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_SELECTED_CLAUSE);
+
+ reset();
+ TIP.flushPendingComposition(convertKeyEvent);
+ is(events.length, 0,
+ description + "flushPendingComposition(convertKeyEvent) changing only clause information shouldn't cause compositionupdate");
+ is(input.value, "foo",
+ description + "modifying composition clause shouldn't cause modifying the focused editor");
+
+ // Separating the selected clause to two clauses
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(2, TIP.ATTR_SELECTED_CLAUSE);
+ TIP.appendClauseToPendingComposition(1, TIP.ATTR_CONVERTED_CLAUSE);
+ TIP.setCaretInPendingComposition(2);
+
+ reset();
+ TIP.flushPendingComposition(convertKeyEvent);
+ is(events.length, 0,
+ description + "flushPendingComposition(convertKeyEvent) separating a clause information shouldn't cause compositionupdate");
+ is(input.value, "foo",
+ description + "separating composition clause shouldn't cause modifying the focused editor");
+
+ // Modifying the composition string
+ TIP.setPendingCompositionString("FOo");
+ TIP.appendClauseToPendingComposition(2, TIP.ATTR_SELECTED_CLAUSE);
+ TIP.appendClauseToPendingComposition(1, TIP.ATTR_CONVERTED_CLAUSE);
+ TIP.setCaretInPendingComposition(2);
+
+ reset();
+ TIP.flushPendingComposition(convertKeyEvent);
+ is(events.length, 1,
+ description + "flushPendingComposition(convertKeyEvent) causing modifying composition string should cause compositionupdate");
+ is(events[0].type, "compositionupdate",
+ description + "flushPendingComposition(convertKeyEvent) causing modifying composition string should cause compositionupdate");
+ is(events[0].data, "FOo",
+ description + "compositionupdate caused by flushPendingComposition(convertKeyEvent) should have new composition string in its data");
+ is(input.value, "FOo",
+ description + "modifying composition clause shouldn't cause modifying the focused editor");
+
+ // Committing the composition string
+ reset();
+ TIP.commitComposition(enterKeyEvent);
+ is(events.length, 2,
+ description + "commitComposition(enterKeyEvent) should cause compositionend and keyup but shoudn't cause compositionupdate");
+ is(events[0].type, "compositionend",
+ description + "commitComposition(enterKeyEvent) should cause compositionend");
+ is(events[0].data, "FOo",
+ description + "compositionend caused by commitComposition(enterKeyEvent) should have the committed string in its data");
+ is(events[1].type, "keyup",
+ description + "commitComposition(enterKeyEvent) should cause keyup");
+ is(input.value, "FOo",
+ description + "commitComposition(enterKeyEvent) shouldn't cause modifying the focused editor");
+
+ // Starting new composition without a call of startComposition()
+ TIP.setPendingCompositionString("bar");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+
+ reset();
+ TIP.flushPendingComposition(printableKeyEvent);
+ is(events.length, 3,
+ description + "flushPendingComposition(printableKeyEvent) without a call of startComposition() should cause both compositionstart and compositionupdate");
+ is(events[0].type, "keydown",
+ description + "flushPendingComposition(printableKeyEvent) without a call of startComposition() should cause keydown");
+ is(events[1].type, "compositionstart",
+ description + "flushPendingComposition(printableKeyEvent) without a call of startComposition() should cause compositionstart");
+ is(events[2].type, "compositionupdate",
+ description + "flushPendingComposition(printableKeyEvent) without a call of startComposition() should cause compositionupdate after compositionstart");
+ is(events[2].data, "bar",
+ description + "compositionupdate caused by flushPendingComposition(printableKeyEvent) without a call of startComposition() should have the composition string in its data");
+ is(input.value, "FOobar",
+ description + "new composition string should cause appending composition string to the focused editor");
+
+ // Canceling the composition
+ reset();
+ TIP.cancelComposition(escKeyEvent);
+ is(events.length, 3,
+ description + "cancelComposition(escKeyEvent) should cause both compositionupdate and compositionend");
+ is(events[0].type, "compositionupdate",
+ description + "cancelComposition(escKeyEvent) should cause compositionupdate");
+ is(events[0].data, "",
+ description + "compositionupdate caused by cancelComposition(escKeyEvent) should have empty string in its data");
+ is(events[1].type, "compositionend",
+ description + "cancelComposition(escKeyEvent) should cause compositionend after compositionupdate");
+ is(events[1].data, "",
+ description + "compositionend caused by cancelComposition(escKeyEvent) should have empty string in its data");
+ is(events[2].type, "keyup",
+ description + "cancelComposition(escKeyEvent) should cause keyup after compositionend");
+ is(input.value, "FOo",
+ description + "canceled composition string should be removed from the focused editor");
+
+ // Starting composition explicitly and canceling it
+ reset();
+ TIP.startComposition(printableKeyEvent);
+ TIP.cancelComposition(escKeyEvent);
+ is(events.length, 4,
+ description + "canceling composition immediately after startComposition() should cause keydown, compositionstart, compositionend and keyup");
+ is(events[0].type, "keydown",
+ description + "canceling composition immediately after startComposition() should cause keydown first");
+ is(events[1].type, "compositionstart",
+ description + "canceling composition immediately after startComposition() should cause compositionstart after keydown");
+ is(events[2].type, "compositionend",
+ description + "canceling composition immediately after startComposition() should cause compositionend after compositionstart");
+ is(events[2].data, "",
+ description + "compositionend caused by canceling composition should have empty string in its data");
+ is(events[3].type, "keyup",
+ description + "canceling composition immediately after startComposition() should cause keyup after compositionend");
+ is(input.value, "FOo",
+ description + "canceling composition shouldn't modify the focused editor");
+
+ // Create composition for next test.
+ TIP.setPendingCompositionString("bar");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.flushPendingComposition();
+ is(input.value, "FOobar",
+ description + "The focused editor should have new composition string \"bar\"");
+
+ // Allow to set empty composition string
+ reset();
+ TIP.flushPendingComposition(backspaceKeyEvent);
+ is(events.length, 1,
+ description + "making composition string empty should cause only compositionupdate");
+ is(events[0].type, "compositionupdate",
+ description + "making composition string empty should cause compositionupdate");
+ is(events[0].data, "",
+ description + "compositionupdate caused by making composition string empty should have empty string in its data");
+
+ // Allow to insert new composition string without compositionend/compositionstart
+ TIP.setPendingCompositionString("buzz");
+ TIP.appendClauseToPendingComposition(4, TIP.ATTR_RAW_CLAUSE);
+
+ reset();
+ TIP.flushPendingComposition(printableKeyEvent);
+ is(events.length, 1,
+ description + "modifying composition string from empty string should cause only compositionupdate");
+ is(events[0].type, "compositionupdate",
+ description + "modifying composition string from empty string should cause compositionupdate");
+ is(events[0].data, "buzz",
+ description + "compositionupdate caused by modifying composition string from empty string should have new composition string in its data");
+ is(input.value, "FOobuzz",
+ description + "new composition string should be appended to the focused editor");
+
+ // Committing with different string
+ reset();
+ TIP.commitCompositionWith("bar", printableKeyEvent);
+ is(events.length, 3,
+ description + "committing with different string should cause compositionupdate and compositionend");
+ is(events[0].type, "compositionupdate",
+ description + "committing with different string should cause compositionupdate first");
+ is(events[0].data, "bar",
+ description + "compositionupdate caused by committing with different string should have the committing string in its data");
+ is(events[1].type, "compositionend",
+ description + "committing with different string should cause compositionend after compositionupdate");
+ is(events[1].data, "bar",
+ description + "compositionend caused by committing with different string should have the committing string in its data");
+ is(events[2].type, "keyup",
+ description + "committing with different string should cause keyup after compositionend");
+ is(input.value, "FOobar",
+ description + "new committed string should be appended to the focused editor");
+
+ // Appending new composition string
+ TIP.setPendingCompositionString("buzz");
+ TIP.appendClauseToPendingComposition(4, TIP.ATTR_RAW_CLAUSE);
+ TIP.flushPendingComposition();
+ is(input.value, "FOobarbuzz",
+ description + "new composition string should be appended to the focused editor");
+
+ // Committing with same string
+ reset();
+ TIP.commitCompositionWith("buzz", enterKeyEvent);
+ is(events.length, 2,
+ description + "committing with same string should cause only compositionend");
+ is(events[0].type, "compositionend",
+ description + "committing with same string should cause compositionend");
+ is(events[0].data, "buzz",
+ description + "compositionend caused by committing with same string should have the committing string in its data");
+ is(events[1].type, "keyup",
+ description + "committing with same string should cause keyup after compositionend");
+ is(input.value, "FOobarbuzz",
+ description + "new committed string should be appended to the focused editor");
+
+ // Inserting commit string directly
+ reset();
+ TIP.commitCompositionWith("boo!", printableKeyEvent);
+ is(events.length, 5,
+ description + "committing text directly should cause compositionstart, compositionupdate and compositionend");
+ is(events[0].type, "keydown",
+ description + "committing text directly should cause keydown first");
+ is(events[1].type, "compositionstart",
+ description + "committing text directly should cause compositionstart after keydown");
+ is(events[2].type, "compositionupdate",
+ description + "committing text directly should cause compositionupdate after compositionstart");
+ is(events[2].data, "boo!",
+ description + "compositionupdate caused by committing text directly should have the committing text in its data");
+ is(events[3].type, "compositionend",
+ description + "committing text directly should cause compositionend after compositionupdate");
+ is(events[3].data, "boo!",
+ description + "compositionend caused by committing text directly should have the committing text in its data");
+ is(events[4].type, "keyup",
+ description + "committing text directly should cause keyup after compositionend");
+ is(input.value, "FOobarbuzzboo!",
+ description + "committing text directly should append the committing text to the focused editor");
+
+ Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", true);
+
+ // Even if "dom.keyboardevent.dispatch_during_composition" is true, keypress event shouldn't be fired during composition
+ reset();
+ TIP.startComposition(printableKeyEvent);
+ is(events.length, 3,
+ description + "TIP.startComposition(printableKeyEvent) should cause keydown, compositionstart and keyup (keypress event shouldn't be fired during composition)");
+ is(events[0].type, "keydown",
+ description + "TIP.startComposition(printableKeyEvent) should cause keydown (keypress event shouldn't be fired during composition)");
+ is(events[1].type, "compositionstart",
+ description + "TIP.startComposition(printableKeyEvent) should cause compositionstart (keypress event shouldn't be fired during composition)");
+ is(events[2].type, "keyup",
+ description + "TIP.startComposition(printableKeyEvent) should cause keyup (keypress event shouldn't be fired during composition)");
+
+ // TIP.flushPendingComposition(printableKeyEvent) should cause keydown and keyup events if "dom.keyboardevent.dispatch_during_composition" is true
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+
+ reset();
+ TIP.flushPendingComposition(printableKeyEvent);
+ is(events.length, 3,
+ description + "TIP.flushPendingComposition(printableKeyEvent) should cause keydown, compositionupdate and keyup (keypress event shouldn't be fired during composition)");
+ is(events[0].type, "keydown",
+ description + "TIP.flushPendingComposition(printableKeyEvent) should cause keydown (keypress event shouldn't be fired during composition)");
+ is(events[1].type, "compositionupdate",
+ description + "TIP.flushPendingComposition(printableKeyEvent) should cause compositionupdate (keypress event shouldn't be fired during composition)");
+ is(events[2].type, "keyup",
+ description + "TIP.flushPendingComposition(printableKeyEvent) should cause keyup (keypress event shouldn't be fired during composition)");
+
+ // TIP.commitComposition(enterKeyEvent) should cause keydown and keyup events if "dom.keyboardevent.dispatch_during_composition" is true
+ reset();
+ TIP.commitComposition(enterKeyEvent);
+ is(events.length, 3,
+ description + "TIP.commitComposition(enterKeyEvent) should cause keydown, compositionend and keyup (keypress event shouldn't be fired during composition)");
+ is(events[0].type, "keydown",
+ description + "TIP.commitComposition(enterKeyEvent) should cause keydown (keypress event shouldn't be fired during composition)");
+ is(events[1].type, "compositionend",
+ description + "TIP.commitComposition(enterKeyEvent) should cause compositionend (keypress event shouldn't be fired during composition)");
+ is(events[2].type, "keyup",
+ description + "TIP.commitComposition(enterKeyEvent) should cause keyup (keypress event shouldn't be fired during composition)");
+
+ // TIP.cancelComposition(escKeyEvent) should cause keydown and keyup events if "dom.keyboardevent.dispatch_during_composition" is true
+ TIP.startComposition();
+ reset();
+ TIP.cancelComposition(escKeyEvent);
+ is(events.length, 3,
+ description + "TIP.cancelComposition(escKeyEvent) should cause keydown, compositionend and keyup (keypress event shouldn't be fired during composition)");
+ is(events[0].type, "keydown",
+ description + "TIP.cancelComposition(escKeyEvent) should cause keydown (keypress event shouldn't be fired during composition)");
+ is(events[1].type, "compositionend",
+ description + "TIP.cancelComposition(escKeyEvent) should cause compositionend (keypress event shouldn't be fired during composition)");
+ is(events[2].type, "keyup",
+ description + "TIP.cancelComposition(escKeyEvent) should cause keyup (keypress event shouldn't be fired during composition)");
+
+ var printableKeydownEvent = new KeyboardEvent("keydown", { key: "b", code: "KeyB", keyCode: KeyboardEvent.DOM_VK_B });
+ var enterKeydownEvent = new KeyboardEvent("keydown", { key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
+ var escKeydownEvent = new KeyboardEvent("keydown", { key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE });
+
+ // TIP.startComposition(printableKeydownEvent) shouldn't cause keyup event even if "dom.keyboardevent.dispatch_during_composition" is true
+ reset();
+ TIP.startComposition(printableKeydownEvent);
+ is(events.length, 2,
+ description + "TIP.startComposition(printableKeydownEvent) should cause keydown and compositionstart (keyup event shouldn't be fired)");
+ is(events[0].type, "keydown",
+ description + "TIP.startComposition(printableKeydownEvent) should cause keydown (keyup event shouldn't be fired)");
+ is(events[1].type, "compositionstart",
+ description + "TIP.startComposition(printableKeydownEvent) should cause compositionstart (keyup event shouldn't be fired)");
+
+ // TIP.flushPendingComposition(printableKeydownEvent) shouldn't cause keyup event even if "dom.keyboardevent.dispatch_during_composition" is true
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+
+ reset();
+ TIP.flushPendingComposition(printableKeydownEvent);
+ is(events.length, 2,
+ description + "TIP.flushPendingComposition(printableKeydownEvent) should cause keydown and compositionupdate (keyup event shouldn't be fired)");
+ is(events[0].type, "keydown",
+ description + "TIP.flushPendingComposition(printableKeydownEvent) should cause keydown (keyup event shouldn't be fired)");
+ is(events[1].type, "compositionupdate",
+ description + "TIP.flushPendingComposition(printableKeydownEvent) should cause compositionupdate (keyup event shouldn't be fired)");
+
+ // TIP.commitComposition(enterKeydownEvent) shouldn't cause keyup event even if "dom.keyboardevent.dispatch_during_composition" is true
+ reset();
+ TIP.commitComposition(enterKeydownEvent);
+ is(events.length, 2,
+ description + "TIP.commitComposition(enterKeydownEvent) should cause keydown and compositionend (keyup event shouldn't be fired)");
+ is(events[0].type, "keydown",
+ description + "TIP.commitComposition(enterKeydownEvent) should cause keydown (keyup event shouldn't be fired)");
+ is(events[1].type, "compositionend",
+ description + "TIP.commitComposition(enterKeydownEvent) should cause compositionend (keyup event shouldn't be fired)");
+
+ // TIP.cancelComposition(escKeydownEvent) shouldn't cause keyup event even if "dom.keyboardevent.dispatch_during_composition" is true
+ TIP.startComposition();
+ reset();
+ TIP.cancelComposition(escKeydownEvent);
+ is(events.length, 2,
+ description + "TIP.cancelComposition(escKeydownEvent) should cause keydown and compositionend (keyup event shouldn't be fired)");
+ is(events[0].type, "keydown",
+ description + "TIP.cancelComposition(escKeydownEvent) should cause keydown (keyup event shouldn't be fired)");
+ is(events[1].type, "compositionend",
+ description + "TIP.cancelComposition(escKeydownEvent) should cause compositionend (keyup event shouldn't be fired)");
+
+ Services.prefs.clearUserPref("dom.keyboardevent.dispatch_during_composition");
+
+ window.removeEventListener("compositionstart", handler, false);
+ window.removeEventListener("compositionupdate", handler, false);
+ window.removeEventListener("compositionend", handler, false);
+ window.removeEventListener("keydown", handler, false);
+ window.removeEventListener("keypress", handler, false);
+ window.removeEventListener("keyup", handler, false);
+}
+
+function runConsumingKeydownBeforeCompositionTests()
+{
+ var description = "runConsumingKeydownBeforeCompositionTests(): ";
+
+ var TIP = createTIP();
+ ok(TIP.beginInputTransactionForTests(window),
+ description + "TIP.beginInputTransactionForTests() should succeed");
+
+ var events;
+
+ function reset()
+ {
+ events = [];
+ }
+
+ function handler(aEvent)
+ {
+ events.push(aEvent);
+ if (aEvent.type == "keydown") {
+ aEvent.preventDefault();
+ }
+ }
+
+ window.addEventListener("compositionstart", handler, false);
+ window.addEventListener("compositionupdate", handler, false);
+ window.addEventListener("compositionend", handler, false);
+ window.addEventListener("keydown", handler, false);
+ window.addEventListener("keypress", handler, false);
+ window.addEventListener("keyup", handler, false);
+
+ input.value = "";
+ input.focus();
+
+ var printableKeyEvent = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
+ var enterKeyEvent = new KeyboardEvent("", { key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
+ var escKeyEvent = new KeyboardEvent("", { key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE });
+
+ Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", false);
+
+ // If keydown before compositionstart is consumed, composition shouldn't be started.
+ reset();
+ ok(!TIP.startComposition(printableKeyEvent),
+ description + "TIP.startComposition(printableKeyEvent) should return false because it's keydown is consumed");
+ is(events.length, 2,
+ description + "TIP.startComposition(printableKeyEvent) should cause only keydown and keyup events");
+ is(events[0].type, "keydown",
+ description + "TIP.startComposition(printableKeyEvent) should cause keydown event first");
+ is(events[1].type, "keyup",
+ description + "TIP.startComposition(printableKeyEvent) should cause keyup event after keydown");
+ ok(!TIP.hasComposition,
+ description + "TIP.startComposition(printableKeyEvent) shouldn't cause composition");
+ is(input.value, "",
+ description + "TIP.startComposition(printableKeyEvent) shouldn't cause inserting text");
+
+ // If keydown before compositionstart caused by flushPendingComposition(printableKeyEvent) is consumed, composition shouldn't be started.
+ reset();
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ ok(!TIP.flushPendingComposition(printableKeyEvent),
+ description + "TIP.flushPendingComposition(printableKeyEvent) should return false because it's keydown is consumed");
+ is(events.length, 2,
+ description + "TIP.flushPendingComposition(printableKeyEvent) should cause only keydown and keyup events");
+ is(events[0].type, "keydown",
+ description + "TIP.flushPendingComposition(printableKeyEvent) should cause keydown event first");
+ is(events[1].type, "keyup",
+ description + "TIP.flushPendingComposition(printableKeyEvent) should cause keyup event after keydown");
+ ok(!TIP.hasComposition,
+ description + "TIP.flushPendingComposition(printableKeyEvent) shouldn't cause composition");
+ is(input.value, "",
+ description + "TIP.flushPendingComposition(printableKeyEvent) shouldn't cause inserting text");
+
+ // If keydown before compositionstart is consumed, composition shouldn't be started.
+ reset();
+ ok(!TIP.commitCompositionWith("foo", printableKeyEvent),
+ description + "TIP.commitCompositionWith(\"foo\", printableKeyEvent) should return false because it's keydown is consumed");
+ is(events.length, 2,
+ description + "TIP.commitCompositionWith(\"foo\", printableKeyEvent) should cause only keydown and keyup events");
+ is(events[0].type, "keydown",
+ description + "TIP.commitCompositionWith(\"foo\", printableKeyEvent) should cause keydown event first");
+ is(events[1].type, "keyup",
+ description + "TIP.commitCompositionWith(\"foo\", printableKeyEvent) should cause keyup event after keydown");
+ ok(!TIP.hasComposition,
+ description + "TIP.commitCompositionWith(\"foo\", printableKeyEvent) shouldn't cause composition");
+ is(input.value, "",
+ description + "TIP.commitCompositionWith(\"foo\", printableKeyEvent) shouldn't cause inserting text");
+
+ Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", true);
+
+ // If composition is already started, TIP.flushPendingComposition(printableKeyEvent) shouldn't be canceled.
+ TIP.startComposition();
+ ok(TIP.hasComposition,
+ description + "Before TIP.flushPendingComposition(printableKeyEvent), composition should've been created");
+ reset();
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ ok(TIP.flushPendingComposition(printableKeyEvent),
+ description + "TIP.flushPendingComposition(printableKeyEvent) should return true even if preceding keydown is consumed because there was a composition already");
+ is(events.length, 3,
+ description + "TIP.flushPendingComposition(printableKeyEvent) should cause only keydown and keyup events");
+ is(events[0].type, "keydown",
+ description + "TIP.flushPendingComposition(printableKeyEvent) should cause keydown event first");
+ is(events[1].type, "compositionupdate",
+ description + "TIP.flushPendingComposition(printableKeyEvent) should cause compositionupdate event after keydown");
+ is(events[2].type, "keyup",
+ description + "TIP.flushPendingComposition(printableKeyEvent) should cause keyup event after compositionupdate");
+ ok(TIP.hasComposition,
+ description + "TIP.flushPendingComposition(printableKeyEvent) shouldn't cause canceling composition");
+ is(input.value, "foo",
+ description + "TIP.flushPendingComposition(printableKeyEvent) should cause inserting text even if preceding keydown is consumed because there was a composition already");
+
+ // If composition is already started, TIP.commitComposition(enterKeyEvent) shouldn't be canceled.
+ reset();
+ TIP.commitComposition(enterKeyEvent);
+ is(events.length, 3,
+ description + "TIP.commitComposition(enterKeyEvent) should cause keydown, compositionend and keyup events");
+ is(events[0].type, "keydown",
+ description + "TIP.commitComposition(enterKeyEvent) should cause keydown event first");
+ is(events[1].type, "compositionend",
+ description + "TIP.commitComposition(enterKeyEvent) should cause compositionend event after keydown");
+ is(events[2].type, "keyup",
+ description + "TIP.commitComposition(enterKeyEvent) should cause keyup event after compositionend");
+ ok(!TIP.hasComposition,
+ description + "TIP.commitComposition(enterKeyEvent) should cause committing composition even if preceding keydown is consumed because there was a composition already");
+ is(input.value, "foo",
+ description + "TIP.commitComposition(enterKeyEvent) should commit composition even if preceding keydown is consumed because there was a composition already");
+
+ // cancelComposition() should work even if preceding keydown event is consumed.
+ input.value = "";
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition();
+ ok(TIP.hasComposition,
+ description + "Before TIP.cancelComposition(escKeyEvent), composition should've been created");
+ is(input.value, "foo",
+ description + "Before TIP.cancelComposition(escKeyEvent) should have composition string");
+ reset();
+ TIP.cancelComposition(escKeyEvent);
+ is(events.length, 4,
+ description + "TIP.cancelComposition(escKeyEvent) should cause keydown, compositionupdate, compositionend and keyup events even if preceding keydown is consumed because there was a composition already");
+ is(events[0].type, "keydown",
+ description + "TIP.cancelComposition(escKeyEvent) should cause keydown event first");
+ is(events[1].type, "compositionupdate",
+ description + "TIP.cancelComposition(escKeyEvent) should cause compositionupdate event after keydown");
+ is(events[2].type, "compositionend",
+ description + "TIP.cancelComposition(escKeyEvent) should cause compositionend event after compositionupdate");
+ is(events[3].type, "keyup",
+ description + "TIP.cancelComposition(escKeyEvent) should cause keyup event after compositionend");
+ ok(!TIP.hasComposition,
+ description + "TIP.cancelComposition(escKeyEvent) should cause canceling composition even if preceding keydown is consumed because there was a composition already");
+ is(input.value, "",
+ description + "TIP.cancelComposition(escKeyEvent) should cancel composition even if preceding keydown is consumed because there was a composition already");
+
+ Services.prefs.clearUserPref("dom.keyboardevent.dispatch_during_composition");
+
+ window.removeEventListener("compositionstart", handler, false);
+ window.removeEventListener("compositionupdate", handler, false);
+ window.removeEventListener("compositionend", handler, false);
+ window.removeEventListener("keydown", handler, false);
+ window.removeEventListener("keypress", handler, false);
+ window.removeEventListener("keyup", handler, false);
+}
+
+function runKeyTests()
+{
+ var description = "runKeyTests(): ";
+ const kModifiers =
+ [ "Alt", "AltGraph", "CapsLock", "Control", "Fn", "FnLock", "Meta", "NumLock",
+ "ScrollLock", "Shift", "Symbol", "SymbolLock", "OS" ];
+
+ var TIP = createTIP();
+ ok(TIP.beginInputTransactionForTests(window),
+ description + "TIP.beginInputTransactionForTests() should succeed");
+
+ var events;
+ var doPreventDefaults;
+
+ function reset()
+ {
+ events = [];
+ doPreventDefaults = [];
+ }
+
+ function handler(aEvent)
+ {
+ events.push(aEvent);
+ if (doPreventDefaults.includes(aEvent.type)) {
+ aEvent.preventDefault();
+ }
+ }
+
+ function checkKeyAttrs(aMethodDescription, aEvent, aExpectedData)
+ {
+ var desc = description + aMethodDescription + ", type=\"" + aEvent.type + "\", key=\"" + aEvent.key + "\", code=\"" + aEvent.code + "\": ";
+ var defaultValues = {
+ key: "Unidentified", code: "", keyCode: 0, charCode: 0,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD, repeat: false, isComposing: false,
+ shiftKey: false, ctrlKey: false, altKey: false, metaKey: false,
+ defaultPrevented: false
+ };
+ function expectedValue(aAttr)
+ {
+ return aExpectedData[aAttr] !== undefined ? aExpectedData[aAttr] : defaultValues[aAttr];
+ }
+ is(aEvent.type, aExpectedData.type,
+ desc + " should cause keydown event");
+ if (aEvent.type != aExpectedData.type) {
+ return;
+ }
+ is(aEvent.defaultPrevented, expectedValue("defaultPrevented"),
+ desc + ".defaultPrevented is wrong");
+ is(aEvent.key, expectedValue("key"),
+ desc + ".key is wrong");
+ is(aEvent.code, expectedValue("code"),
+ desc + ".code is wrong");
+ is(aEvent.location, expectedValue("location"),
+ desc + ".location is wrong");
+ is(aEvent.repeat, expectedValue("repeat"),
+ desc + ".repeat is wrong");
+ is(aEvent.isComposing, expectedValue("isComposing"),
+ desc + ".isComposing is wrong");
+ is(aEvent.keyCode, expectedValue("keyCode"),
+ desc + ".keyCode is wrong");
+ is(aEvent.charCode, expectedValue("charCode"),
+ desc + ".charCode is wrong");
+ is(aEvent.shiftKey, expectedValue("shiftKey"),
+ desc + ".shiftKey is wrong");
+ is(aEvent.ctrlKey, expectedValue("ctrlKey"),
+ desc + ".ctrlKey is wrong");
+ is(aEvent.altKey, expectedValue("altKey"),
+ desc + ".altKey is wrong");
+ is(aEvent.metaKey, expectedValue("metaKey"),
+ desc + ".metaKey is wrong");
+ for (var i = 0; i < kModifiers.length; i++) {
+ is(aEvent.getModifierState(kModifiers[i]), aExpectedData[kModifiers[i]] !== undefined ? aExpectedData[kModifiers[i]] : false,
+ desc + ".getModifierState(\"" + kModifiers[i] + "\") is wrong");
+ }
+ }
+
+ window.addEventListener("keydown", handler, false);
+ window.addEventListener("keypress", handler, false);
+ window.addEventListener("keyup", handler, false);
+
+ input.value = "";
+ input.focus();
+
+
+ // Printable key test:
+ // Emulates pressing 'a' key.
+ var keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
+
+ reset();
+ var doDefaultKeydown = TIP.keydown(keyA);
+
+ is(doDefaultKeydown, TIP.KEYPRESS_IS_CONSUMED,
+ description + "TIP.keydown(keyA) should return 0x02 because the keypress event should be consumed by the input element");
+ is(events.length, 2,
+ description + "TIP.keydown(keyA) should cause keydown and keypress event");
+ checkKeyAttrs("TIP.keydown(keyA)", events[0],
+ { type: "keydown", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0 });
+ checkKeyAttrs("TIP.keydown(keyA)", events[1],
+ { type: "keypress", key: "a", code: "KeyA", keyCode: 0, charCode: "a".charCodeAt(0), defaultPrevented: true });
+ is(input.value, "a",
+ description + "input.value should be \"a\" which is inputted by TIP.keydown(keyA)");
+
+ // Emulates releasing 'a' key.
+ reset();
+ var doDefaultKeyup = TIP.keyup(keyA);
+ ok(doDefaultKeyup,
+ description + "TIP.keyup(keyA) should return true");
+ is(events.length, 1,
+ description + "TIP.keyup(keyA) should cause keyup event");
+ checkKeyAttrs("TIP.keyup(keyA)", events[0],
+ { type: "keyup", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0 });
+ is(input.value, "a",
+ description + "input.value should stay \"a\" which was inputted by TIP.keydown(keyA)");
+
+
+ // Non-printable key test:
+ // Emulates pressing Enter key.
+ var keyEnter = new KeyboardEvent("", { key: "Enter", code: "Enter" });
+
+ reset();
+ doDefaultKeydown = TIP.keydown(keyEnter);
+
+ is(doDefaultKeydown, 0,
+ description + "TIP.keydown(keyEnter) should return 0");
+ is(events.length, 2,
+ description + "TIP.keydown(keyEnter) should cause keydown and keypress event");
+ checkKeyAttrs("TIP.keydown(keyEnter)", events[0],
+ { type: "keydown", key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
+ checkKeyAttrs("TIP.keydown(keyEnter)", events[1],
+ { type: "keypress", key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
+ is(input.value, "a",
+ description + "input.value should stay \"a\" which was inputted by TIP.keydown(keyA)");
+
+ // Emulates releasing Enter key.
+ reset();
+ doDefaultKeyup = TIP.keyup(keyEnter);
+ ok(doDefaultKeyup,
+ description + "TIP.keyup(keyEnter) should return true");
+ is(events.length, 1,
+ description + "TIP.keyup(keyEnter) should cause keyup event");
+ checkKeyAttrs("TIP.keyup(keyEnter)", events[0],
+ { type: "keyup", key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
+ is(input.value, "a",
+ description + "input.value should stay \"a\" which was inputted by TIP.keydown(keyA)");
+
+
+ // KEY_DEFAULT_PREVENTED should cause defaultPrevented = true and not cause keypress event
+ var keyB = new KeyboardEvent("", { key: "b", code: "KeyB", keyCode: KeyboardEvent.DOM_VK_B });
+
+ reset();
+ doDefaultKeydown = TIP.keydown(keyB, TIP.KEY_DEFAULT_PREVENTED);
+ doDefaultKeyup = TIP.keyup(keyB, TIP.KEY_DEFAULT_PREVENTED);
+
+ is(doDefaultKeydown, TIP.KEYDOWN_IS_CONSUMED,
+ description + "TIP.keydown(keyB, TIP.KEY_DEFAULT_PREVENTED) should return 0x01 because it's marked as consumed at dispatching the event");
+ ok(!doDefaultKeyup,
+ description + "TIP.keyup(keyB, TIP.KEY_DEFAULT_PREVENTED) should return false because it's marked as consumed at dispatching the event");
+ is(events.length, 2,
+ description + "TIP.keydown(keyB, TIP.KEY_DEFAULT_PREVENTED) and TIP.keyup(keyB, TIP.KEY_DEFAULT_PREVENTED) should cause keydown and keyup event");
+ checkKeyAttrs("TIP.keydown(keyB, TIP.KEY_DEFAULT_PREVENTED) and TIP.keyup(keyB, TIP.KEY_DEFAULT_PREVENTED)", events[0],
+ { type: "keydown", key: "b", code: "KeyB", keyCode: KeyboardEvent.DOM_VK_B, defaultPrevented: true });
+ checkKeyAttrs("TIP.keydown(keyB, TIP.KEY_DEFAULT_PREVENTED) and TIP.keyup(keyB, TIP.KEY_DEFAULT_PREVENTED)", events[1],
+ { type: "keyup", key: "b", code: "KeyB", keyCode: KeyboardEvent.DOM_VK_B, defaultPrevented: true });
+ is(input.value, "a",
+ description + "input.value shouldn't be modified by default prevented key events");
+
+ // Assume that KeyX causes inputting text "abc"
+ input.value = "";
+ var keyABC = new KeyboardEvent("", { key: "abc", code: "KeyX", keyCode: KeyboardEvent.DOM_VK_A });
+
+ reset();
+ doDefaultKeydown = TIP.keydown(keyABC);
+ doDefaultKeyup = TIP.keyup(keyABC);
+
+ is(doDefaultKeydown, TIP.KEYPRESS_IS_CONSUMED,
+ description + "TIP.keydown(keyABC) should return false because the keypress events should be consumed by the input element");
+ ok(doDefaultKeyup,
+ description + "TIP.keyup(keyABC) should return true");
+ is(events.length, 5,
+ description + "TIP.keydown(keyABC) and TIP.keyup(keyABC) should cause keydown, keypress, keypress, keypress and keyup event");
+ checkKeyAttrs("TIP.keydown(keyABC) and TIP.keyup(keyABC)", events[0],
+ { type: "keydown", key: "abc", code: "KeyX", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0, defaultPrevented: false });
+ checkKeyAttrs("TIP.keydown(keyABC) and TIP.keyup(keyABC)", events[1],
+ { type: "keypress", key: "abc".charAt(0), code: "KeyX", keyCode: 0, charCode: "abc".charCodeAt(0), defaultPrevented: true });
+ checkKeyAttrs("TIP.keydown(keyABC) and TIP.keyup(keyABC)", events[2],
+ { type: "keypress", key: "abc".charAt(1), code: "KeyX", keyCode: 0, charCode: "abc".charCodeAt(1), defaultPrevented: true });
+ checkKeyAttrs("TIP.keydown(keyABC) and TIP.keyup(keyABC)", events[3],
+ { type: "keypress", key: "abc".charAt(2), code: "KeyX", keyCode: 0, charCode: "abc".charCodeAt(2), defaultPrevented: true });
+ checkKeyAttrs("TIP.keydown(keyABC) and TIP.keyup(keyABC)", events[4],
+ { type: "keyup", key: "abc", code: "KeyX", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0, defaultPrevented: false });
+ is(input.value, "abc",
+ description + "input.value should be \"abc\"");
+
+ // If KEY_FORCE_PRINTABLE_KEY is specified, registered key names can be a printable key which inputs the specified value.
+ input.value = "";
+ var keyEnterPrintable = new KeyboardEvent("", { key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
+
+ reset();
+ doDefaultKeydown = TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY);
+ doDefaultKeyup = TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY);
+
+ is(doDefaultKeydown, TIP.KEYPRESS_IS_CONSUMED,
+ description + "TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) should return 0x02 because the keypress events should be consumed by the input element");
+ ok(doDefaultKeyup,
+ description + "TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) should return true");
+ is(events.length, 7,
+ 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");
+ checkKeyAttrs("TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)", events[0],
+ { type: "keydown", key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN, charCode: 0, defaultPrevented: false });
+ checkKeyAttrs("TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)", events[1],
+ { type: "keypress", key: "Enter".charAt(0), code: "Enter", keyCode: 0, charCode: "Enter".charCodeAt(0), defaultPrevented: true });
+ checkKeyAttrs("TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)", events[2],
+ { type: "keypress", key: "Enter".charAt(1), code: "Enter", keyCode: 0, charCode: "Enter".charCodeAt(1), defaultPrevented: true });
+ checkKeyAttrs("TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)", events[3],
+ { type: "keypress", key: "Enter".charAt(2), code: "Enter", keyCode: 0, charCode: "Enter".charCodeAt(2), defaultPrevented: true });
+ checkKeyAttrs("TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)", events[4],
+ { type: "keypress", key: "Enter".charAt(3), code: "Enter", keyCode: 0, charCode: "Enter".charCodeAt(3), defaultPrevented: true });
+ checkKeyAttrs("TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)", events[5],
+ { type: "keypress", key: "Enter".charAt(4), code: "Enter", keyCode: 0, charCode: "Enter".charCodeAt(4), defaultPrevented: true });
+ checkKeyAttrs("TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)", events[6],
+ { type: "keyup", key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN, charCode: 0, defaultPrevented: false });
+ is(input.value, "Enter",
+ description + "input.value should be \"Enter\"");
+
+ // modifiers should be ignored.
+ var keyWithModifiers = new KeyboardEvent("", { key: "Escape", code: "Escape", shiftKey: true, ctrlKey: true, altKey: true, metaKey: true });
+
+ reset();
+ doDefaultKeydown = TIP.keydown(keyWithModifiers);
+ doDefaultKeyup = TIP.keyup(keyWithModifiers);
+
+ is(doDefaultKeydown, 0,
+ description + "TIP.keydown(keyWithModifiers) should return 0");
+ ok(doDefaultKeyup,
+ description + "TIP.keyup(keyWithModifiers) should return true");
+ is(events.length, 3,
+ description + "TIP.keydown(keyWithModifiers) and TIP.keyup(keyWithModifiers) should cause keydown, keypress and keyup event");
+ checkKeyAttrs("TIP.keydown(keyWithModifiers) and TIP.keyup(keyWithModifiers)", events[0],
+ { type: "keydown", key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE });
+ checkKeyAttrs("TIP.keydown(keyWithModifiers) and TIP.keyup(keyWithModifiers)", events[1],
+ { type: "keypress", key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE });
+ checkKeyAttrs("TIP.keydown(keyWithModifiers) and TIP.keyup(keyWithModifiers)", events[2],
+ { type: "keyup", key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE });
+ is(input.value, "Enter",
+ description + "input.value should stay \"Enter\" which was inputted by TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)");
+
+ // Call preventDefault() at keydown
+ input.value = "";
+ reset();
+ doPreventDefaults = [ "keydown" ];
+ doDefaultKeydown = TIP.keydown(keyA);
+ doDefaultKeyup = TIP.keyup(keyA);
+
+ is(doDefaultKeydown, TIP.KEYDOWN_IS_CONSUMED,
+ description + "TIP.keydown(keyA) should return 0x01 because keydown event's preventDefault should be called");
+ ok(doDefaultKeyup,
+ description + "TIP.keyup(keyA) should return true");
+ is(events.length, 2,
+ description + "TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keydown should cause keydown and keyup event");
+ checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keydown", events[0],
+ { type: "keydown", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, defaultPrevented: true });
+ checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keydown", events[1],
+ { type: "keyup", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, defaultPrevented: false });
+ is(input.value, "",
+ description + "input.value shouldn't be modified by TIP.keyup(keyA) if the keydown event is consumed");
+
+ // Call preventDefault() at keypress
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ doDefaultKeydown = TIP.keydown(keyA);
+ doDefaultKeyup = TIP.keyup(keyA);
+
+ is(doDefaultKeydown, TIP.KEYPRESS_IS_CONSUMED,
+ description + "TIP.keydown(keyA) should return 0x02 because keypress event's preventDefault should be called");
+ ok(doDefaultKeyup,
+ description + "TIP.keyup(keyA) should return true");
+ is(events.length, 3,
+ description + "TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keypress should cause keydown, keypress and keyup event");
+ checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keypress", events[0],
+ { type: "keydown", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0, defaultPrevented: false });
+ checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keypress", events[1],
+ { type: "keypress", key: "a", code: "KeyA", keyCode: 0, charCode: "a".charCodeAt(0), defaultPrevented: true });
+ checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keypress", events[2],
+ { type: "keyup", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0, defaultPrevented: false });
+ is(input.value, "",
+ description + "input.value shouldn't be modified by TIP.keyup(keyA) if the keypress event is consumed");
+
+ // Call preventDefault() at keyup
+ input.value = "";
+ reset();
+ doPreventDefaults = [ "keyup" ];
+ doDefaultKeydown = TIP.keydown(keyA);
+ doDefaultKeyup = TIP.keyup(keyA);
+
+ is(doDefaultKeydown, TIP.KEYPRESS_IS_CONSUMED,
+ description + "TIP.keydown(keyA) should return 0x02 because the key event should be consumed by the input element");
+ ok(!doDefaultKeyup,
+ description + "TIP.keyup(keyA) should return false because keyup event's preventDefault should be called");
+ is(events.length, 3,
+ description + "TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keyup should cause keydown, keypress and keyup event");
+ checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keyup", events[0],
+ { type: "keydown", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0, defaultPrevented: false });
+ checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keyup", events[1],
+ { type: "keypress", key: "a", code: "KeyA", keyCode: 0, charCode: "a".charCodeAt(0), defaultPrevented: true });
+ checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keyup", events[2],
+ { type: "keyup", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0, defaultPrevented: true });
+ is(input.value, "a",
+ description + "input.value should be \"a\" by TIP.keyup(keyA) even if the keyup event is consumed");
+
+ // key events during composition
+ try {
+ Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", false);
+
+ ok(TIP.startComposition(), "TIP.startComposition() should start composition");
+
+ input.value = "";
+ reset();
+ TIP.keydown(keyA);
+ is(events.length, 0,
+ description + "TIP.keydown(keyA) shouldn't cause key events during composition if it's disabled by the pref");
+ reset();
+ TIP.keyup(keyA);
+ is(events.length, 0,
+ description + "TIP.keyup(keyA) shouldn't cause key events during composition if it's disabled by the pref");
+
+ Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", true);
+ reset();
+ TIP.keydown(keyA);
+ is(events.length, 1,
+ description + "TIP.keydown(keyA) should cause keydown event even composition if it's enabled by the pref");
+ checkKeyAttrs("TIP.keydown(keyA) during composition", events[0],
+ { type: "keydown", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0, isComposing: true });
+ reset();
+ TIP.keyup(keyA);
+ is(events.length, 1,
+ description + "TIP.keyup(keyA) should cause keyup event even composition if it's enabled by the pref");
+ checkKeyAttrs("TIP.keyup(keyA) during composition", events[0],
+ { type: "keyup", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0, isComposing: true });
+
+ } finally {
+ TIP.cancelComposition();
+ Services.prefs.clearUserPref("dom.keyboardevent.dispatch_during_composition");
+ }
+
+ // Test .location computation
+ const kCodeToLocation = [
+ { code: "BracketLeft", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "BracketRight", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Comma", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Digit0", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Digit1", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Digit2", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Digit3", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Digit4", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Digit5", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Digit6", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Digit7", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Digit8", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Digit9", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Equal", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Minus", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Period", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Slash", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "AltLeft", location: KeyboardEvent.DOM_KEY_LOCATION_LEFT },
+ { code: "AltRight", location: KeyboardEvent.DOM_KEY_LOCATION_RIGHT },
+ { code: "CapsLock", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "ContextMenu", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "ControlLeft", location: KeyboardEvent.DOM_KEY_LOCATION_LEFT },
+ { code: "ControlRight", location: KeyboardEvent.DOM_KEY_LOCATION_RIGHT },
+ { code: "Enter", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "OSLeft", location: KeyboardEvent.DOM_KEY_LOCATION_LEFT },
+ { code: "OSRight", location: KeyboardEvent.DOM_KEY_LOCATION_RIGHT },
+ { code: "ShiftLeft", location: KeyboardEvent.DOM_KEY_LOCATION_LEFT },
+ { code: "ShiftRight", location: KeyboardEvent.DOM_KEY_LOCATION_RIGHT },
+ { code: "Space", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Tab", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "ArrowDown", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "ArrowLeft", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "ArrowRight", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "ArrowUp", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "NumLock", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+ { code: "Numpad0", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "Numpad1", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "Numpad2", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "Numpad3", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "Numpad4", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "Numpad5", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "Numpad6", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "Numpad7", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "Numpad8", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "Numpad9", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadAdd", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadBackspace", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadClear", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadClearEntry", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadComma", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadDecimal", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadDivide", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadEnter", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadEqual", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadMemoryAdd", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadMemoryClear", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadMemoryRecall", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadMemoryStore", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadMemorySubtract", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadMultiply", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadParenLeft", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadParenRight", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ { code: "NumpadSubtract", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+ ];
+ for (var i = 0; i < kCodeToLocation.length; i++) {
+ var keyEvent = new KeyboardEvent("", { code: kCodeToLocation[i].code });
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ // If the location isn't initialized or initialized with 0, it should be computed from the code value.
+ TIP.keydown(keyEvent);
+ TIP.keyup(keyEvent);
+ var longDesc = description + "testing computation of .location of \"" + kCodeToLocation[i].code + "\", ";
+ is(events.length, 3,
+ longDesc + "keydown, keypress and keyup events should be fired");
+ for (var j = 0; j < events.length; j++) {
+ is(events[j].location, kCodeToLocation[i].location,
+ longDesc + " type=\"" + events[j].type + "\", location value is wrong");
+ }
+ // However, if KEY_KEEP_KEY_LOCATION_STANDARD is specified, .location value should be kept as DOM_KEY_LOCATION_STANDARD (0).
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ TIP.keydown(keyEvent, TIP.KEY_KEEP_KEY_LOCATION_STANDARD);
+ TIP.keyup(keyEvent, TIP.KEY_KEEP_KEY_LOCATION_STANDARD);
+ var longDesc = description + "testing if .location is forcibly set to DOM_KEY_LOCATION_STANDARD, ";
+ is(events.length, 3,
+ longDesc + "keydown, keypress and keyup events should be fired");
+ for (var j = 0; j < events.length; j++) {
+ is(events[j].location, KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ longDesc + " type=\"" + events[j].type + "\", location value is not 0");
+ }
+ // If .location is initialized with non-zero value, the value shouldn't be computed again.
+ var keyEventWithLocation = new KeyboardEvent("", { code: kCodeToLocation[i].code, location: 0xFF });
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ TIP.keydown(keyEventWithLocation);
+ TIP.keyup(keyEventWithLocation);
+ longDesc = description + "testing if .location is not computed for \"" + kCodeToLocation[i].location + "\", ";
+ is(events.length, 3,
+ longDesc + "keydown, keypress and keyup events should be fired");
+ for (var j = 0; j < events.length; j++) {
+ is(events[j].location, 0xFF,
+ longDesc + " type=\"" + events[j].type + "\", location shouldn't be computed if it's initialized with non-zero value");
+ }
+ }
+
+ // Test .keyCode value computation
+ const kKeyToKeyCode = [
+ { key: "Cancel", keyCode: KeyboardEvent.DOM_VK_CANCEL },
+ { key: "Help", keyCode: KeyboardEvent.DOM_VK_HELP },
+ { key: "Backspace", keyCode: KeyboardEvent.DOM_VK_BACK_SPACE },
+ { key: "Tab", keyCode: KeyboardEvent.DOM_VK_TAB },
+ { key: "Clear", keyCode: KeyboardEvent.DOM_VK_CLEAR },
+ { key: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN },
+ { key: "Shift", keyCode: KeyboardEvent.DOM_VK_SHIFT, isModifier: true },
+ { key: "Control", keyCode: KeyboardEvent.DOM_VK_CONTROL, isModifier: true },
+ { key: "Alt", keyCode: KeyboardEvent.DOM_VK_ALT, isModifier: true },
+ { key: "Pause", keyCode: KeyboardEvent.DOM_VK_PAUSE },
+ { key: "CapsLock", keyCode: KeyboardEvent.DOM_VK_CAPS_LOCK, isModifier: true, isLockableModifier: true },
+ { key: "Hiragana", keyCode: KeyboardEvent.DOM_VK_KANA },
+ { key: "Katakana", keyCode: KeyboardEvent.DOM_VK_KANA },
+ { key: "HiraganaKatakana", keyCode: KeyboardEvent.DOM_VK_KANA },
+ { key: "KanaMode", keyCode: KeyboardEvent.DOM_VK_KANA },
+ { key: "HangulMode", keyCode: KeyboardEvent.DOM_VK_HANGUL },
+ { key: "Eisu", keyCode: KeyboardEvent.DOM_VK_EISU },
+ { key: "JunjaMode", keyCode: KeyboardEvent.DOM_VK_JUNJA },
+ { key: "FinalMode", keyCode: KeyboardEvent.DOM_VK_FINAL },
+ { key: "HanjaMode", keyCode: KeyboardEvent.DOM_VK_HANJA },
+ { key: "KanjiMode", keyCode: KeyboardEvent.DOM_VK_KANJI },
+ { key: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE },
+ { key: "Convert", keyCode: KeyboardEvent.DOM_VK_CONVERT },
+ { key: "NonConvert", keyCode: KeyboardEvent.DOM_VK_NONCONVERT },
+ { key: "Accept", keyCode: KeyboardEvent.DOM_VK_ACCEPT },
+ { key: "ModeChange", keyCode: KeyboardEvent.DOM_VK_MODECHANGE },
+ { key: "PageUp", keyCode: KeyboardEvent.DOM_VK_PAGE_UP },
+ { key: "PageDown", keyCode: KeyboardEvent.DOM_VK_PAGE_DOWN },
+ { key: "End", keyCode: KeyboardEvent.DOM_VK_END },
+ { key: "Home", keyCode: KeyboardEvent.DOM_VK_HOME },
+ { key: "ArrowLeft", keyCode: KeyboardEvent.DOM_VK_LEFT },
+ { key: "ArrowUp", keyCode: KeyboardEvent.DOM_VK_UP },
+ { key: "ArrowRight", keyCode: KeyboardEvent.DOM_VK_RIGHT },
+ { key: "ArrowDown", keyCode: KeyboardEvent.DOM_VK_DOWN },
+ { key: "Select", keyCode: KeyboardEvent.DOM_VK_SELECT },
+ { key: "Print", keyCode: KeyboardEvent.DOM_VK_PRINT },
+ { key: "Execute", keyCode: KeyboardEvent.DOM_VK_EXECUTE },
+ { key: "PrintScreen", keyCode: KeyboardEvent.DOM_VK_PRINTSCREEN },
+ { key: "Insert", keyCode: KeyboardEvent.DOM_VK_INSERT },
+ { key: "Delete", keyCode: KeyboardEvent.DOM_VK_DELETE },
+ { key: "OS", keyCode: KeyboardEvent.DOM_VK_WIN, isModifier: true },
+ { key: "ContextMenu", keyCode: KeyboardEvent.DOM_VK_CONTEXT_MENU },
+ { key: "F1", keyCode: KeyboardEvent.DOM_VK_F1 },
+ { key: "F2", keyCode: KeyboardEvent.DOM_VK_F2 },
+ { key: "F3", keyCode: KeyboardEvent.DOM_VK_F3 },
+ { key: "F4", keyCode: KeyboardEvent.DOM_VK_F4 },
+ { key: "F5", keyCode: KeyboardEvent.DOM_VK_F5 },
+ { key: "F6", keyCode: KeyboardEvent.DOM_VK_F6 },
+ { key: "F7", keyCode: KeyboardEvent.DOM_VK_F7 },
+ { key: "F8", keyCode: KeyboardEvent.DOM_VK_F8 },
+ { key: "F9", keyCode: KeyboardEvent.DOM_VK_F9 },
+ { key: "F10", keyCode: KeyboardEvent.DOM_VK_F10 },
+ { key: "F11", keyCode: KeyboardEvent.DOM_VK_F11 },
+ { key: "F12", keyCode: KeyboardEvent.DOM_VK_F12 },
+ { key: "F13", keyCode: KeyboardEvent.DOM_VK_F13 },
+ { key: "F14", keyCode: KeyboardEvent.DOM_VK_F14 },
+ { key: "F15", keyCode: KeyboardEvent.DOM_VK_F15 },
+ { key: "F16", keyCode: KeyboardEvent.DOM_VK_F16 },
+ { key: "F17", keyCode: KeyboardEvent.DOM_VK_F17 },
+ { key: "F18", keyCode: KeyboardEvent.DOM_VK_F18 },
+ { key: "F19", keyCode: KeyboardEvent.DOM_VK_F19 },
+ { key: "F20", keyCode: KeyboardEvent.DOM_VK_F20 },
+ { key: "F21", keyCode: KeyboardEvent.DOM_VK_F21 },
+ { key: "F22", keyCode: KeyboardEvent.DOM_VK_F22 },
+ { key: "F23", keyCode: KeyboardEvent.DOM_VK_F23 },
+ { key: "F24", keyCode: KeyboardEvent.DOM_VK_F24 },
+ { key: "NumLock", keyCode: KeyboardEvent.DOM_VK_NUM_LOCK, isModifier: true, isLockableModifier: true },
+ { key: "ScrollLock", keyCode: KeyboardEvent.DOM_VK_SCROLL_LOCK, isModifier: true, isLockableModifier: true },
+ { key: "AudioVolumeMute", keyCode: KeyboardEvent.DOM_VK_VOLUME_MUTE },
+ { key: "AudioVolumeDown", keyCode: KeyboardEvent.DOM_VK_VOLUME_DOWN },
+ { key: "AudioVolumeUp", keyCode: KeyboardEvent.DOM_VK_VOLUME_UP },
+ { key: "Meta", keyCode: KeyboardEvent.DOM_VK_META, isModifier: true },
+ { key: "AltGraph", keyCode: KeyboardEvent.DOM_VK_ALTGR, isModifier: true },
+ { key: "Attn", keyCode: KeyboardEvent.DOM_VK_ATTN },
+ { key: "CrSel", keyCode: KeyboardEvent.DOM_VK_CRSEL },
+ { key: "ExSel", keyCode: KeyboardEvent.DOM_VK_EXSEL },
+ { key: "EraseEof", keyCode: KeyboardEvent.DOM_VK_EREOF },
+ { key: "Play", keyCode: KeyboardEvent.DOM_VK_PLAY },
+ { key: "ZoomToggle", keyCode: KeyboardEvent.DOM_VK_ZOOM },
+ { key: "ZoomIn", keyCode: KeyboardEvent.DOM_VK_ZOOM },
+ { key: "ZoomOut", keyCode: KeyboardEvent.DOM_VK_ZOOM },
+ { key: "Unidentified", keyCode: 0 },
+ { key: "a", keyCode: 0, isPrintable: true },
+ { key: "A", keyCode: 0, isPrintable: true },
+ { key: " ", keyCode: 0, isPrintable: true },
+ { key: "", keyCode: 0, isPrintable: true },
+ ];
+
+ for (var i = 0; i < kKeyToKeyCode.length; i++) {
+ var keyEvent = new KeyboardEvent("", { key: kKeyToKeyCode[i].key });
+ var causeKeypress = !kKeyToKeyCode[i].isModifier;
+ var baseFlags = kKeyToKeyCode[i].isPrintable ? 0 : TIP.KEY_NON_PRINTABLE_KEY;
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ // 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.
+ TIP.keydown(keyEvent, baseFlags);
+ TIP.keyup(keyEvent, baseFlags);
+ var longDesc = description + "testing computation of .keyCode of \"" + kKeyToKeyCode[i].key + "\", ";
+ is(events.length, causeKeypress ? 3 : 2,
+ longDesc + "keydown" + (causeKeypress ? ", keypress" : "") + " and keyup events should be fired");
+ for (var j = 0; j < events.length; j++) {
+ is(events[j].keyCode, events[j].type == "keypress" && kKeyToKeyCode[i].isPrintable ? 0 : kKeyToKeyCode[i].keyCode,
+ longDesc + " type=\"" + events[j].type + "\", keyCode value is wrong");
+ }
+ // However, if KEY_KEEP_KEYCODE_ZERO is specified, .keyCode value should be kept as 0.
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ TIP.keydown(keyEvent, TIP.KEY_KEEP_KEYCODE_ZERO | baseFlags);
+ TIP.keyup(keyEvent, TIP.KEY_KEEP_KEYCODE_ZERO | baseFlags);
+ var longDesc = description + "testing if .keyCode is forcibly set to KEY_KEEP_KEYCODE_ZERO, ";
+ is(events.length, causeKeypress ? 3 : 2,
+ longDesc + "keydown" + (causeKeypress ? ", keypress" : "") + " and keyup events should be fired");
+ for (var j = 0; j < events.length; j++) {
+ is(events[j].keyCode, 0,
+ longDesc + " type=\"" + events[j].type + "\", keyCode value is not 0");
+ }
+ // If .keyCode is initialized with non-zero value, the value shouldn't be computed again.
+ var keyEventWithLocation = new KeyboardEvent("", { key: kKeyToKeyCode[i].key, keyCode: 0xFF });
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ TIP.keydown(keyEventWithLocation, baseFlags);
+ TIP.keyup(keyEventWithLocation, baseFlags);
+ longDesc = description + "testing if .keyCode is not computed for \"" + kKeyToKeyCode[i].key + "\", ";
+ is(events.length, causeKeypress ? 3 : 2,
+ longDesc + "keydown" + (causeKeypress ? ", keypress" : "") + " and keyup events should be fired");
+ for (var j = 0; j < events.length; j++) {
+ is(events[j].keyCode, events[j].type == "keypress" && kKeyToKeyCode[i].isPrintable ? 0 : 0xFF,
+ longDesc + " type=\"" + events[j].type + "\", keyCode shouldn't be computed if it's initialized with non-zero value");
+ }
+ // Unlock lockable modifier if the key is a lockable modifier key.
+ if (kKeyToKeyCode[i].isLockableModifier) {
+ TIP.keydown(keyEvent, baseFlags);
+ TIP.keyup(keyEvent, baseFlags);
+ }
+ }
+
+ // Modifier state tests
+ var sharedTIP = createTIP();
+ ok(sharedTIP.beginInputTransactionForTests(otherWindow),
+ description + "sharedTIP.beginInputTransactionForTests(otherWindow) should return true");
+ TIP.shareModifierStateOf(sharedTIP);
+ var independentTIP = createTIP();
+ const kModifierKeys = [
+ { key: "Alt", code: "AltLeft", isLockable: false },
+ { key: "Alt", code: "AltRight", isLockable: false },
+ { key: "AltGraph", code: "AltRight", isLockable: false },
+ { key: "CapsLock", code: "CapsLock", isLockable: true },
+ { key: "Control", code: "ControlLeft", isLockable: false },
+ { key: "Control", code: "ControlRight", isLockable: false },
+ { key: "Fn", code: "Fn", isLockable: false },
+ { key: "FnLock", code: "", isLockable: true },
+ { key: "Meta", code: "OSLeft", isLockable: false },
+ { key: "Meta", code: "OSRight", isLockable: false },
+ { key: "NumLock", code: "NumLock", isLockable: true },
+ { key: "ScrollLock", code: "ScrollLock", isLockable: true },
+ { key: "Shift", code: "ShiftLeft", isLockable: false },
+ { key: "Shift", code: "ShiftRight", isLockable: false },
+ { key: "Symbol", code: "", isLockable: false },
+ { key: "SymbolLock", code: "", isLockable: true },
+ { key: "OS", code: "OSLeft", isLockable: false },
+ { key: "OS", code: "OSRight", isLockable: false },
+ ];
+
+ function checkModifiers(aTestDesc, aEvent, aType, aKey, aCode, aModifiers)
+ {
+ var desc = description + aTestDesc + ", type=\"" + aEvent.type + "\", key=\"" + aEvent.key + "\", code=\"" + aEvent.code + "\"";
+ is(aEvent.type, aType,
+ desc + ", .type value is wrong");
+ if (aEvent.type != aType) {
+ return;
+ }
+ is(aEvent.key, aKey,
+ desc + ", .key value is wrong");
+ is(aEvent.code, aCode,
+ desc + ", .code value is wrong");
+ is(aEvent.altKey, aModifiers.includes("Alt"),
+ desc + ", .altKey value is wrong");
+ is(aEvent.ctrlKey, aModifiers.includes("Control"),
+ desc + ", .ctrlKey value is wrong");
+ is(aEvent.metaKey, aModifiers.includes("Meta"),
+ desc + ", .metaKey value is wrong");
+ is(aEvent.shiftKey, aModifiers.includes("Shift"),
+ desc + ", .shiftKey value is wrong");
+ /* eslint-disable-next-line no-shadow */
+ for (var i = 0; i < kModifiers.length; i++) {
+ is(aEvent.getModifierState(kModifiers[i]), aModifiers.includes(kModifiers[i]),
+ desc + ", .getModifierState(\"" + kModifiers[i] + "\") returns wrong value");
+ }
+ }
+
+ function checkAllTIPModifiers(aTestDesc, aModifiers)
+ {
+ /* eslint-disable-next-line no-shadow */
+ for (var i = 0; i < kModifiers.length; i++) {
+ is(TIP.getModifierState(kModifiers[i]), aModifiers.includes(kModifiers[i]),
+ aTestDesc + ", TIP.getModifierState(\"" + kModifiers[i] + "\") returns wrong value");
+ is(sharedTIP.getModifierState(kModifiers[i]), TIP.getModifierState(kModifiers[i]),
+ aTestDesc + ", sharedTIP.getModifierState(\"" + kModifiers[i] + "\") returns different value from TIP");
+ is(independentTIP.getModifierState(kModifiers[i]), false,
+ aTestDesc + ", independentTIP.getModifierState(\"" + kModifiers[i] + "\") should return false");
+ }
+ }
+
+ // First, all modifiers must be false.
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ TIP.keydown(keyA);
+ TIP.keyup(keyA);
+
+ is(events.length, 3,
+ description + "TIP.keydown(keyA) and TIP.keyup(keyA) should cause keydown, keypress and keyup");
+ checkModifiers("Before dispatching modifier key events", events[0], "keydown", "a", "KeyA", []);
+ checkModifiers("Before dispatching modifier key events", events[1], "keypress", "a", "KeyA", []);
+ checkModifiers("Before dispatching modifier key events", events[2], "keyup", "a", "KeyA", []);
+
+ // Test each modifier keydown/keyup causes activating/inactivating the modifier state.
+ for (var i = 0; i < kModifierKeys.length; i++) {
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ var modKey = new KeyboardEvent("", { key: kModifierKeys[i].key, code: kModifierKeys[i].code });
+ var testDesc = "A modifier key \"" + kModifierKeys[i].key + "\" (\"" + kModifierKeys[i].code + "\") and a printable key";
+ if (!kModifierKeys[i].isLockable) {
+ TIP.keydown(modKey);
+ checkAllTIPModifiers(testDesc + ", \"" + kModifierKeys[i].key + "\" keydown", [ kModifierKeys[i].key ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ kModifierKeys[i].key ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ kModifierKeys[i].key ]);
+ TIP.keyup(modKey);
+ checkAllTIPModifiers(testDesc + ", \"" + kModifierKeys[i].key + "\" keyup", [ ]);
+ is(events.length, 5,
+ description + testDesc + " should cause 5 events");
+ checkModifiers(testDesc, events[0], "keydown", kModifierKeys[i].key, kModifierKeys[i].code, [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[1], "keydown", "a", "KeyA", [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[2], "keypress", "a", "KeyA", [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[3], "keyup", "a", "KeyA", [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[4], "keyup", kModifierKeys[i].key, kModifierKeys[i].code, [ ]);
+
+ // KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT shouldn't cause key events of modifier keys, but should modify the modifier state.
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ testDesc = "A modifier key \"" + kModifierKeys[i].key + "\" (\"" + kModifierKeys[i].code + "\") with KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT and a printable key";
+ TIP.keydown(modKey, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
+ TIP.keydown(keyA);
+ TIP.keyup(keyA);
+ TIP.keyup(modKey, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
+ TIP.keydown(keyA);
+ TIP.keyup(keyA);
+ is(events.length, 6,
+ description + testDesc + " should cause 6 events");
+ checkModifiers(testDesc, events[0], "keydown", "a", "KeyA", [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[1], "keypress", "a", "KeyA", [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[2], "keyup", "a", "KeyA", [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[3], "keydown", "a", "KeyA", [ ]);
+ checkModifiers(testDesc, events[4], "keypress", "a", "KeyA", [ ]);
+ checkModifiers(testDesc, events[5], "keyup", "a", "KeyA", [ ]);
+ } else {
+ TIP.keydown(modKey);
+ checkAllTIPModifiers(testDesc + ", \"" + kModifierKeys[i].key + "\" first keydown", [ kModifierKeys[i].key ]);
+ TIP.keyup(modKey);
+ checkAllTIPModifiers(testDesc + ", \"" + kModifierKeys[i].key + "\" first keyup", [ kModifierKeys[i].key ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ kModifierKeys[i].key ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ kModifierKeys[i].key ]);
+ TIP.keydown(modKey);
+ checkAllTIPModifiers(testDesc + ", \"" + kModifierKeys[i].key + "\" second keydown", [ ]);
+ TIP.keyup(modKey);
+ checkAllTIPModifiers(testDesc + ", \"" + kModifierKeys[i].key + "\" second keyup", [ ]);
+ is(events.length, 7,
+ description + testDesc + " should cause 7 events");
+ checkModifiers(testDesc, events[0], "keydown", kModifierKeys[i].key, kModifierKeys[i].code, [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[1], "keyup", kModifierKeys[i].key, kModifierKeys[i].code, [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[2], "keydown", "a", "KeyA", [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[3], "keypress", "a", "KeyA", [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[4], "keyup", "a", "KeyA", [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[5], "keydown", kModifierKeys[i].key, kModifierKeys[i].code, [ ]);
+ checkModifiers(testDesc, events[6], "keyup", kModifierKeys[i].key, kModifierKeys[i].code, [ ]);
+
+ // KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT shouldn't cause key events of modifier keys, but should modify the modifier state.
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ testDesc = "A modifier key \"" + kModifierKeys[i].key + "\" (\"" + kModifierKeys[i].code + "\") with KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT and a printable key";
+ TIP.keydown(modKey, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
+ TIP.keyup(modKey, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
+ TIP.keydown(keyA);
+ TIP.keyup(keyA);
+ TIP.keydown(modKey, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
+ TIP.keyup(modKey, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
+ TIP.keydown(keyA);
+ TIP.keyup(keyA);
+ is(events.length, 6,
+ description + testDesc + " should cause 6 events");
+ checkModifiers(testDesc, events[0], "keydown", "a", "KeyA", [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[1], "keypress", "a", "KeyA", [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[2], "keyup", "a", "KeyA", [ kModifierKeys[i].key ]);
+ checkModifiers(testDesc, events[3], "keydown", "a", "KeyA", [ ]);
+ checkModifiers(testDesc, events[4], "keypress", "a", "KeyA", [ ]);
+ checkModifiers(testDesc, events[5], "keyup", "a", "KeyA", [ ]);
+ }
+ }
+
+ // Modifier state should be inactivated only when all pressed modifiers are released
+ var shiftLeft = new KeyboardEvent("", { key: "Shift", code: "ShiftLeft" });
+ var shiftRight = new KeyboardEvent("", { key: "Shift", code: "ShiftRight" });
+ var shiftVirtual = new KeyboardEvent("", { key: "Shift", code: "" });
+ var altGrVirtual = new KeyboardEvent("", { key: "AltGraph", code: "" });
+ var ctrlVirtual = new KeyboardEvent("", { key: "Control", code: "" });
+
+ var testDesc = "ShiftLeft press -> ShiftRight press -> ShiftRight release -> ShiftLeft release";
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ TIP.keydown(shiftLeft);
+ checkAllTIPModifiers(testDesc + ", Left-Shift keydown", [ "Shift" ]);
+ TIP.keydown(shiftRight);
+ checkAllTIPModifiers(testDesc + ", Right-Shift keydown", [ "Shift" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift" ]);
+ TIP.keyup(shiftRight);
+ checkAllTIPModifiers(testDesc + ", Right-Shift keyup", [ "Shift" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Right-Shift keyup)", [ "Shift" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Right-Shift keyup)", [ "Shift" ]);
+ TIP.keyup(shiftLeft);
+ checkAllTIPModifiers(testDesc + ", Left-Shift keyup", [ ]);
+
+ is(events.length, 10,
+ description + testDesc + " should cause 10 events");
+ checkModifiers(testDesc, events[0], "keydown", "Shift", "ShiftLeft", [ "Shift" ]);
+ checkModifiers(testDesc, events[1], "keydown", "Shift", "ShiftRight", [ "Shift" ]);
+ checkModifiers(testDesc, events[2], "keydown", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[3], "keypress", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[4], "keyup", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[5], "keyup", "Shift", "ShiftRight", [ "Shift" ]);
+ checkModifiers(testDesc, events[6], "keydown", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[7], "keypress", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[8], "keyup", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[9], "keyup", "Shift", "ShiftLeft", [ ]);
+
+ testDesc = "ShiftLeft press -> ShiftRight press -> ShiftLeft release -> ShiftRight release";
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ TIP.keydown(shiftLeft);
+ checkAllTIPModifiers(testDesc + ", Left-Shift keydown", [ "Shift" ]);
+ TIP.keydown(shiftRight);
+ checkAllTIPModifiers(testDesc + ", Right-Shift keydown", [ "Shift" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift" ]);
+ TIP.keyup(shiftLeft);
+ checkAllTIPModifiers(testDesc + ", Left-Shift keyup", [ "Shift" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Left-Shift keyup)", [ "Shift" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Left-Shift keyup)", [ "Shift" ]);
+ TIP.keyup(shiftRight);
+ checkAllTIPModifiers(testDesc + ", Right-Shift keyup", [ ]);
+
+ is(events.length, 10,
+ description + testDesc + " should cause 10 events");
+ checkModifiers(testDesc, events[0], "keydown", "Shift", "ShiftLeft", [ "Shift" ]);
+ checkModifiers(testDesc, events[1], "keydown", "Shift", "ShiftRight", [ "Shift" ]);
+ checkModifiers(testDesc, events[2], "keydown", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[3], "keypress", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[4], "keyup", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[5], "keyup", "Shift", "ShiftLeft", [ "Shift" ]);
+ checkModifiers(testDesc, events[6], "keydown", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[7], "keypress", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[8], "keyup", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[9], "keyup", "Shift", "ShiftRight", [ ]);
+
+ testDesc = "ShiftLeft press -> virtual Shift press -> virtual Shift release -> ShiftLeft release";
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ TIP.keydown(shiftLeft);
+ checkAllTIPModifiers(testDesc + ", Left-Shift keydown", [ "Shift" ]);
+ TIP.keydown(shiftVirtual);
+ checkAllTIPModifiers(testDesc + ", Virtual-Shift keydown", [ "Shift" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift" ]);
+ TIP.keyup(shiftVirtual);
+ checkAllTIPModifiers(testDesc + ", Virtual-Shift keyup", [ "Shift" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Virtual-Shift keyup)", [ "Shift" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Virtual-Shift keyup)", [ "Shift" ]);
+ TIP.keyup(shiftLeft);
+ checkAllTIPModifiers(testDesc + ", Left-Shift keyup", [ ]);
+
+ is(events.length, 10,
+ description + testDesc + " should cause 10 events");
+ checkModifiers(testDesc, events[0], "keydown", "Shift", "ShiftLeft", [ "Shift" ]);
+ checkModifiers(testDesc, events[1], "keydown", "Shift", "", [ "Shift" ]);
+ checkModifiers(testDesc, events[2], "keydown", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[3], "keypress", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[4], "keyup", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[5], "keyup", "Shift", "", [ "Shift" ]);
+ checkModifiers(testDesc, events[6], "keydown", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[7], "keypress", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[8], "keyup", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[9], "keyup", "Shift", "ShiftLeft", [ ]);
+
+ testDesc = "virtual Shift press -> ShiftRight press -> ShiftRight release -> virtual Shift release";
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ TIP.keydown(shiftVirtual);
+ checkAllTIPModifiers(testDesc + ", Virtual-Shift keydown", [ "Shift" ]);
+ TIP.keydown(shiftRight);
+ checkAllTIPModifiers(testDesc + ", Right-Shift keydown", [ "Shift" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift" ]);
+ TIP.keyup(shiftRight);
+ checkAllTIPModifiers(testDesc + ", Right-Shift keyup", [ "Shift" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Right-Shift keyup)", [ "Shift" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Right-Shift keyup)", [ "Shift" ]);
+ TIP.keyup(shiftVirtual);
+ checkAllTIPModifiers(testDesc + ", Virtual-Shift keyup", [ ]);
+
+ is(events.length, 10,
+ description + testDesc + " should cause 10 events");
+ checkModifiers(testDesc, events[0], "keydown", "Shift", "", [ "Shift" ]);
+ checkModifiers(testDesc, events[1], "keydown", "Shift", "ShiftRight", [ "Shift" ]);
+ checkModifiers(testDesc, events[2], "keydown", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[3], "keypress", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[4], "keyup", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[5], "keyup", "Shift", "ShiftRight", [ "Shift" ]);
+ checkModifiers(testDesc, events[6], "keydown", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[7], "keypress", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[8], "keyup", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[9], "keyup", "Shift", "", [ ]);
+
+ testDesc = "ShiftLeft press -> ShiftRight press -> ShiftRight release -> ShiftRight release -> ShiftLeft release";
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ TIP.keydown(shiftLeft);
+ checkAllTIPModifiers(testDesc + ", Left-Shift keydown", [ "Shift" ]);
+ TIP.keydown(shiftRight);
+ checkAllTIPModifiers(testDesc + ", Right-Shift keydown", [ "Shift" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift" ]);
+ TIP.keyup(shiftRight);
+ checkAllTIPModifiers(testDesc + ", Right-Shift keyup", [ "Shift" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Virtual-Shift keyup)", [ "Shift" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Virtual-Shift keyup)", [ "Shift" ]);
+ TIP.keyup(shiftRight);
+ checkAllTIPModifiers(testDesc + ", Right-Shift keyup again", [ "Shift" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Virtual-Shift keyup again)", [ "Shift" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Virtual-Shift keyup again)", [ "Shift" ]);
+ TIP.keyup(shiftLeft);
+ checkAllTIPModifiers(testDesc + ", Left-Shift keyup", [ ]);
+
+ is(events.length, 14,
+ description + testDesc + " should cause 14 events");
+ checkModifiers(testDesc, events[0], "keydown", "Shift", "ShiftLeft", [ "Shift" ]);
+ checkModifiers(testDesc, events[1], "keydown", "Shift", "ShiftRight", [ "Shift" ]);
+ checkModifiers(testDesc, events[2], "keydown", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[3], "keypress", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[4], "keyup", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[5], "keyup", "Shift", "ShiftRight", [ "Shift" ]);
+ checkModifiers(testDesc, events[6], "keydown", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[7], "keypress", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[8], "keyup", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[9], "keyup", "Shift", "ShiftRight", [ "Shift" ]);
+ checkModifiers(testDesc, events[10], "keydown", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[11], "keypress", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[12], "keyup", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[13], "keyup", "Shift", "ShiftLeft", [ ]);
+
+ testDesc = "ShiftLeft press -> ShiftLeft press -> ShiftLeft release -> ShiftLeft release";
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ TIP.keydown(shiftLeft);
+ checkAllTIPModifiers(testDesc + ", Left-Shift keydown", [ "Shift" ]);
+ TIP.keydown(shiftLeft);
+ checkAllTIPModifiers(testDesc + ", Left-Shift keydown again", [ "Shift" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift" ]);
+ TIP.keyup(shiftLeft);
+ checkAllTIPModifiers(testDesc + ", Left-Shift keyup", [ ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Left-Shift keyup)", [ ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Left-Shift keyup)", [ ]);
+ TIP.keyup(shiftLeft);
+ checkAllTIPModifiers(testDesc + ", Left-Shift keyup again", [ ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Left-Shift keyup again)", [ ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Left-Shift keyup again)", [ ]);
+
+ is(events.length, 13,
+ description + testDesc + " should cause 13 events");
+ checkModifiers(testDesc, events[0], "keydown", "Shift", "ShiftLeft", [ "Shift" ]);
+ checkModifiers(testDesc, events[1], "keydown", "Shift", "ShiftLeft", [ "Shift" ]);
+ checkModifiers(testDesc, events[2], "keydown", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[3], "keypress", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[4], "keyup", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[5], "keyup", "Shift", "ShiftLeft", [ ]);
+ checkModifiers(testDesc, events[6], "keydown", "a", "KeyA", [ ]);
+ checkModifiers(testDesc, events[7], "keypress", "a", "KeyA", [ ]);
+ checkModifiers(testDesc, events[8], "keyup", "a", "KeyA", [ ]);
+ checkModifiers(testDesc, events[9], "keyup", "Shift", "ShiftLeft", [ ]);
+ checkModifiers(testDesc, events[10], "keydown", "a", "KeyA", [ ]);
+ checkModifiers(testDesc, events[11], "keypress", "a", "KeyA", [ ]);
+ checkModifiers(testDesc, events[12], "keyup", "a", "KeyA", [ ]);
+
+ testDesc = "virtual Shift press -> virtual AltGraph press -> virtual AltGraph release -> virtual Shift release";
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ TIP.keydown(shiftVirtual);
+ checkAllTIPModifiers(testDesc + ", Virtual-Shift keydown", [ "Shift" ]);
+ TIP.keydown(altGrVirtual);
+ checkAllTIPModifiers(testDesc + ", Virtual-AltGraph keydown", [ "Shift", "AltGraph" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift", "AltGraph" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift", "AltGraph" ]);
+ TIP.keyup(altGrVirtual);
+ checkAllTIPModifiers(testDesc + ", Virtual-AltGraph keyup", [ "Shift" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Virtual-AltGraph keyup)", [ "Shift" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Virtual-AltGraph keyup)", [ "Shift" ]);
+ TIP.keyup(shiftVirtual);
+ checkAllTIPModifiers(testDesc + ", Virtual-Shift keyup", [ ]);
+
+ is(events.length, 10,
+ description + testDesc + " should cause 10 events");
+ checkModifiers(testDesc, events[0], "keydown", "Shift", "", [ "Shift" ]);
+ checkModifiers(testDesc, events[1], "keydown", "AltGraph", "", [ "Shift", "AltGraph" ]);
+ checkModifiers(testDesc, events[2], "keydown", "a", "KeyA", [ "Shift", "AltGraph" ]);
+ checkModifiers(testDesc, events[3], "keypress", "a", "KeyA", [ "Shift", "AltGraph" ]);
+ checkModifiers(testDesc, events[4], "keyup", "a", "KeyA", [ "Shift", "AltGraph" ]);
+ checkModifiers(testDesc, events[5], "keyup", "AltGraph", "", [ "Shift" ]);
+ checkModifiers(testDesc, events[6], "keydown", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[7], "keypress", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[8], "keyup", "a", "KeyA", [ "Shift" ]);
+ checkModifiers(testDesc, events[9], "keyup", "Shift", "", [ ]);
+
+ testDesc = "virtual Shift press -> virtual AltGraph press -> virtual Shift release -> virtual AltGr release";
+ reset();
+ doPreventDefaults = [ "keypress" ];
+ TIP.keydown(shiftVirtual);
+ checkAllTIPModifiers(testDesc + ", Virtual-Shift keydown", [ "Shift" ]);
+ TIP.keydown(altGrVirtual);
+ checkAllTIPModifiers(testDesc + ", Virtual-AltGraph keydown", [ "Shift", "AltGraph" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift", "AltGraph" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift", "AltGraph" ]);
+ TIP.keyup(shiftVirtual);
+ checkAllTIPModifiers(testDesc + ", Virtual-Shift keyup", [ "AltGraph" ]);
+ TIP.keydown(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Virtual-Shift keyup)", [ "AltGraph" ]);
+ TIP.keyup(keyA);
+ checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Virtual-Shift keyup)", [ "AltGraph" ]);
+ TIP.keyup(altGrVirtual);
+ checkAllTIPModifiers(testDesc + ", Virtual-AltGraph keyup", [ ]);
+
+ is(events.length, 10,
+ description + testDesc + " should cause 10 events");
+ checkModifiers(testDesc, events[0], "keydown", "Shift", "", [ "Shift" ]);
+ checkModifiers(testDesc, events[1], "keydown", "AltGraph", "", [ "Shift", "AltGraph" ]);
+ checkModifiers(testDesc, events[2], "keydown", "a", "KeyA", [ "Shift", "AltGraph" ]);
+ checkModifiers(testDesc, events[3], "keypress", "a", "KeyA", [ "Shift", "AltGraph" ]);
+ checkModifiers(testDesc, events[4], "keyup", "a", "KeyA", [ "Shift", "AltGraph" ]);
+ checkModifiers(testDesc, events[5], "keyup", "Shift", "", [ "AltGraph" ]);
+ checkModifiers(testDesc, events[6], "keydown", "a", "KeyA", [ "AltGraph" ]);
+ checkModifiers(testDesc, events[7], "keypress", "a", "KeyA", [ "AltGraph" ]);
+ checkModifiers(testDesc, events[8], "keyup", "a", "KeyA", [ "AltGraph" ]);
+ checkModifiers(testDesc, events[9], "keyup", "AltGraph", "", [ ]);
+
+ // shareModifierStateOf(null) should cause resetting the modifier state
+ function checkTIPModifiers(aTestDesc, aTIP, aModifiers)
+ {
+ /* eslint-disable-next-line no-shadow */
+ for (var i = 0; i < kModifiers.length; i++) {
+ is(aTIP.getModifierState(kModifiers[i]), aModifiers.includes(kModifiers[i]),
+ description + aTestDesc + ", aTIP.getModifierState(\"" + kModifiers[i] + "\") returns wrong value");
+ }
+ }
+ TIP.keydown(shiftVirtual);
+ TIP.keydown(altGrVirtual);
+ sharedTIP.shareModifierStateOf(null);
+ checkTIPModifiers("sharedTIP.sharedModifierStateOf(null) shouldn't cause TIP's modifiers reset", TIP, [ "Shift", "AltGraph" ]);
+ checkTIPModifiers("sharedTIP.sharedModifierStateOf(null) should cause sharedTIP modifiers reset", sharedTIP, [ ]);
+
+ // sharedTIP.shareModifierStateOf(null) should be unlinked from TIP.
+ TIP.keydown(ctrlVirtual);
+ checkTIPModifiers("TIP.keydown(ctrlVirtual) should cause TIP's modifiers set", TIP, [ "Shift", "AltGraph", "Control" ]);
+ checkTIPModifiers("TIP.keydown(ctrlVirtual) shouldn't cause sharedTIP modifiers set", sharedTIP, [ ]);
+
+ // beginInputTransactionForTests() shouldn't cause modifier state reset.
+ ok(TIP.beginInputTransactionForTests(otherWindow),
+ description + "TIP.beginInputTransactionForTests(otherWindow) should return true");
+ checkTIPModifiers("TIP.beginInputTransactionForTests(otherWindow) shouldn't cause TIP's modifiers set", TIP, [ "Shift", "AltGraph", "Control" ]);
+ TIP.keyup(shiftLeft);
+ TIP.keyup(altGrVirtual);
+ TIP.keyup(ctrlVirtual);
+ checkTIPModifiers("TIP should keep modifier's physical key state", TIP, [ "Shift" ]);
+ ok(TIP.beginInputTransactionForTests(window),
+ description + "TIP.beginInputTransactionForTests(window) should return true");
+ checkTIPModifiers("TIP.beginInputTransactionForTests(window) shouldn't cause TIP's modifiers set", TIP, [ "Shift" ]);
+ TIP.keyup(shiftVirtual);
+ checkTIPModifiers("TIP should keep modifier's physical key state", TIP, [ ]);
+
+ window.removeEventListener("keydown", handler, false);
+ window.removeEventListener("keypress", handler, false);
+ window.removeEventListener("keyup", handler, false);
+}
+
+function runErrorTests()
+{
+ var description = "runErrorTests(): ";
+
+ var TIP = createTIP();
+ ok(TIP.beginInputTransactionForTests(window),
+ description + "TIP.beginInputTransactionForTests() should succeed");
+
+ input.value = "";
+ input.focus();
+
+ // startComposition() should throw an exception if there is already a composition
+ TIP.startComposition();
+ try {
+ TIP.startComposition();
+ ok(false,
+ description + "startComposition() should fail if it was already called");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_FAILURE"),
+ description + "startComposition() should cause NS_ERROR_FAILURE if there is already composition");
+ } finally {
+ TIP.cancelComposition();
+ }
+
+ // cancelComposition() should throw an exception if there is no composition
+ try {
+ TIP.cancelComposition();
+ ok(false,
+ description + "cancelComposition() should fail if there is no composition");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_FAILURE"),
+ description + "cancelComposition() should cause NS_ERROR_FAILURE if there is no composition");
+ }
+
+ // commitComposition() without commit string should throw an exception if there is no composition
+ try {
+ TIP.commitComposition();
+ ok(false,
+ description + "commitComposition() should fail if there is no composition");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_FAILURE"),
+ description + "commitComposition() should cause NS_ERROR_FAILURE if there is no composition");
+ }
+
+ // commitCompositionWith("") should throw an exception if there is no composition
+ try {
+ TIP.commitCompositionWith("");
+ ok(false,
+ description + "commitCompositionWith(\"\") should fail if there is no composition");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_FAILURE"),
+ description + "commitCompositionWith(\"\") should cause NS_ERROR_FAILURE if there is no composition");
+ }
+
+ // Pending composition string should allow to flush without clause information (for compatibility)
+ try {
+ TIP.setPendingCompositionString("foo");
+ TIP.flushPendingComposition();
+ ok(true,
+ description + "flushPendingComposition() should succeed even if appendClauseToPendingComposition() has never been called");
+ TIP.cancelComposition();
+ } catch (e) {
+ ok(false,
+ description + "flushPendingComposition() shouldn't cause an exception even if appendClauseToPendingComposition() has never been called");
+ }
+
+ // Pending composition string must be filled by clause information
+ try {
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(2, TIP.ATTR_RAW_CLAUSE);
+ TIP.flushPendingComposition();
+ ok(false,
+ description + "flushPendingComposition() should fail if appendClauseToPendingComposition() doesn't fill all composition string");
+ TIP.cancelComposition();
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "flushPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if appendClauseToPendingComposition() doesn't fill all composition string");
+ }
+
+ // Pending composition string must not be shorter than appended clause length
+ try {
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(4, TIP.ATTR_RAW_CLAUSE);
+ TIP.flushPendingComposition();
+ ok(false,
+ description + "flushPendingComposition() should fail if appendClauseToPendingComposition() appends longer clause information");
+ TIP.cancelComposition();
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "flushPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if appendClauseToPendingComposition() appends longer clause information");
+ }
+
+ // Pending composition must not have clause information with empty string
+ try {
+ TIP.appendClauseToPendingComposition(1, TIP.ATTR_RAW_CLAUSE);
+ TIP.flushPendingComposition();
+ ok(false,
+ description + "flushPendingComposition() should fail if there is a clause with empty string");
+ TIP.cancelComposition();
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "flushPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if there is a clause with empty string");
+ }
+
+ // Appending a clause whose length is 0 should cause an exception
+ try {
+ TIP.appendClauseToPendingComposition(0, TIP.ATTR_RAW_CLAUSE);
+ ok(false,
+ description + "appendClauseToPendingComposition() should fail if the length is 0");
+ TIP.flushPendingComposition();
+ TIP.cancelComposition();
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "appendClauseToPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if the length is 0");
+ }
+
+ // Appending a clause whose attribute is invalid should cause an exception
+ try {
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, 0);
+ ok(false,
+ description + "appendClauseToPendingComposition() should fail if the attribute is invalid");
+ TIP.flushPendingComposition();
+ TIP.cancelComposition();
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "appendClauseToPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if the attribute is invalid");
+ }
+
+ // Setting caret position outside of composition string should cause an exception
+ try {
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(4);
+ TIP.flushPendingComposition();
+ ok(false,
+ description + "flushPendingComposition() should fail if caret position is out of composition string");
+ TIP.cancelComposition();
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "flushPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if caret position is out of composition string");
+ }
+
+ // Calling keydown() with a KeyboardEvent initialized with invalid code value should cause an exception.
+ input.value = "";
+ try {
+ var keyInvalidCode = new KeyboardEvent("", { key: "f", code: "InvalidCodeValue", keyCode: KeyboardEvent.DOM_VK_F });
+ TIP.keydown(keyInvalidCode);
+ ok(false,
+ description + "TIP.keydown(keyInvalidCode) should cause throwing an exception because its code value is not registered");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "TIP.keydown(keyInvalidCode) should cause throwing an exception including NS_ERROR_ILLEGAL_VALUE");
+ } finally {
+ is(input.value, "",
+ description + "The input element should not be modified");
+ }
+
+ // Calling keyup() with a KeyboardEvent initialized with invalid code value should cause an exception.
+ input.value = "";
+ try {
+ var keyInvalidCode = new KeyboardEvent("", { key: "f", code: "InvalidCodeValue", keyCode: KeyboardEvent.DOM_VK_F });
+ TIP.keyup(keyInvalidCode);
+ ok(false,
+ description + "TIP.keyup(keyInvalidCode) should cause throwing an exception because its code value is not registered");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "TIP.keyup(keyInvalidCode) should cause throwing an exception including NS_ERROR_ILLEGAL_VALUE");
+ } finally {
+ is(input.value, "",
+ description + "The input element should not be modified");
+ }
+
+ // Calling keydown(KEY_NON_PRINTABLE_KEY) with a KeyboardEvent initialized with non-key name should cause an exception.
+ input.value = "";
+ try {
+ var keyInvalidKey = new KeyboardEvent("", { key: "ESCAPE", code: "Escape", keyCode: KeyboardEvent.DOM_VK_Escape});
+ TIP.keydown(keyInvalidKey, TIP.KEY_NON_PRINTABLE_KEY);
+ ok(false,
+ description + "TIP.keydown(keyInvalidKey, TIP.KEY_NON_PRINTABLE_KEY) should cause throwing an exception because its key value is not registered");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "keydown(keyInvalidKey, TIP.KEY_NON_PRINTABLE_KEY) should cause throwing an exception including NS_ERROR_ILLEGAL_VALUE");
+ } finally {
+ is(input.value, "",
+ description + "The input element should not be modified");
+ }
+
+ // Calling keyup(KEY_NON_PRINTABLE_KEY) with a KeyboardEvent initialized with non-key name should cause an exception.
+ input.value = "";
+ try {
+ var keyInvalidKey = new KeyboardEvent("", { key: "ESCAPE", code: "Escape", keyCode: KeyboardEvent.DOM_VK_Escape});
+ TIP.keydown(keyInvalidKey, TIP.KEY_NON_PRINTABLE_KEY);
+ ok(false,
+ description + "TIP.keyup(keyInvalidKey, TIP.KEY_NON_PRINTABLE_KEY) should cause throwing an exception because its key value is not registered");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "keyup(keyInvalidKey, TIP.KEY_NON_PRINTABLE_KEY) should cause throwing an exception including NS_ERROR_ILLEGAL_VALUE");
+ } finally {
+ is(input.value, "",
+ description + "The input element should not be modified");
+ }
+
+ // KEY_KEEP_KEY_LOCATION_STANDARD flag should be used only when .location is not initialized with non-zero value.
+ try {
+ var keyEvent = new KeyboardEvent("", { code: "Enter", location: KeyboardEvent.DOM_KEY_LOCATION_LEFT });
+ TIP.keydown(keyEvent, TIP.KEY_KEEP_KEY_LOCATION_STANDARD);
+ ok(false,
+ description + "keydown(KEY_KEEP_KEY_LOCATION_STANDARD) should fail if the .location of the key event is initialized with non-zero value");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ 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");
+ }
+ try {
+ var keyEvent = new KeyboardEvent("", { code: "Enter", location: KeyboardEvent.DOM_KEY_LOCATION_LEFT });
+ TIP.keyup(keyEvent, TIP.KEY_KEEP_KEY_LOCATION_STANDARD);
+ ok(false,
+ description + "keyup(KEY_KEEP_KEY_LOCATION_STANDARD) should fail if the .location of the key event is initialized with non-zero value");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ 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");
+ }
+
+ // KEY_KEEP_KEYCODE_ZERO flag should be used only when .keyCode is not initialized with non-zero value.
+ try {
+ var keyEvent = new KeyboardEvent("", { key: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
+ TIP.keydown(keyEvent, TIP.KEY_KEEP_KEYCODE_ZERO);
+ ok(false,
+ description + "keydown(KEY_KEEP_KEYCODE_ZERO) should fail if the .keyCode of the key event is initialized with non-zero value");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "keydown(KEY_KEEP_KEYCODE_ZERO) should cause NS_ERROR_ILLEGAL_VALUE if the .keyCode of the key event is initialized with nonzero value");
+ }
+ try {
+ var keyEvent = new KeyboardEvent("", { key: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
+ TIP.keyup(keyEvent, TIP.KEY_KEEP_KEYCODE_ZERO);
+ ok(false,
+ description + "keyup(KEY_KEEP_KEYCODE_ZERO) should fail if the .keyCode of the key event is initialized with non-zero value");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "keyup(KEY_KEEP_KEYCODE_ZERO) should cause NS_ERROR_ILLEGAL_VALUE if the .keyCode of the key event is initialized with nonzero value");
+ }
+
+ // Specifying KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT with non-modifier key, it should cause an exception.
+ try {
+ var keyEvent = new KeyboardEvent("", { key: "a", code: "ShiftLeft" });
+ TIP.keyup(keyEvent, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
+ ok(false,
+ description + "keydown(KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) should fail if the .key value isn't a modifier key");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "keydown(KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) should cause NS_ERROR_ILLEGAL_VALUE if the .key value isn't a modifier key");
+ }
+ try {
+ var keyEvent = new KeyboardEvent("", { key: "Enter", code: "ShiftLeft" });
+ TIP.keyup(keyEvent, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
+ ok(false,
+ description + "keydown(KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) should fail if the .key value isn't a modifier key");
+ } catch (e) {
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ description + "keydown(KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) should cause NS_ERROR_ILLEGAL_VALUE if the .key value isn't a modifier key");
+ }
+
+ // The type of key events specified to composition methods should be "" or "keydown".
+ var kKeyEventTypes = [
+ { type: "keydown", valid: true },
+ { type: "keypress", valid: false },
+ { type: "keyup", valid: false },
+ { type: "", valid: true },
+ { type: "mousedown", valid: false },
+ { type: "foo", valid: false },
+ ];
+ for (var i = 0; i < kKeyEventTypes[i].length; i++) {
+ var keyEvent =
+ new KeyboardEvent(kKeyEventTypes[i].type, { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
+ var testDescription = description + "type=\"" + kKeyEventTypes[i].type + "\", ";
+ try {
+ TIP.startComposition(keyEvent);
+ ok(kKeyEventTypes[i].valid,
+ testDescription + "TIP.startComposition(keyEvent) should not accept the event type");
+ TIP.cancelComposition();
+ } catch (e) {
+ ok(!kKeyEventTypes[i].valid,
+ testDescription + "TIP.startComposition(keyEvent) should not throw an exception for the event type");
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ testDescription + "TIP.startComposition(keyEvent) should cause NS_ERROR_ILLEGAL_VALUE if the key event type isn't valid");
+ }
+ try {
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition(keyEvent);
+ ok(kKeyEventTypes[i].valid,
+ testDescription + "TIP.flushPendingComposition(keyEvent) should not accept the event type");
+ TIP.cancelComposition();
+ } catch (e) {
+ ok(!kKeyEventTypes[i].valid,
+ testDescription + "TIP.flushPendingComposition(keyEvent) should not throw an exception for the event type");
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ testDescription + "TIP.flushPendingComposition(keyEvent) should cause NS_ERROR_ILLEGAL_VALUE if the key event type isn't valid");
+ }
+ try {
+ TIP.startComposition();
+ TIP.commitComposition(keyEvent);
+ ok(kKeyEventTypes[i].valid,
+ testDescription + "TIP.commitComposition(keyEvent) should not accept the event type");
+ } catch (e) {
+ ok(!kKeyEventTypes[i].valid,
+ testDescription + "TIP.commitComposition(keyEvent) should not throw an exception for the event type");
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ testDescription + "TIP.commitComposition(keyEvent) should cause NS_ERROR_ILLEGAL_VALUE if the key event type isn't valid");
+ TIP.cancelComposition();
+ }
+ try {
+ TIP.commitCompositionWith("foo", keyEvent);
+ ok(kKeyEventTypes[i].valid,
+ testDescription + "TIP.commitCompositionWith(\"foo\", keyEvent) should not accept the event type");
+ } catch (e) {
+ ok(!kKeyEventTypes[i].valid,
+ testDescription + "TIP.commitCompositionWith(\"foo\", keyEvent) should not throw an exception for the event type");
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ testDescription + "TIP.commitCompositionWith(\"foo\", keyEvent) should cause NS_ERROR_ILLEGAL_VALUE if the key event type isn't valid");
+ }
+ try {
+ TIP.startComposition();
+ TIP.cancelComposition(keyEvent);
+ ok(kKeyEventTypes[i].valid,
+ testDescription + "TIP.cancelComposition(keyEvent) should not accept the event type");
+ } catch (e) {
+ ok(!kKeyEventTypes[i].valid,
+ testDescription + "TIP.cancelComposition(keyEvent) should not throw an exception for the event type");
+ ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"),
+ testDescription + "TIP.cancelComposition(keyEvent) should cause NS_ERROR_ILLEGAL_VALUE if the key event type isn't valid");
+ TIP.cancelComposition();
+ }
+ input.value = "";
+ }
+}
+
+function runCommitCompositionTests()
+{
+ var description = "runCommitCompositionTests(): ";
+
+ var TIP = createTIP();
+ ok(TIP.beginInputTransactionForTests(window),
+ description + "TIP.beginInputTransactionForTests() should succeed");
+
+ input.focus();
+
+ // commitComposition() should commit the composition with the last data.
+ input.value = "";
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition();
+ TIP.commitComposition();
+ is(input.value, "foo",
+ description + "commitComposition() should commit the composition with the last data");
+
+ // commitCompositionWith("") should commit the composition with empty string.
+ input.value = "";
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition();
+ TIP.commitCompositionWith("");
+ is(input.value, "",
+ description + "commitCompositionWith(\"\") should commit the composition with empty string");
+
+ function doCommit(aText)
+ {
+ TIP.commitCompositionWith(aText);
+ }
+
+ // doCommit() should commit the composition with the last data.
+ input.value = "";
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition();
+ doCommit();
+ todo_is(input.value, "foo",
+ description + "doCommit() should commit the composition with the last data");
+
+ // doCommit("") should commit the composition with empty string.
+ input.value = "";
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition();
+ doCommit("");
+ is(input.value, "",
+ description + "doCommit(\"\") should commit the composition with empty string");
+
+ // doCommit(null) should commit the composition with empty string.
+ input.value = "";
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition();
+ doCommit(null);
+ is(input.value, "",
+ description + "doCommit(null) should commit the composition with empty string");
+
+ // doCommit(undefined) should commit the composition with the last data.
+ input.value = "";
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition();
+ doCommit(undefined);
+ todo_is(input.value, "foo",
+ description + "doCommit(undefined) should commit the composition with the last data");
+
+ function doCommitWithNullCheck(aText)
+ {
+ TIP.commitCompositionWith(aText ? aText : "");
+ }
+
+ // doCommitWithNullCheck() should commit the composition with the last data.
+ input.value = "";
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition();
+ doCommitWithNullCheck();
+ is(input.value, "",
+ description + "doCommitWithNullCheck() should commit the composition with empty string");
+
+ // doCommitWithNullCheck("") should commit the composition with empty string.
+ input.value = "";
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition();
+ doCommitWithNullCheck("");
+ is(input.value, "",
+ description + "doCommitWithNullCheck(\"\") should commit the composition with empty string");
+
+ // doCommitWithNullCheck(null) should commit the composition with empty string.
+ input.value = "";
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition();
+ doCommitWithNullCheck(null);
+ is(input.value, "",
+ description + "doCommitWithNullCheck(null) should commit the composition with empty string");
+
+ // doCommitWithNullCheck(undefined) should commit the composition with the last data.
+ input.value = "";
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition();
+ doCommitWithNullCheck(undefined);
+ is(input.value, "",
+ description + "doCommitWithNullCheck(undefined) should commit the composition with empty string");
+}
+
+function runUnloadTests1()
+{
+ return new Promise(resolve => {
+ let description = "runUnloadTests1(): ";
+
+ let TIP1 = createTIP();
+ ok(TIP1.beginInputTransactionForTests(childWindow),
+ description + "TIP1.beginInputTransactionForTests() should succeed");
+
+ let oldSrc = iframe.src;
+ let parentWindow = window;
+
+ iframe.addEventListener("load", function (aEvent) {
+ ok(true, description + "dummy page is loaded");
+ childWindow = iframe.contentWindow;
+ textareaInFrame = null;
+ iframe.addEventListener("load", function () {
+ ok(true, description + "old iframe is restored");
+ // And also restore the iframe information with restored contents.
+ childWindow = iframe.contentWindow;
+ textareaInFrame = iframe.contentDocument.getElementById("textarea");
+ SimpleTest.executeSoon(resolve);
+ }, {capture: true, once: true});
+
+ // The composition should be committed internally. So, another TIP should
+ // be able to steal the rights to using TextEventDispatcher.
+ let TIP2 = createTIP();
+ ok(TIP2.beginInputTransactionForTests(parentWindow),
+ description + "TIP2.beginInputTransactionForTests() should succeed");
+
+ input.focus();
+ input.value = "";
+
+ TIP2.setPendingCompositionString("foo");
+ TIP2.appendClauseToPendingComposition(3, TIP2.ATTR_RAW_CLAUSE);
+ TIP2.setCaretInPendingComposition(3);
+ TIP2.flushPendingComposition();
+ is(input.value, "foo",
+ description + "the input in the parent document should have composition string");
+
+ TIP2.cancelComposition();
+
+ // Restore the old iframe content.
+ iframe.src = oldSrc;
+ }, {capture: true, once: true});
+
+ // Start composition in the iframe.
+ textareaInFrame.value = "";
+ textareaInFrame.focus();
+
+ TIP1.setPendingCompositionString("foo");
+ TIP1.appendClauseToPendingComposition(3, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.setCaretInPendingComposition(3);
+ TIP1.flushPendingComposition();
+ is(textareaInFrame.value, "foo",
+ description + "the textarea in the iframe should have composition string");
+
+ // Load different web page on the frame.
+ iframe.src = "data:text/html,<body>dummy page</body>";
+ });
+}
+
+function runUnloadTests2()
+{
+ return new Promise(resolve => {
+ let description = "runUnloadTests2(): ";
+
+ let TIP = createTIP();
+ ok(TIP.beginInputTransactionForTests(childWindow),
+ description + "TIP.beginInputTransactionForTests() should succeed");
+
+ let oldSrc = iframe.src;
+
+ iframe.addEventListener("load", function (aEvent) {
+ ok(true, description + "dummy page is loaded");
+ childWindow = iframe.contentWindow;
+ textareaInFrame = null;
+ iframe.addEventListener("load", function () {
+ ok(true, description + "old iframe is restored");
+ // And also restore the iframe information with restored contents.
+ childWindow = iframe.contentWindow;
+ textareaInFrame = iframe.contentDocument.getElementById("textarea");
+ SimpleTest.executeSoon(resolve);
+ }, {capture: true, once: true});
+
+ input.focus();
+ input.value = "";
+
+ // TIP should be still available in the same top level widget.
+ TIP.setPendingCompositionString("bar");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition();
+ if (input.value == "") {
+ // XXX TextInputProcessor or TextEventDispatcher may have a bug.
+ todo_is(input.value, "bar",
+ description + "the input in the parent document should have composition string");
+ } else {
+ is(input.value, "bar",
+ description + "the input in the parent document should have composition string");
+ }
+
+ TIP.cancelComposition();
+
+ // Restore the old iframe content.
+ iframe.src = oldSrc;
+ }, {capture: true, once: true});
+
+ // Start composition in the iframe.
+ textareaInFrame.value = "";
+ textareaInFrame.focus();
+
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.setCaretInPendingComposition(3);
+ TIP.flushPendingComposition();
+ is(textareaInFrame.value, "foo",
+ description + "the textarea in the iframe should have composition string");
+
+ // Load different web page on the frame.
+ iframe.src = "data:text/html,<body>dummy page</body>";
+ });
+}
+
+async function runCallbackTests(aForTests)
+{
+ let description = "runCallbackTests(aForTests=" + aForTests + "): ";
+
+ input.value = "";
+ input.focus();
+ input.blur();
+
+ let TIP = createTIP();
+ let notifications = [];
+ let waitingNextNotification;
+ function callback(aTIP, aNotification)
+ {
+ if (aTIP == TIP) {
+ notifications.push(aNotification);
+ }
+ switch (aNotification.type) {
+ case "request-to-commit":
+ aTIP.commitComposition();
+ break;
+ case "request-to-cancel":
+ aTIP.cancelComposition();
+ break;
+ }
+ if (waitingNextNotification) {
+ SimpleTest.executeSoon(waitingNextNotification);
+ waitingNextNotification = undefined;
+ }
+ return true;
+ }
+
+ function dumpUnexpectedNotifications(aExpectedCount)
+ {
+ if (notifications.length <= aExpectedCount) {
+ return;
+ }
+ for (let i = aExpectedCount; i < notifications.length; i++) {
+ ok(false,
+ description + "Unexpected notification: " + notifications[i].type);
+ }
+ }
+
+ function waitUntilNotificationsReceived()
+ {
+ return new Promise(resolve => {
+ if (notifications.length) {
+ SimpleTest.executeSoon(resolve);
+ } else {
+ waitingNextNotification = resolve;
+ }
+ });
+ }
+
+ function checkPositionChangeNotification(aNotification, aDescription)
+ {
+ is(!aNotification || aNotification.type, "notify-position-change",
+ aDescription + " should cause position change notification");
+ }
+
+ function checkSelectionChangeNotification(aNotification, aDescription, aExpected)
+ {
+ is(aNotification.type, "notify-selection-change",
+ aDescription + " should cause selection change notification");
+ if (aNotification.type != "notify-selection-change") {
+ return;
+ }
+ is(aNotification.hasRange, aExpected.hasRange !== false,
+ `${aDescription} should cause selection change notification whose hasRange is ${aExpected.hasRange}`);
+ if (aNotification.hasRange) {
+ is(aNotification.offset, aExpected.offset,
+ `${aDescription} should cause selection change notification whose offset is ${aExpected.offset}`);
+ is(aNotification.text, aExpected.text,
+ `${aDescription} should cause selection change notification whose text is "${aExpected.text}"`);
+ is(aNotification.length, aExpected.text.length,
+ `${aDescription} should cause selection change notification whose length is ${aExpected.text.length}`);
+ is(aNotification.reversed, aExpected.reversed || false,
+ `${aDescription} should cause selection change notification whose reversed is ${aExpected.reversed || false}`);
+ }
+ is(aNotification.collapsed, aExpected.hasRange === false || !aExpected.text.length,
+ `${aDescription} should cause selection change notification whose collapsed is ${aExpected.hasRange === false || !aExpected.text.length}`);
+ is(aNotification.writingMode, aExpected.writingMode || "horizontal-tb",
+ `${aDescription} should cause selection change notification whose writingMode is ${aExpected.writingMode || "horizontal-tb"}`);
+ is(aNotification.causedByComposition, aExpected.causedByComposition || false,
+ `${aDescription} should cause selection change notification whose causedByComposition is ${aExpected.causedByComposition || false}`);
+ is(aNotification.causedBySelectionEvent, aExpected.causedBySelectionEvent || false,
+ `${aDescription} should cause selection change notification whose causedBySelectionEvent is ${aExpected.causedBySelectionEvent || false}`);
+ is(aNotification.occurredDuringComposition, aExpected.occurredDuringComposition || false,
+ `${aDescription} should cause cause selection change notification whose occurredDuringComposition is ${aExpected.occurredDuringComposition || false}`);
+ }
+
+ function checkTextChangeNotification(aNotification, aDescription, aExpected)
+ {
+ is(aNotification.type, "notify-text-change",
+ aDescription + " should cause text change notification");
+ if (aNotification.type != "notify-text-change") {
+ return;
+ }
+ is(aNotification.offset, aExpected.offset,
+ aDescription + " should cause text change notification whose offset is " + aExpected.offset);
+ is(aNotification.removedLength, aExpected.removedLength,
+ aDescription + " should cause text change notification whose removedLength is " + aExpected.removedLength);
+ is(aNotification.addedLength, aExpected.addedLength,
+ aDescription + " should cause text change notification whose addedLength is " + aExpected.addedLength);
+ is(aNotification.causedOnlyByComposition, aExpected.causedOnlyByComposition || false,
+ aDescription + " should cause text change notification whose causedOnlyByComposition is " + (aExpected.causedOnlyByComposition || false));
+ is(aNotification.includingChangesDuringComposition, aExpected.includingChangesDuringComposition || false,
+ aDescription + " should cause text change notification whose includingChangesDuringComposition is " + (aExpected.includingChangesDuringComposition || false));
+ is(aNotification.includingChangesWithoutComposition, typeof aExpected.includingChangesWithoutComposition === "boolean" ? aExpected.includingChangesWithoutComposition : true,
+ aDescription + " should cause text change notification whose includingChangesWithoutComposition is " + (typeof aExpected.includingChangesWithoutComposition === "boolean" ? aExpected.includingChangesWithoutComposition : true));
+ }
+
+ if (aForTests) {
+ TIP.beginInputTransactionForTests(window, callback);
+ } else {
+ TIP.beginInputTransaction(window, callback);
+ }
+
+ notifications = [];
+ input.focus();
+ is(notifications.length, 1,
+ description + "input.focus() should cause a notification");
+ is(notifications[0].type, "notify-focus",
+ description + "input.focus() should cause \"notify-focus\"");
+ dumpUnexpectedNotifications(1);
+
+ notifications = [];
+ input.blur();
+ is(notifications.length, 1,
+ description + "input.blur() should cause a notification");
+ is(notifications[0].type, "notify-blur",
+ description + "input.blur() should cause \"notify-focus\"");
+ dumpUnexpectedNotifications(1);
+
+ input.focus();
+ await waitUntilNotificationsReceived();
+ notifications = [];
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.flushPendingComposition();
+ is(notifications.length, 3,
+ description + "creating composition string 'foo' should cause 3 notifications");
+ checkTextChangeNotification(notifications[0], description + "creating composition string 'foo'",
+ { offset: 0, removedLength: 0, addedLength: 3,
+ causedOnlyByComposition: true, includingChangesDuringComposition: false, includingChangesWithoutComposition: false});
+ checkSelectionChangeNotification(notifications[1], description + "creating composition string 'foo'",
+ { offset: 3, text: "", causedByComposition: true, occurredDuringComposition: true });
+ checkPositionChangeNotification(notifications[2], description + "creating composition string 'foo'");
+ dumpUnexpectedNotifications(3);
+
+ notifications = [];
+ synthesizeMouseAtCenter(input, {});
+ is(notifications.length, 3,
+ description + "synthesizeMouseAtCenter(input, {}) during composition should cause 3 notifications");
+ is(notifications[0].type, "request-to-commit",
+ description + "synthesizeMouseAtCenter(input, {}) during composition should cause \"request-to-commit\"");
+ checkTextChangeNotification(notifications[1], description + "synthesizeMouseAtCenter(input, {}) during composition",
+ { offset: 0, removedLength: 3, addedLength: 3,
+ causedOnlyByComposition: true, includingChangesDuringComposition: false, includingChangesWithoutComposition: false});
+ checkPositionChangeNotification(notifications[2], description + "synthesizeMouseAtCenter(input, {}) during composition");
+ dumpUnexpectedNotifications(3);
+
+ input.focus();
+ await waitUntilNotificationsReceived();
+ notifications = [];
+ // XXX On macOS, window.moveBy() doesn't cause notify-position-change.
+ // Investigate this later (although, we cannot notify position change to
+ // native IME on macOS).
+ // Wayland also does not support it.
+ var isWayland = Services.prefs.getBoolPref("widget.wayland.test-workarounds.enabled", false);
+ if (!kIsMac && !isWayland) {
+ window.moveBy(0, 10);
+ await waitUntilNotificationsReceived();
+ is(notifications.length, 1,
+ description + "window.moveBy(0, 10) should cause a notification");
+ checkPositionChangeNotification(notifications[0], description + "window.moveBy(0, 10)");
+ dumpUnexpectedNotifications(1);
+
+ notifications = [];
+ window.moveBy(10, 0);
+ await waitUntilNotificationsReceived();
+ is(notifications.length, 1,
+ description + "window.moveBy(10, 0) should cause a notification");
+ checkPositionChangeNotification(notifications[0], description + "window.moveBy(10, 0)");
+ dumpUnexpectedNotifications(1);
+ }
+
+ input.focus();
+ input.value = "abc"
+ notifications = [];
+ input.selectionStart = input.selectionEnd = 0;
+ await waitUntilNotificationsReceived();
+ notifications = [];
+ let rightArrowKeyEvent =
+ new KeyboardEvent("", { key: "ArrowRight", code: "ArrowRight", keyCode: KeyboardEvent.DOM_VK_RIGHT });
+ TIP.keydown(rightArrowKeyEvent);
+ TIP.keyup(rightArrowKeyEvent);
+ is(notifications.length, 1,
+ description + "ArrowRight key press should cause a notification");
+ checkSelectionChangeNotification(notifications[0], description + "ArrowRight key press", { offset: 1, text: "" });
+ dumpUnexpectedNotifications(1);
+
+ notifications = [];
+ let shiftKeyEvent =
+ new KeyboardEvent("", { key: "Shift", code: "ShiftLeft", keyCode: KeyboardEvent.DOM_VK_SHIFT });
+ let leftArrowKeyEvent =
+ new KeyboardEvent("", { key: "ArrowLeft", code: "ArrowLeft", keyCode: KeyboardEvent.DOM_VK_LEFT });
+ TIP.keydown(shiftKeyEvent);
+ TIP.keydown(leftArrowKeyEvent);
+ TIP.keyup(leftArrowKeyEvent);
+ TIP.keyup(shiftKeyEvent);
+ is(notifications.length, 1,
+ description + "ArrowLeft key press with Shift should cause a notification");
+ checkSelectionChangeNotification(notifications[0], description + "ArrowLeft key press with Shift", { offset: 0, text: "a", reversed: true });
+ dumpUnexpectedNotifications(1);
+
+ TIP.keydown(rightArrowKeyEvent);
+ TIP.keyup(rightArrowKeyEvent);
+ notifications = [];
+ TIP.keydown(shiftKeyEvent);
+ TIP.keydown(rightArrowKeyEvent);
+ TIP.keyup(rightArrowKeyEvent);
+ TIP.keyup(shiftKeyEvent);
+ is(notifications.length, 1,
+ description + "ArrowRight key press with Shift should cause a notification");
+ checkSelectionChangeNotification(notifications[0], description + "ArrowRight key press with Shift", { offset: 1, text: "b" });
+ dumpUnexpectedNotifications(1);
+
+ notifications = [];
+ input.editor.selection.removeAllRanges();
+ await waitUntilNotificationsReceived();
+ is(notifications.length, 1,
+ `${description}Removing all selection ranges should cause a selection change notification`);
+ checkSelectionChangeNotification(
+ notifications[0],
+ `${description}Removing all selection ranges in editor`,
+ { hasRange: false }
+ );
+ dumpUnexpectedNotifications(1);
+
+ notifications = [];
+ let TIP2 = createTIP();
+ if (aForTests) {
+ TIP2.beginInputTransactionForTests(window, callback);
+ } else {
+ TIP2.beginInputTransaction(window, callback);
+ }
+ is(notifications.length, 1,
+ description + "Initializing another TIP should cause a notification");
+ is(notifications[0].type, "notify-end-input-transaction",
+ description + "Initializing another TIP should cause \"notify-detached\"");
+ dumpUnexpectedNotifications(1);
+}
+
+async function runFocusNotificationTestAfterDrop() {
+ const inputs = document.querySelectorAll("input[type=text]");
+ inputs[0].value = "abc";
+ inputs[1].value = "";
+
+ const TIP = createTIP();
+ let notifications = [];
+ function callback(aTIP, aNotification)
+ {
+ if (aTIP != TIP) {
+ return true;
+ }
+ switch (aNotification.type) {
+ case "request-to-commit":
+ aTIP.commitComposition();
+ break;
+ case "request-to-cancel":
+ aTIP.cancelComposition();
+ break;
+ case "notify-focus":
+ case "notify-blur":
+ notifications.push(aNotification.type);
+ break;
+ }
+ return true;
+ }
+
+ inputs[0].focus();
+ TIP.beginInputTransactionForTests(window, callback);
+ inputs[0].select();
+ try {
+ notifications = [];
+ await synthesizePlainDragAndDrop({
+ srcSelection: SpecialPowers.wrap(inputs[0]).editor.selection,
+ destElement: inputs[1],
+ });
+ } catch (ex) {
+ ok(false, `runFocusNotificationTestAfterDrop: unexpected error during DnD (${ex.message})`);
+ return;
+ }
+ is(
+ document.activeElement,
+ inputs[1],
+ "runFocusNotificationTestAfterDrop: Dropping to the second <input> should make it focused"
+ );
+ ok(
+ notifications.length > 1,
+ "runFocusNotificationTestAfterDrop: At least two notifications should be fired"
+ );
+ if (notifications.length) {
+ is(
+ notifications[notifications.length - 1],
+ "notify-focus",
+ "runFocusNotificationTestAfterDrop: focus notification should've been fired at last"
+ );
+ }
+}
+
+async function runQuerySelectionEventTestAtTextChangeNotification() {
+ contenteditable.innerHTML = "<p>abc</p><p>def</p>";
+ contenteditable.focus();
+ // Ensure to send notify-focus from IMEContentObserver
+ await new Promise(
+ resolve => requestAnimationFrame(
+ () => requestAnimationFrame(resolve)
+ )
+ );
+ document.execCommand("selectall");
+ // Ensure to send notify-selection-change from IMEContentObserver
+ await new Promise(
+ resolve => requestAnimationFrame(
+ () => requestAnimationFrame(resolve)
+ )
+ );
+
+ const kTestName = "runQuerySelectionEventTestAtTextChangeNotification";
+ await new Promise(resolve => {
+ const TIP = createTIP();
+ TIP.beginInputTransactionForTests(window, (aTIP, aNotification) => {
+ if (aTIP != TIP) {
+ return true;
+ }
+ switch (aNotification.type) {
+ case "request-to-commit":
+ aTIP.commitComposition();
+ break;
+ case "request-to-cancel":
+ aTIP.cancelComposition();
+ break;
+ case "notify-text-change":
+ const textContent = synthesizeQueryTextContent(0, 100);
+ if (textContent?.text.includes("abc")) {
+ break; // Different notification which we want to test, wait next one.
+ }
+ ok(
+ textContent?.succeeded,
+ `${kTestName}: query text content should succeed from notify-text-change handler`
+ );
+ const selectedText = synthesizeQuerySelectedText();
+ ok(
+ selectedText?.succeeded,
+ `${kTestName}: query selected text should succeed from notify-text-change handler`
+ );
+ if (textContent?.succeeded && selectedText?.succeeded) {
+ is(
+ selectedText.text,
+ textContent.text,
+ `${kTestName}: selected text should be same as all text`
+ );
+ }
+ resolve();
+ break;
+ }
+ return true;
+ });
+ // TODO: We want to do this while selection is batched but can flush
+ // pending notifications, however, I have no idea how to do it.
+ contenteditable.firstChild.remove();
+ });
+}
+
+async function runIMEStateUpdateTests() {
+ const TIP = createTIP();
+ let notifications = [];
+ function callback(aTIP, aNotification)
+ {
+ if (aTIP != TIP) {
+ return true;
+ }
+ switch (aNotification.type) {
+ case "request-to-commit":
+ aTIP.commitComposition();
+ break;
+ case "request-to-cancel":
+ aTIP.cancelComposition();
+ break;
+ case "notify-focus":
+ case "notify-blur":
+ notifications.push(aNotification.type);
+ break;
+ }
+ return true;
+ }
+
+ contenteditable.focus();
+ TIP.beginInputTransactionForTests(window, callback);
+ await new Promise(resolve => requestAnimationFrame(() =>
+ requestAnimationFrame(resolve)
+ )); // wait for flushing pending notifications if there is.
+
+ // run IMEStateManager::UpdateIMEState to disable IME
+ notifications = [];
+ const editor = getHTMLEditor(window);
+ editor.flags |= Ci.nsIEditor.eEditorReadonlyMask;
+ await new Promise(resolve => requestAnimationFrame(() =>
+ requestAnimationFrame(resolve)
+ )); // wait for flush pending notification even if handled asynchronously.
+ is(
+ notifications.length ? notifications[0] : undefined,
+ "notify-blur",
+ "runIMEStateUpdateTests: Making the HTMLEditor readonly should cause a blur notification"
+ );
+ is(
+ notifications.length,
+ 1,
+ `runIMEStateUpdateTests: Making the HTMLEditor readonly should not cause any other notifications, but got ${
+ notifications.length > 1 ? notifications[1] : ""
+ } notification`
+ );
+ is(
+ SpecialPowers.getDOMWindowUtils(window)?.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ `runIMEStateUpdateTests: Making the HTMLEditor readonly should make IME disabled`
+ );
+
+ // run IMEStateManager::UpdateIMEState to enable IME
+ notifications = [];
+ editor.flags &= ~Ci.nsIEditor.eEditorReadonlyMask;
+ await new Promise(resolve => requestAnimationFrame(() =>
+ requestAnimationFrame(resolve)
+ )); // wait for flush pending notification even if handled asynchronously.
+ is(
+ notifications.length ? notifications[0] : undefined,
+ "notify-focus",
+ "runIMEStateUpdateTests: Making the HTMLEditor editable should cause a focus notification without blur notification"
+ );
+ is(
+ notifications.length,
+ 1,
+ `runIMEStateUpdateTests: Making the HTMLEditor editable should not cause any other notifications, but got ${
+ notifications.length > 1 ? notifications[1] : ""
+ } notification`
+ );
+ is(
+ SpecialPowers.getDOMWindowUtils(window)?.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+ `runIMEStateUpdateTests: Making the HTMLEditor readonly should make IME disabled`
+ );
+}
+
+async function runTextNotificationChangesDuringNoFrame() {
+ const TIP = createTIP();
+ let onTextChange;
+ function callback(aTIP, aNotification)
+ {
+ if (aTIP != TIP) {
+ return true;
+ }
+ switch (aNotification.type) {
+ case "request-to-commit":
+ aTIP.commitComposition();
+ break;
+ case "request-to-cancel":
+ aTIP.cancelComposition();
+ break;
+ case "notify-text-change":
+ if (onTextChange) {
+ onTextChange(aNotification);
+ }
+ break;
+ }
+ return true;
+ }
+
+ function promiseTextChangeNotification() {
+ return new Promise(resolve => onTextChange = resolve);
+ }
+
+ function waitForTick() {
+ return new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
+ }
+
+ const input = document.querySelector("input[type=text]");
+ input.focus();
+ TIP.beginInputTransactionForTests(window, callback);
+
+ await (async function test_text_change_notification_for_value_set_during_no_frame() {
+ const description = "runTextNotificationChangesDuringNoFrame: test_text_change_notification_for_value_set_during_no_frame";
+ input.value = "Start";
+ input.style.display = "inline";
+ input.getBoundingClientRect();
+ await waitForTick();
+ const waitNotifications = promiseTextChangeNotification();
+ input.style.display = "block";
+ input.value = "Changed";
+ info(`${description}: waiting for notifications...`);
+ const notification = await waitNotifications;
+ is(
+ notification?.offset,
+ 0,
+ `${description}: offset should be 0`
+ );
+ is(
+ notification?.removedLength,
+ "Start".length,
+ `${description}: removedLength should be the length of the old value`
+ );
+ is(
+ notification?.addedLength,
+ "Changed".length,
+ `${description}: addedLength should be the length of the new value`
+ );
+ })();
+
+ await (async function test_text_change_notification_for_multiple_value_set_during_no_frame() {
+ const description = "runTextNotificationChangesDuringNoFrame: test_text_change_notification_for_multiple_value_set_during_no_frame";
+ input.value = "Start";
+ input.style.display = "inline";
+ input.getBoundingClientRect();
+ await waitForTick();
+ const waitNotifications = promiseTextChangeNotification();
+ input.style.display = "block";
+ input.value = "Changed";
+ input.value = "Again!";
+ info(`${description}: waiting for notifications...`);
+ const notification = await waitNotifications;
+ is(
+ notification?.offset,
+ 0,
+ `${description}: offset should be 0`
+ );
+ is(
+ notification?.removedLength,
+ "Start".length,
+ `${description}: removedLength should be the length of the old value`
+ );
+ is(
+ notification?.addedLength,
+ "Again!".length,
+ `${description}: addedLength should be the length of the new value`
+ );
+ })();
+
+ await (async function test_text_change_notification_for_value_set_and_typing_character_during_no_frame() {
+ const description = "runTextNotificationChangesDuringNoFrame: test_text_change_notification_for_value_set_and_typing_character_during_no_frame";
+ input.value = "Start";
+ input.style.display = "inline";
+ input.getBoundingClientRect();
+ await waitForTick();
+ const waitNotifications = promiseTextChangeNotification();
+ input.style.display = "block";
+ input.value = "Change";
+ const dKey = new KeyboardEvent("", { code: "KeyD", key: "d", keyCode: KeyboardEvent.DOM_VK_D });
+ TIP.keydown(dKey);
+ TIP.keyup(dKey);
+ info(`${description}: waiting for notifications...`);
+ const notification = await waitNotifications;
+ is(
+ notification?.offset,
+ 0,
+ `${description}: offset should be 0`
+ );
+ is(
+ notification?.removedLength,
+ "Start".length,
+ `${description}: removedLength should be the length of the old value`
+ );
+ is(
+ notification?.addedLength,
+ "Change".length,
+ `${description}: addedLength should be the length of the new (set) value`
+ );
+ })();
+
+ input.style.display = "";
+
+ textarea.focus();
+ TIP.beginInputTransaction(window, callback);
+
+ await (async function test_text_change_notification_for_multi_line_value_set_during_no_frame() {
+ const description = "runTextNotificationChangesDuringNoFrame: test_text_change_notification_for_multi_line_value_set_during_no_frame";
+ textarea.value = "Start\n2nd Line";
+ textarea.style.display = "inline";
+ textarea.getBoundingClientRect();
+ await waitForTick();
+ const waitNotifications = promiseTextChangeNotification();
+ textarea.style.display = "block";
+ textarea.value = "Changed\n2nd Line";
+ info(`${description}: waiting for notifications...`);
+ const notification = await waitNotifications;
+ is(
+ notification?.offset,
+ 0,
+ `${description}: offset should be 0`
+ );
+ is(
+ notification?.removedLength,
+ getNativeText("Start\n2nd Line").length,
+ `${description}: removedLength should be the length of the old value`
+ );
+ is(
+ notification?.addedLength,
+ getNativeText("Changed\n2nd Line").length,
+ `${description}: addedLength should be the length of the new value`
+ );
+ })();
+
+ textarea.style.display = "";
+}
+
+async function runTests()
+{
+ textareaInFrame = iframe.contentDocument.getElementById("textarea");
+ runBeginInputTransactionMethodTests();
+ runReleaseTests();
+ runCompositionTests();
+ runCompositionWithKeyEventTests();
+ runConsumingKeydownBeforeCompositionTests();
+ runKeyTests();
+ runErrorTests();
+ runCommitCompositionTests();
+ await runCallbackTests(false);
+ await runCallbackTests(true);
+ await runTextNotificationChangesDuringNoFrame();
+ await runFocusNotificationTestAfterDrop();
+ await runUnloadTests1();
+ await runUnloadTests2();
+ await runQuerySelectionEventTestAtTextChangeNotification();
+ await runIMEStateUpdateTests();
+
+ finish();
+}
+
+]]>
+</script>
+
+</window>