<?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 type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> <body xmlns="http://www.w3.org/1999/xhtml"> <p id="display"> <input id="input" type="text"/><br/> <iframe id="iframe" width="300" height="150" src="data:text/html,<textarea id='textarea' cols='20' rows='4'></textarea>"></iframe><br/> </p> <div id="content" style="display: none"> </div> <pre id="test"> </pre> </body> <script class="testbody" type="application/javascript"> <![CDATA[ var SpecialPowers = window.opener.wrappedJSObject.SpecialPowers; var SimpleTest = window.opener.wrappedJSObject.SimpleTest; SimpleTest.waitForFocus(runTests, window); 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 finish() { window.close(); } function onunload() { SimpleTest.finish(); } var iframe = document.getElementById("iframe"); var childWindow = iframe.contentWindow; var textareaInFrame; var input = document.getElementById("input"); var otherWindow = window.opener; var otherDocument = otherWindow.document; var inputInChildWindow = otherDocument.getElementById("input"); function createTIP() { return Components.classes["@mozilla.org/text-input-processor;1"]. createInstance(Components.interfaces.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 be committed with the last composing string"); 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); 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 a call of TIP1.flushPendingComposition();"); }, false); input.addEventListener("compositionupdate", 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 compositionupdate event handler during a call of TIP1.flushPendingComposition();"); }, false); input.addEventListener("text", 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 text event handler during a call of TIP1.flushPendingComposition();"); }, false); input.addEventListener("input", 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 input event handler during a call of TIP1.flushPendingComposition();"); }, false); TIP1.beginInputTransaction(window, simpleCallback); TIP1.setPendingCompositionString(composingStr); TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE); TIP1.flushPendingComposition(); is(events.length, 4, description + "compositionstart, compositionupdate, text 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, "input", description + "events[3] should be input"); 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); input.removeEventListener(aEvent.type, arguments.callee, false); 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();"); }, false); input.addEventListener("compositionend", 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 compositionend event handler during a call of TIP1.commitComposition();"); }, false); input.addEventListener("input", 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 input event handler during a call of TIP1.commitComposition();"); }, false); TIP1.commitComposition(); is(events.length, 3, description + "text, 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, "compositionend", description + "events[1] should be compositionend"); is(events[2].type, "input", description + "events[2] should be input"); // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during commitCompositionWith("bar"). 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.commitCompositionWith(\"bar\");"); }, false); input.addEventListener("compositionupdate", 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 during compositionupdate event handler TIP1.commitCompositionWith(\"bar\");"); }, false); input.addEventListener("text", 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 during text event handler TIP1.commitCompositionWith(\"bar\");"); }, false); input.addEventListener("compositionend", 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 during compositionend event handler TIP1.commitCompositionWith(\"bar\");"); }, false); input.addEventListener("input", 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 during input event handler TIP1.commitCompositionWith(\"bar\");"); }, false); TIP1.beginInputTransaction(window, simpleCallback); TIP1.commitCompositionWith("bar"); is(events.length, 5, description + "compositionstart, compositionupdate, text, 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, "compositionend", description + "events[3] should be compositionend"); is(events[4].type, "input", description + "events[4] should be input"); // 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); input.removeEventListener(aEvent.type, arguments.callee, false); 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();"); }, false); input.addEventListener("text", 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 text event handler during a call of TIP1.cancelComposition();"); }, false); input.addEventListener("compositionend", 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 compositionend event handler during a call of TIP1.cancelComposition();"); }, false); input.addEventListener("input", 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 input event handler during a call of TIP1.cancelComposition();"); }, false); TIP1.cancelComposition(); is(events.length, 4, description + "compositionupdate, text, 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, "compositionend", description + "events[2] should be compositionend"); is(events[3].type, "input", description + "events[3] should be input"); // 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); input.removeEventListener(aEvent.type, arguments.callee, false); 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();"); }, false); input.addEventListener("keypress", 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 keypress event handler during a call of TIP1.keydown();"); }, false); input.addEventListener("input", 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 input event handler during a call of TIP1.keydown();"); }, false); input.addEventListener("keyup", 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 keyup event handler during a call of TIP1.keyup();"); }, false); var keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A }); TIP1.keydown(keyA); TIP1.keyup(keyA); is(events.length, 4, description + "keydown, keypress, 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, "input", description + "events[2] should be input"); is(events[3].type, "keyup", description + "events[3] 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); 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 a call of TIP1.flushPendingComposition();"); }, false); input.addEventListener("compositionupdate", 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 compositionupdate event handler during a call of TIP1.flushPendingComposition();"); }, false); input.addEventListener("text", 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 text event handler during a call of TIP1.flushPendingComposition();"); }, false); input.addEventListener("input", 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 input event handler during a call of TIP1.flushPendingComposition();"); }, false); TIP1.beginInputTransactionForTests(window); TIP1.setPendingCompositionString(composingStr); TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE); TIP1.flushPendingComposition(); is(events.length, 4, description + "compositionstart, compositionupdate, text 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, "input", description + "events[3] should be input"); 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); input.removeEventListener(aEvent.type, arguments.callee, false); 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();"); }, false); input.addEventListener("compositionend", 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 compositionend event handler during a call of TIP1.commitComposition();"); }, false); input.addEventListener("input", 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 input event handler during a call of TIP1.commitComposition();"); }, false); TIP1.commitComposition(); is(events.length, 3, description + "text, 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, "compositionend", description + "events[1] should be compositionend"); is(events[2].type, "input", description + "events[2] should be input"); // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during commitCompositionWith("bar"). 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.commitCompositionWith(\"bar\");"); }, false); input.addEventListener("compositionupdate", 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 during compositionupdate event handler TIP1.commitCompositionWith(\"bar\");"); }, false); input.addEventListener("text", 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 during text event handler TIP1.commitCompositionWith(\"bar\");"); }, false); input.addEventListener("compositionend", 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 during compositionend event handler TIP1.commitCompositionWith(\"bar\");"); }, false); input.addEventListener("input", 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 during input event handler TIP1.commitCompositionWith(\"bar\");"); }, false); TIP1.beginInputTransactionForTests(window); TIP1.commitCompositionWith("bar"); is(events.length, 5, description + "compositionstart, compositionupdate, text, 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, "compositionend", description + "events[3] should be compositionend"); is(events[4].type, "input", description + "events[4] should be input"); // 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); input.removeEventListener(aEvent.type, arguments.callee, false); 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();"); }, false); input.addEventListener("text", 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 text event handler during a call of TIP1.cancelComposition();"); }, false); input.addEventListener("compositionend", 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 compositionend event handler during a call of TIP1.cancelComposition();"); }, false); input.addEventListener("input", 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 input event handler during a call of TIP1.cancelComposition();"); }, false); TIP1.cancelComposition(); is(events.length, 4, description + "compositionupdate, text, 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, "compositionend", description + "events[2] should be compositionend"); is(events[3].type, "input", description + "events[3] should be input"); // 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); input.removeEventListener(aEvent.type, arguments.callee, false); 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();"); }, false); input.addEventListener("keypress", 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 keypress event handler during a call of TIP1.keydown();"); }, false); input.addEventListener("input", 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 input event handler during a call of TIP1.keydown();"); }, false); input.addEventListener("keyup", 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 keyup event handler during a call of TIP1.keyup();"); }, false); var keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A }); TIP1.keydown(keyA); TIP1.keyup(keyA); is(events.length, 4, description + "keydown, keypress, 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, "input", description + "events[2] should be input"); is(events[3].type, "keyup", description + "events[3] 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); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, 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() with another window fails to begin new input transaction with different TextEventDispatcher during flushPendingComposition(). events = []; input.addEventListener("compositionstart", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); input.addEventListener("compositionupdate", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); input.addEventListener("text", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); input.addEventListener("input", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); TIP1.beginInputTransaction(window, simpleCallback); TIP1.setPendingCompositionString(composingStr); TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE); TIP1.flushPendingComposition(); is(events.length, 4, description + "compositionstart, compositionupdate, text 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, "input", description + "events[3] should be input"); 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); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); input.addEventListener("compositionend", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); input.addEventListener("input", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); TIP1.commitComposition(); is(events.length, 3, description + "text, 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, "compositionend", description + "events[1] should be compositionend"); is(events[2].type, "input", description + "events[2] should be input"); // 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); input.removeEventListener(aEvent.type, arguments.callee, false); 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\")"); } }, false); input.addEventListener("compositionupdate", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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\")"); } }, false); input.addEventListener("text", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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\")"); } }, false); input.addEventListener("compositionend", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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\")"); } }, false); input.addEventListener("input", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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\")"); } }, false); TIP1.beginInputTransaction(window, simpleCallback); TIP1.commitCompositionWith("bar"); is(events.length, 5, description + "compositionstart, compositionupdate, text, 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, "compositionend", description + "events[3] should be compositionend"); is(events[4].type, "input", description + "events[4] should be input"); // 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); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); input.addEventListener("text", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); input.addEventListener("compositionend", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); input.addEventListener("input", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); TIP1.cancelComposition(); is(events.length, 4, description + "compositionupdate, text, 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, "compositionend", description + "events[2] should be compositionend"); is(events[3].type, "input", description + "events[3] should be input"); // 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); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); input.addEventListener("keypress", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); input.addEventListener("input", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); input.addEventListener("keyup", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); var keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A }); TIP1.keydown(keyA); TIP1.keyup(keyA); is(events.length, 4, description + "keydown, keypress, 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, "input", description + "events[2] should be input"); is(events[3].type, "keyup", description + "events[3] 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); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); 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); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); input.addEventListener("compositionupdate", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); input.addEventListener("text", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); input.addEventListener("input", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); TIP1.beginInputTransactionForTests(window, simpleCallback); TIP1.setPendingCompositionString(composingStr); TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE); TIP1.flushPendingComposition(); is(events.length, 4, description + "compositionstart, compositionupdate, text 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, "input", description + "events[3] should be input"); 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); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); input.addEventListener("compositionend", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); input.addEventListener("input", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); TIP1.commitComposition(); is(events.length, 3, description + "text, 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, "compositionend", description + "events[1] should be compositionend"); is(events[2].type, "input", description + "events[2] should be input"); // 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); input.removeEventListener(aEvent.type, arguments.callee, false); 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\")"); } }, false); input.addEventListener("compositionupdate", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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\")"); } }, false); input.addEventListener("text", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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\")"); } }, false); input.addEventListener("compositionend", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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\")"); } }, false); input.addEventListener("input", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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\")"); } }, false); TIP1.beginInputTransactionForTests(window, simpleCallback); TIP1.commitCompositionWith("bar"); is(events.length, 5, description + "compositionstart, compositionupdate, text, 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, "compositionend", description + "events[3] should be compositionend"); is(events[4].type, "input", description + "events[4] should be input"); // 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); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); input.addEventListener("text", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); input.addEventListener("compositionend", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); input.addEventListener("input", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); TIP1.cancelComposition(); is(events.length, 4, description + "compositionupdate, text, 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, "compositionend", description + "events[2] should be compositionend"); is(events[3].type, "input", description + "events[3] should be input"); // 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); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); input.addEventListener("keypress", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); input.addEventListener("input", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); input.addEventListener("keyup", function (aEvent) { events.push(aEvent); input.removeEventListener(aEvent.type, arguments.callee, false); 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()"); } }, false); var keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A }); TIP1.keydown(keyA); TIP1.keyup(keyA); is(events.length, 4, description + "keydown, keypress, 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, "input", description + "events[2] should be input"); is(events[3].type, "keyup", description + "events[3] 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. SpecialPowers.gc(); 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 shoudn'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 }); SpecialPowers.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"); SpecialPowers.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)"); SpecialPowers.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 }); SpecialPowers.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"); SpecialPowers.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"); SpecialPowers.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.indexOf(aEvent.type) >= 0) { 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"); if (SpecialPowers.getBoolPref("dom.keyboardevent.code.enabled")) { 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 { SpecialPowers.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"); SpecialPowers.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(); SpecialPowers.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"); if (SpecialPowers.getBoolPref("dom.keyboardevent.code.enabled")) { is(aEvent.code, aCode, desc + ", .code value is wrong"); } is(aEvent.altKey, aModifiers.indexOf("Alt") >= 0, desc + ", .altKey value is wrong"); is(aEvent.ctrlKey, aModifiers.indexOf("Control") >= 0, desc + ", .ctrlKey value is wrong"); is(aEvent.metaKey, aModifiers.indexOf("Meta") >= 0, desc + ", .metaKey value is wrong"); is(aEvent.shiftKey, aModifiers.indexOf("Shift") >= 0, desc + ", .shiftKey value is wrong"); for (var i = 0; i < kModifiers.length; i++) { is(aEvent.getModifierState(kModifiers[i]), aModifiers.indexOf(kModifiers[i]) >= 0, desc + ", .getModifierState(\"" + kModifiers[i] + "\") returns wrong value"); } } function checkAllTIPModifiers(aTestDesc, aModifiers) { for (var i = 0; i < kModifiers.length; i++) { is(TIP.getModifierState(kModifiers[i]), aModifiers.indexOf(kModifiers[i]) >= 0, 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) { for (var i = 0; i < kModifiers.length; i++) { is(aTIP.getModifierState(kModifiers[i]), aModifiers.indexOf(kModifiers[i]) >= 0, 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(aNextTest) { var description = "runUnloadTests1(): "; var TIP1 = createTIP(); ok(TIP1.beginInputTransactionForTests(childWindow), description + "TIP1.beginInputTransactionForTests() should succeed"); var oldSrc = iframe.src; var parentWindow = window; iframe.addEventListener("load", function (aEvent) { ok(true, description + "dummy page is loaded"); iframe.removeEventListener("load", arguments.callee, true); 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. iframe.removeEventListener("load", arguments.callee, true); childWindow = iframe.contentWindow; textareaInFrame = iframe.contentDocument.getElementById("textarea"); setTimeout(aNextTest, 0); }, true); // The composition should be committed internally. So, another TIP should // be able to steal the rights to using TextEventDispatcher. var 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; }, 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(aNextTest) { var description = "runUnloadTests2(): "; var TIP = createTIP(); ok(TIP.beginInputTransactionForTests(childWindow), description + "TIP.beginInputTransactionForTests() should succeed"); var oldSrc = iframe.src; var parentWindow = window; iframe.addEventListener("load", function (aEvent) { ok(true, description + "dummy page is loaded"); iframe.removeEventListener("load", arguments.callee, true); 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. iframe.removeEventListener("load", arguments.callee, true); childWindow = iframe.contentWindow; textareaInFrame = iframe.contentDocument.getElementById("textarea"); setTimeout(aNextTest, 0); }, 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; }, 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>"; } function runCallbackTests(aForTests) { var description = "runCallbackTests(aForTests=" + aForTests + "): "; input.value = ""; input.focus(); input.blur(); var TIP = createTIP(); var notifications = []; function callback(aTIP, aNotification) { switch (aNotification.type) { case "request-to-commit": aTIP.commitComposition(); break; case "request-to-cancel": aTIP.cancelComposition(); break; } if (aTIP == TIP) { notifications.push(aNotification); } return true; } function dumpUnexpectedNotifications(aExpectedCount) { if (notifications.length <= aExpectedCount) { return; } for (var i = aExpectedCount; i < notifications.length; i++) { ok(false, description + "Unexpected notification: " + notifications[i].type); } } 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(); TIP.setPendingCompositionString("foo"); TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE); TIP.flushPendingComposition(); notifications = []; synthesizeMouseAtCenter(input, {}); is(notifications.length, 1, description + "synthesizeMouseAtCenter(input, {}) during composition should cause a notification"); is(notifications[0].type, "request-to-commit", description + "synthesizeMouseAtCenter(input, {}) during composition should cause \"request-to-commit\""); dumpUnexpectedNotifications(1); notifications = []; var 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); } function runTests() { textareaInFrame = iframe.contentDocument.getElementById("textarea"); runBeginInputTransactionMethodTests(); runReleaseTests(); runCompositionTests(); runCompositionWithKeyEventTests(); runConsumingKeydownBeforeCompositionTests(); runKeyTests(); runErrorTests(); runCommitCompositionTests(); runCallbackTests(false); runCallbackTests(true); runUnloadTests1(function () { runUnloadTests2(function () { finish(); }); }); } ]]> </script> </window>