Bug 1260710 - Test for routing hardware key events to IME; r=masayuki
authorChun-Min Chang <cchang@mozilla.com>
Wed, 06 Apr 2016 13:05:20 +0800
changeset 331104 922f7078062a2ad6693a9c8829ef06629d22bc03
parent 331103 79caf78bcaae2b16a71ef8d8d42566a8f58ba6bb
child 331105 f56cdf13b64ebe37c0e3dc0577d587dc56a26480
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmasayuki
bugs1260710
milestone48.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 1260710 - Test for routing hardware key events to IME; r=masayuki MozReview-Commit-ID: 92Uw9TLbO2K
dom/inputmethod/mochitest/bug1110030_helper.js
dom/inputmethod/mochitest/file_test_empty_app.html
dom/inputmethod/mochitest/mochitest.ini
dom/inputmethod/mochitest/test_forward_hardware_key_to_ime.html
new file mode 100644
--- /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;
+    }
+  },
+};
new file mode 100644
--- /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>
--- a/dom/inputmethod/mochitest/mochitest.ini
+++ b/dom/inputmethod/mochitest/mochitest.ini
@@ -18,16 +18,21 @@ support-files =
 [test_bug978918.html]
 [test_bug1026997.html]
 [test_bug1043828.html]
 [test_bug1059163.html]
 [test_bug1066515.html]
 [test_bug1175399.html]
 [test_bug1137557.html]
 [test_focus_blur_manage_events.html]
+[test_forward_hardware_key_to_ime.html]
+skip-if = buildapp != 'mulet'
+support-files =
+  bug1110030_helper.js
+  file_test_empty_app.html
 [test_input_registry_events.html]
 [test_sendkey_cancel.html]
 [test_setSupportsSwitching.html]
 [test_simple_manage_events.html]
 [test_sync_edit.html]
 [test_two_inputs.html]
 [test_two_selects.html]
 [test_unload.html]
new file mode 100644
--- /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="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/NativeKeyCodes.js"></script>
+  <script type="text/javascript" src="bug1110030_helper.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=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>