Bug 1255806: remove use of nsIDOMWindowUtils.sendKeyEvent from Marionette r=ato a=test-only
authorDavid Burns <dburns@mozilla.com>
Tue, 10 May 2016 21:07:56 +0100
changeset 333239 18e0dc8fa500281dc7fb9864b2954190b356a5de
parent 333238 0515fd909bc87d4c9e962da6278eb7b7bf6d93e9
child 333240 d9a268310116151364c10b7f2d91c3bef687ffe5
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)
reviewersato, test-only
bugs1255806
milestone48.0a2
Bug 1255806: remove use of nsIDOMWindowUtils.sendKeyEvent from Marionette r=ato a=test-only nsIDOMWindowUtils.sendKeyEvent() has been made obsolete so this removes its use in Marionette MozReview-Commit-ID: 970gSXdubbA
testing/marionette/event.js
--- a/testing/marionette/event.js
+++ b/testing/marionette/event.js
@@ -1,17 +1,17 @@
 /* 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/. */
 
 // Provides functionality for creating and sending DOM events.
 
 "use strict";
 
-const {interfaces: Ci, utils: Cu} = Components;
+const {interfaces: Ci, utils: Cu, classes: Cc} = Components;
 
 Cu.import("resource://gre/modules/Log.jsm");
 const logger = Log.repository.getLogger("Marionette");
 
 Cu.import("chrome://marionette/content/element.js");
 Cu.import("chrome://marionette/content/error.js");
 
 this.EXPORTED_SYMBOLS = ["event"];
@@ -485,60 +485,381 @@ event.isKeypressFiredKey = function(key)
  *    of that type is fired.  Otherwise, a keydown, a keypress, and then a
  *     keyup event are fired in sequence.
  * @param {Window=} window
  *     Window object.  Defaults to the current window.
  *
  * @throws {TypeError}
  *     If unknown key.
  */
-event.synthesizeKey = function(key, ev, window = undefined) {
-  let domutils = getDOMWindowUtils(window);
+event.synthesizeKey = function(key, event, win = undefined)
+{
+  var TIP = getTIP_(win);
+  if (!TIP) {
+    return;
+  }
+  var KeyboardEvent = getKeyboardEvent_(win);
+  var modifiers = emulateToActivateModifiers_(TIP, event, win);
+  var keyEventDict = createKeyboardEventDictionary_(key, event, win);
+  var keyEvent = new KeyboardEvent("", keyEventDict.dictionary);
+  var dispatchKeydown =
+    !("type" in event) || event.type === "keydown" || !event.type;
+  var dispatchKeyup =
+    !("type" in event) || event.type === "keyup"   || !event.type;
+
+  try {
+    if (dispatchKeydown) {
+      TIP.keydown(keyEvent, keyEventDict.flags);
+      if ("repeat" in event && event.repeat > 1) {
+        keyEventDict.dictionary.repeat = true;
+        var repeatedKeyEvent = new KeyboardEvent("", keyEventDict.dictionary);
+        for (var i = 1; i < event.repeat; i++) {
+          TIP.keydown(repeatedKeyEvent, keyEventDict.flags);
+        }
+      }
+    }
+    if (dispatchKeyup) {
+      TIP.keyup(keyEvent, keyEventDict.flags);
+    }
+  } finally {
+    emulateToInactivateModifiers_(TIP, modifiers, win);
+  }
+};
+
+var TIPMap = new WeakMap();
 
-  let keyCode = 0;
-  let charCode = 0;
-  if (key.indexOf("VK_") === 0) {
+function getTIP_(win, callback)
+{
+  if (!win) {
+    win = window;
+  }
+  var tip;
+  if (TIPMap.has(win)) {
+    tip = TIPMap.get(win);
+  } else {
+    tip =
+      Cc["@mozilla.org/text-input-processor;1"].
+        createInstance(Ci.nsITextInputProcessor);
+    TIPMap.set(win, tip);
+  }
+  if (!tip.beginInputTransactionForTests(win, callback)) {
+    tip = null;
+    TIPMap.delete(win);
+  }
+  return tip;
+}
+
+function getKeyboardEvent_(win = window)
+{
+  if (typeof KeyboardEvent != "undefined") {
+    try {
+      // See if the object can be instantiated; sometimes this yields
+      // 'TypeError: can't access dead object' or 'KeyboardEvent is not a constructor'.
+      new KeyboardEvent("", {});
+      return KeyboardEvent;
+    } catch (ex) {}
+  }
+  if (typeof content != "undefined" && ("KeyboardEvent" in content)) {
+    return content.KeyboardEvent;
+  }
+  return win.KeyboardEvent;
+}
+
+function createKeyboardEventDictionary_(key, keyEvent, win = window) {
+  var result = { dictionary: null, flags: 0 };
+  var keyCodeIsDefined = "keyCode" in keyEvent;
+  var keyCode =
+    (keyCodeIsDefined && keyEvent.keyCode >= 0 && keyEvent.keyCode <= 255) ?
+      keyEvent.keyCode : 0;
+  var keyName = "Unidentified";
+  if (key.indexOf("KEY_") == 0) {
+    keyName = key.substr("KEY_".length);
+    result.flags |= Ci.nsITextInputProcessor.KEY_NON_PRINTABLE_KEY;
+  } else if (key.indexOf("VK_") == 0) {
     keyCode = Ci.nsIDOMKeyEvent["DOM_" + key];
     if (!keyCode) {
-      throw new TypeError("Unknown key: " + key);
+      throw "Unknown key: " + key;
+    }
+    keyName = guessKeyNameFromKeyCode_(keyCode, win);
+    result.flags |= Ci.nsITextInputProcessor.KEY_NON_PRINTABLE_KEY;
+  } else if (key != "") {
+    keyName = key;
+    if (!keyCodeIsDefined) {
+      keyCode = computeKeyCodeFromChar_(key.charAt(0));
+    }
+    if (!keyCode) {
+      result.flags |= Ci.nsITextInputProcessor.KEY_KEEP_KEYCODE_ZERO;
     }
-  } else {
-    charCode = key.charCodeAt(0);
-    keyCode = computeKeyCodeFromChar_(key.charAt(0));
+    result.flags |= Ci.nsITextInputProcessor.KEY_FORCE_PRINTABLE_KEY;
+  }
+  var locationIsDefined = "location" in keyEvent;
+  if (locationIsDefined && keyEvent.location === 0) {
+    result.flags |= Ci.nsITextInputProcessor.KEY_KEEP_KEY_LOCATION_STANDARD;
+  }
+  result.dictionary = {
+    key: keyName,
+    code: "code" in keyEvent ? keyEvent.code : "",
+    location: locationIsDefined ? keyEvent.location : 0,
+    repeat: "repeat" in keyEvent ? keyEvent.repeat === true : false,
+    keyCode: keyCode,
+  };
+  return result;
+}
+
+function emulateToActivateModifiers_(TIP, keyEvent, win = window)
+{
+  if (!keyEvent) {
+    return null;
+  }
+  var KeyboardEvent = getKeyboardEvent_(win);
+  var navigator = getNavigator_(win);
+
+  var modifiers = {
+    normal: [
+      { key: "Alt",        attr: "altKey" },
+      { key: "AltGraph",   attr: "altGraphKey" },
+      { key: "Control",    attr: "ctrlKey" },
+      { key: "Fn",         attr: "fnKey" },
+      { key: "Meta",       attr: "metaKey" },
+      { key: "OS",         attr: "osKey" },
+      { key: "Shift",      attr: "shiftKey" },
+      { key: "Symbol",     attr: "symbolKey" },
+      { key: isMac_(win) ? "Meta" : "Control",
+                           attr: "accelKey" },
+    ],
+    lockable: [
+      { key: "CapsLock",   attr: "capsLockKey" },
+      { key: "FnLock",     attr: "fnLockKey" },
+      { key: "NumLock",    attr: "numLockKey" },
+      { key: "ScrollLock", attr: "scrollLockKey" },
+      { key: "SymbolLock", attr: "symbolLockKey" },
+    ]
   }
 
-  let modifiers = event.parseModifiers_(ev);
+  for (var i = 0; i < modifiers.normal.length; i++) {
+    if (!keyEvent[modifiers.normal[i].attr]) {
+      continue;
+    }
+    if (TIP.getModifierState(modifiers.normal[i].key)) {
+      continue; // already activated.
+    }
+    var event = new KeyboardEvent("", { key: modifiers.normal[i].key });
+    TIP.keydown(event,
+      TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
+    modifiers.normal[i].activated = true;
+  }
+  for (var i = 0; i < modifiers.lockable.length; i++) {
+    if (!keyEvent[modifiers.lockable[i].attr]) {
+      continue;
+    }
+    if (TIP.getModifierState(modifiers.lockable[i].key)) {
+      continue; // already activated.
+    }
+    var event = new KeyboardEvent("", { key: modifiers.lockable[i].key });
+    TIP.keydown(event,
+      TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
+    TIP.keyup(event,
+      TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
+    modifiers.lockable[i].activated = true;
+  }
+  return modifiers;
+}
 
-  // send keydown + (optional) keypress + keyup events
-  if (!("type" in ev) || !ev.type) {
-    let keyDownDefaultHappened = domutils.sendKeyEvent(
-        "keydown", keyCode, 0, modifiers);
-    if (event.isKeypressFiredKey(keyCode)) {
-      domutils.sendKeyEvent(
-          "keypress",
-          charCode ? 0 : keyCode,
-          charCode,
-          modifiers,
-          !keyDownDefaultHappened);
+function emulateToInactivateModifiers_(TIP, modifiers, win = window)
+{
+  if (!modifiers) {
+    return;
+  }
+  var KeyboardEvent = getKeyboardEvent_(win);
+  for (var i = 0; i < modifiers.normal.length; i++) {
+    if (!modifiers.normal[i].activated) {
+      continue;
+    }
+    var event = new KeyboardEvent("", { key: modifiers.normal[i].key });
+    TIP.keyup(event,
+      TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
+  }
+  for (var i = 0; i < modifiers.lockable.length; i++) {
+    if (!modifiers.lockable[i].activated) {
+      continue;
+    }
+    if (!TIP.getModifierState(modifiers.lockable[i].key)) {
+      continue; // who already inactivated this?
     }
-    domutils.sendKeyEvent("keyup", keyCode, 0, modifiers);
+    var event = new KeyboardEvent("", { key: modifiers.lockable[i].key });
+    TIP.keydown(event,
+      TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
+    TIP.keyup(event,
+      TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
+  }
+}
+
+function getNavigator_(win = window)
+{
+  if (typeof navigator != "undefined") {
+    return navigator;
+  }
+  return win.navigator;
+}
+
+function isMac_(win = window) {
+  if (win) {
+    try {
+      return win.navigator.platform.indexOf("Mac") > -1;
+    } catch (ex) {}
+  }
+  return navigator.platform.indexOf("Mac") > -1;
+}
 
-  // send standalone keypress event
-  } else if (ev.type == "keypress") {
-    domutils.sendKeyEvent(
-        ev.type,
-        charCode ? 0 : keyCode,
-        charCode,
-        modifiers);
-
-  // send other standalone event than keypress
-  } else {
-    domutils.sendKeyEvent(ev.type, keyCode, 0, modifiers);
+function guessKeyNameFromKeyCode_(aKeyCode, win = window)
+{
+  var KeyboardEvent = getKeyboardEvent_(win);
+  switch (aKeyCode) {
+    case KeyboardEvent.DOM_VK_CANCEL:
+      return "Cancel";
+    case KeyboardEvent.DOM_VK_HELP:
+      return "Help";
+    case KeyboardEvent.DOM_VK_BACK_SPACE:
+      return "Backspace";
+    case KeyboardEvent.DOM_VK_TAB:
+      return "Tab";
+    case KeyboardEvent.DOM_VK_CLEAR:
+      return "Clear";
+    case KeyboardEvent.DOM_VK_RETURN:
+      return "Enter";
+    case KeyboardEvent.DOM_VK_SHIFT:
+      return "Shift";
+    case KeyboardEvent.DOM_VK_CONTROL:
+      return "Control";
+    case KeyboardEvent.DOM_VK_ALT:
+      return "Alt";
+    case KeyboardEvent.DOM_VK_PAUSE:
+      return "Pause";
+    case KeyboardEvent.DOM_VK_EISU:
+      return "Eisu";
+    case KeyboardEvent.DOM_VK_ESCAPE:
+      return "Escape";
+    case KeyboardEvent.DOM_VK_CONVERT:
+      return "Convert";
+    case KeyboardEvent.DOM_VK_NONCONVERT:
+      return "NonConvert";
+    case KeyboardEvent.DOM_VK_ACCEPT:
+      return "Accept";
+    case KeyboardEvent.DOM_VK_MODECHANGE:
+      return "ModeChange";
+    case KeyboardEvent.DOM_VK_PAGE_UP:
+      return "PageUp";
+    case KeyboardEvent.DOM_VK_PAGE_DOWN:
+      return "PageDown";
+    case KeyboardEvent.DOM_VK_END:
+      return "End";
+    case KeyboardEvent.DOM_VK_HOME:
+      return "Home";
+    case KeyboardEvent.DOM_VK_LEFT:
+      return "ArrowLeft";
+    case KeyboardEvent.DOM_VK_UP:
+      return "ArrowUp";
+    case KeyboardEvent.DOM_VK_RIGHT:
+      return "ArrowRight";
+    case KeyboardEvent.DOM_VK_DOWN:
+      return "ArrowDown";
+    case KeyboardEvent.DOM_VK_SELECT:
+      return "Select";
+    case KeyboardEvent.DOM_VK_PRINT:
+      return "Print";
+    case KeyboardEvent.DOM_VK_EXECUTE:
+      return "Execute";
+    case KeyboardEvent.DOM_VK_PRINTSCREEN:
+      return "PrintScreen";
+    case KeyboardEvent.DOM_VK_INSERT:
+      return "Insert";
+    case KeyboardEvent.DOM_VK_DELETE:
+      return "Delete";
+    case KeyboardEvent.DOM_VK_WIN:
+      return "OS";
+    case KeyboardEvent.DOM_VK_CONTEXT_MENU:
+      return "ContextMenu";
+    case KeyboardEvent.DOM_VK_SLEEP:
+      return "Standby";
+    case KeyboardEvent.DOM_VK_F1:
+      return "F1";
+    case KeyboardEvent.DOM_VK_F2:
+      return "F2";
+    case KeyboardEvent.DOM_VK_F3:
+      return "F3";
+    case KeyboardEvent.DOM_VK_F4:
+      return "F4";
+    case KeyboardEvent.DOM_VK_F5:
+      return "F5";
+    case KeyboardEvent.DOM_VK_F6:
+      return "F6";
+    case KeyboardEvent.DOM_VK_F7:
+      return "F7";
+    case KeyboardEvent.DOM_VK_F8:
+      return "F8";
+    case KeyboardEvent.DOM_VK_F9:
+      return "F9";
+    case KeyboardEvent.DOM_VK_F10:
+      return "F10";
+    case KeyboardEvent.DOM_VK_F11:
+      return "F11";
+    case KeyboardEvent.DOM_VK_F12:
+      return "F12";
+    case KeyboardEvent.DOM_VK_F13:
+      return "F13";
+    case KeyboardEvent.DOM_VK_F14:
+      return "F14";
+    case KeyboardEvent.DOM_VK_F15:
+      return "F15";
+    case KeyboardEvent.DOM_VK_F16:
+      return "F16";
+    case KeyboardEvent.DOM_VK_F17:
+      return "F17";
+    case KeyboardEvent.DOM_VK_F18:
+      return "F18";
+    case KeyboardEvent.DOM_VK_F19:
+      return "F19";
+    case KeyboardEvent.DOM_VK_F20:
+      return "F20";
+    case KeyboardEvent.DOM_VK_F21:
+      return "F21";
+    case KeyboardEvent.DOM_VK_F22:
+      return "F22";
+    case KeyboardEvent.DOM_VK_F23:
+      return "F23";
+    case KeyboardEvent.DOM_VK_F24:
+      return "F24";
+    case KeyboardEvent.DOM_VK_NUM_LOCK:
+      return "NumLock";
+    case KeyboardEvent.DOM_VK_SCROLL_LOCK:
+      return "ScrollLock";
+    case KeyboardEvent.DOM_VK_VOLUME_MUTE:
+      return "VolumeMute";
+    case KeyboardEvent.DOM_VK_VOLUME_DOWN:
+      return "VolumeDown";
+    case KeyboardEvent.DOM_VK_VOLUME_UP:
+      return "VolumeUp";
+    case KeyboardEvent.DOM_VK_META:
+      return "Meta";
+    case KeyboardEvent.DOM_VK_ALTGR:
+      return "AltGraph";
+    case KeyboardEvent.DOM_VK_ATTN:
+      return "Attn";
+    case KeyboardEvent.DOM_VK_CRSEL:
+      return "CrSel";
+    case KeyboardEvent.DOM_VK_EXSEL:
+      return "ExSel";
+    case KeyboardEvent.DOM_VK_EREOF:
+      return "EraseEof";
+    case KeyboardEvent.DOM_VK_PLAY:
+      return "Play";
+    default:
+      return "Unidentified";
   }
-};
+}
 
 /**
  * Indicate that an event with an original target and type is expected
  * to be fired, or not expected to be fired.
  */
 function expectEvent_(expectedTarget, expectedEvent, testName) {
   if (!expectedTarget || !expectedEvent) {
     return null;