summaryrefslogtreecommitdiff
path: root/dom/inputmethod/mochitest
diff options
context:
space:
mode:
Diffstat (limited to 'dom/inputmethod/mochitest')
-rw-r--r--dom/inputmethod/mochitest/bug1110030_helper.js267
-rw-r--r--dom/inputmethod/mochitest/chrome.ini52
-rw-r--r--dom/inputmethod/mochitest/file_blank.html4
-rw-r--r--dom/inputmethod/mochitest/file_inputmethod.html25
-rw-r--r--dom/inputmethod/mochitest/file_test_app.html11
-rw-r--r--dom/inputmethod/mochitest/file_test_bug1066515.html6
-rw-r--r--dom/inputmethod/mochitest/file_test_bug1137557.html6
-rw-r--r--dom/inputmethod/mochitest/file_test_bug1175399.html1
-rw-r--r--dom/inputmethod/mochitest/file_test_empty_app.html10
-rw-r--r--dom/inputmethod/mochitest/file_test_focus_blur_manage_events.html22
-rw-r--r--dom/inputmethod/mochitest/file_test_sendkey_cancel.html14
-rw-r--r--dom/inputmethod/mochitest/file_test_setSupportsSwitching.html5
-rw-r--r--dom/inputmethod/mochitest/file_test_simple_manage_events.html1
-rw-r--r--dom/inputmethod/mochitest/file_test_sms_app.html14
-rw-r--r--dom/inputmethod/mochitest/file_test_sms_app_1066515.html14
-rw-r--r--dom/inputmethod/mochitest/file_test_sync_edit.html1
-rw-r--r--dom/inputmethod/mochitest/file_test_two_inputs.html1
-rw-r--r--dom/inputmethod/mochitest/file_test_two_selects.html1
-rw-r--r--dom/inputmethod/mochitest/file_test_unload.html1
-rw-r--r--dom/inputmethod/mochitest/file_test_unload_action.html1
-rw-r--r--dom/inputmethod/mochitest/inputmethod_common.js24
-rw-r--r--dom/inputmethod/mochitest/test_basic.html212
-rw-r--r--dom/inputmethod/mochitest/test_bug1026997.html101
-rw-r--r--dom/inputmethod/mochitest/test_bug1043828.html183
-rw-r--r--dom/inputmethod/mochitest/test_bug1059163.html87
-rw-r--r--dom/inputmethod/mochitest/test_bug1066515.html93
-rw-r--r--dom/inputmethod/mochitest/test_bug1137557.html1799
-rw-r--r--dom/inputmethod/mochitest/test_bug1175399.html62
-rw-r--r--dom/inputmethod/mochitest/test_bug944397.html107
-rw-r--r--dom/inputmethod/mochitest/test_bug949059.html40
-rw-r--r--dom/inputmethod/mochitest/test_bug953044.html52
-rw-r--r--dom/inputmethod/mochitest/test_bug960946.html108
-rw-r--r--dom/inputmethod/mochitest/test_bug978918.html77
-rw-r--r--dom/inputmethod/mochitest/test_focus_blur_manage_events.html199
-rw-r--r--dom/inputmethod/mochitest/test_forward_hardware_key_to_ime.html149
-rw-r--r--dom/inputmethod/mochitest/test_input_registry_events.html251
-rw-r--r--dom/inputmethod/mochitest/test_sendkey_cancel.html67
-rw-r--r--dom/inputmethod/mochitest/test_setSupportsSwitching.html130
-rw-r--r--dom/inputmethod/mochitest/test_simple_manage_events.html154
-rw-r--r--dom/inputmethod/mochitest/test_sync_edit.html81
-rw-r--r--dom/inputmethod/mochitest/test_two_inputs.html184
-rw-r--r--dom/inputmethod/mochitest/test_two_selects.html182
-rw-r--r--dom/inputmethod/mochitest/test_unload.html167
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>
+