Bug 1332279 - Include keyCode in KeyboardEvents synthesized for key actions; r?ato draft
authorMaja Frydrychowicz <mjzffr@gmail.com>
Sat, 01 Apr 2017 02:37:30 -0400
changeset 554621 b5ccf1c0a50ec7901245bd77c0cc1d6da1c6a6ea
parent 554611 00a166a8640dffa2e0f48650f966d75ca3c1836e
child 554622 919ce88077df2b81e09559aa5b0c5d9a8ca6cb9a
push id52015
push userbmo:mjzffr@gmail.com
push dateSat, 01 Apr 2017 07:12:08 +0000
reviewersato
bugs1332279
milestone55.0a1
Bug 1332279 - Include keyCode in KeyboardEvents synthesized for key actions; r?ato The key dispatch functions now pass the raw key to event.js, which determines the keyCode for the event. Note the change in Normalized Key Value for Enter versus Return. The browser throws an exception when the event key attribute is set to "Return" and KEY_NON_PRINTABLE_KEY is set, which implies that the key value isn't valid. Changing it to Enter fixes the issue. MozReview-Commit-ID: 831f4EcqI1P
testing/marionette/action.js
testing/marionette/event.js
--- a/testing/marionette/action.js
+++ b/testing/marionette/action.js
@@ -54,17 +54,17 @@ const MODIFIER_NAME_LOOKUP = {
 /** Map from raw key (codepoint) to normalized key value */
 const NORMALIZED_KEY_LOOKUP = {
   "\uE000": "Unidentified",
   "\uE001": "Cancel",
   "\uE002": "Help",
   "\uE003": "Backspace",
   "\uE004": "Tab",
   "\uE005": "Clear",
-  "\uE006": "Return",
+  "\uE006": "Enter",
   "\uE007": "Enter",
   "\uE008": "Shift",
   "\uE009": "Control",
   "\uE00A": "Alt",
   "\uE00B": "Pause",
   "\uE00C": "Escape",
   "\uE00D": " ",
   "\uE00E": "PageUp",
@@ -874,18 +874,17 @@ action.Key = class {
     this.code =  KEY_CODE_LOOKUP[rawKey];
     this.location = KEY_LOCATION_LOOKUP[rawKey] || 0;
     this.altKey = false;
     this.shiftKey = false;
     this.ctrlKey = false;
     this.metaKey = false;
     this.repeat = false;
     this.isComposing = false;
-    // Prevent keyCode from being guessed in event.js; we don't want to use it anyway.
-    this.keyCode = 0;
+    // keyCode will be computed by event.sendKeyDown
   }
 
   update(inputState) {
     this.altKey = inputState.alt;
     this.shiftKey = inputState.shift;
     this.ctrlKey = inputState.ctrl;
     this.metaKey = inputState.meta;
   }
@@ -1077,17 +1076,17 @@ function dispatchKeyDown(a, inputState, 
     keyEvent.repeat = inputState.isPressed(keyEvent.key);
     inputState.press(keyEvent.key);
     if (keyEvent.key in MODIFIER_NAME_LOOKUP) {
       inputState.setModState(keyEvent.key, true);
     }
     // Append a copy of |a| with keyUp subtype
     action.inputsToCancel.push(Object.assign({}, a, {subtype: action.KeyUp}));
     keyEvent.update(inputState);
-    event.sendKeyDown(keyEvent.key, keyEvent, win);
+    event.sendKeyDown(a.value, keyEvent, win);
 
     resolve();
   });
 }
 
 /**
  * Dispatch a keyUp action equivalent to releasing a key on a keyboard.
  *
@@ -1108,17 +1107,17 @@ function dispatchKeyUp(a, inputState, wi
       resolve();
       return;
     }
     if (keyEvent.key in MODIFIER_NAME_LOOKUP) {
       inputState.setModState(keyEvent.key, false);
     }
     inputState.release(keyEvent.key);
     keyEvent.update(inputState);
-    event.sendKeyUp(keyEvent.key, keyEvent, win);
+    event.sendKeyUp(a.value, keyEvent, win);
 
     resolve();
   });
 }
 
 /**
  * Dispatch a pointerDown action equivalent to pressing a pointer-device
  * button.
--- a/testing/marionette/event.js
+++ b/testing/marionette/event.js
@@ -352,16 +352,20 @@ event.synthesizeMouseScroll = function (
       modifiers);
 };
 
 function computeKeyCodeFromChar_(char) {
   if (char.length != 1) {
     return 0;
   }
 
+  if (char in VIRTUAL_KEYCODE_LOOKUP) {
+    return Ci.nsIDOMKeyEvent["DOM_" + VIRTUAL_KEYCODE_LOOKUP[char]];
+  }
+
   if (char >= "a" && char <= "z") {
     return Ci.nsIDOMKeyEvent.DOM_VK_A + char.charCodeAt(0) - "a".charCodeAt(0);
   }
   if (char >= "A" && char <= "Z") {
     return Ci.nsIDOMKeyEvent.DOM_VK_A + char.charCodeAt(0) - "A".charCodeAt(0);
   }
   if (char >= "0" && char <= "9") {
     return Ci.nsIDOMKeyEvent.DOM_VK_0 + char.charCodeAt(0) - "0".charCodeAt(0);
@@ -577,50 +581,52 @@ function getKeyboardEvent_(win = window)
   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 keyCodeIsDefined = "keyCode" in keyEvent && keyEvent.keyCode != undefined;
   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 "Unknown key: " + key;
     }
     keyName = guessKeyNameFromKeyCode_(keyCode, win);
-    result.flags |= Ci.nsITextInputProcessor.KEY_NON_PRINTABLE_KEY;
+    if (!isPrintable(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;
     }
-    // keyName was already determined in keyEvent so no fall-back needed
-    if (!("key" in keyEvent && keyName == keyEvent.key)) {
+    // only force printable if "raw character" and event key match, like "a"
+    if (!("key" in keyEvent && key != keyEvent.key)) {
       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,
+    key: "key" in keyEvent ? keyEvent.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;
 }
 
@@ -1215,16 +1221,92 @@ const VIRTUAL_KEYCODE_LOOKUP = {
 
 function getKeyCode(c) {
   if (c in VIRTUAL_KEYCODE_LOOKUP) {
     return VIRTUAL_KEYCODE_LOOKUP[c];
   }
   return c;
 }
 
+function isPrintable(c, win = window) {
+  let KeyboardEvent = getKeyboardEvent_(win);
+  let NON_PRINT_KEYS = [
+    KeyboardEvent.DOM_VK_CANCEL,
+    KeyboardEvent.DOM_VK_HELP,
+    KeyboardEvent.DOM_VK_BACK_SPACE,
+    KeyboardEvent.DOM_VK_TAB,
+    KeyboardEvent.DOM_VK_CLEAR,
+    KeyboardEvent.DOM_VK_SHIFT,
+    KeyboardEvent.DOM_VK_CONTROL,
+    KeyboardEvent.DOM_VK_ALT,
+    KeyboardEvent.DOM_VK_PAUSE,
+    KeyboardEvent.DOM_VK_EISU,
+    KeyboardEvent.DOM_VK_ESCAPE,
+    KeyboardEvent.DOM_VK_CONVERT,
+    KeyboardEvent.DOM_VK_NONCONVERT,
+    KeyboardEvent.DOM_VK_ACCEPT,
+    KeyboardEvent.DOM_VK_MODECHANGE,
+    KeyboardEvent.DOM_VK_PAGE_UP,
+    KeyboardEvent.DOM_VK_PAGE_DOWN,
+    KeyboardEvent.DOM_VK_END,
+    KeyboardEvent.DOM_VK_HOME,
+    KeyboardEvent.DOM_VK_LEFT,
+    KeyboardEvent.DOM_VK_UP,
+    KeyboardEvent.DOM_VK_RIGHT,
+    KeyboardEvent.DOM_VK_DOWN,
+    KeyboardEvent.DOM_VK_SELECT,
+    KeyboardEvent.DOM_VK_PRINT,
+    KeyboardEvent.DOM_VK_EXECUTE,
+    KeyboardEvent.DOM_VK_PRINTSCREEN,
+    KeyboardEvent.DOM_VK_INSERT,
+    KeyboardEvent.DOM_VK_DELETE,
+    KeyboardEvent.DOM_VK_WIN,
+    KeyboardEvent.DOM_VK_CONTEXT_MENU,
+    KeyboardEvent.DOM_VK_SLEEP,
+    KeyboardEvent.DOM_VK_F1,
+    KeyboardEvent.DOM_VK_F2,
+    KeyboardEvent.DOM_VK_F3,
+    KeyboardEvent.DOM_VK_F4,
+    KeyboardEvent.DOM_VK_F5,
+    KeyboardEvent.DOM_VK_F6,
+    KeyboardEvent.DOM_VK_F7,
+    KeyboardEvent.DOM_VK_F8,
+    KeyboardEvent.DOM_VK_F9,
+    KeyboardEvent.DOM_VK_F10,
+    KeyboardEvent.DOM_VK_F11,
+    KeyboardEvent.DOM_VK_F12,
+    KeyboardEvent.DOM_VK_F13,
+    KeyboardEvent.DOM_VK_F14,
+    KeyboardEvent.DOM_VK_F15,
+    KeyboardEvent.DOM_VK_F16,
+    KeyboardEvent.DOM_VK_F17,
+    KeyboardEvent.DOM_VK_F18,
+    KeyboardEvent.DOM_VK_F19,
+    KeyboardEvent.DOM_VK_F20,
+    KeyboardEvent.DOM_VK_F21,
+    KeyboardEvent.DOM_VK_F22,
+    KeyboardEvent.DOM_VK_F23,
+    KeyboardEvent.DOM_VK_F24,
+    KeyboardEvent.DOM_VK_NUM_LOCK,
+    KeyboardEvent.DOM_VK_SCROLL_LOCK,
+    KeyboardEvent.DOM_VK_VOLUME_MUTE,
+    KeyboardEvent.DOM_VK_VOLUME_DOWN,
+    KeyboardEvent.DOM_VK_VOLUME_UP,
+    KeyboardEvent.DOM_VK_META,
+    KeyboardEvent.DOM_VK_ALTGR,
+    KeyboardEvent.DOM_VK_ATTN,
+    KeyboardEvent.DOM_VK_CRSEL,
+    KeyboardEvent.DOM_VK_EXSEL,
+    KeyboardEvent.DOM_VK_EREOF,
+    KeyboardEvent.DOM_VK_PLAY,
+    KeyboardEvent.DOM_VK_RETURN,
+  ];
+  return !(NON_PRINT_KEYS.includes(c));
+}
+
 event.sendKeyDown = function (keyToSend, modifiers, document) {
   modifiers.type = "keydown";
   event.sendSingleKey(keyToSend, modifiers, document);
   // TODO This doesn't do anything since |synthesizeKeyEvent| ignores explicit
   // keypress request, and instead figures out itself when to send keypress
   if (["VK_SHIFT", "VK_CONTROL", "VK_ALT", "VK_META"].indexOf(getKeyCode(keyToSend)) < 0) {
     modifiers.type = "keypress";
     event.sendSingleKey(keyToSend, modifiers, document);
@@ -1246,27 +1328,27 @@ event.sendKeyUp = function (keyToSend, m
  * @param {?} modifiers
  *     Object with properties used in KeyboardEvent (shiftkey, repeat, ...)
  *     as well as, the event |type| such as keydown. All properties are optional.
  * @param {Window=} window
  *     Window object.  If |window| is undefined, the event is synthesized in
  *     current window.
  */
 event.sendSingleKey = function (keyToSend, modifiers, window = undefined) {
-  let keyCode = getKeyCode(keyToSend);
-  if (keyCode in KEYCODES_LOOKUP) {
+  let keyName = getKeyCode(keyToSend);
+  if (keyName in KEYCODES_LOOKUP) {
     // We assume that if |keyToSend| is a raw code point (like "\uE009") then
     // |modifiers| does not already have correct value for corresponding
     // |modName| attribute (like ctrlKey), so that value needs to be flipped
-    let modName = KEYCODES_LOOKUP[keyCode];
+    let modName = KEYCODES_LOOKUP[keyName];
     modifiers[modName] = !modifiers[modName];
-  } else if (modifiers.shiftKey && keyCode != "Shift") {
-    keyCode = keyCode.toUpperCase();
+  } else if (modifiers.shiftKey && keyName != "Shift") {
+    keyName = keyName.toUpperCase();
   }
-  event.synthesizeKey(keyCode, modifiers, window);
+  event.synthesizeKey(keyName, modifiers, window);
 };
 
 /**
  * Focus element and, if a textual input field and no previous selection
  * state exists, move the caret to the end of the input field.
  *
  * @param {Element} element
  *     Element to focus.