diff options
Diffstat (limited to 'dom/inputmethod/mochitest')
43 files changed, 4966 insertions, 0 deletions
diff --git a/dom/inputmethod/mochitest/bug1110030_helper.js b/dom/inputmethod/mochitest/bug1110030_helper.js new file mode 100644 index 0000000000..54f15825bf --- /dev/null +++ b/dom/inputmethod/mochitest/bug1110030_helper.js @@ -0,0 +1,267 @@ +// *********************************** +// * Global variables +// *********************************** +const kIsWin = navigator.platform.indexOf("Win") == 0; + +// Bit value for the keyboard events +const kKeyDown = 0x01; +const kKeyPress = 0x02; +const kKeyUp = 0x04; + +// Pair the event name to its bit value +const kEventCode = { + 'keydown' : kKeyDown, + 'keypress' : kKeyPress, + 'keyup' : kKeyUp +}; + +// Holding the current test case's infomation: +var gCurrentTest; + +// The current used input method of this test +var gInputMethod; + +// *********************************** +// * Utilities +// *********************************** +function addKeyEventListeners(eventTarget, handler) +{ + Object.keys(kEventCode).forEach(function(type) { + eventTarget.addEventListener(type, handler); + }); +} + +function eventToCode(type) +{ + return kEventCode[type]; +} + +// To test key events that will be generated by input method here, +// we need to convert alphabets to native key code. +// (Our input method for testing will handle alphabets) +// On the other hand, to test key events that will not be generated by IME, +// we use 0-9 for such case in our testing. +function guessNativeKeyCode(key) +{ + let nativeCodeName = (kIsWin)? 'WIN_VK_' : 'MAC_VK_ANSI_'; + if (/^[A-Z]$/.test(key)) { + nativeCodeName += key; + } else if (/^[a-z]$/.test(key)) { + nativeCodeName += key.toUpperCase(); + } else if (/^[0-9]$/.test(key)) { + nativeCodeName += key.toString(); + } else { + return 0; + } + + return eval(nativeCodeName); +} + +// *********************************** +// * Frame loader and frame scripts +// *********************************** +function frameScript() +{ + function handler(e) { + sendAsyncMessage("forwardevent", { type: e.type, key: e.key }); + } + function notifyFinish(e) { + if (e.type != 'keyup') return; + sendAsyncMessage("finish"); + } + let input = content.document.getElementById('test-input'); + input.addEventListener('keydown', handler); + input.addEventListener('keypress', handler); + input.addEventListener('keyup', handler); + input.addEventListener('keyup', notifyFinish); +} + +function loadTestFrame(goNext) { + let iframe = document.createElement('iframe'); + iframe.src = 'file_test_empty_app.html'; + iframe.setAttribute('mozbrowser', true); + + iframe.addEventListener("mozbrowserloadend", function onloadend() { + iframe.removeEventListener("mozbrowserloadend", onloadend); + iframe.focus(); + var mm = SpecialPowers.getBrowserFrameMessageManager(iframe); + mm.addMessageListener("forwardevent", function(msg) { + inputtextEventReceiver(msg.json); + }); + mm.addMessageListener("finish", function(msg) { + if(goNext) { + goNext(); + } + }); + mm.loadFrameScript("data:,(" + frameScript.toString() + ")();", false); + return; + }); + + document.body.appendChild(iframe); +} + +// *********************************** +// * Event firer and listeners +// *********************************** +function fireEvent(callback) +{ + let key = gCurrentTest.key; + synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, guessNativeKeyCode(key), {}, + key, key, (callback) ? callback : null); +} + +function hardwareEventReceiver(evt) +{ + if (!gCurrentTest) { + return; + } + gCurrentTest.hardwareinput.receivedEvents |= eventToCode(evt.type); + gCurrentTest.hardwareinput.receivedKeys += evt.key; +} + +function inputtextEventReceiver(evt) +{ + if (!gCurrentTest) { + return; + } + gCurrentTest.inputtext.receivedEvents |= eventToCode(evt.type); + gCurrentTest.inputtext.receivedKeys += evt.key; +} + +// *********************************** +// * Event verifier +// *********************************** +function verifyResults(test) +{ + // Verify results received from inputcontent.hardwareinput + is(test.hardwareinput.receivedEvents, + test.hardwareinput.expectedEvents, + "received events from inputcontent.hardwareinput are wrong"); + + is(test.hardwareinput.receivedKeys, + test.hardwareinput.expectedKeys, + "received keys from inputcontent.hardwareinput are wrong"); + + // Verify results received from actual input text + is(test.inputtext.receivedEvents, + test.inputtext.expectedEvents, + "received events from input text are wrong"); + + is(test.inputtext.receivedKeys, + test.inputtext.expectedKeys, + "received keys from input text are wrong"); +} + +function areEventsSame(test) +{ + return (test.hardwareinput.receivedEvents == + test.hardwareinput.expectedEvents) && + (test.inputtext.receivedEvents == + test.inputtext.expectedEvents); +} + +// *********************************** +// * Input Method +// *********************************** +// The method input used in this test +// only handles alphabets +function InputMethod(inputContext) +{ + this._inputContext = inputContext; + this.init(); +} + +InputMethod.prototype = { + init: function im_init() { + this._setKepMap(); + }, + + handler: function im_handler(evt) { + // Ignore the key if the event is defaultPrevented + if (evt.defaultPrevented) { + return; + } + + // Finish if there is no _inputContext + if (!this._inputContext) { + return; + } + + // Generate the keyDict for inputcontext.keydown/keyup + let keyDict = this._generateKeyDict(evt); + + // Ignore the key if IME doesn't want to handle it + if (!keyDict) { + return; + } + + // Call preventDefault if the key will be handled. + evt.preventDefault(); + + // Call inputcontext.keydown/keyup + this._inputContext[evt.type](keyDict); + }, + + mapKey: function im_keymapping(key) { + if (!this._mappingTable) { + return; + } + return this._mappingTable[key]; + }, + + _setKepMap: function im_setKeyMap() { + // A table to map characters: + // { + // 'A': 'B' + // 'a': 'b' + // 'B': 'C' + // 'b': 'c' + // .. + // .. + // 'Z': 'A', + // 'z': 'a', + // } + this._mappingTable = {}; + + let rotation = 1; + + for (let i = 0 ; i < 26 ; i++) { + // Convert 'A' to 'B', 'B' to 'C', ..., 'Z' to 'A' + this._mappingTable[String.fromCharCode(i + 'A'.charCodeAt(0))] = + String.fromCharCode((i+rotation)%26 + 'A'.charCodeAt(0)); + + // Convert 'a' to 'b', 'b' to 'c', ..., 'z' to 'a' + this._mappingTable[String.fromCharCode(i + 'a'.charCodeAt(0))] = + String.fromCharCode((i+rotation)%26 + 'a'.charCodeAt(0)); + } + }, + + _generateKeyDict: function im_generateKeyDict(evt) { + + let mappedKey = this.mapKey(evt.key); + + if (!mappedKey) { + return; + } + + let keyDict = { + key: mappedKey, + code: this._guessCodeFromKey(mappedKey), + repeat: evt.repeat, + }; + + return keyDict; + }, + + _guessCodeFromKey: function im_guessCodeFromKey(key) { + if (/^[A-Z]$/.test(key)) { + return "Key" + key; + } else if (/^[a-z]$/.test(key)) { + return "Key" + key.toUpperCase(); + } else if (/^[0-9]$/.test(key)) { + return "Digit" + key.toString(); + } else { + return 0; + } + }, +}; diff --git a/dom/inputmethod/mochitest/chrome.ini b/dom/inputmethod/mochitest/chrome.ini new file mode 100644 index 0000000000..7575c2215e --- /dev/null +++ b/dom/inputmethod/mochitest/chrome.ini @@ -0,0 +1,52 @@ +[DEFAULT] +# dom/inputmethod only made sense on B2G +skip-if = true +support-files = + bug1110030_helper.js + inputmethod_common.js + file_inputmethod.html + file_blank.html + file_test_app.html + file_test_bug1066515.html + file_test_bug1137557.html + file_test_bug1175399.html + file_test_empty_app.html + file_test_focus_blur_manage_events.html + file_test_sendkey_cancel.html + file_test_setSupportsSwitching.html + file_test_simple_manage_events.html + file_test_sms_app.html + file_test_sms_app_1066515.html + file_test_sync_edit.html + file_test_two_inputs.html + file_test_two_selects.html + file_test_unload.html + file_test_unload_action.html + +[test_basic.html] +[test_bug944397.html] +[test_bug949059.html] +[test_bug953044.html] +[test_bug960946.html] +[test_bug978918.html] +[test_bug1026997.html] +[test_bug1043828.html] +[test_bug1059163.html] +disabled = fails because receiving bad values +[test_bug1066515.html] +[test_bug1137557.html] +[test_bug1175399.html] +[test_focus_blur_manage_events.html] +disabled = fails because receiving bad events # also depends on bug 1254823 +[test_forward_hardware_key_to_ime.html] +skip-if = true # Test only ran on Mulet +[test_input_registry_events.html] +disabled = timeout on pine +[test_sendkey_cancel.html] +[test_setSupportsSwitching.html] +[test_simple_manage_events.html] +disabled = fails because receiving bad events +[test_sync_edit.html] +[test_two_inputs.html] +[test_two_selects.html] +[test_unload.html] diff --git a/dom/inputmethod/mochitest/file_blank.html b/dom/inputmethod/mochitest/file_blank.html new file mode 100644 index 0000000000..7879e1ce9f --- /dev/null +++ b/dom/inputmethod/mochitest/file_blank.html @@ -0,0 +1,4 @@ +<html> +<body> +</body> +</html> diff --git a/dom/inputmethod/mochitest/file_inputmethod.html b/dom/inputmethod/mochitest/file_inputmethod.html new file mode 100644 index 0000000000..193cb05056 --- /dev/null +++ b/dom/inputmethod/mochitest/file_inputmethod.html @@ -0,0 +1,25 @@ +<html> +<body> +<script> + var im = navigator.mozInputMethod; + if (im) { + im.oninputcontextchange = onIcc; + + if (im.inputcontext) { + onIcc(); + } + } + + function onIcc() { + var ctx = im.inputcontext; + if (ctx) { + ctx.replaceSurroundingText(location.hash).then(function() { + /* Happy flow */ + }, function(err) { + dump('ReplaceSurroundingText failed ' + err + '\n'); + }); + } + } +</script> +</body> +</html> diff --git a/dom/inputmethod/mochitest/file_test_app.html b/dom/inputmethod/mochitest/file_test_app.html new file mode 100644 index 0000000000..3063e97498 --- /dev/null +++ b/dom/inputmethod/mochitest/file_test_app.html @@ -0,0 +1,11 @@ +<!DOCTYPE HTML> +<html> +<body> +<input id="test-input" type="text" value="Yuan" x-inputmode="verbatim" lang="zh"/> +<script type="application/javascript;version=1.7"> + let input = document.getElementById('test-input'); + input.focus(); + dump('file_test_app.html was loaded.'); +</script> +</body> +</html> diff --git a/dom/inputmethod/mochitest/file_test_bug1066515.html b/dom/inputmethod/mochitest/file_test_bug1066515.html new file mode 100644 index 0000000000..6331ec40e9 --- /dev/null +++ b/dom/inputmethod/mochitest/file_test_bug1066515.html @@ -0,0 +1,6 @@ +<!DOCTYPE HTML> +<html> +<body> +<div id="text" contenteditable>Jan Jongboom</div> +</body> +</html> diff --git a/dom/inputmethod/mochitest/file_test_bug1137557.html b/dom/inputmethod/mochitest/file_test_bug1137557.html new file mode 100644 index 0000000000..dc0c8d77e5 --- /dev/null +++ b/dom/inputmethod/mochitest/file_test_bug1137557.html @@ -0,0 +1,6 @@ +<!DOCTYPE HTML> +<html> +<body> +<textarea rows=30 cols=30></textarea> +</body> +</html> diff --git a/dom/inputmethod/mochitest/file_test_bug1175399.html b/dom/inputmethod/mochitest/file_test_bug1175399.html new file mode 100644 index 0000000000..3fa7da46cc --- /dev/null +++ b/dom/inputmethod/mochitest/file_test_bug1175399.html @@ -0,0 +1 @@ +<html><body><input value="First" readonly></body></html> diff --git a/dom/inputmethod/mochitest/file_test_empty_app.html b/dom/inputmethod/mochitest/file_test_empty_app.html new file mode 100644 index 0000000000..c071c1a317 --- /dev/null +++ b/dom/inputmethod/mochitest/file_test_empty_app.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html> +<body> +<input id="test-input" type="text" value=""/> +<script type="application/javascript;version=1.7"> + let input = document.getElementById('test-input'); + input.focus(); +</script> +</body> +</html> diff --git a/dom/inputmethod/mochitest/file_test_focus_blur_manage_events.html b/dom/inputmethod/mochitest/file_test_focus_blur_manage_events.html new file mode 100644 index 0000000000..bb8c445733 --- /dev/null +++ b/dom/inputmethod/mochitest/file_test_focus_blur_manage_events.html @@ -0,0 +1,22 @@ +<html><body> +<input type="text"> +<input type="search"> +<textarea></textarea> +<p contenteditable></p> +<input type="number"> +<input type="tel"> +<input type="url"> +<input type="email"> +<input type="password"> +<input type="datetime"> +<input type="date" value="2015-08-03" min="1990-01-01" max="2020-01-01"> +<input type="month"> +<input type="week"> +<input type="time"> +<input type="datetime-local"> +<input type="color"> +<select><option selected>foo</option><option disabled>bar</option> +<optgroup label="group"><option>baz</option></optgroup></select> +<select multiple><option selected>foo</option><option disabled>bar</option> +<optgroup label="group"><option>baz</option></optgroup></select> +</body></html> diff --git a/dom/inputmethod/mochitest/file_test_sendkey_cancel.html b/dom/inputmethod/mochitest/file_test_sendkey_cancel.html new file mode 100644 index 0000000000..f40ee69598 --- /dev/null +++ b/dom/inputmethod/mochitest/file_test_sendkey_cancel.html @@ -0,0 +1,14 @@ +<!DOCTYPE HTML> +<html> +<body> +<input id="test-input" type="text" value="Yolo"/> +<script type="application/javascript;version=1.7"> + let input = document.getElementById('test-input'); + input.focus(); + + input.addEventListener('keydown', function(e) { + e.preventDefault(); + }); +</script> +</body> +</html> diff --git a/dom/inputmethod/mochitest/file_test_setSupportsSwitching.html b/dom/inputmethod/mochitest/file_test_setSupportsSwitching.html new file mode 100644 index 0000000000..5b5e1733ae --- /dev/null +++ b/dom/inputmethod/mochitest/file_test_setSupportsSwitching.html @@ -0,0 +1,5 @@ +<html><body> +<input type="text"> +<input type="number"> +<input type="password"> +</body></html> diff --git a/dom/inputmethod/mochitest/file_test_simple_manage_events.html b/dom/inputmethod/mochitest/file_test_simple_manage_events.html new file mode 100644 index 0000000000..5c0c25cf4f --- /dev/null +++ b/dom/inputmethod/mochitest/file_test_simple_manage_events.html @@ -0,0 +1 @@ +<html><body><input type="text"></body></html> diff --git a/dom/inputmethod/mochitest/file_test_sms_app.html b/dom/inputmethod/mochitest/file_test_sms_app.html new file mode 100644 index 0000000000..7aa3e6081b --- /dev/null +++ b/dom/inputmethod/mochitest/file_test_sms_app.html @@ -0,0 +1,14 @@ +<!DOCTYPE HTML> +<html> +<body> + <div id="messages-input" x-inputmode="-moz-sms" contenteditable="true" + autofocus="autofocus">Httvb<br></div> + <script type="application/javascript;version=1.7"> + let input = document.getElementById('messages-input'); + input.focus(); + </script> +</body> +</html> + </div> +</body> +</html> diff --git a/dom/inputmethod/mochitest/file_test_sms_app_1066515.html b/dom/inputmethod/mochitest/file_test_sms_app_1066515.html new file mode 100644 index 0000000000..a515c90b59 --- /dev/null +++ b/dom/inputmethod/mochitest/file_test_sms_app_1066515.html @@ -0,0 +1,14 @@ +<!DOCTYPE HTML> +<html> +<body> + <div id="messages-input" x-inputmode="-moz-sms" contenteditable="true" + autofocus="autofocus">fxos<br>hello <b>world</b></div> + <script type="application/javascript;version=1.7"> + let input = document.getElementById('messages-input'); + input.focus(); + </script> +</body> +</html> + </div> +</body> +</html> diff --git a/dom/inputmethod/mochitest/file_test_sync_edit.html b/dom/inputmethod/mochitest/file_test_sync_edit.html new file mode 100644 index 0000000000..c450ad5cf7 --- /dev/null +++ b/dom/inputmethod/mochitest/file_test_sync_edit.html @@ -0,0 +1 @@ +<html><body><input value="First"></body></html> diff --git a/dom/inputmethod/mochitest/file_test_two_inputs.html b/dom/inputmethod/mochitest/file_test_two_inputs.html new file mode 100644 index 0000000000..af7a2866d8 --- /dev/null +++ b/dom/inputmethod/mochitest/file_test_two_inputs.html @@ -0,0 +1 @@ +<html><body><input value="First"><input value="Second"></body></html> diff --git a/dom/inputmethod/mochitest/file_test_two_selects.html b/dom/inputmethod/mochitest/file_test_two_selects.html new file mode 100644 index 0000000000..be2204f6ef --- /dev/null +++ b/dom/inputmethod/mochitest/file_test_two_selects.html @@ -0,0 +1 @@ +<html><body><select><option>First</option></select><select><option>Second</option></select></html> diff --git a/dom/inputmethod/mochitest/file_test_unload.html b/dom/inputmethod/mochitest/file_test_unload.html new file mode 100644 index 0000000000..d1a939405d --- /dev/null +++ b/dom/inputmethod/mochitest/file_test_unload.html @@ -0,0 +1 @@ +<html><body><form id="form"><input value="First"><input type="submit"></form></body></html> diff --git a/dom/inputmethod/mochitest/file_test_unload_action.html b/dom/inputmethod/mochitest/file_test_unload_action.html new file mode 100644 index 0000000000..20a9bb3afb --- /dev/null +++ b/dom/inputmethod/mochitest/file_test_unload_action.html @@ -0,0 +1 @@ +<html><body><input value="Second"></body></html> diff --git a/dom/inputmethod/mochitest/inputmethod_common.js b/dom/inputmethod/mochitest/inputmethod_common.js new file mode 100644 index 0000000000..ad8103c9f2 --- /dev/null +++ b/dom/inputmethod/mochitest/inputmethod_common.js @@ -0,0 +1,24 @@ +function inputmethod_setup(callback) { + SimpleTest.waitForExplicitFinish(); + SimpleTest.requestCompleteLog(); + let appInfo = SpecialPowers.Cc['@mozilla.org/xre/app-info;1'] + .getService(SpecialPowers.Ci.nsIXULAppInfo); + if (appInfo.name != 'B2G') { + SpecialPowers.Cu.import("resource://gre/modules/Keyboard.jsm", this); + } + + let prefs = [ + ['dom.mozBrowserFramesEnabled', true], + ['network.disable.ipc.security', true], + // Enable navigator.mozInputMethod. + ['dom.mozInputMethod.enabled', true] + ]; + SpecialPowers.pushPrefEnv({set: prefs}, function() { + SimpleTest.waitForFocus(callback); + }); +} + +function inputmethod_cleanup() { + SpecialPowers.wrap(navigator.mozInputMethod).setActive(false); + SimpleTest.finish(); +} diff --git a/dom/inputmethod/mochitest/test_basic.html b/dom/inputmethod/mochitest/test_basic.html new file mode 100644 index 0000000000..bf22e99dd4 --- /dev/null +++ b/dom/inputmethod/mochitest/test_basic.html @@ -0,0 +1,212 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=932145 +--> +<head> + <title>Basic test for InputMethod API.</title> + <script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript;version=1.7" src="inputmethod_common.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=932145">Mozilla Bug 932145</a> +<p id="display"></p> +<pre id="test"> +<script class="testbody" type="application/javascript;version=1.7"> + +SimpleTest.requestFlakyTimeout("untriaged"); + +// The input context. +var gContext = null; + +inputmethod_setup(function() { + runTest(); +}); + +function runTest() { + let im = navigator.mozInputMethod; + + im.oninputcontextchange = function() { + ok(true, 'inputcontextchange event was fired.'); + im.oninputcontextchange = null; + + gContext = im.inputcontext; + if (!gContext) { + ok(false, 'Should have a non-null inputcontext.'); + inputmethod_cleanup(); + return; + } + + is(gContext.type, 'input', 'The input context type should match.'); + is(gContext.inputType, 'text', 'The inputType should match.'); + is(gContext.inputMode, 'verbatim', 'The inputMode should match.'); + is(gContext.lang, 'zh', 'The language should match.'); + is(gContext.text, 'Yuan', 'Should get the text.'); + is(gContext.textBeforeCursor + gContext.textAfterCursor, 'Yuan', + 'Should get the text around the cursor.'); + + test_setSelectionRange(); + }; + + // Set current page as an input method. + SpecialPowers.wrap(im).setActive(true); + + let iframe = document.createElement('iframe'); + iframe.src = 'file_test_app.html'; + iframe.setAttribute('mozbrowser', true); + document.body.appendChild(iframe); +} + +function test_setSelectionRange() { + // Move cursor position to 2. + gContext.setSelectionRange(2, 0).then(function() { + is(gContext.selectionStart, 2, 'selectionStart was set successfully.'); + is(gContext.selectionEnd, 2, 'selectionEnd was set successfully.'); + test_sendKey(); + }, function(e) { + ok(false, 'setSelectionRange failed:' + e.name); + console.error(e); + inputmethod_cleanup(); + }); +} + +function test_sendKey() { + // Add '-' to current cursor posistion and move the cursor position to 3. + gContext.sendKey(0, '-'.charCodeAt(0), 0).then(function() { + is(gContext.text, 'Yu-an', + 'sendKey should changed the input field correctly.'); + is(gContext.textBeforeCursor + gContext.textAfterCursor, 'Yu-an', + 'sendKey should changed the input field correctly.'); + test_deleteSurroundingText(); + }, function(e) { + ok(false, 'sendKey failed:' + e.name); + inputmethod_cleanup(); + }); +} + +function test_deleteSurroundingText() { + // Remove one character before current cursor position and move the cursor + // position back to 2. + gContext.deleteSurroundingText(-1, 1).then(function() { + ok(true, 'deleteSurroundingText finished'); + is(gContext.text, 'Yuan', + 'deleteSurroundingText should changed the input field correctly.'); + is(gContext.textBeforeCursor + gContext.textAfterCursor, 'Yuan', + 'deleteSurroundingText should changed the input field correctly.'); + test_replaceSurroundingText(); + }, function(e) { + ok(false, 'deleteSurroundingText failed:' + e.name); + inputmethod_cleanup(); + }); +} + +function test_replaceSurroundingText() { + // Replace 'Yuan' with 'Xulei'. + gContext.replaceSurroundingText('Xulei', -2, 4).then(function() { + ok(true, 'replaceSurroundingText finished'); + is(gContext.text, 'Xulei', + 'replaceSurroundingText changed the input field correctly.'); + is(gContext.textBeforeCursor + gContext.textAfterCursor, 'Xulei', + 'replaceSurroundingText changed the input field correctly.'); + test_setComposition(); + }, function(e) { + ok(false, 'replaceSurroundingText failed: ' + e.name); + inputmethod_cleanup(); + }); +} + +function test_setComposition() { + gContext.setComposition('XXX').then(function() { + ok(true, 'setComposition finished'); + test_endComposition(); + }, function(e) { + ok(false, 'setComposition failed: ' + e.name); + inputmethod_cleanup(); + }); +} + +function test_endComposition() { + gContext.endComposition('2013').then(function() { + is(gContext.text, 'Xulei2013', + 'endComposition changed the input field correctly.'); + is(gContext.textBeforeCursor + gContext.textAfterCursor, 'Xulei2013', + 'endComposition changed the input field correctly.'); + test_onSelectionChange(); + }, function (e) { + ok(false, 'endComposition failed: ' + e.name); + inputmethod_cleanup(); + }); +} + +function test_onSelectionChange() { + var sccTimeout = setTimeout(function() { + ok(false, 'selectionchange event not fired'); + cleanup(true); + }, 3000); + + function cleanup(failed) { + gContext.onselectionchange = null; + clearTimeout(sccTimeout); + if (failed) { + inputmethod_cleanup(); + } + else { + test_onSurroundingTextChange(); + } + } + + gContext.onselectionchange = function(evt) { + ok(true, 'onselectionchange fired'); + is(evt.detail.selectionStart, 10); + is(evt.detail.selectionEnd, 10); + ok(evt.detail.ownAction); + }; + + gContext.sendKey(0, 'j'.charCodeAt(0), 0).then(function() { + cleanup(); + }, function(e) { + ok(false, 'sendKey failed: ' + e.name); + cleanup(true); + }); +} + +function test_onSurroundingTextChange() { + var sccTimeout = setTimeout(function() { + ok(false, 'surroundingtextchange event not fired'); + cleanup(true); + }, 3000); + + function cleanup(failed) { + gContext.onsurroundingtextchange = null; + clearTimeout(sccTimeout); + if (failed) { + inputmethod_cleanup(); + } + else { + // in case we want more tests leave this + inputmethod_cleanup(); + } + } + + gContext.onsurroundingtextchange = function(evt) { + ok(true, 'onsurroundingtextchange fired'); + is(evt.detail.text, 'Xulei2013jj'); + is(evt.detail.textBeforeCursor, 'Xulei2013jj'); + is(evt.detail.textAfterCursor, ''); + ok(evt.detail.ownAction); + }; + + gContext.sendKey(0, 'j'.charCodeAt(0), 0).then(function() { + cleanup(); + }, function(e) { + ok(false, 'sendKey failed: ' + e.name); + cleanup(true); + }); +} + +</script> +</pre> +</body> +</html> + diff --git a/dom/inputmethod/mochitest/test_bug1026997.html b/dom/inputmethod/mochitest/test_bug1026997.html new file mode 100644 index 0000000000..3d44e6cbd3 --- /dev/null +++ b/dom/inputmethod/mochitest/test_bug1026997.html @@ -0,0 +1,101 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1026997 +--> +<head> + <title>SelectionChange on InputMethod API.</title> + <script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript;version=1.7" src="inputmethod_common.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1026997">Mozilla Bug 1026997</a> +<p id="display"></p> +<pre id="test"> +<script class="testbody" type="application/javascript;version=1.7"> + +inputmethod_setup(function() { + runTest(); +}); + +// The frame script running in file_test_app.html. +function appFrameScript() { + let input = content.document.getElementById('test-input'); + + input.focus(); + + function next(start, end) { + input.setSelectionRange(start, end); + } + + addMessageListener("test:KeyBoard:nextSelection", function(event) { + let json = event.json; + next(json[0], json[1]); + }); +} + +function runTest() { + let actions = [ + [0, 4], + [1, 1], + [3, 3], + [2, 3] + ]; + + let counter = 0; + let mm = null; + let ic = null; + + let im = navigator.mozInputMethod; + im.oninputcontextchange = function() { + ok(true, 'inputcontextchange event was fired.'); + im.oninputcontextchange = null; + + ic = im.inputcontext; + if (!ic) { + ok(false, 'Should have a non-null inputcontext.'); + inputmethod_cleanup(); + return; + } + + ic.onselectionchange = function() { + is(ic.selectionStart, actions[counter][0], "start"); + is(ic.selectionEnd, actions[counter][1], "end"); + + if (++counter === actions.length) { + inputmethod_cleanup(); + return; + } + + next(); + }; + + next(); + }; + + // Set current page as an input method. + SpecialPowers.wrap(im).setActive(true); + + // Create an app frame to recieve keyboard inputs. + let app = document.createElement('iframe'); + app.src = 'file_test_app.html'; + app.setAttribute('mozbrowser', true); + document.body.appendChild(app); + app.addEventListener('mozbrowserloadend', function() { + mm = SpecialPowers.getBrowserFrameMessageManager(app); + mm.loadFrameScript('data:,(' + appFrameScript.toString() + ')();', false); + next(); + }); + + function next() { + if (ic && mm) { + mm.sendAsyncMessage('test:KeyBoard:nextSelection', actions[counter]); + } + } +} +</script> +</pre> +</body> +</html> + diff --git a/dom/inputmethod/mochitest/test_bug1043828.html b/dom/inputmethod/mochitest/test_bug1043828.html new file mode 100644 index 0000000000..84c1dc0895 --- /dev/null +++ b/dom/inputmethod/mochitest/test_bug1043828.html @@ -0,0 +1,183 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1043828 +--> +<head> + <title>Basic test for Switching Keyboards.</title> + <script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript;version=1.7" src="inputmethod_common.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1043828">Mozilla Bug 1043828</a> +<p id="display"></p> +<pre id="test"> +<script class="testbody" type="application/javascript;version=1.7"> + +SimpleTest.requestFlakyTimeout("untriaged"); + +inputmethod_setup(function() { + runTest(); +}); + +// The KB frame script running in Keyboard B. +function kbFrameScript() { + function tryGetText() { + var ctx = content.navigator.mozInputMethod.inputcontext; + if (ctx) { + var p = ctx.getText(); + p.then(function(){ + sendAsyncMessage('test:InputMethod:getText:Resolve'); + }, function(e){ + sendAsyncMessage('test:InputMethod:getText:Reject'); + }); + } else { + dump("Could not get inputcontext") ; + } + } + + addMessageListener('test:InputMethod:getText:Do', function(){ + tryGetText(); + }); +} + +function runTest() { + let app, keyboardA, keyboardB; + let getTextPromise; + let mmKeyboardA, mmKeyboardB; + + /** + * Test flow: + * 1. Create two keyboard iframes & a mozbrowser iframe with a text field in it & focus the text + * field. + * 2. Set keyboard frame A as active input. Wait 200ms. + * 3. Set keyboard frame B as active input. Wait 200ms. + * 4. Set keyboard frame A as inactive. Wait 200ms. + * 5. Allow frame b to use getText() with inputcontext to get the content from the text field + * iframe. Wait 200ms. + * [Test would succeed if the Promise returned by getText() resolves correctly. + * Test would fail if otherwise] + */ + + let path = location.pathname; + let basePath = location.protocol + '//' + location.host + + path.substring(0, path.lastIndexOf('/')); + + const WAIT_TIME = 200; + + // STEP 1: Create the frames. + function step1() { + // app + app = document.createElement('iframe'); + app.src = basePath + '/file_test_app.html'; + app.setAttribute('mozbrowser', true); + document.body.appendChild(app); + + // keyboards + keyboardA = document.createElement('iframe'); + keyboardA.setAttribute('mozbrowser', true); + document.body.appendChild(keyboardA); + + keyboardB = document.createElement('iframe'); + keyboardB.setAttribute('mozbrowser', true); + document.body.appendChild(keyboardB); + + // simulate two different keyboard apps + let imeUrl = basePath + '/file_blank.html'; + + keyboardA.src = imeUrl; + keyboardB.src = imeUrl; + + var handler = { + handleEvent: function(){ + keyboardB.removeEventListener('mozbrowserloadend', this); + + mmKeyboardB = SpecialPowers.getBrowserFrameMessageManager(keyboardB); + + mmKeyboardB.loadFrameScript('data:,(' + kbFrameScript.toString() + ')();', false); + + mmKeyboardB.addMessageListener('test:InputMethod:getText:Resolve', function() { + info('getText() was resolved'); + inputmethod_cleanup(); + }); + + mmKeyboardB.addMessageListener('test:InputMethod:getText:Reject', function() { + ok(false, 'getText() was rejected'); + inputmethod_cleanup(); + }); + + setTimeout(function(){ + step2(); + }, WAIT_TIME); + } + }; + + keyboardB.addEventListener('mozbrowserloadend', handler); + } + + // STEP 2: Set keyboard A active + function step2() { + info('step2'); + let req = keyboardA.setInputMethodActive(true); + + req.onsuccess = function(){ + setTimeout(function(){ + step3(); + }, WAIT_TIME); + }; + + req.onerror = function(){ + ok(false, 'setInputMethodActive failed: ' + this.error.name); + inputmethod_cleanup(); + }; + } + + // STEP 3: Set keyboard B active + function step3() { + info('step3'); + let req = keyboardB.setInputMethodActive(true); + + req.onsuccess = function(){ + setTimeout(function(){ + step4(); + }, WAIT_TIME); + }; + + req.onerror = function(){ + ok(false, 'setInputMethodActive failed: ' + this.error.name); + inputmethod_cleanup(); + }; + } + + // STEP 4: Set keyboard A inactive + function step4() { + info('step4'); + let req = keyboardA.setInputMethodActive(false); + + req.onsuccess = function(){ + setTimeout(function(){ + step5(); + }, WAIT_TIME); + }; + + req.onerror = function(){ + ok(false, 'setInputMethodActive failed: ' + this.error.name); + inputmethod_cleanup(); + }; + } + + // STEP 5: getText + function step5() { + info('step5'); + mmKeyboardB.sendAsyncMessage('test:InputMethod:getText:Do'); + } + + step1(); +} + +</script> +</pre> +</body> +</html> + diff --git a/dom/inputmethod/mochitest/test_bug1059163.html b/dom/inputmethod/mochitest/test_bug1059163.html new file mode 100644 index 0000000000..c54a03b0ec --- /dev/null +++ b/dom/inputmethod/mochitest/test_bug1059163.html @@ -0,0 +1,87 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1059163 +--> +<head> + <title>Basic test for repeat sendKey events</title> + <script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript;version=1.7" src="inputmethod_common.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1059163">Mozilla Bug 1059163</a> +<p id="display"></p> +<pre id="test"> +<script class="testbody" type="application/javascript;version=1.7"> +inputmethod_setup(function() { + runTest(); +}); + +// The frame script running in the file +function appFrameScript() { + let document = content.document; + let window = content.document.defaultView; + + let t = document.getElementById('text'); + t.focus(); + + let range = document.createRange(); + range.selectNodeContents(t); + range.collapse(false); + let selection = window.getSelection(); + selection.removeAllRanges(); + selection.addRange(range); + + addMessageListener('test:InputMethod:clear', function() { + t.innerHTML = ''; + }); +} + +function runTest() { + let im = navigator.mozInputMethod; + + // Set current page as an input method. + SpecialPowers.wrap(im).setActive(true); + + // Create an app frame to recieve keyboard inputs. + let app = document.createElement('iframe'); + app.src = 'file_test_bug1066515.html'; + app.setAttribute('mozbrowser', true); + document.body.appendChild(app); + + app.addEventListener('mozbrowserloadend', function() { + let mm = SpecialPowers.getBrowserFrameMessageManager(app); + mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false); + + im.oninputcontextchange = function() { + is(im.inputcontext.type, 'contenteditable', 'type'); + is(im.inputcontext.inputType, 'textarea', 'inputType'); + + if (im.inputcontext) { + im.oninputcontextchange = null; + register(); + } + }; + + function register() { + im.inputcontext.onselectionchange = function() { + im.inputcontext.onselectionchange = null; + + is(im.inputcontext.textBeforeCursor, '', 'textBeforeCursor'); + is(im.inputcontext.textAfterCursor, '', 'textAfterCursor'); + is(im.inputcontext.selectionStart, 0, 'selectionStart'); + is(im.inputcontext.selectionEnd, 0, 'selectionEnd'); + + inputmethod_cleanup(); + }; + + mm.sendAsyncMessage('test:InputMethod:clear'); + } + }); +} +</script> +</pre> +</body> +</html> + diff --git a/dom/inputmethod/mochitest/test_bug1066515.html b/dom/inputmethod/mochitest/test_bug1066515.html new file mode 100644 index 0000000000..56fe107721 --- /dev/null +++ b/dom/inputmethod/mochitest/test_bug1066515.html @@ -0,0 +1,93 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1066515 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1066515</title> + <script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript;version=1.7" src="inputmethod_common.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1066515">Mozilla Bug 1066515</a> +<p id="display"></p> +<pre id="test"> +<script class="testbody" type="application/javascript;version=1.7"> + +// The input context. +var gContext = null; + +inputmethod_setup(function() { + runTest(); +}); + +function runTest() { + let im = navigator.mozInputMethod; + + im.oninputcontextchange = function() { + ok(true, 'inputcontextchange event was fired.'); + im.oninputcontextchange = null; + + gContext = im.inputcontext; + if (!gContext) { + ok(false, 'Should have a non-null inputcontext.'); + inputmethod_cleanup(); + return; + } + + test_replaceSurroundingTextWithinTextNode(); + }; + + // Set current page as an input method. + SpecialPowers.wrap(im).setActive(true); + + let iframe = document.createElement('iframe'); + iframe.src = 'file_test_sms_app_1066515.html'; + iframe.setAttribute('mozbrowser', true); + document.body.appendChild(iframe); +} + +function test_replaceSurroundingTextWithinTextNode() { + // Set cursor position after 'f'. + gContext.setSelectionRange(1, 0); + + // Replace 'fxos' to 'Hitooo' which the range is within current text node. + gContext.replaceSurroundingText('Hitooo', -1, 4).then(function() { + gContext.getText().then(function(text) { + is(text, 'Hitooo\nhello world', 'replaceSurroundingText successfully.'); + test_replaceSurroundingTextSpanMultipleNodes(); + }, function(e) { + ok(false, 'getText failed: ' + e.name); + inputmethod_cleanup(); + }); + }, function(e) { + ok(false, 'replaceSurroundingText failed: ' + e.name); + inputmethod_cleanup(); + }); +} + +function test_replaceSurroundingTextSpanMultipleNodes() { + // Set cursor position to the beginning. + gContext.setSelectionRange(0, 0); + + // Replace whole content editable element to 'abc'. + gContext.replaceSurroundingText('abc', 0, 100).then(function() { + gContext.getText().then(function(text) { + is(text, 'abc', 'replaceSurroundingText successfully.'); + inputmethod_cleanup(); + }, function(e) { + ok(false, 'getText failed: ' + e.name); + inputmethod_cleanup(); + }); + }, function(e) { + ok(false, 'replaceSurroundingText failed: ' + e.name); + inputmethod_cleanup(); + }); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/inputmethod/mochitest/test_bug1137557.html b/dom/inputmethod/mochitest/test_bug1137557.html new file mode 100644 index 0000000000..1f50536620 --- /dev/null +++ b/dom/inputmethod/mochitest/test_bug1137557.html @@ -0,0 +1,1799 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1137557 +--> +<head> + <title>Test for new API arguments accepting D3E properties</title> + <script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript;version=1.7" src="inputmethod_common.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1137557">Mozilla Bug 1137557</a> +<p id="display"></p> +<pre id="test"> +<script class="testbody" type="application/javascript;version=1.7"> + +inputmethod_setup(function() { + runTest(); +}); + +let gEventDetails = []; +let gCurrentValue = ''; +let gTestDescription = ''; + +let appFrameScript = function appFrameScript() { + let input = content.document.body.firstElementChild; + + input.focus(); + + function sendEventDetail(evt) { + var eventDetail; + + switch (evt.type) { + case 'compositionstart': + case 'compositionupdate': + case 'compositionend': + eventDetail = { + type: evt.type, + value: input.value, + data: evt.data + }; + break; + + case 'input': + eventDetail = { + type: evt.type, + value: input.value + }; + break; + + default: // keyboard events + eventDetail = { + type: evt.type, + charCode: evt.charCode, + keyCode: evt.keyCode, + key: evt.key, + code: evt.code, + location: evt.location, + repeat: evt.repeat, + value: input.value, + shift: evt.getModifierState('Shift'), + capsLock: evt.getModifierState('CapsLock'), + control: evt.getModifierState('Control'), + alt: evt.getModifierState('Alt') + }; + break; + } + + sendAsyncMessage('test:eventDetail', eventDetail); + } + + input.addEventListener('compositionstart', sendEventDetail); + input.addEventListener('compositionupdate', sendEventDetail); + input.addEventListener('compositionend', sendEventDetail); + input.addEventListener('input', sendEventDetail); + input.addEventListener('keydown', sendEventDetail); + input.addEventListener('keypress', sendEventDetail); + input.addEventListener('keyup', sendEventDetail); +}; + +function waitForInputContextChange() { + return new Promise((resolve) => { + navigator.mozInputMethod.oninputcontextchange = resolve; + }); +} + +function assertEventDetail(expectedDetails, testName) { + is(gEventDetails.length, expectedDetails.length, + testName + ' expects ' + expectedDetails.map(d => d.type).join(', ') + ' events, got ' + gEventDetails.map(d => d.type).join(', ')); + + expectedDetails.forEach((expectedDetail, j) => { + for (let key in expectedDetail) { + is(gEventDetails[j][key], expectedDetail[key], + testName + ' expects ' + key + ' of ' + gEventDetails[j].type + ' to be equal to ' + expectedDetail[key]); + } + }); +} + +function sendKeyAndAssertResult(testdata) { + var dict = testdata.dict; + var testName = gTestDescription + 'sendKey(' + JSON.stringify(dict) + ')'; + var promise = navigator.mozInputMethod.inputcontext.sendKey(dict); + + if (testdata.expectedReject) { + promise = promise + .then(() => { + ok(false, testName + ' should not resolve.'); + }, (e) => { + ok(true, testName + ' rejects.'); + ok(e instanceof testdata.expectedReject, 'Reject with type.'); + }) + + return promise; + } + + promise = promise + .then((res) => { + is(res, true, + testName + ' should resolve to true.'); + + var expectedEventDetail = []; + + var expectedValues = testdata.expectedValues; + + expectedEventDetail.push({ + type: 'keydown', + key: expectedValues.key, + charCode: 0, + code: expectedValues.code || '', + keyCode: expectedValues.keyCode || 0, + location: expectedValues.location ? expectedValues.location : 0, + repeat: expectedValues.repeat || false, + value: gCurrentValue, + shift: false, + capsLock: false, + control: false, + alt: false + }); + + if (testdata.expectedKeypress) { + expectedEventDetail.push({ + type: 'keypress', + key: expectedValues.key, + charCode: expectedValues.charCode, + code: expectedValues.code || '', + keyCode: expectedValues.charCode ? 0 : expectedValues.keyCode, + location: expectedValues.location ? expectedValues.location : 0, + repeat: expectedValues.repeat || false, + value: gCurrentValue, + shift: false, + capsLock: false, + control: false, + alt: false + }); + } + + if (testdata.expectedInput) { + switch (testdata.expectedInput) { + case 'Enter': + gCurrentValue += '\n'; + break; + case 'Backspace': + gCurrentValue = + gCurrentValue.substr(0, gCurrentValue.length - 1); + break; + default: + gCurrentValue += testdata.expectedInput; + break; + } + + expectedEventDetail.push({ + type: 'input', + value: gCurrentValue + }); + } + + if (!testdata.expectedRepeat) { + expectedEventDetail.push({ + type: 'keyup', + key: expectedValues.key, + charCode: 0, + code: expectedValues.code || '', + keyCode: expectedValues.keyCode || 0, + location: expectedValues.location ? expectedValues.location : 0, + repeat: expectedValues.repeat || false, + value: gCurrentValue, + shift: false, + capsLock: false, + control: false, + alt: false + }); + } + + assertEventDetail(expectedEventDetail, testName); + gEventDetails = []; + }, (e) => { + ok(false, testName + ' should not reject. ' + e); + }); + + return promise; +} + +function runSendKeyAlphabetTests() { + gTestDescription = 'runSendKeyAlphabetTests(): '; + var promiseQueue = Promise.resolve(); + + // Test the plain alphabets + var codeA = 'A'.charCodeAt(0); + for (var i = 0; i < 26; i++) { + // callbacks in then() are deferred; must only reference these block-scoped + // variable instead of i. + let keyCode = codeA + i; + let code = 'Key' + String.fromCharCode(keyCode); + + [String.fromCharCode(keyCode), + String.fromCharCode(keyCode).toLowerCase()] + .forEach((chr) => { + // Test plain alphabet + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: '', + keyCode: keyCode, + charCode: chr.charCodeAt(0) + } + }); + }); + + // Test plain alphabet with keyCode set + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr, + keyCode: keyCode + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: '', + keyCode: keyCode, + charCode: chr.charCodeAt(0) + } + }); + }); + + // Test plain alphabet with keyCode set to keyCode + 1, + // expects keyCode to follow key value and ignore the incorrect value. + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr, + keyCode: keyCode + 1 + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: '', + keyCode: keyCode, + charCode: chr.charCodeAt(0) + } + }); + }); + + // Test plain alphabet with code set + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr, + code: code + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: code, + keyCode: keyCode, + charCode: chr.charCodeAt(0) + } + }); + }); + + // Test plain alphabet with code set to Digit1, + // expects keyCode to follow key value. + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr, + code: 'Digit1' + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: 'Digit1', + keyCode: keyCode, + charCode: chr.charCodeAt(0) + } + }); + }); + + // Test plain alphabet with keyCode set to DOM_VK_1, + // expects keyCode to follow key value. + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr, + keyCode: KeyboardEvent.DOM_VK_1 + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: '', + keyCode: keyCode, + charCode: chr.charCodeAt(0) + } + }); + }); + + // Test plain alphabet with code set to Digit1 + // and keyCode set to DOM_VK_1, + // expects keyCode to follow key value. + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr, + code: 'Digit1', + keyCode: KeyboardEvent.DOM_VK_1 + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: 'Digit1', + keyCode: keyCode, + charCode: chr.charCodeAt(0) + } + }); + }); + }); + } + + return promiseQueue; +} + +function runSendKeyNumberTests() { + gTestDescription = 'runSendKeyNumberTests(): '; + var promiseQueue = Promise.resolve(); + + // Test numbers + var code0 = '0'.charCodeAt(0); + for (var i = 0; i < 10; i++) { + // callbacks in then() are deferred; must only reference these block-scoped + // variable instead of i. + let keyCode = code0 + i; + let chr = String.fromCharCode(keyCode); + + // Test plain number + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: '', + keyCode: keyCode, + charCode: chr.charCodeAt(0) + } + }); + }); + + // Test plain number with keyCode set + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr, + keyCode: keyCode + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: '', + keyCode: keyCode, + charCode: chr.charCodeAt(0) + } + }); + }); + + // Test plain number with keyCode set to keyCode + 1, + // expects keyCode to follow key value and ignore the incorrect value. + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr, + keyCode: keyCode + 1 + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: '', + keyCode: keyCode, + charCode: chr.charCodeAt(0) + } + }); + }); + + // Test plain number with code set + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr, + code: 'Digit' + chr + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: 'Digit' + chr, + keyCode: keyCode, + charCode: chr.charCodeAt(0) + } + }); + }); + + // Test plain upper caps alphabet with code set to KeyA, + // expects keyCode to follow key value. + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr, + code: 'KeyA' + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: 'KeyA', + keyCode: keyCode, + charCode: chr.charCodeAt(0) + } + }); + }); + + // Test plain upper caps alphabet with code set to KeyA, + // and keyCode set to DOM_VK_A. + // expects keyCode to follow key value. + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr, + code: 'KeyA', + keyCode: KeyboardEvent.DOM_VK_A + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: 'KeyA', + keyCode: keyCode, + charCode: chr.charCodeAt(0) + } + }); + }); + } + + return promiseQueue; +} + +function runSendKeyDvorakTests() { + gTestDescription = 'runSendKeyDvorakTests(): '; + var promiseQueue = Promise.resolve(); + + // Test Dvorak layout emulation + var qwertyCodeForDvorakKeys = [ + 'KeyR', 'KeyT', 'KeyY', 'KeyU', 'KeyI', 'KeyO', 'KeyP', + 'KeyA', 'KeyS', 'KeyD', 'KeyF', 'KeyG', + 'KeyH', 'KeyJ', 'KeyK', 'KeyL', 'Semicolon', + 'KeyX', 'KeyC', 'KeyV', 'KeyB', 'KeyN', + 'KeyM', 'Comma', 'Period', 'Slash']; + var dvorakKeys = 'PYFGCRL' + + 'AOEUIDHTNS' + + 'QJKXBMWVZ'; + for (var i = 0; i < dvorakKeys.length; i++) { + // callbacks in then() are deferred; must only reference these block-scoped + // variable instead of i. + let keyCode = dvorakKeys.charCodeAt(i); + let code = qwertyCodeForDvorakKeys[i]; + + [dvorakKeys.charAt(i), dvorakKeys.charAt(i).toLowerCase()] + .forEach((chr) => { + // Test alphabet with code set to Qwerty code, + // expects keyCode to follow key value. + // (This is *NOT* the expected scenario for emulating a Dvorak keyboard, + // even though expected results are the same.) + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr, + code: code + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: code, + keyCode: keyCode, + charCode: chr.charCodeAt(0) + } + }); + }); + + // Test alphabet with code set to Qwerty code and keyCode set, + // expects keyCode to follow key/keyCode value. + // (This is the expected scenario for emulating a Dvorak keyboard) + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr, + keyCode: keyCode, + code: code + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: code, + keyCode: keyCode, + charCode: chr.charCodeAt(0) + } + }); + }); + }); + } + + var qwertyCodeForDvorakSymbols = [ + 'Minus', 'Equal', + 'KeyQ', 'KeyW', 'KeyE', 'BracketLeft', 'BracketRight', 'Backslash', + 'Quote', 'KeyZ']; + + var shiftDvorakSymbols = '{}\"<>?+|_:'; + var dvorakSymbols = '[]\',./=\\-;'; + var dvorakSymbolsKeyCodes = [ + KeyboardEvent.DOM_VK_OPEN_BRACKET, + KeyboardEvent.DOM_VK_CLOSE_BRACKET, + KeyboardEvent.DOM_VK_QUOTE, + KeyboardEvent.DOM_VK_COMMA, + KeyboardEvent.DOM_VK_PERIOD, + KeyboardEvent.DOM_VK_SLASH, + KeyboardEvent.DOM_VK_EQUALS, + KeyboardEvent.DOM_VK_BACK_SLASH, + KeyboardEvent.DOM_VK_HYPHEN_MINUS, + KeyboardEvent.DOM_VK_SEMICOLON + ]; + + for (var i = 0; i < dvorakSymbols.length; i++) { + // callbacks in then() are deferred; must only reference these block-scoped + // variable instead of i. + let keyCode = dvorakSymbolsKeyCodes[i]; + let code = qwertyCodeForDvorakSymbols[i]; + + [dvorakSymbols.charAt(i), shiftDvorakSymbols.charAt(i)] + .forEach((chr) => { + // Test symbols with code set to Qwerty code, + // expects keyCode to be 0. + // (This is *NOT* the expected scenario for emulating a Dvorak keyboard) + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr, + code: code + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: code, + keyCode: 0, + charCode: chr.charCodeAt(0) + } + }); + }); + + // Test alphabet with code set to Qwerty code and keyCode set, + // expects keyCode to follow keyCode value. + // (This is the expected scenario for emulating a Dvorak keyboard) + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr, + keyCode: keyCode, + code: code + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: code, + keyCode: keyCode, + charCode: chr.charCodeAt(0) + } + }); + }); + }); + } + + return promiseQueue; +} + +function runSendKeyDigitKeySymbolsTests() { + gTestDescription = 'runSendKeyDigitKeySymbolsTests(): '; + var promiseQueue = Promise.resolve(); + + var digitKeySymbols = ')!@#$%^&*('; + for (var i = 0; i < digitKeySymbols.length; i++) { + // callbacks in then() are deferred; must only reference these block-scoped + // variable instead of i. + let keyCode = KeyboardEvent['DOM_VK_' + i]; + let chr = digitKeySymbols.charAt(i); + let code = 'Digit' + i; + + // Test plain symbol + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: '', keyCode: 0, + charCode: chr.charCodeAt(0) + } + }); + }); + + // Test plain symbol with keyCode set + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr, + keyCode: keyCode + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: '', + keyCode: keyCode, + charCode: chr.charCodeAt(0) + } + }); + }); + + // Test plain symbol with code set + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr, + code: code + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: code, + keyCode: 0, + charCode: chr.charCodeAt(0) + } + }); + }); + + // Test plain symbol with code set to KeyA, + // expects keyCode to be 0. + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr, + code: 'KeyA' + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: 'KeyA', + keyCode: 0, + charCode: chr.charCodeAt(0) + } + }); + }); + + // Test plain symbol with keyCode set to DOM_VK_A, + // expects keyCode to follow the keyCode set. + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr, + keyCode: KeyboardEvent.DOM_VK_A + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: '', + keyCode: KeyboardEvent.DOM_VK_A, + charCode: chr.charCodeAt(0) + } + }); + }); + + // Test plain symbol with code set to KeyA + // expects keyCode to follow the keyCode set. + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr, + code: 'KeyA', + keyCode: KeyboardEvent.DOM_VK_A + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: 'KeyA', + keyCode: KeyboardEvent.DOM_VK_A, + charCode: chr.charCodeAt(0) + } + }); + }); + } + + return promiseQueue; +} + +function runSendKeyUSKeyboardSymbolsTests() { + gTestDescription = 'runSendKeyUSKeyboardSymbolsTests(): '; + var promiseQueue = Promise.resolve(); + + // Test printable symbols on US Keyboard + var symbols = ' ;:=+,<-_.>/?`~[{\\|]}\'\"'; + var symbolKeyCodes = [ + KeyboardEvent.DOM_VK_SPACE, + KeyboardEvent.DOM_VK_SEMICOLON, + KeyboardEvent.DOM_VK_SEMICOLON, + KeyboardEvent.DOM_VK_EQUALS, + KeyboardEvent.DOM_VK_EQUALS, + KeyboardEvent.DOM_VK_COMMA, + KeyboardEvent.DOM_VK_COMMA, + KeyboardEvent.DOM_VK_HYPHEN_MINUS, + KeyboardEvent.DOM_VK_HYPHEN_MINUS, + KeyboardEvent.DOM_VK_PERIOD, + KeyboardEvent.DOM_VK_PERIOD, + KeyboardEvent.DOM_VK_SLASH, + KeyboardEvent.DOM_VK_SLASH, + KeyboardEvent.DOM_VK_BACK_QUOTE, + KeyboardEvent.DOM_VK_BACK_QUOTE, + KeyboardEvent.DOM_VK_OPEN_BRACKET, + KeyboardEvent.DOM_VK_OPEN_BRACKET, + KeyboardEvent.DOM_VK_BACK_SLASH, + KeyboardEvent.DOM_VK_BACK_SLASH, + KeyboardEvent.DOM_VK_CLOSE_BRACKET, + KeyboardEvent.DOM_VK_CLOSE_BRACKET, + KeyboardEvent.DOM_VK_QUOTE, + KeyboardEvent.DOM_VK_QUOTE + ]; + var symbolCodes = [ + 'Space', + 'Semicolon', + 'Semicolon', + 'Equal', + 'Equal', + 'Comma', + 'Comma', + 'Minus', + 'Minus', + 'Period', + 'Period', + 'Slash', + 'Slash', + 'Backquote', + 'Backquote', + 'BracketLeft', + 'BracketLeft', + 'Backslash', + 'Backslash', + 'BracketRight', + 'BracketRight', + 'Quote', + 'Quote' + ]; + for (var i = 0; i < symbols.length; i++) { + // callbacks in then() are deferred; must only reference these block-scoped + // variable instead of i. + let keyCode = symbolKeyCodes[i]; + let chr = symbols.charAt(i); + let code = symbolCodes[i]; + + // Test plain symbol + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: '', + keyCode: 0, + charCode: chr.charCodeAt(0) + } + }); + }); + + // Test plain symbol with keyCode set + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr, + keyCode: keyCode + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: '', + keyCode: keyCode, + charCode: chr.charCodeAt(0) + } + }); + }); + + // Test plain symbol with code set + // expects keyCode to be 0. + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr, + code: code + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: code, + keyCode: 0, + charCode: chr.charCodeAt(0) + } + }); + }); + + // Test plain symbol with code set to KeyA, + // expects keyCode to be 0. + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr, + code: 'KeyA' + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: 'KeyA', + keyCode: 0, + charCode: chr.charCodeAt(0) + } + }); + }); + + // Test plain symbol with keyCode set to DOM_VK_A, + // expects keyCode to follow the keyCode set. + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr, + keyCode: KeyboardEvent.DOM_VK_A + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: '', + keyCode: KeyboardEvent.DOM_VK_A, + charCode: chr.charCodeAt(0) + } + }); + }); + + // Test plain symbol with code set to KeyA + // expects keyCode to follow the keyCode set. + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr, + code: 'KeyA', + keyCode: KeyboardEvent.DOM_VK_A + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: 'KeyA', + keyCode: KeyboardEvent.DOM_VK_A, + charCode: chr.charCodeAt(0) + } + }); + }); + } + + return promiseQueue; +} + +function runSendKeyGreekLettersTests() { + gTestDescription = 'runSendKeyGreekLettersTests(): '; + var promiseQueue = Promise.resolve(); + + // Test Greek letters + var greekLetters = + '\u0391\u0392\u0393\u0394\u0395\u0396\u0397\u0398\u0399\u039a\u039b\u039c' + + '\u039d\u039e\u039f\u03a0\u03a1\u03a3\u03a4\u03a5\u03a6\u03a7\u03a8\u03a9' + + '\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc' + + '\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9' + + '\u03c2'; + var greekLettersLayoutMap = + 'ABGDEZHUIKLMNJOPRSTYFXCVABGDEZHUIKLMNJOPRSTYFXCVQ'; + for (var i = 0; i < greekLetters.length; i++) { + // callbacks in then() are deferred; must only reference these block-scoped + // variable instead of i. + let keyCode = greekLettersLayoutMap.charCodeAt(i); + let chr = greekLetters.charAt(i); + let code = 'Key' + greekLettersLayoutMap.charAt(i); + + // Test plain alphabet + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: '', + keyCode: 0, + charCode: chr.charCodeAt(0) + } + }); + }); + + // Test plain alphabet with keyCode set + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr, + keyCode: keyCode + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: '', + keyCode: keyCode, + charCode: chr.charCodeAt(0) + } + }); + }); + + // Test plain alphabet with code set, + // expects keyCode to be 0. + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr, + code: code + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: code, + keyCode: 0, + charCode: chr.charCodeAt(0) + } + }); + }); + + // Test plain alphabet with code set to Digit1, + // expects keyCode to be 0. + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr, + code: 'Digit1' + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: 'Digit1', + keyCode: 0, + charCode: chr.charCodeAt(0) + } + }); + }); + + // Test plain alphabet with code set to Digit1, + // and keyCode set to DOM_VK_A. + // expects keyCode to follow the keyCode set. + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: chr, + code: 'Digit1', + keyCode: KeyboardEvent.DOM_VK_A + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: 'Digit1', + keyCode: KeyboardEvent.DOM_VK_A, + charCode: chr.charCodeAt(0) + } + }); + }); + } + + return promiseQueue; +} + +function runSendKeyEnterTests() { + gTestDescription = 'runSendKeyEnterTests(): '; + var promiseQueue = Promise.resolve(); + + // Test Enter with code unset + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: 'Enter' + }, + expectedKeypress: true, + expectedInput: '\n', + expectedValues: { + key: 'Enter', code: '', + keyCode: KeyboardEvent.DOM_VK_RETURN, + charCode: 0 + } + }); + }); + + // Test Enter with code set + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: 'Enter', + code: 'Enter' + }, + expectedKeypress: true, + expectedInput: '\n', + expectedValues: { + key: 'Enter', code: 'Enter', + keyCode: KeyboardEvent.DOM_VK_RETURN, + charCode: 0 + } + }); + }); + + // Test Enter with keyCode explict set to zero + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: { + key: 'Enter', + keyCode: 0 + }, + expectedKeypress: true, + expectedValues: { + key: 'Enter', code: '', + keyCode: 0, + charCode: 0 + } + }); + }); + + return promiseQueue; +} + +function runSendKeyNumpadTests() { + gTestDescription = 'runSendKeyNumpadTests(): '; + var promiseQueue = Promise.resolve(); + + var tests = []; + ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] + .forEach(function(key) { + let charCode = key.charCodeAt(0); + + tests.push({ + dict: { + key: key, + code: 'Numpad' + key + }, + expectedKeypress: true, + expectedInput: key, + expectedValues: { + key: key, code: 'Numpad' + key, + keyCode: charCode, charCode: charCode, + location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD + } + }); + }); + + [['+', 'NumpadAdd'], + [',', 'NumpadComma'], + ['.', 'NumpadDecimal'], + ['.', 'NumpadComma'], // Locale-specific NumpadComma + [',', 'NumpadDecimal'], // Locale-specific NumpadDecimal + ['/', 'NumpadDivide'], + ['=', 'NumpadEqual'], + // ['#', 'NumpadHash'], // Not supported yet. + ['*', 'NumpadMultiply'], + ['(', 'NumpadParenLeft'], + [')', 'NumpadParenRight'], + // ['*', 'NumpadStar'], // Not supported yet. + ['-', 'NumpadSubtract']].forEach(function([key, code]) { + tests.push({ + dict: { + key: key, + code: code + }, + expectedKeypress: true, + expectedInput: key, + expectedValues: { + key: key, code: code, keyCode: 0, charCode: key.charCodeAt(0), + location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD + } + }); + }); + + [ + 'NumpadComma', // Locale-specific NumpadComma -- outputs nothing + 'NumpadClear', + 'NumpadClearEntry', + 'NumpadMemoryAdd', + 'NumpadMemoryClear', + 'NumpadMemoryRecall', + 'NumpadMemoryStore', + 'NumpadMemorySubtract' + ].forEach(function(code) { + tests.push({ + dict: { + key: 'Unidentified', + code: code + }, + expectedKeypress: true, + expectedValues: { + key: 'Unidentified', code: code, keyCode: 0, charCode: 0, + location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD + } + }); + }); + + tests.push({ + dict: { + key: 'Enter', + code: 'NumpadEnter' + }, + expectedKeypress: true, + expectedInput: '\n', + expectedValues: { + key: 'Enter', code: 'NumpadEnter', + keyCode: KeyboardEvent.DOM_VK_RETURN, charCode: 0, + location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD + } + }); + + tests.push({ + dict: { + key: 'Backspace', + code: 'NumpadBackspace' + }, + expectedKeypress: true, + expectedInput: 'Backspace', // Special value + expectedValues: { + key: 'Backspace', code: 'NumpadBackspace', + keyCode: KeyboardEvent.DOM_VK_BACK_SPACE, charCode: 0, + location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD + } + }); + + tests.forEach((test) => { + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult(test); + }); + }); + + return promiseQueue; +} + +function runSendKeyRejectionTests() { + gTestDescription = 'runSendKeyRejectionTests(): '; + var promiseQueue = Promise.resolve(); + + promiseQueue = promiseQueue.then(() => { + return sendKeyAndAssertResult({ + dict: undefined, + expectedReject: TypeError + }); + }); + + return promiseQueue; +} + +function setCompositionAndAssertResult(testdata) { + var dict = testdata.dict; + var testName; + var promise; + + if (dict) { + testName = gTestDescription + + 'setComposition(' + testdata.text + + ', undefined, undefined, ' + + JSON.stringify(dict) + ')'; + promise = navigator.mozInputMethod.inputcontext + .setComposition(testdata.text, undefined, undefined, dict); + } else { + testName = gTestDescription + + 'setComposition(' + testdata.text + ')'; + promise = navigator.mozInputMethod.inputcontext + .setComposition(testdata.text); + } + + if (testdata.expectedReject) { + promise = promise + .then(() => { + ok(false, testName + ' should not resolve.'); + }, (e) => { + ok(true, testName + ' rejects.'); + ok(e instanceof testdata.expectedReject, 'Reject with type.'); + }) + + return promise; + } + + promise = promise + .then((res) => { + is(res, true, + testName + ' should resolve to true.'); + + var expectedEventDetail = []; + + var expectedValues = testdata.expectedValues; + + if (testdata.expectsKeyEvents && + (testdata.startsComposition || + testdata.dispatchKeyboardEventDuringComposition)) { + expectedEventDetail.push({ + type: 'keydown', + key: expectedValues.key, + charCode: 0, + code: expectedValues.code || '', + keyCode: expectedValues.keyCode || 0, + location: 0, + repeat: expectedValues.repeat || false, + value: gCurrentValue, + shift: false, + capsLock: false, + control: false, + alt: false + }); + } + + if (testdata.startsComposition) { + expectedEventDetail.push({ + type: 'compositionstart', + data: '', + value: gCurrentValue + }); + } + + expectedEventDetail.push({ + type: 'compositionupdate', + data: testdata.text, + value: gCurrentValue + }); + + expectedEventDetail.push({ + type: 'input', + value: gCurrentValue += testdata.expectedInput + }); + + if (testdata.expectsKeyEvents && + testdata.dispatchKeyboardEventDuringComposition) { + expectedEventDetail.push({ + type: 'keyup', + key: expectedValues.key, + charCode: 0, + code: expectedValues.code || '', + keyCode: expectedValues.keyCode || 0, + location: 0, + repeat: expectedValues.repeat || false, + value: gCurrentValue, + shift: false, + capsLock: false, + control: false, + alt: false + }); + } + + assertEventDetail(expectedEventDetail, testName); + gEventDetails = []; + }, (e) => { + ok(false, testName + ' should not reject. ' + e); + }); + + return promise; +} + +function endCompositionAndAssertResult(testdata) { + var dict = testdata.dict; + var testName; + var promise; + if (dict) { + testName = gTestDescription + + 'endComposition(' + testdata.text + ', ' + JSON.stringify(dict) + ')'; + promise = navigator.mozInputMethod.inputcontext + .endComposition(testdata.text, dict); + } else { + testName = gTestDescription + + 'endComposition(' + testdata.text + ')'; + promise = navigator.mozInputMethod.inputcontext + .endComposition(testdata.text); + } + + if (testdata.expectedReject) { + promise = promise + .then(() => { + ok(false, testName + ' should not resolve.'); + }, (e) => { + ok(true, testName + ' rejects.'); + ok(e instanceof testdata.expectedReject, 'Reject with type.'); + }) + + return promise; + } + + promise = promise + .then((res) => { + is(res, true, + testName + ' should resolve to true.'); + + var expectedEventDetail = []; + + var expectedValues = testdata.expectedValues; + + if (testdata.expectsKeyEvents && + testdata.dispatchKeyboardEventDuringComposition) { + expectedEventDetail.push({ + type: 'keydown', + key: expectedValues.key, + charCode: 0, + code: expectedValues.code || '', + keyCode: expectedValues.keyCode || 0, + location: 0, + repeat: expectedValues.repeat || false, + value: gCurrentValue, + shift: false, + capsLock: false, + control: false, + alt: false + }); + } + + expectedEventDetail.push({ + type: 'compositionend', + data: testdata.text, + value: gCurrentValue + }); + + expectedEventDetail.push({ + type: 'input', + value: gCurrentValue + }); + + if (testdata.expectsKeyEvents) { + expectedEventDetail.push({ + type: 'keyup', + key: expectedValues.key, + charCode: 0, + code: expectedValues.code || '', + keyCode: expectedValues.keyCode || 0, + location: 0, + repeat: expectedValues.repeat || false, + value: gCurrentValue, + shift: false, + capsLock: false, + control: false, + alt: false + }); + } + + assertEventDetail(expectedEventDetail, testName); + gEventDetails = []; + }, (e) => { + ok(false, testName + ' should not reject. ' + e); + }); + + return promise; +} + +function runCompositionWithKeyEventTests() { + var promiseQueue = Promise.resolve(); + + [true, false].forEach((dispatchKeyboardEventDuringComposition) => { + gTestDescription = 'runCompositionWithKeyEventTests() (dispatchKeyboardEvent =' + dispatchKeyboardEventDuringComposition + '): '; + + promiseQueue = promiseQueue + .then(() => { + SpecialPowers.setBoolPref( + 'dom.keyboardevent.dispatch_during_composition', + dispatchKeyboardEventDuringComposition); + }) + .then(() => { + return setCompositionAndAssertResult({ + text: 'foo', + expectsKeyEvents: true, + startsComposition: true, + dispatchKeyboardEventDuringComposition: dispatchKeyboardEventDuringComposition, + expectedInput: 'foo', + dict: { + key: 'a', + code: 'KeyA', + keyCode: KeyboardEvent.DOM_VK_A + }, + expectedValues: { + key: 'a', + code: 'KeyA', + keyCode: KeyboardEvent.DOM_VK_A + } + }); + }) + .then(() => { + return setCompositionAndAssertResult({ + text: 'foobar', + expectsKeyEvents: true, + startsComposition: false, + dispatchKeyboardEventDuringComposition: dispatchKeyboardEventDuringComposition, + expectedInput: 'bar', + dict: { + key: 'a', + code: 'KeyA', + keyCode: KeyboardEvent.DOM_VK_A + }, + expectedValues: { + key: 'a', + code: 'KeyA', + keyCode: KeyboardEvent.DOM_VK_A + } + }); + }) + .then(() => { + return endCompositionAndAssertResult({ + text: 'foobar', + expectsKeyEvents: true, + dispatchKeyboardEventDuringComposition: dispatchKeyboardEventDuringComposition, + expectedInput: '', + dict: { + key: 'a', + code: 'KeyA', + keyCode: KeyboardEvent.DOM_VK_A + }, + expectedValues: { + key: 'a', + code: 'KeyA', + keyCode: KeyboardEvent.DOM_VK_A + } + }); + }) + .then(() => { + SpecialPowers.clearUserPref( + 'dom.keyboardevent.dispatch_during_composition'); + }); + }); + + return promiseQueue; +} + +function runCompositionWithoutKeyEventTests() { + var promiseQueue = Promise.resolve(); + + gTestDescription = 'runCompositionWithoutKeyEventTests(): '; + + promiseQueue = promiseQueue + .then(() => { + return setCompositionAndAssertResult({ + text: 'foo', + expectsKeyEvents: false, + startsComposition: true, + expectedInput: 'foo' + }); + }) + .then(() => { + return setCompositionAndAssertResult({ + text: 'foobar', + expectsKeyEvents: false, + startsComposition: false, + expectedInput: 'bar' + }); + }) + .then(() => { + return endCompositionAndAssertResult({ + text: 'foobar', + expectsKeyEvents: false, + expectedInput: '' + }); + }); + + return promiseQueue; +} + +function keydownAndAssertResult(testdata) { + var dict = testdata.dict; + var testName = gTestDescription + 'keydown(' + JSON.stringify(dict) + ')'; + var promise = navigator.mozInputMethod.inputcontext.keydown(dict); + + if (testdata.expectedReject) { + promise = promise + .then(() => { + ok(false, testName + ' should not resolve.'); + }, (e) => { + ok(true, testName + ' rejects.'); + ok(e instanceof testdata.expectedReject, 'Reject with type.'); + }) + + return promise; + } + + promise = promise + .then((res) => { + is(res, true, + testName + ' should resolve to true.'); + + var expectedEventDetail = []; + + var expectedValues = testdata.expectedValues; + + expectedEventDetail.push({ + type: 'keydown', + key: expectedValues.key, + charCode: 0, + code: expectedValues.code || '', + keyCode: expectedValues.keyCode || 0, + location: 0, + repeat: expectedValues.repeat || false, + value: gCurrentValue, + shift: false, + capsLock: false, + control: false, + alt: false + }); + + if (testdata.expectedKeypress) { + expectedEventDetail.push({ + type: 'keypress', + key: expectedValues.key, + charCode: expectedValues.charCode, + code: expectedValues.code || '', + keyCode: expectedValues.charCode ? 0 : expectedValues.keyCode, + location: 0, + repeat: expectedValues.repeat || false, + value: gCurrentValue, + shift: false, + capsLock: false, + control: false, + alt: false + }); + } + + if (testdata.expectedInput) { + expectedEventDetail.push({ + type: 'input', + value: gCurrentValue += testdata.expectedInput + }); + } + + assertEventDetail(expectedEventDetail, testName); + gEventDetails = []; + }, (e) => { + ok(false, testName + ' should not reject. ' + e); + }); + + return promise; +} + +function keyupAndAssertResult(testdata) { + var dict = testdata.dict; + var testName = gTestDescription + 'keyup(' + JSON.stringify(dict) + ')'; + var promise = navigator.mozInputMethod.inputcontext.keyup(dict); + + if (testdata.expectedReject) { + promise = promise + .then(() => { + ok(false, testName + ' should not resolve.'); + }, (e) => { + ok(true, testName + ' rejects.'); + ok(e instanceof testdata.expectedReject, 'Reject with type.'); + }) + + return promise; + } + + promise = promise + .then((res) => { + is(res, true, + testName + ' should resolve to true.'); + + var expectedEventDetail = []; + + var expectedValues = testdata.expectedValues; + + expectedEventDetail.push({ + type: 'keyup', + key: expectedValues.key, + charCode: 0, + code: expectedValues.code || '', + keyCode: expectedValues.keyCode || 0, + location: 0, + repeat: expectedValues.repeat || false, + value: gCurrentValue, + shift: false, + capsLock: false, + control: false, + alt: false + }); + + assertEventDetail(expectedEventDetail, testName); + gEventDetails = []; + }, (e) => { + ok(false, testName + ' should not reject. ' + e); + }); + + return promise; +} + +function runKeyDownUpTests() { + gTestDescription = 'runKeyDownUpTests(): '; + var promiseQueue = Promise.resolve(); + + let chr = 'a'; + let code = 'KeyA'; + let keyCode = KeyboardEvent.DOM_VK_A; + + promiseQueue = promiseQueue + .then(() => { + return keydownAndAssertResult({ + dict: { + key: chr, + code: code, + keyCode: keyCode + }, + expectedKeypress: true, + expectedInput: chr, + expectedValues: { + key: chr, code: code, + keyCode: keyCode, + charCode: chr.charCodeAt(0) + } + }); + }) + .then(() => { + return keyupAndAssertResult({ + dict: { + key: chr, + code: code, + keyCode: keyCode + }, + expectedValues: { + key: chr, code: code, + keyCode: keyCode, + charCode: chr.charCodeAt(0) + } + }); + }); + + return promiseQueue; +} + +function runKeyDownUpRejectionTests() { + gTestDescription = 'runKeyDownUpRejectionTests(): '; + var promiseQueue = Promise.resolve(); + + promiseQueue = promiseQueue.then(() => { + return keydownAndAssertResult({ + dict: undefined, + expectedReject: TypeError + }); + }); + + promiseQueue = promiseQueue.then(() => { + return keyupAndAssertResult({ + dict: undefined, + expectedReject: TypeError + }); + }); + + return promiseQueue; +} + +function runRepeatTests() { + gTestDescription = 'runRepeatTests(): '; + var promiseQueue = Promise.resolve(); + + // Test repeat + promiseQueue = promiseQueue + .then(() => { + return sendKeyAndAssertResult({ + dict: { + key: 'A', + repeat: true + }, + expectedKeypress: true, + expectedRepeat: true, + expectedInput: 'A', + expectedValues: { + repeat: true, + key: 'A', code: '', + keyCode: KeyboardEvent.DOM_VK_A, + charCode: 'A'.charCodeAt(0) + } + }); + }) + .then(() => { + return keyupAndAssertResult({ + dict: { + key: 'A' + }, + expectedKeypress: true, + expectedRepeat: true, + expectedInput: 'A', + expectedValues: { + key: 'A', code: '', + keyCode: KeyboardEvent.DOM_VK_A, + charCode: 'A'.charCodeAt(0) + } + }); + }); + + return promiseQueue; +} + +function runTest() { + let im = navigator.mozInputMethod; + + // Set current page as an input method. + SpecialPowers.wrap(im).setActive(true); + + let iframe = document.createElement('iframe'); + iframe.src = 'file_test_bug1137557.html'; + iframe.setAttribute('mozbrowser', true); + document.body.appendChild(iframe); + + let mm = SpecialPowers.getBrowserFrameMessageManager(iframe); + + iframe.addEventListener('mozbrowserloadend', function() { + mm.addMessageListener('test:eventDetail', function(msg) { + gEventDetails.push(msg.data); + }); + mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false); + }); + + waitForInputContextChange() + .then(() => { + var inputcontext = navigator.mozInputMethod.inputcontext; + + ok(!!inputcontext, 'Receving the first input context'); + }) + .then(() => runSendKeyAlphabetTests()) + .then(() => runSendKeyNumberTests()) + .then(() => runSendKeyDvorakTests()) + .then(() => runSendKeyDigitKeySymbolsTests()) + .then(() => runSendKeyUSKeyboardSymbolsTests()) + .then(() => runSendKeyGreekLettersTests()) + .then(() => runSendKeyEnterTests()) + .then(() => runSendKeyNumpadTests()) + .then(() => runSendKeyRejectionTests()) + .then(() => runCompositionWithKeyEventTests()) + .then(() => runCompositionWithoutKeyEventTests()) + .then(() => runKeyDownUpTests()) + .then(() => runKeyDownUpRejectionTests()) + .then(() => runRepeatTests()) + .catch((err) => { + console.error(err); + is(false, err.message); + }) + .then(() => { + var p = waitForInputContextChange(); + + // Revoke our right from using the IM API. + SpecialPowers.wrap(im).setActive(false); + + return p; + }) + .then(() => { + var inputcontext = navigator.mozInputMethod.inputcontext; + + is(inputcontext, null, 'Receving null input context'); + + inputmethod_cleanup(); + }) + .catch((err) => { + console.error(err); + is(false, err.message); + }); +} + +</script> +</pre> +</body> +</html> + diff --git a/dom/inputmethod/mochitest/test_bug1175399.html b/dom/inputmethod/mochitest/test_bug1175399.html new file mode 100644 index 0000000000..64fc85e882 --- /dev/null +++ b/dom/inputmethod/mochitest/test_bug1175399.html @@ -0,0 +1,62 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1175399 +--> +<head> + <title>Test focus when page unloads</title> + <script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript;version=1.7" src="inputmethod_common.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1175399">Mozilla Bug 1175399</a> +<p id="display"></p> +<pre id="test"> +<script class="testbody" type="application/javascript;version=1.7"> + +inputmethod_setup(function() { + runTest(); +}); + +let appFrameScript = function appFrameScript() { + let input = content.document.body.firstElementChild; + input.focus(); + + content.setTimeout(function() { + sendAsyncMessage('test:step'); + }); +}; + +function runTest() { + let im = navigator.mozInputMethod; + + // Set current page as an input method. + SpecialPowers.wrap(im).setActive(true); + + let iframe = document.createElement('iframe'); + iframe.src = 'file_test_bug1175399.html'; + iframe.setAttribute('mozbrowser', true); + document.body.appendChild(iframe); + + let mm = SpecialPowers.getBrowserFrameMessageManager(iframe); + im.oninputcontextchange = function() { + is(false, 'should not receive inputcontextchange event'); + }; + + iframe.addEventListener('mozbrowserloadend', function() { + mm.addMessageListener('test:step', function() { + let inputcontext = navigator.mozInputMethod.inputcontext; + is(inputcontext, null, 'inputcontext is null'); + + inputmethod_cleanup(); + }); + mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false); + }); +} + +</script> +</pre> +</body> +</html> + diff --git a/dom/inputmethod/mochitest/test_bug944397.html b/dom/inputmethod/mochitest/test_bug944397.html new file mode 100644 index 0000000000..4be95f3954 --- /dev/null +++ b/dom/inputmethod/mochitest/test_bug944397.html @@ -0,0 +1,107 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=944397 +--> +<head> + <title>Basic test for InputMethod API.</title> + <script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript;version=1.7" src="inputmethod_common.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=944397">Mozilla Bug 944397</a> +<p id="display"></p> +<pre id="test"> +<script class="testbody" type="application/javascript;version=1.7"> + +SimpleTest.requestFlakyTimeout("untriaged"); + +inputmethod_setup(function() { + runTest(); +}); + +// The frame script running in file_test_app.html. +function appFrameScript() { + let input = content.document.getElementById('test-input'); + input.oninput = function() { + sendAsyncMessage('test:InputMethod:oninput', { + value: input.value + }); + }; +} + +function runTest() { + let app, keyboard; + + /** + * So this test does the following: + * 1. Create a mozbrowser iframe with a text field in it, and focus the text field + * 2. 100ms. after loading we create new keyboard iframe, that will try to execute + * replaceSurroundingText on the current active inputcontext + * 3. That should trigger 'input' event on the said text field + * 4. And if that happens we know everything is OK + */ + + let path = location.pathname; + let basePath = location.protocol + '//' + location.host + + path.substring(0, path.lastIndexOf('/')); + + // STEP 1: Create an app frame to recieve keyboard inputs. + function step1() { + app = document.createElement('iframe'); + app.src = basePath + '/file_test_app.html'; + app.setAttribute('mozbrowser', true); + document.body.appendChild(app); + app.addEventListener('mozbrowserloadend', function() { + let mm = SpecialPowers.getBrowserFrameMessageManager(app); + mm.loadFrameScript('data:,(' + appFrameScript.toString() + ')();', false); + mm.addMessageListener("test:InputMethod:oninput", function(ev) { + step4(SpecialPowers.wrap(ev).json.value); + }); + + step2(); + }); + } + + function step2() { + // STEP 2a: Create a browser frame to load the input method app. + keyboard = document.createElement('iframe'); + keyboard.setAttribute('mozbrowser', true); + document.body.appendChild(keyboard); + + // STEP 2b: Grant input privileges to the keyboard iframe + let imeUrl = basePath + '/file_inputmethod.html#data'; + + // STEP 2c: Tell Gecko to use this iframe as its keyboard app + let req = keyboard.setInputMethodActive(true); + + req.onsuccess = function() { + ok(true, 'setInputMethodActive succeeded.'); + }; + + req.onerror = function() { + ok(false, 'setInputMethodActive failed: ' + this.error.name); + inputmethod_cleanup(); + }; + + // STEP 3: Loads the input method app to the browser frame after a delay. + setTimeout(function() { + keyboard.src = imeUrl; + }, 100); + } + + function step4(val) { + ok(true, 'Keyboard input was received.'); + is(val, '#dataYuan', 'Input value'); + inputmethod_cleanup(); + } + + step1(); +} + +</script> +</pre> +</body> +</html> + diff --git a/dom/inputmethod/mochitest/test_bug949059.html b/dom/inputmethod/mochitest/test_bug949059.html new file mode 100644 index 0000000000..495f3d63c8 --- /dev/null +++ b/dom/inputmethod/mochitest/test_bug949059.html @@ -0,0 +1,40 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=949059 +--> +<head> + <title>Test "mgmt" property of MozInputMethod.</title> + <script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript;version=1.7" src="inputmethod_common.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=949059">Mozilla Bug 949059</a> +<p id="display"></p> +<pre id="test"> +<script class="testbody" type="application/javascript;version=1.7"> + +inputmethod_setup(function() { + runTest(); +}); + +function runTest() { + let im = navigator.mozInputMethod; + + // Treat current page as an input method and activate it. + SpecialPowers.wrap(im).setActive(true); + ok(im.mgmt, 'The mgmt property should not be null.'); + + // Deactivate current page. + SpecialPowers.wrap(im).setActive(false); + ok(im.mgmt, 'The mgmt property should not be null.'); + + inputmethod_cleanup(); +} + +</script> +</pre> +</body> +</html> + diff --git a/dom/inputmethod/mochitest/test_bug953044.html b/dom/inputmethod/mochitest/test_bug953044.html new file mode 100644 index 0000000000..2b1f022a6a --- /dev/null +++ b/dom/inputmethod/mochitest/test_bug953044.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=953044 +--> +<head> + <title>Basic test for InputMethod API.</title> + <script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript;version=1.7" src="inputmethod_common.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=953044">Mozilla Bug 953044</a> +<p id="display"></p> +<pre id="test"> +<script class="testbody" type="application/javascript;version=1.7"> + +inputmethod_setup(function() { + runTest(); +}); + +function runTest() { + // Create an app frame to recieve keyboard inputs. + let app = document.createElement('iframe'); + app.src = 'file_test_app.html'; + app.setAttribute('mozbrowser', true); + document.body.appendChild(app); + + // Create a browser frame to load the input method app. + let keyboard = document.createElement('iframe'); + keyboard.setAttribute('mozbrowser', true); + document.body.appendChild(keyboard); + + // Bug 953044 setInputMethodActive(false) before input method app loads should + // always succeed. + let req = keyboard.setInputMethodActive(false); + req.onsuccess = function() { + ok(true, 'setInputMethodActive before loading succeeded.'); + inputmethod_cleanup(); + }; + + req.onerror = function() { + ok(false, 'setInputMethodActive before loading failed: ' + this.error.name); + inputmethod_cleanup(); + }; +} + +</script> +</pre> +</body> +</html> + diff --git a/dom/inputmethod/mochitest/test_bug960946.html b/dom/inputmethod/mochitest/test_bug960946.html new file mode 100644 index 0000000000..f65ff42e74 --- /dev/null +++ b/dom/inputmethod/mochitest/test_bug960946.html @@ -0,0 +1,108 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=960946 +--> +<head> + <title>Basic test for repeat sendKey events</title> + <script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript;version=1.7" src="inputmethod_common.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=960946">Mozilla Bug 960946</a> +<p id="display"></p> +<pre id="test"> +<script class="testbody" type="application/javascript;version=1.7"> + +// The input context. +var gContext = null; +var gCounter = 0; +var gBackSpaceCounter = 0; +var result = ["keydown", "keypress", "keydown","keypress", + "keydown", "keypress", "keyup" + ]; + +inputmethod_setup(function() { + runTest(); +}); + +var input; +// The frame script running in file_test_backspace_event.html. +function appFrameScript() { + let input = content.document.getElementById('test-input'); + input.onkeydown = input.onkeypress = input.onkeyup = function(event) { + dump('key event was fired in file_test_backspace_event.html: ' + event.type +'\n'); + sendAsyncMessage('test:KeyBoard:keyEvent', {'type':event.type}); + }; +} + +function runTest() { + let im = navigator.mozInputMethod; + + im.oninputcontextchange = function() { + ok(true, 'inputcontextchange event was fired.'); + im.oninputcontextchange = null; + + gContext = im.inputcontext; + if (!gContext) { + ok(false, 'Should have a non-null inputcontext.'); + inputmethod_cleanup(); + return; + } + + test_sendKey(); + }; + + // Set current page as an input method. + SpecialPowers.wrap(im).setActive(true); + + // Create an app frame to recieve keyboard inputs. + let app = document.createElement('iframe'); + app.src = 'file_test_app.html'; + app.setAttribute('mozbrowser', true); + document.body.appendChild(app); + app.addEventListener('mozbrowserloadend', function() { + let mm = SpecialPowers.getBrowserFrameMessageManager(app); + mm.loadFrameScript('data:,(' + appFrameScript.toString() + ')();', false); + mm.addMessageListener("test:KeyBoard:keyEvent", function(event) { + ok(true, 'Keyboard input was received.'); + is(SpecialPowers.wrap(event).json.type, result[gCounter], "expected event"); + gCounter++; + }); + }); +} + +function test_sendKey() { + // Move cursor position to 4. + gContext.setSelectionRange(4, 0).then(function() { + is(gContext.selectionStart, 4, 'selectionStart was set successfully.'); + is(gContext.selectionEnd, 4, 'selectionEnd was set successfully.'); + for(let i = 0; i < 2; i++) { + test_sendBackspace(true); + } + test_sendBackspace(false); + }, function(e) { + ok(false, 'setSelectionRange failed:' + e.name); + inputmethod_cleanup(); + }); +} + +function test_sendBackspace(repeat) { + // Send backspace + gContext.sendKey(KeyEvent.DOM_VK_BACK_SPACE, 0, 0, repeat).then(function() { + ok(true, 'sendKey success'); + gBackSpaceCounter++; + if (gBackSpaceCounter == 3) { + inputmethod_cleanup(); + } + }, function(e) { + ok(false, 'sendKey failed:' + e.name); + inputmethod_cleanup(); + }); +} +</script> +</pre> +</body> +</html> + diff --git a/dom/inputmethod/mochitest/test_bug978918.html b/dom/inputmethod/mochitest/test_bug978918.html new file mode 100644 index 0000000000..56e02a57d5 --- /dev/null +++ b/dom/inputmethod/mochitest/test_bug978918.html @@ -0,0 +1,77 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=978918 +--> +<head> + <title>Basic test for InputMethod API.</title> + <script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript;version=1.7" src="inputmethod_common.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=978918">Mozilla Bug 978918</a> +<p id="display"></p> +<pre id="test"> +<script class="testbody" type="application/javascript;version=1.7"> + +// The input context. +var gContext = null; + +inputmethod_setup(function() { + runTest(); +}); + +function runTest() { + let im = navigator.mozInputMethod; + + im.oninputcontextchange = function() { + ok(true, 'inputcontextchange event was fired.'); + im.oninputcontextchange = null; + + gContext = im.inputcontext; + if (!gContext) { + ok(false, 'Should have a non-null inputcontext.'); + inputmethod_cleanup(); + return; + } + + test_setSelectionRange(); + }; + + // Set current page as an input method. + SpecialPowers.wrap(im).setActive(true); + + let iframe = document.createElement('iframe'); + iframe.src = 'file_test_sms_app.html'; + iframe.setAttribute('mozbrowser', true); + document.body.appendChild(iframe); +} + +function test_setSelectionRange() { + gContext.setSelectionRange(0, 100).then(function() { + is(gContext.selectionStart, 0, 'selectionStart was set successfully.'); + is(gContext.selectionEnd, 5, 'selectionEnd was set successfully.'); + test_replaceSurroundingText(); + }, function(e) { + ok(false, 'setSelectionRange failed:' + e.name); + inputmethod_cleanup(); + }); +} + +function test_replaceSurroundingText() { + // Replace 'Httvb' with 'Hito'. + gContext.replaceSurroundingText('Hito', 0, 100).then(function() { + ok(true, 'replaceSurroundingText finished'); + inputmethod_cleanup(); + }, function(e) { + ok(false, 'replaceSurroundingText failed: ' + e.name); + inputmethod_cleanup(); + }); +} + +</script> +</pre> +</body> +</html> + diff --git a/dom/inputmethod/mochitest/test_focus_blur_manage_events.html b/dom/inputmethod/mochitest/test_focus_blur_manage_events.html new file mode 100644 index 0000000000..939639050d --- /dev/null +++ b/dom/inputmethod/mochitest/test_focus_blur_manage_events.html @@ -0,0 +1,199 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1201407 +--> +<head> + <title>Test inputcontextfocus and inputcontextblur event</title> + <script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript;version=1.7" src="inputmethod_common.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1201407">Mozilla Bug 1201407</a> +<p id="display"></p> +<pre id="test"> +<script class="testbody" type="application/javascript;version=1.7"> + +let contentFrameMM; + +function setupTestRunner() { + info('setupTestRunner'); + let im = navigator.mozInputMethod; + + let expectedEventDetails = [ + { type: 'input', inputType: 'text' }, + { type: 'input', inputType: 'search' }, + { type: 'textarea', inputType: 'textarea' }, + { type: 'contenteditable', inputType: 'textarea' }, + { type: 'input', inputType: 'number' }, + { type: 'input', inputType: 'tel' }, + { type: 'input', inputType: 'url' }, + { type: 'input', inputType: 'email' }, + { type: 'input', inputType: 'password' }, + { type: 'input', inputType: 'datetime' }, + { type: 'input', inputType: 'date', + value: '2015-08-03', min: '1990-01-01', max: '2020-01-01' }, + { type: 'input', inputType: 'month' }, + { type: 'input', inputType: 'week' }, + { type: 'input', inputType: 'time' }, + { type: 'input', inputType: 'datetime-local' }, + { type: 'input', inputType: 'color' }, + { type: 'select', inputType: 'select-one', + choices: { + multiple: false, + choices: [ + { group: false, inGroup: false, text: 'foo', + disabled: false, selected: true, optionIndex: 0 }, + { group: false, inGroup: false, text: 'bar', + disabled: true, selected: false, optionIndex: 1 }, + { group: true, text: 'group', disabled: false }, + { group: false, inGroup: true, text: 'baz', + disabled: false, selected: false, optionIndex: 2 } ] } + }, + { type: 'select', inputType: 'select-multiple', + choices: { + multiple: true, + choices: [ + { group: false, inGroup: false, text: 'foo', + disabled: false, selected: true, optionIndex: 0 }, + { group: false, inGroup: false, text: 'bar', + disabled: true, selected: false, optionIndex: 1 }, + { group: true, text: 'group', disabled: false }, + { group: false, inGroup: true, text: 'baz', + disabled: false, selected: false, optionIndex: 2 } ] } + } + ]; + + let expectBlur = false; + + function deepAssertObject(obj, expectedObj, desc) { + for (let prop in expectedObj) { + if (typeof expectedObj[prop] === 'object') { + deepAssertObject(obj[prop], expectedObj[prop], desc + '.' + prop); + } else { + is(obj[prop], expectedObj[prop], desc + '.' + prop); + } + } + } + + im.mgmt.oninputcontextfocus = + im.mgmt.oninputcontextblur = function(evt) { + if (expectBlur) { + is(evt.type, 'inputcontextblur', 'evt.type'); + evt.preventDefault(); + expectBlur = false; + + return; + } + + let expectedEventDetail = expectedEventDetails.shift(); + + if (!expectedEventDetail) { + ok(false, 'Receving extra events'); + inputmethod_cleanup(); + + return; + } + + is(evt.type, 'inputcontextfocus', 'evt.type'); + evt.preventDefault(); + expectBlur = true; + + let detail = evt.detail; + deepAssertObject(detail, expectedEventDetail, 'detail'); + + if (expectedEventDetails.length) { + contentFrameMM.sendAsyncMessage('test:next'); + } else { + im.mgmt.oninputcontextfocus = im.mgmt.oninputcontextblur = null; + inputmethod_cleanup(); + } + }; +} + +function setupInputAppFrame() { + info('setupInputAppFrame'); + return new Promise((resolve, reject) => { + let appFrameScript = function appFrameScript() { + let im = content.navigator.mozInputMethod; + + im.mgmt.oninputcontextfocus = + im.mgmt.oninputcontextblur = function(evt) { + sendAsyncMessage('text:appEvent', { type: evt.type }); + }; + + content.document.body.textContent = 'I am a input app'; + }; + + let path = location.pathname; + let basePath = location.protocol + '//' + location.host + + path.substring(0, path.lastIndexOf('/')); + let imeUrl = basePath + '/file_blank.html'; + + let inputAppFrame = document.createElement('iframe'); + inputAppFrame.setAttribute('mozbrowser', true); + inputAppFrame.src = imeUrl; + document.body.appendChild(inputAppFrame); + + let mm = SpecialPowers.getBrowserFrameMessageManager(inputAppFrame); + inputAppFrame.addEventListener('mozbrowserloadend', function() { + mm.addMessageListener('text:appEvent', function(msg) { + ok(false, 'Input app should not receive ' + msg.data.type + ' event.'); + }); + mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false); + + // Set the input app frame to be active + let req = inputAppFrame.setInputMethodActive(true); + resolve(req); + }); + }); +} + +function setupContentFrame() { + info('setupContentFrame'); + return new Promise((resolve, reject) => { + let contentFrameScript = function contentFrameScript() { + let input = content.document.body.firstElementChild; + + let i = 0; + + input.focus(); + + addMessageListener('test:next', function() { + content.document.body.children[++i].focus(); + }); + }; + + let iframe = document.createElement('iframe'); + iframe.src = 'file_test_focus_blur_manage_events.html'; + iframe.setAttribute('mozbrowser', true); + document.body.appendChild(iframe); + + let mm = contentFrameMM = + SpecialPowers.getBrowserFrameMessageManager(iframe); + + iframe.addEventListener('mozbrowserloadend', function() { + mm.loadFrameScript('data:,(' + encodeURIComponent(contentFrameScript.toString()) + ')();', false); + + resolve(); + }); + }); +} + +inputmethod_setup(function() { + Promise.resolve() + .then(() => setupTestRunner()) + .then(() => setupContentFrame()) + .then(() => setupInputAppFrame()) + .catch((e) => { + ok(false, 'Error' + e.toString()); + console.error(e); + }); +}); + +</script> +</pre> +</body> +</html> + diff --git a/dom/inputmethod/mochitest/test_forward_hardware_key_to_ime.html b/dom/inputmethod/mochitest/test_forward_hardware_key_to_ime.html new file mode 100644 index 0000000000..13bd58ce9a --- /dev/null +++ b/dom/inputmethod/mochitest/test_forward_hardware_key_to_ime.html @@ -0,0 +1,149 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1110030 +--> +<head> + <title>Forwarding Hardware Key to InputMethod</title> + <script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript;version=1.7" src="inputmethod_common.js"></script> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/NativeKeyCodes.js"></script> + <script type="text/javascript" src="bug1110030_helper.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1110030">Mozilla Bug 1110030</a> +<p id="display"></p> +<pre id="test"> +<script class="testbody" type="application/javascript;version=1.7"> +// The input context. +var gContext = null; + +// The test cases. +var gTests; + +inputmethod_setup(function() { + setInputContext(); +}); + +function setInputContext() { + let im = navigator.mozInputMethod; + + im.oninputcontextchange = function() { + ok(true, 'inputcontextchange event was fired.'); + im.oninputcontextchange = null; + + gContext = im.inputcontext; + if (!gContext || !gContext.hardwareinput) { + ok(false, 'Should have a non-null inputcontext.hardwareinput'); + inputmethod_cleanup(); + return; + } + + prepareTest(); + }; + + // Set current page as an input method. + SpecialPowers.wrap(im).setActive(true); + + // verifyResultsAndMoveNext will be called after input#text-input + // receives all expected key events and it will verify results + // and start next test. + loadTestFrame(verifyResultsAndMoveNext); +} + +function prepareTest() +{ + // Set the used input method of this test + gInputMethod = new InputMethod(gContext); + + // Add listenr to hardwareinput + addKeyEventListeners(gContext.hardwareinput, function (evt) { + hardwareEventReceiver(evt); + gInputMethod.handler(evt); + }); + + // Set the test cases + gTests = [ + // Case 1: IME handle the key input + { + key: 'z', + hardwareinput: { + expectedEvents: kKeyDown | kKeyUp, + receivedEvents: 0, + expectedKeys: 'zz', // One for keydown, the other for keyup + receivedKeys: '', + }, + inputtext: { + expectedEvents: kKeyDown | kKeyPress | kKeyUp, + receivedEvents: 0, + expectedKeys: gInputMethod.mapKey('z') + // for keydown + gInputMethod.mapKey('z') + // for keypress + gInputMethod.mapKey('z'), // for keyup + receivedKeys: '', + } + }, + // case 2: IME doesn't handle the key input + { + key: '7', + hardwareinput: { + expectedEvents: kKeyDown | kKeyUp, + receivedEvents: 0, + expectedKeys: '77', // One for keydown, the other for keyup + receivedKeys: '', + }, + inputtext: { + expectedEvents: kKeyDown | kKeyPress | kKeyUp, + receivedEvents: 0, + expectedKeys: '777', // keydown, keypress, keyup all will receive key + receivedKeys: '', + } + }, + // case 3: IME is disable + // This case is same as + // dom/events/test/test_dom_before_after_keyboard_event*.html + ]; + + startTesting(); +} + +function startTesting() +{ + if (gTests.length <= 0) { + finish(); + return; + } + + gCurrentTest = gTests.shift(); + + fireEvent(); +} + +function verifyResultsAndMoveNext() +{ + verifyResults(gCurrentTest); + startTesting(); +} + +function finish() +{ + inputmethod_cleanup(); +} + +function errorHandler(msg) +{ + // Clear the test cases + if (gTests) { + gTests = []; + } + + ok(false, msg); + + inputmethod_cleanup(); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/inputmethod/mochitest/test_input_registry_events.html b/dom/inputmethod/mochitest/test_input_registry_events.html new file mode 100644 index 0000000000..e2c3c1f9d7 --- /dev/null +++ b/dom/inputmethod/mochitest/test_input_registry_events.html @@ -0,0 +1,251 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1201407 +--> +<head> + <title>Test addinputrequest and removeinputrequest event</title> + <script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript;version=1.7" src="inputmethod_common.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1201407">Mozilla Bug 1201407</a> +<p id="display"></p> +<pre id="test"> +<script class="testbody" type="application/javascript;version=1.7"> + +let appFrameMM; +let nextStep; + +function setupInputAppFrame() { + info('setupInputAppFrame'); + return new Promise((resolve, reject) => { + let appFrameScript = function appFrameScript() { + let im = content.navigator.mozInputMethod; + + addMessageListener('test:callAddInput', function() { + im.addInput('foo', { + launch_path: 'bar.html', + name: 'Foo', + description: 'foobar', + types: ['text', 'password'] + }) + .then((r) => { + sendAsyncMessage('test:resolved', { resolved: true, result: r }); + }, (e) => { + sendAsyncMessage('test:rejected', { rejected: true, error: e }); + }); + }); + + addMessageListener('test:callRemoveInput', function() { + im.removeInput('foo') + .then((r) => { + sendAsyncMessage('test:resolved', { resolved: true, result: r }); + }, (e) => { + sendAsyncMessage('test:rejected', { rejected: true, error: e }); + }); + }); + + im.mgmt.onaddinputrequest = + im.mgmt.onremoveinputrequest = function(evt) { + sendAsyncMessage('test:appEvent', { type: evt.type }); + }; + + content.document.body.textContent = 'I am a input app'; + }; + + let path = location.pathname; + let basePath = location.protocol + '//' + location.host + + path.substring(0, path.lastIndexOf('/')); + let imeUrl = basePath + '/file_blank.html'; + + let inputAppFrame = document.createElement('iframe'); + inputAppFrame.setAttribute('mozbrowser', true); + // FIXME: Bug 1270790 + inputAppFrame.setAttribute('remote', true); + inputAppFrame.src = imeUrl; + document.body.appendChild(inputAppFrame); + + let mm = appFrameMM = + SpecialPowers.getBrowserFrameMessageManager(inputAppFrame); + + inputAppFrame.addEventListener('mozbrowserloadend', function() { + mm.addMessageListener('test:appEvent', function(msg) { + ok(false, 'Input app should not receive ' + msg.data.type + ' event.'); + }); + mm.addMessageListener('test:resolved', function(msg) { + nextStep && nextStep(msg.data); + }); + mm.addMessageListener('test:rejected', function(msg) { + nextStep && nextStep(msg.data); + }); + mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false); + + resolve(); + }); + }); +} + +function Deferred() { + this.promise = new Promise((res, rej) => { + this.resolve = res; + this.reject = rej; + }); + return this; +} + +function deepAssertObject(obj, expectedObj, desc) { + for (let prop in expectedObj) { + if (typeof expectedObj[prop] === 'object') { + deepAssertObject(obj[prop], expectedObj[prop], desc + '.' + prop); + } else { + is(obj[prop], expectedObj[prop], desc + '.' + prop); + } + } +} + +function setupTestRunner() { + let im = navigator.mozInputMethod; + let d; + + let i = -1; + nextStep = function next(evt) { + i++; + info('Step ' + i); + + switch (i) { + case 0: + appFrameMM.sendAsyncMessage('test:callAddInput'); + + break; + + case 1: + is(evt.type, 'addinputrequest', 'evt.type'); + deepAssertObject(evt.detail, { + inputId: 'foo', + manifestURL: null, // todo + inputManifest: { + launch_path: 'bar.html', + name: 'Foo', + description: 'foobar', + types: ['text', 'password'] + } + }, 'detail'); + + d = new Deferred(); + evt.detail.waitUntil(d.promise); + evt.preventDefault(); + + Promise.resolve().then(next); + break; + + case 2: + d.resolve(); + d = null; + break; + + case 3: + ok(evt.resolved, 'resolved'); + appFrameMM.sendAsyncMessage('test:callAddInput'); + + break; + + case 4: + is(evt.type, 'addinputrequest', 'evt.type'); + + d = new Deferred(); + evt.detail.waitUntil(d.promise); + evt.preventDefault(); + + Promise.resolve().then(next); + break; + + case 5: + d.reject('Foo Error'); + d = null; + break; + + case 6: + ok(evt.rejected, 'rejected'); + is(evt.error, 'Foo Error', 'rejected'); + + + appFrameMM.sendAsyncMessage('test:callRemoveInput'); + + break; + + case 7: + is(evt.type, 'removeinputrequest', 'evt.type'); + deepAssertObject(evt.detail, { + inputId: 'foo', + manifestURL: null // todo + }, 'detail'); + + d = new Deferred(); + evt.detail.waitUntil(d.promise); + evt.preventDefault(); + + Promise.resolve().then(next); + break; + + case 8: + d.resolve(); + d = null; + break; + + case 9: + ok(evt.resolved, 'resolved'); + appFrameMM.sendAsyncMessage('test:callRemoveInput'); + + break; + + case 10: + is(evt.type, 'removeinputrequest', 'evt.type'); + + d = new Deferred(); + evt.detail.waitUntil(d.promise); + evt.preventDefault(); + + Promise.resolve().then(next); + break; + + case 11: + d.reject('Foo Error'); + d = null; + break; + + case 12: + ok(evt.rejected, 'rejected'); + is(evt.error, 'Foo Error', 'rejected'); + inputmethod_cleanup(); + + break; + + default: + ok(false, 'received extra call.'); + inputmethod_cleanup(); + + break; + } + } + + im.mgmt.onaddinputrequest = + im.mgmt.onremoveinputrequest = nextStep; +} + +inputmethod_setup(function() { + Promise.resolve() + .then(() => setupTestRunner()) + .then(() => setupInputAppFrame()) + .then(() => nextStep()) + .catch((e) => { + ok(false, 'Error' + e.toString()); + console.error(e); + }); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/inputmethod/mochitest/test_sendkey_cancel.html b/dom/inputmethod/mochitest/test_sendkey_cancel.html new file mode 100644 index 0000000000..8affa8c097 --- /dev/null +++ b/dom/inputmethod/mochitest/test_sendkey_cancel.html @@ -0,0 +1,67 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=952080 +--> +<head> + <title>SendKey with canceled keydown test for InputMethod API.</title> + <script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript;version=1.7" src="inputmethod_common.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=952080">Mozilla Bug 952080</a> +<p id="display"></p> +<pre id="test"> +<script class="testbody" type="application/javascript;version=1.7"> + +// The input context. +var gContext = null; + +inputmethod_setup(function() { + runTest(); +}); + +function runTest() { + let im = navigator.mozInputMethod; + + im.oninputcontextchange = function() { + ok(true, 'inputcontextchange event was fired.'); + im.oninputcontextchange = null; + + gContext = im.inputcontext; + if (!gContext) { + ok(false, 'Should have a non-null inputcontext.'); + inputmethod_cleanup(); + return; + } + + test(); + }; + + // Set current page as an input method. + SpecialPowers.wrap(im).setActive(true); + + let iframe = document.createElement('iframe'); + iframe.src = 'file_test_sendkey_cancel.html'; + iframe.setAttribute('mozbrowser', true); + document.body.appendChild(iframe); +} + +function test() { + gContext.sendKey(0, 'j', 0).then(function() { + ok(false, 'sendKey was incorrectly resolved'); + + inputmethod_cleanup(); + }, function(e) { + ok(true, 'sendKey was rejected'); + + inputmethod_cleanup(); + }); +} + +</script> +</pre> +</body> +</html> + diff --git a/dom/inputmethod/mochitest/test_setSupportsSwitching.html b/dom/inputmethod/mochitest/test_setSupportsSwitching.html new file mode 100644 index 0000000000..2a7540cdea --- /dev/null +++ b/dom/inputmethod/mochitest/test_setSupportsSwitching.html @@ -0,0 +1,130 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1197682 +--> +<head> + <title>Test inputcontext#inputType and MozInputMethodManager#supportsSwitching()</title> + <script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript;version=1.7" src="inputmethod_common.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1197682">Mozilla Bug 1197682</a> +<p id="display"></p> +<pre id="test"> +<script class="testbody" type="application/javascript;version=1.7"> + +inputmethod_setup(function() { + runTest(); +}); + +let appFrameScript = function appFrameScript() { + let input = content.document.body.firstElementChild; + + let i = 1; + + input.focus(); + + addMessageListener('test:next', function() { + i++; + switch (i) { + case 2: + content.document.body.children[1].focus(); + i++; // keep the same count with the parent frame. + + break; + + case 4: + content.document.body.lastElementChild.focus(); + i++; // keep the same count with the parent frame. + + break; + + case 6: + content.document.body.lastElementChild.blur(); + + break; + } + }); +}; + +function runTest() { + let im = navigator.mozInputMethod; + + let i = 0; + im.oninputcontextchange = function(evt) { + var inputcontext = navigator.mozInputMethod.inputcontext; + + i++; + switch (i) { + case 1: + ok(!!inputcontext, '1) Receving the input context'); + is(inputcontext.inputType, 'text', '1) input type'); + is(im.mgmt.supportsSwitching(), true, '1) supports switching'); + + mm.sendAsyncMessage('test:next'); + break; + + case 2: + is(inputcontext, null, '2) Receving null inputcontext'); + + break; + + case 3: + ok(!!inputcontext, '3) Receving the input context'); + is(inputcontext.inputType, 'number', '3) input type'); + is(im.mgmt.supportsSwitching(), false, '3) supports switching'); + + mm.sendAsyncMessage('test:next'); + break; + + case 4: + is(inputcontext, null, '4) Receving null inputcontext'); + + break; + + case 5: + ok(!!inputcontext, '5) Receving the input context'); + is(inputcontext.inputType, 'password', '5) input type'); + is(im.mgmt.supportsSwitching(), true, '5) supports switching'); + + mm.sendAsyncMessage('test:next'); + break; + + case 6: + is(inputcontext, null, '6) Receving null inputcontext'); + is(im.mgmt.supportsSwitching(), false, '6) supports switching'); + + inputmethod_cleanup(); + break; + + default: + ok(false, 'Receving extra inputcontextchange calls'); + inputmethod_cleanup(); + + break; + } + }; + + // Set current page as an input method. + SpecialPowers.wrap(im).setActive(true); + // Set text and password inputs as supports switching (and not supported for number type) + im.mgmt.setSupportsSwitchingTypes(['text', 'password']); + + let iframe = document.createElement('iframe'); + iframe.src = 'file_test_setSupportsSwitching.html'; + iframe.setAttribute('mozbrowser', true); + document.body.appendChild(iframe); + + let mm = SpecialPowers.getBrowserFrameMessageManager(iframe); + + iframe.addEventListener('mozbrowserloadend', function() { + mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false); + }); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/inputmethod/mochitest/test_simple_manage_events.html b/dom/inputmethod/mochitest/test_simple_manage_events.html new file mode 100644 index 0000000000..bbcac498d3 --- /dev/null +++ b/dom/inputmethod/mochitest/test_simple_manage_events.html @@ -0,0 +1,154 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1201407 +--> +<head> + <title>Test simple manage notification events on MozInputMethodManager</title> + <script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript;version=1.7" src="inputmethod_common.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1201407">Mozilla Bug 1201407</a> +<p id="display"></p> +<pre id="test"> +<script class="testbody" type="application/javascript;version=1.7"> + +let appFrameMM; +let nextStep; + +function setupTestRunner() { + info('setupTestRunner'); + let im = navigator.mozInputMethod; + + let i = 0; + im.mgmt.onshowallrequest = + im.mgmt.onnextrequest = nextStep = function(evt) { + i++; + switch (i) { + case 1: + is(evt.type, 'inputcontextchange', '1) inputcontextchange event'); + appFrameMM.sendAsyncMessage('test:callShowAll'); + + break; + + case 2: + is(evt.type, 'showallrequest', '2) showallrequest event'); + ok(evt.target, im.mgmt, '2) evt.target'); + evt.preventDefault(); + + appFrameMM.sendAsyncMessage('test:callNext'); + + break; + + case 3: + is(evt.type, 'nextrequest', '3) nextrequest event'); + ok(evt.target, im.mgmt, '3) evt.target'); + evt.preventDefault(); + + im.mgmt.onshowallrequest = + im.mgmt.onnextrequest = nextStep = null; + + inputmethod_cleanup(); + break; + + default: + ok(false, 'Receving extra events'); + inputmethod_cleanup(); + + break; + } + }; +} + +function setupInputAppFrame() { + info('setupInputAppFrame'); + return new Promise((resolve, reject) => { + let appFrameScript = function appFrameScript() { + let im = content.navigator.mozInputMethod; + + addMessageListener('test:callShowAll', function() { + im.mgmt.showAll(); + }); + + addMessageListener('test:callNext', function() { + im.mgmt.next(); + }); + + im.mgmt.onshowallrequest = + im.mgmt.onnextrequest = function(evt) { + sendAsyncMessage('test:appEvent', { type: evt.type }); + }; + + im.oninputcontextchange = function(evt) { + sendAsyncMessage('test:inputcontextchange', {}); + }; + + content.document.body.textContent = 'I am a input app'; + }; + + let path = location.pathname; + let basePath = location.protocol + '//' + location.host + + path.substring(0, path.lastIndexOf('/')); + let imeUrl = basePath + '/file_blank.html'; + + let inputAppFrame = document.createElement('iframe'); + inputAppFrame.setAttribute('mozbrowser', true); + inputAppFrame.src = imeUrl; + document.body.appendChild(inputAppFrame); + + let mm = appFrameMM = + SpecialPowers.getBrowserFrameMessageManager(inputAppFrame); + + inputAppFrame.addEventListener('mozbrowserloadend', function() { + mm.addMessageListener('test:appEvent', function(msg) { + ok(false, 'Input app should not receive ' + msg.data.type + ' event.'); + }); + mm.addMessageListener('test:inputcontextchange', function(msg) { + nextStep && nextStep({ type: 'inputcontextchange' }); + }); + mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false); + + // Set the input app frame to be active + let req = inputAppFrame.setInputMethodActive(true); + resolve(req); + }); + }); +} + +function setupContentFrame() { + let contentFrameScript = function contentFrameScript() { + let input = content.document.body.firstElementChild; + + input.focus(); + }; + + let iframe = document.createElement('iframe'); + iframe.src = 'file_test_simple_manage_events.html'; + iframe.setAttribute('mozbrowser', true); + document.body.appendChild(iframe); + + let mm = SpecialPowers.getBrowserFrameMessageManager(iframe); + + iframe.addEventListener('mozbrowserloadend', function() { + mm.loadFrameScript('data:,(' + encodeURIComponent(contentFrameScript.toString()) + ')();', false); + }); +} + +inputmethod_setup(function() { + Promise.resolve() + .then(() => setupTestRunner()) + .then(() => setupContentFrame()) + .then(() => setupInputAppFrame()) + .catch((e) => { + ok(false, 'Error' + e.toString()); + console.error(e); + }); +}); + +</script> +</pre> +</body> +</html> + diff --git a/dom/inputmethod/mochitest/test_sync_edit.html b/dom/inputmethod/mochitest/test_sync_edit.html new file mode 100644 index 0000000000..a32ea23fae --- /dev/null +++ b/dom/inputmethod/mochitest/test_sync_edit.html @@ -0,0 +1,81 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1079455 +--> +<head> + <title>Sync edit of an input</title> + <script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript;version=1.7" src="inputmethod_common.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1079455">Mozilla Bug 1079455</a> +<p id="display"></p> +<pre id="test"> +<script class="testbody" type="application/javascript;version=1.7"> + +inputmethod_setup(function() { + runTest(); +}); + +let appFrameScript = function appFrameScript() { + let input = content.document.body.firstElementChild; + + input.focus(); + input.value = 'First1'; + input.blur(); +}; + +function runTest() { + let im = navigator.mozInputMethod; + + let i = 0; + im.oninputcontextchange = function() { + let inputcontext = im.inputcontext; + i++; + switch (i) { + case 1: + ok(!!inputcontext, 'Should receive inputcontext from focus().'); + is(inputcontext.textAfterCursor, 'First'); + + break; + + case 2: + ok(!!inputcontext, 'Should receive inputcontext from value change.'); + is(inputcontext.textBeforeCursor, 'First1'); + + break; + + case 3: + ok(!inputcontext, 'Should lost inputcontext from blur().'); + + inputmethod_cleanup(); + break; + + default: + ok(false, 'Unknown event count.'); + + inputmethod_cleanup(); + } + }; + + // Set current page as an input method. + SpecialPowers.wrap(im).setActive(true); + + let iframe = document.createElement('iframe'); + iframe.src = 'file_test_sync_edit.html'; + iframe.setAttribute('mozbrowser', true); + document.body.appendChild(iframe); + + let mm = SpecialPowers.getBrowserFrameMessageManager(iframe); + iframe.addEventListener('mozbrowserloadend', function() { + mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false); + }); +} + +</script> +</pre> +</body> +</html> + diff --git a/dom/inputmethod/mochitest/test_two_inputs.html b/dom/inputmethod/mochitest/test_two_inputs.html new file mode 100644 index 0000000000..e0f007cbcc --- /dev/null +++ b/dom/inputmethod/mochitest/test_two_inputs.html @@ -0,0 +1,184 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1057898 +https://bugzilla.mozilla.org/show_bug.cgi?id=952741 +--> +<head> + <title>Test switching between two inputs</title> + <script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript;version=1.7" src="inputmethod_common.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1057898">Mozilla Bug 1057898</a> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=952741">Mozilla Bug 952741</a> +<p id="display"></p> +<pre id="test"> +<script class="testbody" type="application/javascript;version=1.7"> + +inputmethod_setup(function() { + runTest(); +}); + +let appFrameScript = function appFrameScript() { + let input1 = content.document.body.firstElementChild; + let input2 = content.document.body.children[1]; + + let i = 1; + + input1.focus(); + + addMessageListener('test:next', function() { + i++; + switch (i) { + case 2: + input2.focus(); + i++; // keep the same count with the parent frame. + + break; + + case 4: + input2.blur(); + + break; + + case 5: + input2.focus(); + + break; + + case 6: + input1.focus(); + i++; // keep the same count with the parent frame. + + break; + + case 8: + content.document.body.removeChild(input1); + + break; + + case 9: + input2.focus(); + + break; + + case 10: + content.document.body.removeChild(input2); + + break; + } + }); +}; + +function runTest() { + let im = navigator.mozInputMethod; + + let i = 0; + im.oninputcontextchange = function(evt) { + var inputcontext = navigator.mozInputMethod.inputcontext; + + i++; + switch (i) { + // focus on the first input receives the first input context. + case 1: + ok(!!inputcontext, '1) Receving the first input context'); + is(inputcontext.textAfterCursor, 'First'); + + mm.sendAsyncMessage('test:next'); + break; + + // focus on the second input should implicitly blur the first input + case 2: + is(inputcontext, null, '2) Receving null inputcontext'); + + break; + + // ... and results the second input context. + case 3: + ok(!!inputcontext, '3) Receving the second input context'); + is(inputcontext.textAfterCursor, 'Second'); + + mm.sendAsyncMessage('test:next'); + break; + + // blur on the second input results null input context + case 4: + is(inputcontext, null, '4) Receving null inputcontext'); + + mm.sendAsyncMessage('test:next'); + break; + + // focus on the second input receives the second input context. + case 5: + ok(!!inputcontext, '5) Receving the second input context'); + is(inputcontext.textAfterCursor, 'Second'); + + mm.sendAsyncMessage('test:next'); + break; + + // focus on the second input should implicitly blur the first input + case 6: + is(inputcontext, null, '6) Receving null inputcontext'); + + break; + + // ... and results the second input context. + case 7: + ok(!!inputcontext, '7) Receving the first input context'); + is(inputcontext.textAfterCursor, 'First'); + + mm.sendAsyncMessage('test:next'); + break; + + // remove on the first focused input results null input context + case 8: + is(inputcontext, null, '8) Receving null inputcontext'); + + mm.sendAsyncMessage('test:next'); + break; + + // input context for the second input. + case 9: + ok(!!inputcontext, '9) Receving the second input context'); + is(inputcontext.textAfterCursor, 'Second'); + + mm.sendAsyncMessage('test:next'); + break; + + // remove on the second focused input results null input context + case 10: + is(inputcontext, null, '10) Receving null inputcontext'); + + inputmethod_cleanup(); + break; + + default: + ok(false, 'Receving extra inputcontextchange calls'); + inputmethod_cleanup(); + + break; + } + }; + + // Set current page as an input method. + SpecialPowers.wrap(im).setActive(true); + + let iframe = document.createElement('iframe'); + iframe.src = 'file_test_two_inputs.html'; + iframe.setAttribute('mozbrowser', true); + document.body.appendChild(iframe); + + let mm = SpecialPowers.getBrowserFrameMessageManager(iframe); + + iframe.addEventListener('mozbrowserloadend', function() { + mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false); + }); +} + +</script> +</pre> +</body> +</html> + diff --git a/dom/inputmethod/mochitest/test_two_selects.html b/dom/inputmethod/mochitest/test_two_selects.html new file mode 100644 index 0000000000..9869b8c14c --- /dev/null +++ b/dom/inputmethod/mochitest/test_two_selects.html @@ -0,0 +1,182 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1079728 +--> +<head> + <title>Test switching between two inputs</title> + <script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript;version=1.7" src="inputmethod_common.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1079728">Mozilla Bug 1079728</a> +<p id="display"></p> +<pre id="test"> +<script class="testbody" type="application/javascript;version=1.7"> + +inputmethod_setup(function() { + runTest(); +}); + +let appFrameScript = function appFrameScript() { + let select1 = content.document.body.firstElementChild; + let select2 = content.document.body.children[1]; + + let i = 1; + + select1.focus(); + + addMessageListener('test:next', function() { + i++; + switch (i) { + case 2: + select2.focus(); + i++; // keep the same count with the parent frame. + + break; + + case 4: + select2.blur(); + + break; + + case 5: + select2.focus(); + + break; + + case 6: + select1.focus(); + i++; // keep the same count with the parent frame. + + break; + + case 8: + content.document.body.removeChild(select1); + + break; + + case 9: + select2.focus(); + + break; + + case 10: + content.document.body.removeChild(select2); + + break; + } + }); +}; + +function runTest() { + let im = navigator.mozInputMethod; + + let i = 0; + im.oninputcontextchange = function(evt) { + var inputcontext = navigator.mozInputMethod.inputcontext; + + i++; + switch (i) { + // focus on the first input receives the first input context. + case 1: + ok(!!inputcontext, '1) Receving the first input context'); + is(inputcontext.textAfterCursor, 'First'); + + mm.sendAsyncMessage('test:next'); + break; + + // focus on the second input should implicitly blur the first input + case 2: + is(inputcontext, null, '2) Receving null inputcontext'); + + break; + + // ... and results the second input context. + case 3: + ok(!!inputcontext, '3) Receving the second input context'); + is(inputcontext.textAfterCursor, 'Second'); + + mm.sendAsyncMessage('test:next'); + break; + + // blur on the second input results null input context + case 4: + is(inputcontext, null, '4) Receving null inputcontext'); + + mm.sendAsyncMessage('test:next'); + break; + + // focus on the second input receives the second input context. + case 5: + ok(!!inputcontext, '5) Receving the second input context'); + is(inputcontext.textAfterCursor, 'Second'); + + mm.sendAsyncMessage('test:next'); + break; + + // focus on the second input should implicitly blur the first input + case 6: + is(inputcontext, null, '6) Receving null inputcontext'); + + break; + + // ... and results the second input context. + case 7: + ok(!!inputcontext, '7) Receving the first input context'); + is(inputcontext.textAfterCursor, 'First'); + + mm.sendAsyncMessage('test:next'); + break; + + // remove on the first focused input results null input context + case 8: + is(inputcontext, null, '8) Receving null inputcontext'); + + mm.sendAsyncMessage('test:next'); + break; + + // input context for the second input. + case 9: + ok(!!inputcontext, '9) Receving the second input context'); + is(inputcontext.textAfterCursor, 'Second'); + + mm.sendAsyncMessage('test:next'); + break; + + // remove on the second focused input results null input context + case 10: + is(inputcontext, null, '10) Receving null inputcontext'); + + inputmethod_cleanup(); + break; + + default: + ok(false, 'Receving extra inputcontextchange calls'); + inputmethod_cleanup(); + + break; + } + }; + + // Set current page as an input method. + SpecialPowers.wrap(im).setActive(true); + + let iframe = document.createElement('iframe'); + iframe.src = 'file_test_two_selects.html'; + iframe.setAttribute('mozbrowser', true); + document.body.appendChild(iframe); + + let mm = SpecialPowers.getBrowserFrameMessageManager(iframe); + + iframe.addEventListener('mozbrowserloadend', function() { + mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false); + }); +} + +</script> +</pre> +</body> +</html> + diff --git a/dom/inputmethod/mochitest/test_unload.html b/dom/inputmethod/mochitest/test_unload.html new file mode 100644 index 0000000000..bef9d1485b --- /dev/null +++ b/dom/inputmethod/mochitest/test_unload.html @@ -0,0 +1,167 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1122463 +https://bugzilla.mozilla.org/show_bug.cgi?id=820057 +--> +<head> + <title>Test focus when page unloads</title> + <script type="application/javascript;version=1.7" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript;version=1.7" src="inputmethod_common.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1122463">Mozilla Bug 1122463</a> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=820057">Mozilla Bug 820057</a> +<p id="display"></p> +<pre id="test"> +<script class="testbody" type="application/javascript;version=1.7"> + +inputmethod_setup(function() { + runTest(); +}); + +let appFrameScript = function appFrameScript() { + let form1 = content.document.body.firstElementChild; + let input1 = form1.firstElementChild; + let submit1 = form1.lastElementChild; + let input2; + + let cancelSubmit = function(evt) { + evt.preventDefault(); + }; + + // Content of the second page. + form1.action = 'file_test_unload_action.html'; + + let i = 1; + + input1.focus(); + + addMessageListener('test:next', function() { + i++; + switch (i) { + case 2: + // Click the submit button, trigger the submit event and make our + // installed event listener preventing the submission. + form1.addEventListener('submit', cancelSubmit); + submit1.click(); + + sendAsyncMessage('test:step'); + + break; + + case 3: + // Actually submit the form. + form1.removeEventListener('submit', cancelSubmit); + submit1.click(); + + break; + + case 4: + if (!content.document.body) { + content.onload = function() { + content.onload = null; + + let input2 = content.document.body.firstElementChild; + input2.focus(); + }; + + return; + } + + input2 = content.document.body.firstElementChild; + input2.focus(); + + break; + + case 5: + content.location.href = 'data:text/html,Hello!'; + + break; + } + }); +}; + +function runTest() { + let im = navigator.mozInputMethod; + + let i = 0; + function nextStep() { + let inputcontext = navigator.mozInputMethod.inputcontext; + + i++; + switch (i) { + // focus on the first input receives the first input context. + case 1: + ok(!!inputcontext, '1) Receving the first input context'); + is(inputcontext.textAfterCursor, 'First'); + + mm.sendAsyncMessage('test:next'); + break; + + // Cancelled submission should not cause us lost focus. + case 2: + ok(!!inputcontext, '2) Receving the first input context'); + is(inputcontext.textAfterCursor, 'First'); + + mm.sendAsyncMessage('test:next'); + break; + + // Real submit and page transition should cause us lost focus. + // XXX: Unless we could delay the page transition, we does not know if + // the inputcontext is lost because of the submit or the pagehide/beforeload + // event. + case 3: + is(inputcontext, null, '3) Receving null inputcontext'); + + mm.sendAsyncMessage('test:next'); + + break; + + // Regaining focus of input in the second page. + case 4: + ok(!!inputcontext, '4) Receving the second input context'); + is(inputcontext.textAfterCursor, 'Second'); + + mm.sendAsyncMessage('test:next'); + + break; + + // Page transition should cause us lost focus + case 5: + is(inputcontext, null, '5) Receving null inputcontext'); + + inputmethod_cleanup(); + + break; + } + } + + // Set current page as an input method. + SpecialPowers.wrap(im).setActive(true); + + let iframe = document.createElement('iframe'); + iframe.src = 'file_test_unload.html'; + iframe.setAttribute('mozbrowser', true); + document.body.appendChild(iframe); + + let mm = SpecialPowers.getBrowserFrameMessageManager(iframe); + im.oninputcontextchange = nextStep; + + let frameScriptLoaded = false; + iframe.addEventListener('mozbrowserloadend', function() { + if (frameScriptLoaded) + return; + + frameScriptLoaded = true; + mm.addMessageListener('test:step', nextStep); + mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false); + }); +} + +</script> +</pre> +</body> +</html> + |