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 293262 922f7078062a2ad6693a9c8829ef06629d22bc03
parent 293261 79caf78bcaae2b16a71ef8d8d42566a8f58ba6bb
child 293263 f56cdf13b64ebe37c0e3dc0577d587dc56a26480
push id18749
push usercbook@mozilla.com
push dateFri, 15 Apr 2016 12:01:19 +0000
treeherderfx-team@8f7045b63b07 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmasayuki
bugs1260710
milestone48.0a1
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>