Bug 932145 - Mochitest support for Keyboard/IME API. r=fabrice
☠☠ backed out by 3ce6af03ee93 ☠ ☠
authorYuan Xulei <xyuan@mozilla.com>
Thu, 19 Dec 2013 11:05:12 -0500
changeset 177252 f9b9eae77c664fa45ac0ac78f4a1b2facca98306
parent 177251 c63f180b117be6f95665fa730e5c14de8234309a
child 177253 778e7a7b267e8dd4ad211d5f2c547b3e6acb9ef0
push id3343
push userffxbld
push dateMon, 17 Mar 2014 21:55:32 +0000
treeherdermozilla-beta@2f7d3415f79f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfabrice
bugs932145
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 932145 - Mochitest support for Keyboard/IME API. r=fabrice
dom/inputmethod/Keyboard.jsm
dom/inputmethod/mochitest/file_test_app.html
dom/inputmethod/mochitest/inputmethod_common.js
dom/inputmethod/mochitest/mochitest.ini
dom/inputmethod/mochitest/moz.build
dom/inputmethod/mochitest/test_basic.html
dom/inputmethod/moz.build
testing/mochitest/android.json
testing/mochitest/androidx86.json
--- a/dom/inputmethod/Keyboard.jsm
+++ b/dom/inputmethod/Keyboard.jsm
@@ -98,17 +98,22 @@ this.Keyboard = {
       }
 
       // That should never happen.
       if (!mm) {
         dump("!! No message manager found for " + msg.name);
         return;
       }
 
-      if (!mm.assertPermission("input")) {
+      let testing = false;
+      try {
+        testing = Services.prefs.getBoolPref("dom.mozInputMethod.testing");
+      } catch (e) {
+      }
+      if (!testing && !mm.assertPermission("input")) {
         dump("Keyboard message " + msg.name +
         " from a content process with no 'input' privileges.");
         return;
       }
     }
 
     switch (msg.name) {
       case 'Forms:Input':
@@ -175,21 +180,19 @@ this.Keyboard = {
                              .frameLoader.messageManager;
 
     ppmm.broadcastAsyncMessage(newEventName, msg.data);
   },
 
   handleFocusChange: function keyboardHandleFocusChange(msg) {
     this.forwardEvent('Keyboard:FocusChange', msg);
 
-    let browser = Services.wm.getMostRecentWindow("navigator:browser");
-
     // Chrome event, used also to render value selectors; that's why we need
     // the info about choices / min / max here as well...
-    browser.shell.sendChromeEvent({
+    this.sendChromeEvent({
       type: 'inputmethod-contextchange',
       inputType: msg.data.type,
       value: msg.data.value,
       choices: JSON.stringify(msg.data.choices),
       min: msg.data.min,
       max: msg.data.max
     });
   },
@@ -214,25 +217,23 @@ this.Keyboard = {
     this.sendAsyncMessage('Forms:Select:Blur', {});
   },
 
   replaceSurroundingText: function keyboardReplaceSurroundingText(msg) {
     this.sendAsyncMessage('Forms:ReplaceSurroundingText', msg.data);
   },
 
   showInputMethodPicker: function keyboardShowInputMethodPicker() {
-    let browser = Services.wm.getMostRecentWindow("navigator:browser");
-    browser.shell.sendChromeEvent({
+    this.sendChromeEvent({
       type: "inputmethod-showall"
     });
   },
 
   switchToNextInputMethod: function keyboardSwitchToNextInputMethod() {
-    let browser = Services.wm.getMostRecentWindow("navigator:browser");
-    browser.shell.sendChromeEvent({
+    this.sendChromeEvent({
       type: "inputmethod-next"
     });
   },
 
   getText: function keyboardGetText(msg) {
     this.sendAsyncMessage('Forms:GetText', msg.data);
   },
 
@@ -262,12 +263,19 @@ this.Keyboard = {
   _layouts: null,
   setLayouts: function keyboardSetLayoutCount(layouts) {
     // The input method plugins may not have loaded yet,
     // cache the layouts so on init we can respond immediately instead
     // of going back and forth between keyboard_manager
     this._layouts = layouts;
 
     ppmm.broadcastAsyncMessage('Keyboard:LayoutsChange', layouts);
+  },
+
+  sendChromeEvent: function(event) {
+    let browser = Services.wm.getMostRecentWindow("navigator:browser");
+    if (browser && browser.shell) {
+      browser.shell.sendChromeEvent(event);;
+    }
   }
 };
 
 this.Keyboard.init();
new file mode 100644
--- /dev/null
+++ b/dom/inputmethod/mochitest/file_test_app.html
@@ -0,0 +1,10 @@
+<!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();
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/inputmethod/mochitest/inputmethod_common.js
@@ -0,0 +1,32 @@
+function inputmethod_setup(callback) {
+  SimpleTest.waitForExplicitFinish();
+  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 permissions = [];
+  ['input-manage', 'browser'].forEach(function(name) {
+    permissions.push({
+      type: name,
+      allow: true,
+      context: document
+    });
+  });
+
+  SpecialPowers.pushPermissions(permissions, function() {
+    let prefs = [
+      ['dom.mozBrowserFramesEnabled', true],
+      // Enable navigator.mozInputMethod.
+      ['dom.mozInputMethod.enabled', true],
+      // Bypass the permission check for mozInputMethod API.
+      ['dom.mozInputMethod.testing', true]
+    ];
+    SpecialPowers.pushPrefEnv({set: prefs}, callback);
+  });
+}
+
+function inputmethod_cleanup() {
+  SimpleTest.finish();
+}
new file mode 100644
--- /dev/null
+++ b/dom/inputmethod/mochitest/mochitest.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+support-files =
+  inputmethod_common.js
+  file_test_app.html
+
+[test_basic.html]
+
new file mode 100644
--- /dev/null
+++ b/dom/inputmethod/mochitest/moz.build
@@ -0,0 +1,8 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+MOCHITEST_MANIFESTS += ['mochitest.ini']
+
new file mode 100644
--- /dev/null
+++ b/dom/inputmethod/mochitest/test_basic.html
@@ -0,0 +1,134 @@
+<!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="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
+  <link rel="stylesheet" type="text/css" href="/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">
+
+// 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;
+    }
+
+    todo_is(gContext.type, 'input', 'The input context type should match.');
+    is(gContext.inputType, 'text', 'The inputType should match.');
+    is(gContext.inputMode, 'verbatim', 'The input mode should match.');
+    is(gContext.lang, 'zh', 'The language should match.');
+    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);
+    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.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, 0).then(function() {
+    ok(true, 'deleteSurroundingText finished');
+    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, 2).then(function() {
+    ok(true, 'replaceSurroundingText finished');
+    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() {
+    todo_is(gContext.textBeforeCursor + gContext.textAfterCursor, 'Xulei2013',
+       'endComposition changed the input field correctly.');
+    inputmethod_cleanup();
+  }, function (e) {
+    ok(false, 'endComposition failed: ' + e.name);
+    inputmethod_cleanup();
+  });
+}
+
+</script>
+</pre>
+</body>
+</html>
+
--- a/dom/inputmethod/moz.build
+++ b/dom/inputmethod/moz.build
@@ -1,14 +1,16 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+TEST_DIRS += ['mochitest']
+
 XPIDL_SOURCES += [
     'nsIB2GKeyboard.idl',
 ]
 
 XPIDL_MODULE = 'dom_inputmethod'
 
 EXTRA_COMPONENTS += [
     'InputMethod.manifest',
--- a/testing/mochitest/android.json
+++ b/testing/mochitest/android.json
@@ -133,16 +133,17 @@
  "dom/indexedDB/ipc/test_ipc.html": "bug 783513",
  "dom/indexedDB/test/test_third_party.html": "TIMED_OUT",
  "dom/indexedDB/test/test_event_propagation.html": "TIMED_OUT, bug 780855",
  "dom/indexedDB/test/test_app_isolation_inproc.html": "TIMED_OUT",
  "dom/indexedDB/test/test_app_isolation_oop.html": "TIMED_OUT",
  "dom/indexedDB/test/test_webapp_clearBrowserData_inproc_inproc.html": "No test app installed",
  "dom/indexedDB/test/test_webapp_clearBrowserData_inproc_oop.html": "No test app installed",
  "dom/indexedDB/test/test_webapp_clearBrowserData_oop_inproc.html": "No test app installed",
+ "dom/inputmethod": "Not supported on Android",
  "dom/network/tests/test_network_basics.html": "",
  "dom/permission/tests/test_permission_basics.html": "",
  "dom/mobilemessage/tests/test_sms_basics.html": "Bug 909036",
  "dom/media/tests/ipc/test_ipc.html":"bug 910661",
  "dom/tests/mochitest/ajax/jquery/test_jQuery.html": "bug 775227",
  "dom/tests/mochitest/ajax/offline/test_simpleManifest.html": "TIMED_OUT",
  "dom/tests/mochitest/ajax/offline/test_updatingManifest.html": "TIMED_OUT",
  "dom/tests/mochitest/ajax/offline/test_xhtmlManifest.xhtml": "TIMED_OUT",
--- a/testing/mochitest/androidx86.json
+++ b/testing/mochitest/androidx86.json
@@ -210,16 +210,17 @@
  "dom/indexedDB/ipc/test_ipc.html": "bug 783513",
  "dom/indexedDB/test/test_third_party.html": "TIMED_OUT",
  "dom/indexedDB/test/test_event_propagation.html": "TIMED_OUT, bug 780855",
  "dom/indexedDB/test/test_app_isolation_inproc.html": "TIMED_OUT",
  "dom/indexedDB/test/test_app_isolation_oop.html": "TIMED_OUT",
  "dom/indexedDB/test/test_webapp_clearBrowserData_inproc_inproc.html": "No test app installed",
  "dom/indexedDB/test/test_webapp_clearBrowserData_inproc_oop.html": "No test app installed",
  "dom/indexedDB/test/test_webapp_clearBrowserData_oop_inproc.html": "No test app installed",
+ "dom/inputmethod": "Not supported on Android",
  "dom/media/tests/ipc/test_ipc.html": "x86 only bug 936226",
  "dom/network/tests/test_network_basics.html": "",
  "dom/permission/tests/test_permission_basics.html": "",
  "dom/mobilemessage/tests/test_sms_basics.html": "Bug 909036",
  "dom/tests/mochitest/ajax/jquery/test_jQuery.html": "bug 775227",
  "dom/tests/mochitest/ajax/offline/test_simpleManifest.html": "TIMED_OUT",
  "dom/tests/mochitest/ajax/offline/test_updatingManifest.html": "TIMED_OUT",
  "dom/tests/mochitest/ajax/offline/test_xhtmlManifest.xhtml": "TIMED_OUT",