Bug 1359855 - Prevent loading any DevTools module until users interact with any devtool entrypoint. r=jdescottes draft
authorAlexandre Poirot <poirot.alex@gmail.com>
Tue, 18 Jul 2017 11:05:47 +0200
changeset 614025 f7c0bd61413c249b247a4556acef52f203b68857
parent 614024 8586cb4303ec53dccc4227144a4da533af85c659
child 614026 d93261c33d30cdd94bd59d9de0fed26956c7d1a8
push id69893
push userbmo:poirot.alex@gmail.com
push dateSun, 23 Jul 2017 20:50:47 +0000
reviewersjdescottes
bugs1359855
milestone56.0a1
Bug 1359855 - Prevent loading any DevTools module until users interact with any devtool entrypoint. r=jdescottes MozReview-Commit-ID: 4rORySoFRQY
devtools/client/definitions.js
devtools/client/devtools-startup.js
devtools/client/framework/browser-menus.js
devtools/client/framework/devtools-browser.js
devtools/client/locales/en-US/key-shortcuts.properties
devtools/client/locales/en-US/menus.properties
devtools/client/locales/en-US/startup.properties
devtools/client/menus.js
devtools/shared/tests/browser/browser_l10n_localizeMarkup.js
--- a/devtools/client/definitions.js
+++ b/devtools/client/definitions.js
@@ -25,18 +25,21 @@ loader.lazyGetter(this, "ScratchpadPanel
 loader.lazyGetter(this, "DomPanel", () => require("devtools/client/dom/dom-panel").DomPanel);
 
 // Other dependencies
 loader.lazyRequireGetter(this, "CommandUtils", "devtools/client/shared/developer-toolbar", true);
 loader.lazyRequireGetter(this, "CommandState", "devtools/shared/gcli/command-state", true);
 loader.lazyImporter(this, "ResponsiveUIManager", "resource://devtools/client/responsivedesign/responsivedesign.jsm");
 loader.lazyImporter(this, "ScratchpadManager", "resource://devtools/client/scratchpad/scratchpad-manager.jsm");
 
-const {LocalizationHelper} = require("devtools/shared/l10n");
-const L10N = new LocalizationHelper("devtools/client/locales/startup.properties");
+const {MultiLocalizationHelper} = require("devtools/shared/l10n");
+const L10N = new MultiLocalizationHelper(
+  "devtools/client/locales/startup.properties",
+  "devtools/client/locales/key-shortcuts.properties"
+);
 
 var Tools = {};
 exports.Tools = Tools;
 
 // Definitions
 Tools.options = {
   id: "options",
   ordinal: 0,
@@ -57,27 +60,26 @@ Tools.options = {
   build: function (iframeWindow, toolbox) {
     return new OptionsPanel(iframeWindow, toolbox);
   }
 };
 
 Tools.inspector = {
   id: "inspector",
   accesskey: l10n("inspector.accesskey"),
-  key: l10n("inspector.commandkey"),
   ordinal: 1,
-  modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift",
   icon: "chrome://devtools/skin/images/tool-inspector.svg",
   invertIconForDarkTheme: true,
   url: "chrome://devtools/content/inspector/inspector.xhtml",
   label: l10n("inspector.label"),
   panelLabel: l10n("inspector.panelLabel"),
   get tooltip() {
     return l10n("inspector.tooltip2",
-    (osString == "Darwin" ? "Cmd+Opt+" : "Ctrl+Shift+") + this.key);
+    (osString == "Darwin" ? "Cmd+Opt+" : "Ctrl+Shift+") +
+    l10n("inspector.commandkey"));
   },
   inMenu: true,
   commands: [
     "devtools/client/responsivedesign/resize-commands",
     "devtools/client/inspector/inspector-commands"
   ],
 
   preventClosingOnKey: true,
@@ -90,30 +92,29 @@ Tools.inspector = {
   },
 
   build: function (iframeWindow, toolbox) {
     return new InspectorPanel(iframeWindow, toolbox);
   }
 };
 Tools.webConsole = {
   id: "webconsole",
-  key: l10n("cmd.commandkey"),
   accesskey: l10n("webConsoleCmd.accesskey"),
-  modifiers: Services.appinfo.OS == "Darwin" ? "accel,alt" : "accel,shift",
   ordinal: 2,
   oldWebConsoleURL: "chrome://devtools/content/webconsole/webconsole.xul",
   newWebConsoleURL: "chrome://devtools/content/webconsole/webconsole.xhtml",
   icon: "chrome://devtools/skin/images/tool-webconsole.svg",
   invertIconForDarkTheme: true,
   label: l10n("ToolboxTabWebconsole.label"),
   menuLabel: l10n("MenuWebconsole.label"),
   panelLabel: l10n("ToolboxWebConsole.panelLabel"),
   get tooltip() {
     return l10n("ToolboxWebconsole.tooltip2",
-    (osString == "Darwin" ? "Cmd+Opt+" : "Ctrl+Shift+") + this.key);
+    (osString == "Darwin" ? "Cmd+Opt+" : "Ctrl+Shift+") +
+    l10n("webconsole.commandkey"));
   },
   inMenu: true,
   commands: "devtools/client/webconsole/console-commands",
 
   preventClosingOnKey: true,
   onkey: function (panel, toolbox) {
     if (toolbox.splitConsole) {
       return toolbox.focusConsoleInput();
@@ -141,29 +142,28 @@ switchWebconsole();
 
 Services.prefs.addObserver(
   "devtools.webconsole.new-frontend-enabled",
   { observe: switchWebconsole }
 );
 
 Tools.jsdebugger = {
   id: "jsdebugger",
-  key: l10n("debuggerMenu.commandkey"),
   accesskey: l10n("debuggerMenu.accesskey"),
-  modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift",
   ordinal: 3,
   icon: "chrome://devtools/skin/images/tool-debugger.svg",
   invertIconForDarkTheme: true,
   highlightedicon: "chrome://devtools/skin/images/tool-debugger-paused.svg",
   url: "chrome://devtools/content/debugger/debugger.xul",
   label: l10n("ToolboxDebugger.label"),
   panelLabel: l10n("ToolboxDebugger.panelLabel"),
   get tooltip() {
     return l10n("ToolboxDebugger.tooltip2",
-    (osString == "Darwin" ? "Cmd+Opt+" : "Ctrl+Shift+") + this.key);
+    (osString == "Darwin" ? "Cmd+Opt+" : "Ctrl+Shift+") +
+    l10n("debugger.commandkey"));
   },
   inMenu: true,
   commands: "devtools/client/debugger/debugger-commands",
 
   isTargetSupported: function () {
     return true;
   },
 
@@ -189,29 +189,27 @@ switchDebugger();
 
 Services.prefs.addObserver(
   "devtools.debugger.new-debugger-frontend",
   { observe: switchDebugger }
 );
 
 Tools.styleEditor = {
   id: "styleeditor",
-  key: l10n("open.commandkey"),
   ordinal: 4,
   visibilityswitch: "devtools.styleeditor.enabled",
   accesskey: l10n("open.accesskey"),
-  modifiers: "shift",
   icon: "chrome://devtools/skin/images/tool-styleeditor.svg",
   invertIconForDarkTheme: true,
   url: "chrome://devtools/content/styleeditor/styleeditor.xul",
   label: l10n("ToolboxStyleEditor.label"),
   panelLabel: l10n("ToolboxStyleEditor.panelLabel"),
   get tooltip() {
     return l10n("ToolboxStyleEditor.tooltip3",
-    "Shift+" + functionkey(this.key));
+    "Shift+" + functionkey(l10n("styleeditor.commandkey")));
   },
   inMenu: true,
   commands: "devtools/client/styleeditor/styleeditor-commands",
 
   isTargetSupported: function (target) {
     return target.hasActor("styleEditor") || target.hasActor("styleSheets");
   },
 
@@ -268,21 +266,20 @@ Tools.performance = {
   icon: "chrome://devtools/skin/images/tool-profiler.svg",
   invertIconForDarkTheme: true,
   highlightedicon: "chrome://devtools/skin/images/tool-profiler-active.svg",
   url: "chrome://devtools/content/performance/performance.xul",
   visibilityswitch: "devtools.performance.enabled",
   label: l10n("performance.label"),
   panelLabel: l10n("performance.panelLabel"),
   get tooltip() {
-    return l10n("performance.tooltip", "Shift+" + functionkey(this.key));
+    return l10n("performance.tooltip", "Shift+" +
+    functionkey(l10n("performance.commandkey")));
   },
   accesskey: l10n("performance.accesskey"),
-  key: l10n("performance.commandkey"),
-  modifiers: "shift",
   inMenu: true,
 
   isTargetSupported: function (target) {
     return target.hasActor("profiler");
   },
 
   build: function (frame, target) {
     return new PerformancePanel(frame, target);
@@ -308,55 +305,53 @@ Tools.memory = {
   build: function (frame, target) {
     return new MemoryPanel(frame, target);
   }
 };
 
 Tools.netMonitor = {
   id: "netmonitor",
   accesskey: l10n("netmonitor.accesskey"),
-  key: l10n("netmonitor.commandkey2"),
   ordinal: 9,
-  modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift",
   visibilityswitch: "devtools.netmonitor.enabled",
   icon: "chrome://devtools/skin/images/tool-network.svg",
   invertIconForDarkTheme: true,
   url: "chrome://devtools/content/netmonitor/index.html",
   label: l10n("netmonitor.label"),
   panelLabel: l10n("netmonitor.panelLabel"),
   get tooltip() {
     return l10n("netmonitor.tooltip2",
-    (osString == "Darwin" ? "Cmd+Opt+" : "Ctrl+Shift+") + this.key);
+    (osString == "Darwin" ? "Cmd+Opt+" : "Ctrl+Shift+") +
+    l10n("netmonitor.commandkey"));
   },
   inMenu: true,
 
   isTargetSupported: function (target) {
     return target.getTrait("networkMonitor");
   },
 
   build: function (iframeWindow, toolbox) {
     return new NetMonitorPanel(iframeWindow, toolbox);
   }
 };
 
 Tools.storage = {
   id: "storage",
-  key: l10n("storage.commandkey"),
   ordinal: 10,
   accesskey: l10n("storage.accesskey"),
-  modifiers: "shift",
   visibilityswitch: "devtools.storage.enabled",
   icon: "chrome://devtools/skin/images/tool-storage.svg",
   invertIconForDarkTheme: true,
   url: "chrome://devtools/content/storage/storage.xul",
   label: l10n("storage.label"),
   menuLabel: l10n("storage.menuLabel"),
   panelLabel: l10n("storage.panelLabel"),
   get tooltip() {
-    return l10n("storage.tooltip3", "Shift+" + functionkey(this.key));
+    return l10n("storage.tooltip3", "Shift+" +
+    functionkey(l10n("storage.commandkey")));
   },
   inMenu: true,
 
   isTargetSupported: function (target) {
     return target.isLocalTab ||
            (target.hasActor("storage") && target.getTrait("storageInspector"));
   },
 
@@ -405,28 +400,27 @@ Tools.scratchpad = {
   build: function (iframeWindow, toolbox) {
     return new ScratchpadPanel(iframeWindow, toolbox);
   }
 };
 
 Tools.dom = {
   id: "dom",
   accesskey: l10n("dom.accesskey"),
-  key: l10n("dom.commandkey"),
   ordinal: 13,
-  modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift",
   visibilityswitch: "devtools.dom.enabled",
   icon: "chrome://devtools/skin/images/tool-dom.svg",
   invertIconForDarkTheme: true,
   url: "chrome://devtools/content/dom/dom.html",
   label: l10n("dom.label"),
   panelLabel: l10n("dom.panelLabel"),
   get tooltip() {
     return l10n("dom.tooltip",
-      (osString == "Darwin" ? "Cmd+Opt+" : "Ctrl+Shift+") + this.key);
+      (osString == "Darwin" ? "Cmd+Opt+" : "Ctrl+Shift+") +
+      l10n("dom.commandkey"));
   },
   inMenu: true,
 
   isTargetSupported: function (target) {
     return target.getTrait("webConsoleCommands");
   },
 
   build: function (iframeWindow, toolbox) {
--- a/devtools/client/devtools-startup.js
+++ b/devtools/client/devtools-startup.js
@@ -1,30 +1,162 @@
 /* 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/. */
 
 /**
  * This XPCOM component is loaded very early.
- * It handles command line arguments like -jsconsole, but also ensures starting
+ * Be careful to lazy load dependencies as much as possible.
+ *
+ * It manages all the possible entry points for DevTools:
+ * - Handles command line arguments like -jsconsole,
+ * - Register all key shortcuts,
+ * - Listen for "Web Developer" system menu opening, under "Tools",
+ * - Inject the wrench icon in toolbar customization, which is used
+ *   by the "Web Developer" list displayed in the hamburger menu,
+ * - Register the JSON Viewer protocol handler.
+ *
+ * Only once any of these entry point is fired, this module ensures starting
  * core modules like 'devtools-browser.js' that hooks the browser windows
  * and ensure setting up tools.
- *
- * Be careful to lazy load dependencies as much as possible.
  **/
 
 "use strict";
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 const kDebuggerPrefs = [
   "devtools.debugger.remote-enabled",
   "devtools.chrome.enabled"
 ];
 const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
-XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+                                  "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+                                  "resource://gre/modules/AppConstants.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "Bundle", function () {
+  const kUrl = "chrome://devtools/locale/key-shortcuts.properties";
+  return Services.strings.createBundle(kUrl);
+});
+
+XPCOMUtils.defineLazyGetter(this, "KeyShortcuts", function () {
+  const isMac = AppConstants.platform == "macosx";
+
+  // Common modifier shared by most key shortcuts
+  const modifiers = isMac ? "accel,alt" : "accel,shift";
+
+  // List of all key shortcuts triggering installation UI
+  // `id` should match tool's id from client/definitions.js
+  return [
+    // The following keys are also registered in /client/menus.js
+    // And should be synced.
+
+    // Both are toggling the toolbox on the last selected panel
+    // or the default one.
+    {
+      id: "toggleToolbox",
+      shortcut: Bundle.GetStringFromName("toggleToolbox.commandkey"),
+      modifiers
+    },
+    // All locales are using F12
+    {
+      id: "toggleToolboxF12",
+      shortcut: Bundle.GetStringFromName("toggleToolboxF12.commandkey"),
+      modifiers: "" // F12 is the only one without modifiers
+    },
+    // Toggle the visibility of the Developer Toolbar (=gcli)
+    {
+      id: "toggleToolbar",
+      shortcut: Bundle.GetStringFromName("toggleToolbar.commandkey"),
+      modifiers: "shift"
+    },
+    // Open WebIDE window
+    {
+      id: "webide",
+      shortcut: Bundle.GetStringFromName("webide.commandkey"),
+      modifiers: "shift"
+    },
+    // Open the Browser Toolbox
+    {
+      id: "browserToolbox",
+      shortcut: Bundle.GetStringFromName("browserToolbox.commandkey"),
+      modifiers: "accel,alt,shift"
+    },
+    // Open the Browser Console
+    {
+      id: "browserConsole",
+      shortcut: Bundle.GetStringFromName("browserConsole.commandkey"),
+      modifiers: "accel,shift"
+    },
+    // Toggle the Responsive Design Mode
+    {
+      id: "responsiveDesignMode",
+      shortcut: Bundle.GetStringFromName("responsiveDesignMode.commandkey"),
+      modifiers
+    },
+    // Open ScratchPad window
+    {
+      id: "scratchpad",
+      shortcut: Bundle.GetStringFromName("scratchpad.commandkey"),
+      modifiers: "shift"
+    },
+
+    // The following keys are also registered in /client/definitions.js
+    // and should be synced.
+
+    // Key for opening the Inspector
+    {
+      toolId: "inspector",
+      shortcut: Bundle.GetStringFromName("inspector.commandkey"),
+      modifiers
+    },
+    // Key for opening the Web Console
+    {
+      toolId: "webconsole",
+      shortcut: Bundle.GetStringFromName("webconsole.commandkey"),
+      modifiers
+    },
+    // Key for opening the Debugger
+    {
+      toolId: "jsdebugger",
+      shortcut: Bundle.GetStringFromName("debugger.commandkey"),
+      modifiers
+    },
+    // Key for opening the Network Monitor
+    {
+      toolId: "netmonitor",
+      shortcut: Bundle.GetStringFromName("netmonitor.commandkey"),
+      modifiers
+    },
+    // Key for opening the Style Editor
+    {
+      toolId: "styleeditor",
+      shortcut: Bundle.GetStringFromName("styleeditor.commandkey"),
+      modifiers: "shift"
+    },
+    // Key for opening the Performance Panel
+    {
+      toolId: "performance",
+      shortcut: Bundle.GetStringFromName("performance.commandkey"),
+      modifiers: "shift"
+    },
+    // Key for opening the Storage Panel
+    {
+      toolId: "storage",
+      shortcut: Bundle.GetStringFromName("storage.commandkey"),
+      modifiers: "shift"
+    },
+    // Key for opening the DOM Panel
+    {
+      toolId: "dom",
+      shortcut: Bundle.GetStringFromName("dom.commandkey"),
+      modifiers
+    },
+  ];
+});
 
 function DevToolsStartup() {}
 
 DevToolsStartup.prototype = {
   handle: function (cmdLine) {
     let consoleFlag = cmdLine.handleFlag("jsconsole", false);
     let debuggerFlag = cmdLine.handleFlag("jsdebugger", false);
     let devtoolsFlag = cmdLine.handleFlag("devtools", false);
@@ -43,34 +175,114 @@ DevToolsStartup.prototype = {
       // We get an error if the option is given but not followed by a value.
       // By catching and trying again, the value is effectively optional.
       debuggerServerFlag = cmdLine.handleFlag("start-debugger-server", false);
     }
     if (debuggerServerFlag) {
       this.handleDebuggerServerFlag(cmdLine, debuggerServerFlag);
     }
 
-    let onStartup = window => {
-      Services.obs.removeObserver(onStartup,
-                                  "browser-delayed-startup-finished");
-      // Ensure loading core module once firefox is ready
-      this.initDevTools();
+    let onWindowReady = window => {
+      this.hookWindow(window);
 
       if (devtoolsFlag) {
         this.handleDevToolsFlag(window);
+        // This listener is called for all Firefox windows, but we want to execute
+        // that command only once
+        devtoolsFlag = false;
       }
     };
-    Services.obs.addObserver(onStartup, "browser-delayed-startup-finished");
+    Services.obs.addObserver(onWindowReady, "browser-delayed-startup-finished");
+  },
+
+  /**
+   * Register listeners to all possible entry points for Developer Tools.
+   * But instead of implementing the actual actions, defer to DevTools codebase.
+   * In most cases, it only needs to call this.initDevTools which handles the rest.
+   * We do that to prevent loading any DevTools module until the user intent to use them.
+   */
+  hookWindow(window) {
+    this.hookKeyShortcuts(window);
+
+    // All the other hooks are only necessary if the tools aren't loaded yet.
+    if (this.initialized) {
+      return;
+    }
+
+    this.hookWebDeveloperMenu(window);
+  },
+
+  /**
+   * We listen to the "Web Developer" system menu, which is under "Tools" main item.
+   * This menu item is hardcoded empty in Firefox UI. We listen for its opening to
+   * populate it lazily. Loading main DevTools module is going to populate it.
+   */
+  hookWebDeveloperMenu(window) {
+    let menu = window.document.getElementById("webDeveloperMenu");
+    menu.addEventListener("popupshowing", () => this.initDevTools(), { once: true });
   },
 
+  hookKeyShortcuts(window) {
+    let doc = window.document;
+    let keyset = doc.createElement("keyset");
+    keyset.setAttribute("id", "devtoolsKeyset");
+
+    for (let key of KeyShortcuts) {
+      let xulKey = this.createKey(doc, key, () => this.onKey(window, key));
+      keyset.appendChild(xulKey);
+    }
+
+    // Appending a <key> element is not always enough. The <keyset> needs
+    // to be detached and reattached to make sure the <key> is taken into
+    // account (see bug 832984).
+    let mainKeyset = doc.getElementById("mainKeyset");
+    mainKeyset.parentNode.insertBefore(keyset, mainKeyset);
+  },
+
+  onKey(window, key) {
+    let require = this.initDevTools();
+    let { gDevToolsBrowser } = require("devtools/client/framework/devtools-browser");
+    gDevToolsBrowser.onKeyShortcut(window, key);
+  },
+
+  // Create a <xul:key> DOM Element
+  createKey(doc, { id, toolId, shortcut, modifiers: mod }, oncommand) {
+    let k = doc.createElement("key");
+    k.id = "key_" + (id || toolId);
+
+    if (shortcut.startsWith("VK_")) {
+      k.setAttribute("keycode", shortcut);
+    } else {
+      k.setAttribute("key", shortcut);
+    }
+
+    if (mod) {
+      k.setAttribute("modifiers", mod);
+    }
+
+    // Bug 371900: command event is fired only if "oncommand" attribute is set.
+    k.setAttribute("oncommand", ";");
+    k.addEventListener("command", oncommand);
+
+    return k;
+  },
+
+  /**
+   * Boolean flag to check if DevTools have been already initialized or not.
+   * By initialized, we mean that its main modules are loaded.
+   */
+  initialized: false,
+
   initDevTools: function () {
-    let { loader } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+    this.initialized = true;
+    let { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
     // Ensure loading main devtools module that hooks up into browser UI
     // and initialize all devtools machinery.
-    loader.require("devtools/client/framework/devtools-browser");
+    require("devtools/client/framework/devtools-browser");
+    return require;
   },
 
   handleConsoleFlag: function (cmdLine) {
     let window = Services.wm.getMostRecentWindow("devtools:webconsole");
     if (!window) {
       this.initDevTools();
 
       let { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
@@ -84,17 +296,17 @@ DevToolsStartup.prototype = {
 
     if (cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_AUTO) {
       cmdLine.preventDefault = true;
     }
   },
 
   // Open the toolbox on the selected tab once the browser starts up.
   handleDevToolsFlag: function (window) {
-    const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+    const require = this.initDevTools();
     const {gDevTools} = require("devtools/client/framework/devtools");
     const {TargetFactory} = require("devtools/client/framework/target");
     let target = TargetFactory.forTab(window.gBrowser.selectedTab);
     gDevTools.showToolbox(target);
   },
 
   _isRemoteDebuggingEnabled() {
     let remoteDebuggingEnabled = false;
--- a/devtools/client/framework/browser-menus.js
+++ b/devtools/client/framework/browser-menus.js
@@ -1,18 +1,18 @@
 /* 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/. */
 
 "use strict";
 
 /**
- * This module inject dynamically menu items and key shortcuts into browser UI.
+ * This module inject dynamically menu items into browser UI.
  *
- * Menu and shortcut definitions are fetched from:
+ * Menu definitions are fetched from:
  * - devtools/client/menus for top level entires
  * - devtools/client/definitions for tool-specifics entries
  */
 
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const MENUS_L10N = new LocalizationHelper("devtools/client/locales/menus.properties");
 
 loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
@@ -22,63 +22,20 @@ loader.lazyRequireGetter(this, "gDevTool
 // Maps browser xul document => list of DOM Elements
 const FragmentsCache = new Map();
 
 function l10n(key) {
   return MENUS_L10N.getStr(key);
 }
 
 /**
- * Create a xul:key element
- *
- * @param {XULDocument} doc
- *        The document to which keys are to be added.
- * @param {String} id
- *        key's id, automatically prefixed with "key_".
- * @param {String} shortcut
- *        The key shortcut value.
- * @param {String} keytext
- *        If `shortcut` refers to a function key, refers to the localized
- *        string to describe a non-character shortcut.
- * @param {String} modifiers
- *        Space separated list of modifier names.
- * @param {Function} oncommand
- *        The function to call when the shortcut is pressed.
- *
- * @return XULKeyElement
- */
-function createKey({ doc, id, shortcut, keytext, modifiers, oncommand }) {
-  let k = doc.createElement("key");
-  k.id = "key_" + id;
-
-  if (shortcut.startsWith("VK_")) {
-    k.setAttribute("keycode", shortcut);
-    if (keytext) {
-      k.setAttribute("keytext", keytext);
-    }
-  } else {
-    k.setAttribute("key", shortcut);
-  }
-
-  if (modifiers) {
-    k.setAttribute("modifiers", modifiers);
-  }
-
-  // Bug 371900: command event is fired only if "oncommand" attribute is set.
-  k.setAttribute("oncommand", ";");
-  k.addEventListener("command", oncommand);
-
-  return k;
-}
-
-/**
  * Create a xul:menuitem element
  *
  * @param {XULDocument} doc
- *        The document to which keys are to be added.
+ *        The document to which menus are to be added.
  * @param {String} id
  *        Element id.
  * @param {String} label
  *        Menu label.
  * @param {String} accesskey (optional)
  *        Access key of the menuitem, used as shortcut while opening the menu.
  * @param {Boolean} isCheckbox (optional)
  *        If true, the menuitem will act as a checkbox and have an optional
@@ -96,39 +53,16 @@ function createMenuItem({ doc, id, label
   if (isCheckbox) {
     menuitem.setAttribute("type", "checkbox");
     menuitem.setAttribute("autocheck", "false");
   }
   return menuitem;
 }
 
 /**
- * Add a <key> to <keyset id="devtoolsKeyset">.
- * Appending a <key> element is not always enough. The <keyset> needs
- * to be detached and reattached to make sure the <key> is taken into
- * account (see bug 832984).
- *
- * @param {XULDocument} doc
- *        The document to which keys are to be added
- * @param {XULElement} or {DocumentFragment} keys
- *        Keys to add
- */
-function attachKeybindingsToBrowser(doc, keys) {
-  let devtoolsKeyset = doc.getElementById("devtoolsKeyset");
-
-  if (!devtoolsKeyset) {
-    devtoolsKeyset = doc.createElement("keyset");
-    devtoolsKeyset.setAttribute("id", "devtoolsKeyset");
-  }
-  devtoolsKeyset.appendChild(keys);
-  let mainKeyset = doc.getElementById("mainKeyset");
-  mainKeyset.parentNode.insertBefore(devtoolsKeyset, mainKeyset);
-}
-
-/**
  * Add a menu entry for a tool definition
  *
  * @param {Object} toolDefinition
  *        Tool definition of the tool to add a menu entry.
  * @param {XULDocument} doc
  *        The document to which the tool menu item is to be added.
  */
 function createToolMenuElements(toolDefinition, doc) {
@@ -140,62 +74,45 @@ function createToolMenuElements(toolDefi
     return;
   }
 
   let oncommand = function (id, event) {
     let window = event.target.ownerDocument.defaultView;
     gDevToolsBrowser.selectToolCommand(window.gBrowser, id);
   }.bind(null, id);
 
-  let key = null;
-  if (toolDefinition.key) {
-    key = createKey({
-      doc,
-      id,
-      shortcut: toolDefinition.key,
-      modifiers: toolDefinition.modifiers,
-      oncommand: oncommand
-    });
-  }
-
   let menuitem = createMenuItem({
     doc,
     id: "menuitem_" + id,
     label: toolDefinition.menuLabel || toolDefinition.label,
     accesskey: toolDefinition.accesskey
   });
-  if (key) {
-    // Refer to the key in order to display the key shortcut at menu ends
-    menuitem.setAttribute("key", key.id);
-  }
+  // Refer to the key in order to display the key shortcut at menu ends
+  // This <key> element is being created by devtools/client/devtools-startup.js
+  menuitem.setAttribute("key", "key_" + id);
   menuitem.addEventListener("command", oncommand);
 
   return {
-    key,
     menuitem
   };
 }
 
 /**
  * Create xul menuitem, key elements for a given tool.
  * And then insert them into browser DOM.
  *
  * @param {XULDocument} doc
  *        The document to which the tool is to be registered.
  * @param {Object} toolDefinition
  *        Tool definition of the tool to register.
  * @param {Object} prevDef
  *        The tool definition after which the tool menu item is to be added.
  */
 function insertToolMenuElements(doc, toolDefinition, prevDef) {
-  let { key, menuitem } = createToolMenuElements(toolDefinition, doc);
-
-  if (key) {
-    attachKeybindingsToBrowser(doc, key);
-  }
+  let { menuitem } = createToolMenuElements(toolDefinition, doc);
 
   let ref;
   if (prevDef) {
     let menuitem = doc.getElementById("menuitem_" + prevDef.id);
     ref = menuitem && menuitem.nextSibling ? menuitem.nextSibling : null;
   } else {
     ref = doc.getElementById("menu_devtools_separator");
   }
@@ -249,32 +166,29 @@ function addAllToolsToMenu(doc) {
     }
 
     if (elements.key) {
       fragKeys.appendChild(elements.key);
     }
     fragMenuItems.appendChild(elements.menuitem);
   }
 
-  attachKeybindingsToBrowser(doc, fragKeys);
-
   let mps = doc.getElementById("menu_devtools_separator");
   if (mps) {
     mps.parentNode.insertBefore(fragMenuItems, mps);
   }
 }
 
 /**
- * Add global menus and shortcuts that are not panel specific.
+ * Add global menus that are not panel specific.
  *
  * @param {XULDocument} doc
- *        The document to which keys and menus are to be added.
+ *        The document to which menus are to be added.
  */
 function addTopLevelItems(doc) {
-  let keys = doc.createDocumentFragment();
   let menuItems = doc.createDocumentFragment();
 
   let { menuitems } = require("../menus");
   for (let item of menuitems) {
     if (item.separator) {
       let separator = doc.createElement("menuseparator");
       separator.id = item.id;
       menuItems.appendChild(separator);
@@ -287,104 +201,72 @@ function addTopLevelItems(doc) {
         id,
         label: l10n(l10nKey + ".label"),
         accesskey: l10n(l10nKey + ".accesskey"),
         isCheckbox: item.checkbox
       });
       menuitem.addEventListener("command", item.oncommand);
       menuItems.appendChild(menuitem);
 
-      if (item.key && l10nKey) {
-        // Create a <key>
-        let shortcut = l10n(l10nKey + ".key");
-        let key = createKey({
-          doc,
-          id: item.key.id,
-          shortcut: shortcut,
-          keytext: shortcut.startsWith("VK_") ? l10n(l10nKey + ".keytext") : null,
-          modifiers: item.key.modifiers,
-          oncommand: item.oncommand
-        });
-        // Refer to the key in order to display the key shortcut at menu ends
-        menuitem.setAttribute("key", key.id);
-        keys.appendChild(key);
-      }
-      if (item.additionalKeys) {
-        // Create additional <key>
-        for (let key of item.additionalKeys) {
-          let shortcut = l10n(key.l10nKey + ".key");
-          let node = createKey({
-            doc,
-            id: key.id,
-            shortcut: shortcut,
-            keytext: shortcut.startsWith("VK_") ? l10n(key.l10nKey + ".keytext") : null,
-            modifiers: key.modifiers,
-            oncommand: item.oncommand
-          });
-          keys.appendChild(node);
-        }
+      if (item.keyId) {
+        menuitem.setAttribute("key", "key_" + item.keyId);
       }
     }
   }
 
   // Cache all nodes before insertion to be able to remove them on unload
   let nodes = [];
-  for (let node of keys.children) {
-    nodes.push(node);
-  }
   for (let node of menuItems.children) {
     nodes.push(node);
   }
   FragmentsCache.set(doc, nodes);
 
-  attachKeybindingsToBrowser(doc, keys);
-
   let menu = doc.getElementById("menuWebDeveloperPopup");
   menu.appendChild(menuItems);
 
   // There is still "Page Source" menuitem hardcoded into browser.xul. Instead
   // of manually inserting everything around it, move it to the expected
   // position.
   let pageSource = doc.getElementById("menu_pageSource");
   let endSeparator = doc.getElementById("devToolsEndSeparator");
   menu.insertBefore(pageSource, endSeparator);
 }
 
 /**
- * Remove global menus and shortcuts that are not panel specific.
+ * Remove global menus that are not panel specific.
  *
  * @param {XULDocument} doc
- *        The document to which keys and menus are to be added.
+ *        The document to which menus are to be added.
  */
 function removeTopLevelItems(doc) {
   let nodes = FragmentsCache.get(doc);
   if (!nodes) {
     return;
   }
   FragmentsCache.delete(doc);
   for (let node of nodes) {
     node.remove();
   }
 }
 
 /**
- * Add menus and shortcuts to a browser document
+ * Add menus to a browser document
  *
  * @param {XULDocument} doc
- *        The document to which keys and menus are to be added.
+ *        The document to which menus are to be added.
  */
 exports.addMenus = function (doc) {
   addTopLevelItems(doc);
 
   addAllToolsToMenu(doc);
 };
 
 /**
- * Remove menus and shortcuts from a browser document
+ * Remove menus from a browser document
  *
  * @param {XULDocument} doc
- *        The document to which keys and menus are to be removed.
+ *        The document to which menus are to be removed.
  */
 exports.removeMenus = function (doc) {
   // We only remove top level entries. Per-tool entries are removed while
   // unregistering each tool.
   removeTopLevelItems(doc);
 };
--- a/devtools/client/framework/devtools-browser.js
+++ b/devtools/client/framework/devtools-browser.js
@@ -20,16 +20,19 @@ const {gDevTools} = require("./devtools"
 // Load target and toolbox lazily as they need gDevTools to be fully initialized
 loader.lazyRequireGetter(this, "TargetFactory", "devtools/client/framework/target", true);
 loader.lazyRequireGetter(this, "Toolbox", "devtools/client/framework/toolbox", true);
 loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true);
 loader.lazyRequireGetter(this, "DebuggerClient", "devtools/shared/client/main", true);
 loader.lazyRequireGetter(this, "BrowserMenus", "devtools/client/framework/browser-menus");
 loader.lazyRequireGetter(this, "appendStyleSheet", "devtools/client/shared/stylesheet-utils", true);
 loader.lazyRequireGetter(this, "DeveloperToolbar", "devtools/client/shared/developer-toolbar", true);
+loader.lazyImporter(this, "BrowserToolboxProcess", "resource://devtools/client/framework/ToolboxProcess.jsm");
+loader.lazyImporter(this, "ResponsiveUIManager", "resource://devtools/client/responsivedesign/responsivedesign.jsm");
+loader.lazyImporter(this, "ScratchpadManager", "resource://devtools/client/scratchpad/scratchpad-manager.jsm");
 
 loader.lazyImporter(this, "CustomizableUI", "resource:///modules/CustomizableUI.jsm");
 loader.lazyImporter(this, "CustomizableWidgets", "resource:///modules/CustomizableWidgets.jsm");
 loader.lazyImporter(this, "AppConstants", "resource://gre/modules/AppConstants.jsm");
 loader.lazyImporter(this, "LightweightThemeManager", "resource://gre/modules/LightweightThemeManager.jsm");
 
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const L10N = new LocalizationHelper("devtools/client/locales/toolbox.properties");
@@ -270,16 +273,63 @@ var gDevToolsBrowser = exports.gDevTools
       gDevTools.showToolbox(target, toolId).then(newToolbox => {
         newToolbox.fireCustomKey(toolId);
         gDevTools.emit("select-tool-command", toolId);
       });
     }
   },
 
   /**
+   * Called by devtools/client/devtools-startup.js when a key shortcut is pressed
+   *
+   * @param  {Window} window
+   *         The top level browser window from which the key shortcut is pressed.
+   * @param  {Object} key
+   *         Key object describing the key shortcut being pressed. It comes
+   *         from devtools-startup.js's KeyShortcuts array. The useful fields here
+   *         are:
+   *         - `toolId` used to identify a toolbox's panel like inspector or webconsole,
+   *         - `id` used to identify any other key shortcuts like scratchpad or
+   *         about:debugging
+   */
+  onKeyShortcut(window, key) {
+    // If this is a toolbox's panel key shortcut, delegate to selectToolCommand
+    if (key.toolId) {
+      gDevToolsBrowser.selectToolCommand(window.gBrowser, key.toolId);
+      return;
+    }
+    // Otherwise implement all other key shortcuts individually here
+    switch (key.id) {
+      case "toggleToolbox":
+      case "toggleToolboxF12":
+        gDevToolsBrowser.toggleToolboxCommand(window.gBrowser);
+        break;
+      case "toggleToolbar":
+        window.DeveloperToolbar.focusToggle();
+        break;
+      case "webide":
+        gDevToolsBrowser.openWebIDE();
+        break;
+      case "browserToolbox":
+        BrowserToolboxProcess.init();
+        break;
+      case "browserConsole":
+        let HUDService = require("devtools/client/webconsole/hudservice");
+        HUDService.openBrowserConsoleOrFocus();
+        break;
+      case "responsiveDesignMode":
+        ResponsiveUIManager.toggle(window, window.gBrowser.selectedTab);
+        break;
+      case "scratchpad":
+        ScratchpadManager.openScratchpad();
+        break;
+    }
+  },
+
+  /**
    * Open a tab on "about:debugging", optionally pre-select a given tab.
    */
    // Used by browser-sets.inc, command
   openAboutDebugging(gBrowser, hash) {
     let url = "about:debugging" + (hash ? "#" + hash : "");
     gBrowser.selectedTab = gBrowser.addTab(url);
   },
 
new file mode 100644
--- /dev/null
+++ b/devtools/client/locales/en-US/key-shortcuts.properties
@@ -0,0 +1,67 @@
+# 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/.
+
+# LOCALIZATION NOTE (toogleToolbox.commandkey):
+# Key pressed to open a toolbox with the default panel selected
+toggleToolbox.commandkey=I
+
+# LOCALIZATION NOTE (toogleToolboxF12.commandkey):
+# Alternative key pressed to open a toolbox with the default panel selected
+toggleToolboxF12.commandkey=VK_F12
+
+# LOCALIZATION NOTE (toogleToolbar.commandkey):
+# Key pressed to open the Developer Toolbar (a.k.a gcli)
+toggleToolbar.commandkey=VK_F2
+
+# LOCALIZATION NOTE (webide.commandkey):
+# Key pressed to open the Web IDE window
+webide.commandkey=VK_F8
+
+# LOCALIZATION NOTE (browserToolbox.commandkey):
+# Key pressed to open the Browser Toolbox, used for debugging Firefox itself
+browserToolbox.commandkey=I
+
+# LOCALIZATION NOTE (browserConsole.commandkey):
+# Key pressed to open the Browser Console, used for debugging Firefox itself
+browserConsole.commandkey=J
+
+# LOCALIZATION NOTE (responsiveDesignMode.commandkey):
+# Key pressed to toggle on the Responsive Design Mode
+responsiveDesignMode.commandkey=M
+
+# LOCALIZATION NOTE (scratchpad.commandkey):
+# Key pressed to open the Scratchpad in its own window
+scratchpad.commandkey=VK_F4
+
+# LOCALIZATION NOTE (inspector.commandkey):
+# Key pressed to open a toolbox with the inspector panel selected
+inspector.commandkey=C
+
+# LOCALIZATION NOTE (webconsole.commandkey):
+# Key pressed to open a toolbox with the web console panel selected
+webconsole.commandkey=K
+
+# LOCALIZATION NOTE (debugger.commandkey):
+# Key pressed to open a toolbox with the debugger panel selected
+debugger.commandkey=S
+
+# LOCALIZATION NOTE (netmonitor.commandkey):
+# Key pressed to open a toolbox with the network monitor panel selected
+netmonitor.commandkey=E
+
+# LOCALIZATION NOTE (styleeditor.commandkey):
+# Key pressed to open a toolbox with the style editor panel selected
+styleeditor.commandkey=VK_F7
+
+# LOCALIZATION NOTE (performance.commandkey):
+# Key pressed to open a toolbox with the performance panel selected
+performance.commandkey=VK_F5
+
+# LOCALIZATION NOTE (storage.commandkey):
+# Key pressed to open a toolbox with the storage panel selected
+storage.commandkey=VK_F9
+
+# LOCALIZATION NOTE (dom.commandkey):
+# Key pressed to open a toolbox with the DOM panel selected
+dom.commandkey=W
--- a/devtools/client/locales/en-US/menus.properties
+++ b/devtools/client/locales/en-US/menus.properties
@@ -1,67 +1,54 @@
 # 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/.
 
-devToolsCmd.key = VK_F12
-devToolsCmd.keytext = F12
-
 devtoolsServiceWorkers.label = Service Workers
 devtoolsServiceWorkers.accesskey = k
 
 devtoolsConnect.label = Connect…
 devtoolsConnect.accesskey = C
 
 browserConsoleCmd.label = Browser Console
 browserConsoleCmd.accesskey = B
-browserConsoleCmd.key = j
 
 responsiveDesignMode.label = Responsive Design Mode
 responsiveDesignMode.accesskey = R
-responsiveDesignMode.key = M
 
 eyedropper.label = Eyedropper
 eyedropper.accesskey = Y
 
 # LOCALIZATION NOTE (scratchpad.label): This menu item label appears
 # in the Tools menu. See bug 653093.
 # The Scratchpad is intended to provide a simple text editor for creating
 # and evaluating bits of JavaScript code for the purposes of function
 # prototyping, experimentation and convenient scripting.
 #
 # It's quite possible that you won't have a good analogue for the word
 # "Scratchpad" in your locale. You should feel free to find a close
 # approximation to it or choose a word (or words) that means
 # "simple discardable text editor".
 scratchpad.label = Scratchpad
 scratchpad.accesskey = s
-scratchpad.key = VK_F4
-scratchpad.keytext = F4
 
 # LOCALIZATION NOTE (browserToolboxMenu.label): This is the label for the
 # application menu item that opens the browser toolbox UI in the Tools menu.
 browserToolboxMenu.label = Browser Toolbox
 browserToolboxMenu.accesskey = e
-browserToolboxMenu.key = i
 
 # LOCALIZATION NOTE (browserContentToolboxMenu.label): This is the label for the
 # application menu item that opens the browser content toolbox UI in the Tools menu.
 # This toolbox allows to debug the chrome of the content process in multiprocess builds.
 browserContentToolboxMenu.label = Browser Content Toolbox
 browserContentToolboxMenu.accesskey = x
 
 devToolbarMenu.label = Developer Toolbar
 devToolbarMenu.accesskey = v
-devToolbarMenu.key = VK_F2
-devToolbarMenu.keytext = F2
 
 webide.label = WebIDE
 webide.accesskey = W
-webide.key = VK_F8
-webide.keytext = F8
 
 devToolboxMenuItem.label = Toggle Tools
 devToolboxMenuItem.accesskey = T
-devToolboxMenuItem.key = I
 
 getMoreDevtoolsCmd.label = Get More Tools
 getMoreDevtoolsCmd.accesskey = M
--- a/devtools/client/locales/en-US/startup.properties
+++ b/devtools/client/locales/en-US/startup.properties
@@ -30,19 +30,18 @@ options.firebugTheme.label2=Firebug
 # This string is displayed in the title of the tab when the profiler is
 # displayed inside the developer tools window and in the Developer Tools Menu.
 performance.label=Performance
 
 # LOCALIZATION NOTE (performance.panelLabel):
 # This is used as the label for the toolbox panel.
 performance.panelLabel=Performance Panel
 
-# LOCALIZATION NOTE (performance.commandkey, performance.accesskey)
+# LOCALIZATION NOTE (performance.accesskey)
 # Used for the menuitem in the tool menu
-performance.commandkey=VK_F5
 performance.accesskey=P
 
 # LOCALIZATION NOTE (performance.tooltip):
 # This string is displayed in the tooltip of the tab when the profiler is
 # displayed inside the developer tools window.
 # Keyboard shortcut for Performance Tools will be shown inside brackets.
 performance.tooltip=Performance (%S)
 
@@ -59,17 +58,16 @@ ToolboxTabWebconsole.label=Console
 ToolboxWebConsole.panelLabel=Console Panel
 
 # LOCALIZATION NOTE (ToolboxWebconsole.tooltip2): the string displayed in the
 # tooltip of the tab when the Web Console is displayed inside the developer
 # tools window.
 # Keyboard shortcut for Console will be shown inside the brackets.
 ToolboxWebconsole.tooltip2=Web Console (%S)
 
-cmd.commandkey=K
 webConsoleCmd.accesskey=W
 
 # LOCALIZATION NOTE (ToolboxDebugger.label):
 # This string is displayed in the title of the tab when the debugger is
 # displayed inside the developer tools window and in the Developer Tools Menu.
 ToolboxDebugger.label=Debugger
 
 # LOCALIZATION NOTE (ToolboxDebugger.panelLabel):
@@ -77,19 +75,18 @@ ToolboxDebugger.label=Debugger
 ToolboxDebugger.panelLabel=Debugger Panel
 
 # LOCALIZATION NOTE (ToolboxDebugger.tooltip2):
 # This string is displayed in the tooltip of the tab when the debugger is
 # displayed inside the developer tools window..
 # A keyboard shortcut for JS Debugger will be shown inside brackets.
 ToolboxDebugger.tooltip2=JavaScript Debugger (%S)
 
-# LOCALIZATION NOTE (debuggerMenu.commandkey, debuggerMenu.accesskey)
+# LOCALIZATION NOTE (debuggerMenu.accesskey)
 # Used for the menuitem in the tool menu
-debuggerMenu.commandkey=S
 debuggerMenu.accesskey=D
 
 # LOCALIZATION NOTE (ToolboxStyleEditor.label):
 # This string is displayed in the title of the tab when the style editor is
 # displayed inside the developer tools window and in the Developer Tools Menu.
 ToolboxStyleEditor.label=Style Editor
 
 # LOCALIZATION NOTE (ToolboxStyleEditor.panelLabel):
@@ -97,20 +94,16 @@ ToolboxStyleEditor.label=Style Editor
 ToolboxStyleEditor.panelLabel=Style Editor Panel
 
 # LOCALIZATION NOTE (ToolboxStyleEditor.tooltip3):
 # This string is displayed in the tooltip of the tab when the style editor is
 # displayed inside the developer tools window.
 # A keyboard shortcut for Stylesheet Editor will be shown inside the latter pair of brackets.
 ToolboxStyleEditor.tooltip3=Stylesheet Editor (CSS) (%S)
 
-# LOCALIZATION NOTE  (open.commandkey): This the key to use in
-# conjunction with shift to open the style editor
-open.commandkey=VK_F7
-
 # LOCALIZATION NOTE (open.accesskey): The access key used to open the style
 # editor.
 open.accesskey=l
 
 # LOCALIZATION NOTE (ToolboxShaderEditor.label):
 # This string is displayed in the title of the tab when the Shader Editor is
 # displayed inside the developer tools window and in the Developer Tools Menu.
 ToolboxShaderEditor.label=Shader Editor
@@ -150,17 +143,16 @@ ToolboxWebAudioEditor1.panelLabel=Web Au
 # LOCALIZATION NOTE (ToolboxWebAudioEditor1.tooltip):
 # This string is displayed in the tooltip of the tab when the Web Audio Editor is
 # displayed inside the developer tools window.
 ToolboxWebAudioEditor1.tooltip=Web Audio context visualizer and audio node inspector
 
 # LOCALIZATION NOTE (inspector.*)
 # Used for the menuitem in the tool menu
 inspector.label=Inspector
-inspector.commandkey=C
 inspector.accesskey=I
 
 # LOCALIZATION NOTE (inspector.panelLabel)
 # Labels applied to the panel and views within the panel in the toolbox
 inspector.panelLabel=Inspector Panel
 
 # LOCALIZATION NOTE (inspector.tooltip2)
 # Keyboard shortcut for DOM and Style Inspector will be shown inside brackets.
@@ -170,31 +162,26 @@ inspector.tooltip2=DOM and Style Inspect
 # This string is displayed in the title of the tab when the Network Monitor is
 # displayed inside the developer tools window and in the Developer Tools Menu.
 netmonitor.label=Network
 
 # LOCALIZATION NOTE (netmonitor.panelLabel):
 # This is used as the label for the toolbox panel.
 netmonitor.panelLabel=Network Panel
 
-# LOCALIZATION NOTE (netmonitor.commandkey2, netmonitor.accesskey)
+# LOCALIZATION NOTE (netmonitor.accesskey)
 # Used for the menuitem in the tool menu
-netmonitor.commandkey2=E
 netmonitor.accesskey=N
 
 # LOCALIZATION NOTE (netmonitor.tooltip2):
 # This string is displayed in the tooltip of the tab when the Network Monitor is
 # displayed inside the developer tools window.
 # Keyboard shortcut for Network Monitor will be shown inside the brackets.
 netmonitor.tooltip2=Network Monitor (%S)
 
-# LOCALIZATION NOTE  (storage.commandkey): This the key to use in
-# conjunction with shift to open the storage editor
-storage.commandkey=VK_F9
-
 # LOCALIZATION NOTE (storage.accesskey): The access key used to open the storage
 # editor.
 storage.accesskey=a
 
 # LOCALIZATION NOTE (storage.label):
 # This string is displayed as the label of the tab in the developer tools window
 storage.label=Storage
 
@@ -245,19 +232,18 @@ memory.tooltip=Memory
 # This string is displayed in the title of the tab when the DOM panel is
 # displayed inside the developer tools window and in the Developer Tools Menu.
 dom.label=DOM
 
 # LOCALIZATION NOTE (dom.panelLabel):
 # This is used as the label for the toolbox panel.
 dom.panelLabel=DOM Panel
 
-# LOCALIZATION NOTE (dom.commandkey, dom.accesskey)
+# LOCALIZATION NOTE (dom.accesskey)
 # Used for the menuitem in the tool menu
-dom.commandkey=W
 dom.accesskey=D
 
 # LOCALIZATION NOTE (dom.tooltip):
 # This string is displayed in the tooltip of the tab when the DOM is
 # displayed inside the developer tools window.
 # Keyboard shortcut for DOM panel will be shown inside the brackets.
 dom.tooltip=DOM (%S)
 
--- a/devtools/client/menus.js
+++ b/devtools/client/menus.js
@@ -11,62 +11,44 @@
  *
  * Various fields are necessary for historical compatiblity with XUL/addons:
  * - id:
  *   used as <xul:menuitem> id attribute
  * - l10nKey:
  *   prefix used to locale localization strings from menus.properties
  * - oncommand:
  *   function called when the menu item or key shortcut are fired
- * - key:
- *    - id:
- *      prefixed by 'key_' to compute <xul:key> id attribute
- *    - modifiers:
- *      optional modifiers for the key shortcut
- *    - keytext:
- *      boolean, to set to true for key shortcut using regular character
- * - additionalKeys:
- *   Array of additional keys, see `key` definition.
+ * - keyId:
+ *   Identifier used in devtools/client/devtools-startup.js
+ *   Helps figuring out the DOM id for the related <xul:key>
+ *   in order to have the key text displayed in menus.
  * - disabled:
  *   If true, the menuitem and key shortcut are going to be hidden and disabled
  *   on startup, until some runtime code eventually enable them.
  * - checkbox:
  *   If true, the menuitem is prefixed by a checkbox and runtime code can
  *   toggle it.
  */
 
-const Services = require("Services");
-const isMac = Services.appinfo.OS === "Darwin";
-
 loader.lazyRequireGetter(this, "gDevToolsBrowser", "devtools/client/framework/devtools-browser", true);
 loader.lazyRequireGetter(this, "CommandUtils", "devtools/client/shared/developer-toolbar", true);
 loader.lazyRequireGetter(this, "TargetFactory", "devtools/client/framework/target", true);
 
 loader.lazyImporter(this, "BrowserToolboxProcess", "resource://devtools/client/framework/ToolboxProcess.jsm");
 loader.lazyImporter(this, "ResponsiveUIManager", "resource://devtools/client/responsivedesign/responsivedesign.jsm");
 loader.lazyImporter(this, "ScratchpadManager", "resource://devtools/client/scratchpad/scratchpad-manager.jsm");
 
 exports.menuitems = [
   { id: "menu_devToolbox",
     l10nKey: "devToolboxMenuItem",
     oncommand(event) {
       let window = event.target.ownerDocument.defaultView;
       gDevToolsBrowser.toggleToolboxCommand(window.gBrowser);
     },
-    key: {
-      id: "devToolboxMenuItem",
-      modifiers: isMac ? "accel,alt" : "accel,shift",
-      // This is the only one with a letter key
-      // and needs to be translated differently
-      keytext: true,
-    },
-    additionalKeys: [{
-      id: "devToolboxMenuItemF12",
-      l10nKey: "devToolsCmd",
-    }],
+    keyId: "toggleToolbox",
     checkbox: true
   },
   { id: "menu_devtools_separator",
     separator: true },
   { id: "menu_devToolbar",
     l10nKey: "devToolbarMenu",
     disabled: true,
     oncommand(event) {
@@ -75,76 +57,58 @@ exports.menuitems = [
       // or close the toolbar and when hitting the key shortcut where we just
       // focus the toolbar if it doesn't already has it.
       if (event.target.tagName.toLowerCase() == "menuitem") {
         gDevToolsBrowser.getDeveloperToolbar(window).toggle();
       } else {
         gDevToolsBrowser.getDeveloperToolbar(window).focusToggle();
       }
     },
-    key: {
-      id: "devToolbar",
-      modifiers: "shift"
-    },
+    keyId: "toggleToolbar",
     checkbox: true
   },
   { id: "menu_webide",
     l10nKey: "webide",
     disabled: true,
     oncommand() {
       gDevToolsBrowser.openWebIDE();
     },
-    key: {
-      id: "webide",
-      modifiers: "shift"
-    }
+    keyId: "webide",
   },
   { id: "menu_browserToolbox",
     l10nKey: "browserToolboxMenu",
     disabled: true,
     oncommand() {
       BrowserToolboxProcess.init();
     },
-    key: {
-      id: "browserToolbox",
-      modifiers: "accel,alt,shift",
-      keytext: true
-    }
+    keyId: "browserToolbox",
   },
   { id: "menu_browserContentToolbox",
     l10nKey: "browserContentToolboxMenu",
     disabled: true,
     oncommand(event) {
       let window = event.target.ownerDocument.defaultView;
       gDevToolsBrowser.openContentProcessToolbox(window.gBrowser);
     }
   },
   { id: "menu_browserConsole",
     l10nKey: "browserConsoleCmd",
     oncommand() {
       let HUDService = require("devtools/client/webconsole/hudservice");
       HUDService.openBrowserConsoleOrFocus();
     },
-    key: {
-      id: "browserConsole",
-      modifiers: "accel,shift",
-      keytext: true
-    }
+    keyId: "browserConsole",
   },
   { id: "menu_responsiveUI",
     l10nKey: "responsiveDesignMode",
     oncommand(event) {
       let window = event.target.ownerDocument.defaultView;
       ResponsiveUIManager.toggle(window, window.gBrowser.selectedTab);
     },
-    key: {
-      id: "responsiveUI",
-      modifiers: isMac ? "accel,alt" : "accel,shift",
-      keytext: true
-    },
+    keyId: "responsiveDesignMode",
     checkbox: true
   },
   { id: "menu_eyedropper",
     l10nKey: "eyedropper",
     oncommand(event) {
       let window = event.target.ownerDocument.defaultView;
       let target = TargetFactory.forTab(window.gBrowser.selectedTab);
 
@@ -152,20 +116,17 @@ exports.menuitems = [
     },
     checkbox: true
   },
   { id: "menu_scratchpad",
     l10nKey: "scratchpad",
     oncommand() {
       ScratchpadManager.openScratchpad();
     },
-    key: {
-      id: "scratchpad",
-      modifiers: "shift"
-    }
+    keyId: "scratchpad",
   },
   { id: "menu_devtools_serviceworkers",
     l10nKey: "devtoolsServiceWorkers",
     disabled: true,
     oncommand(event) {
       let window = event.target.ownerDocument.defaultView;
       gDevToolsBrowser.openAboutDebugging(window.gBrowser, "workers");
     }
--- a/devtools/shared/tests/browser/browser_l10n_localizeMarkup.js
+++ b/devtools/shared/tests/browser/browser_l10n_localizeMarkup.js
@@ -7,31 +7,31 @@
 
 const { localizeMarkup, LocalizationHelper } = require("devtools/shared/l10n");
 
 add_task(function* () {
   info("Check that the strings used for this test are still valid");
   let STARTUP_L10N = new LocalizationHelper("devtools/client/locales/startup.properties");
   let TOOLBOX_L10N = new LocalizationHelper("devtools/client/locales/toolbox.properties");
   let str1 = STARTUP_L10N.getStr("inspector.label");
-  let str2 = STARTUP_L10N.getStr("inspector.commandkey");
+  let str2 = STARTUP_L10N.getStr("inspector.accesskey");
   let str3 = TOOLBOX_L10N.getStr("toolbox.defaultTitle");
   ok(str1 && str2 && str3, "If this failed, strings should be updated in the test");
 
   info("Create the test markup");
   let div = document.createElement("div");
   div.innerHTML =
   `<div data-localization-bundle="devtools/client/locales/startup.properties">
      <div id="d0" data-localization="content=inspector.someInvalidKey"></div>
      <div id="d1" data-localization="content=inspector.label">Text will disappear</div>
-     <div id="d2" data-localization="content=inspector.label;title=inspector.commandkey">
+     <div id="d2" data-localization="content=inspector.label;title=inspector.accesskey">
      </div>
      <!-- keep the following data-localization on two separate lines -->
      <div id="d3" data-localization="content=inspector.label;
-                                     title=inspector.commandkey"></div>
+                                     title=inspector.accesskey"></div>
      <div id="d4" data-localization="aria-label=inspector.label">Some content</div>
      <div data-localization-bundle="devtools/client/locales/toolbox.properties">
        <div id="d5" data-localization="content=toolbox.defaultTitle"></div>
      </div>
    </div>
   `;
 
   info("Use localization helper to localize the test markup");