Bug 1303384 - Enter shortcut with shortcut draft
authorMark Striemer <mstriemer@mozilla.com>
Fri, 15 Jun 2018 15:47:45 -0700
changeset 819348 7bc76fa8acc7285a81d6a3a29a0d78986d102efc
parent 819347 9f5708d280e2dadc7b7233b40e62166de42120b9
child 819349 101c2b084fab98754a13b4e751a2ca5e86a753e8
push id116524
push userbmo:mstriemer@mozilla.com
push dateTue, 17 Jul 2018 17:54:26 +0000
bugs1303384
milestone63.0a1
Bug 1303384 - Enter shortcut with shortcut MozReview-Commit-ID: FEDE6nJvdsO
toolkit/modules/ShortcutUtils.jsm
toolkit/mozapps/extensions/content/extensions.js
--- a/toolkit/modules/ShortcutUtils.jsm
+++ b/toolkit/modules/ShortcutUtils.jsm
@@ -15,29 +15,18 @@ XPCOMUtils.defineLazyGetter(this, "Platf
 });
 
 XPCOMUtils.defineLazyGetter(this, "Keys", function() {
   return Services.strings.createBundle(
     "chrome://global/locale/keys.properties");
 });
 
 var ShortcutUtils = {
-  /**
-    * Prettifies the modifier keys for an element.
-    *
-    * @param Node aElemKey
-    *        The key element to get the modifiers from.
-    * @param boolean aNoCloverLeaf
-    *        Pass true to use a descriptive string instead of the cloverleaf symbol. (OS X only)
-    * @return string
-    *         A prettified and properly separated modifier keys string.
-    */
-  prettifyShortcut(aElemKey, aNoCloverLeaf) {
+  getModifierString(elemMod, aNoCloverLeaf) {
     let elemString = "";
-    let elemMod = aElemKey.getAttribute("modifiers");
     let haveCloverLeaf = false;
 
     if (elemMod.match("accel")) {
       if (Services.appinfo.OS == "Darwin") {
         // XXX bug 779642 Use "Cmd-" literal vs. cloverleaf meta-key until
         // Orion adds variable height lines.
         if (aNoCloverLeaf) {
           elemString += "Cmd-";
@@ -79,32 +68,56 @@ var ShortcutUtils = {
         PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
     }
 
     if (haveCloverLeaf) {
       elemString += PlatformKeys.GetStringFromName("VK_META") +
         PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
     }
 
+    return elemString;
+  },
+
+  getKeyString(keyCode, keyAttribute) {
     let key;
-    let keyCode = aElemKey.getAttribute("keycode");
     if (keyCode) {
       keyCode = keyCode.toUpperCase();
       try {
         let bundle = keyCode == "VK_RETURN" ? PlatformKeys : Keys;
         // Some keys might not exist in the locale file, which will throw.
         key = bundle.GetStringFromName(keyCode);
       } catch (ex) {
         Cu.reportError("Error finding " + keyCode + ": " + ex);
         key = keyCode.replace(/^VK_/, "");
       }
     } else {
-      key = aElemKey.getAttribute("key");
+      key = keyAttribute;
       key = key.toUpperCase();
     }
+
+    return key;
+  },
+
+  /**
+    * Prettifies the modifier keys for an element.
+    *
+    * @param Node aElemKey
+    *        The key element to get the modifiers from.
+    * @param boolean aNoCloverLeaf
+    *        Pass true to use a descriptive string instead of the cloverleaf symbol. (OS X only)
+    * @return string
+    *         A prettified and properly separated modifier keys string.
+    */
+  prettifyShortcut(aElemKey, aNoCloverLeaf) {
+    let elemString = this.getModifierString(
+      aElemKey.getAttribute("modifiers"),
+      aNoCloverLeaf);
+    let key = this.getKeyString(
+      aElemKey.getAttribute("keycode"),
+      aElemKey.getAttribute("key"));
     return elemString + key;
   },
 
   findShortcut(aElemCommand) {
     let document = aElemCommand.ownerDocument;
     return document.querySelector("key[command=\"" + aElemCommand.getAttribute("id") + "\"]");
   }
 };
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -59,16 +59,61 @@ const XMLURI_PARSE_ERROR = "http://www.m
 
 var gViewDefault = "addons://discover/";
 
 XPCOMUtils.defineLazyGetter(this, "extensionStylesheets", () => {
   const {ExtensionParent} = ChromeUtils.import("resource://gre/modules/ExtensionParent.jsm", {});
   return ExtensionParent.extensionStylesheets;
 });
 
+let validKeys = new Set([
+  "Home", "End", "PageUp", "PageDown", "Insert", "Delete",
+  "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12",
+  "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
+  "MediaNextTrack", "MediaPlayPause", "MediaPrevTrack", "MediaStop",
+]);
+let remapKeys = {
+  a: "A", b: "B", c: "C", d: "D", e: "E", f: "F", g: "G",
+  h: "H", i: "I", j: "J", k: "K", l: "L", m: "M", n: "N", o: "O", p: "P",
+  q: "Q", r: "R", s: "S",
+  t: "T", u: "U", v: "V",
+  w: "W", x: "X",
+  y: "Y",
+  z: "Z",
+  ArrowUp: "Up",
+  ArrowRight: "Right",
+  ArrowDown: "Down",
+  ArrowLeft: "Left",
+  ",": "Comma",
+  ".": "Period",
+  " ": "Space",
+};
+
+function getModifiersAttribute(chromeModifiers) {
+  let modifiersMap = {
+    Alt: "alt",
+    Command: "accel",
+    Ctrl: "accel",
+    MacCtrl: "control",
+    Shift: "shift",
+  };
+  return Array.from(chromeModifiers, modifier => {
+    return modifiersMap[modifier];
+  }).join(" ");
+}
+
+function getStringForKey(key) {
+  if (remapKeys.hasOwnProperty(key)) {
+    return remapKeys[key];
+  } else if (validKeys.has(key)) {
+    return key;
+  }
+  return "";
+}
+
 var gStrings = {};
 XPCOMUtils.defineLazyServiceGetter(gStrings, "bundleSvc",
                                    "@mozilla.org/intl/stringbundle;1",
                                    "nsIStringBundleService");
 
 XPCOMUtils.defineLazyGetter(gStrings, "brand", function() {
   return this.bundleSvc.createBundle("chrome://branding/locale/brand.properties");
 });
@@ -2724,49 +2769,67 @@ var gDetailView = {
     });
 
     if (aAddon.isWebExtension) {
       let extension = ExtensionParent.GlobalManager.extensionMap.get(aAddon.id);
       let {shortcuts} = extension;
       let commandsSection = document.getElementById("commands-grid");
       let commandsRows = document.getElementById("commands-rows");
       commandsRows.textContent = "";
-      let setPlaceholder = (input, name, value) => {
-        let placeholder;
-        if (value) {
-          let key = shortcuts.buildKeyFromShortcut(document, name, value);
-          placeholder = ShortcutUtils.prettifyShortcut(key);
-        } else {
-          placeholder = "Not set";
+      let setPlaceholder = (input, name, modifiers, key) => {
+        let placeholder = "";
+        if (modifiers.length > 0) {
+          let modifiersString = getModifiersAttribute(modifiers);
+          placeholder += ShortcutUtils.getModifierString(modifiersString);
         }
+        if (key) {
+          placeholder += ShortcutUtils.getKeyString(key, key);
+        }
+        input.setAttribute("value", placeholder);
         input.setAttribute("placeholder", placeholder);
       };
       shortcuts.allCommands().then(commands => {
         commandsSection.hidden = commands.length == 0;
         for (let command of commands) {
           let row = document.createElement("row");
           // FIXME: Update the class names.
           row.setAttribute("class", "detail-row-complex");
 
           let label = document.createElement("label");
           label.setAttribute("class", "detail-row-label");
           label.setAttribute("value", command.description || command.name);
           row.appendChild(label);
 
           let shortcut = document.createElement("textbox");
-          setPlaceholder(shortcut, command.name, command.shortcut);
-          shortcut.addEventListener("keydown", async (e) => {
-            if (e.key != "Enter") return;
-            let value = e.target.value;
-
-            shortcut.value = "";
-            setPlaceholder(shortcut, command.name, value);
-
-            await shortcuts.updateCommand({name: command.name, shortcut: value});
-          });
+          let commandModifiers = command.shortcut.split("+");
+          let commandKey = commandModifiers.pop();
+          setPlaceholder(shortcut, command.name, commandModifiers, commandKey);
+          shortcut.setAttribute("value", "");
+          let updateShortcut = async (e) => {
+            let modifierMap = {
+              MacCtrl: e.ctrlKey,
+              Alt: e.altKey,
+              Command: e.metaKey,
+              Shift: e.shiftKey,
+            };
+            let modifierParts = Object.entries(modifierMap)
+              .filter(([key, isDown]) => isDown)
+              .map(([key]) => key);
+            let keyString = getStringForKey(e.key);
+            setPlaceholder(shortcut, command.name, modifierParts, keyString);
+
+            if (keyString && (modifierParts.length == 2 || (modifierParts.length == 1 && modifierParts[0] != "Shift"))) {
+              let value = modifierParts.concat(remapKeys[e.key] || e.key).join("+");
+              shortcut.setAttribute("value", "");
+              shortcut.blur();
+              await shortcuts.updateCommand({name: command.name, shortcut: value});
+            }
+          };
+          shortcut.addEventListener("keydown", updateShortcut);
+          shortcut.addEventListener("keyup", updateShortcut);
           row.appendChild(shortcut);
 
           commandsRows.appendChild(row);
         }
       });
     }
   },