Bug 1266478 - Integrate Menu API with inspector;r=pbro draft
authorBrian Grinstead <bgrinstead@mozilla.com>
Fri, 10 Jun 2016 13:02:59 -0700
changeset 377588 52ce732c12a54502b924f002463b36e4a44b91fa
parent 377326 59fbde27dc99259f88e3dddce64707b2884eefff
child 523390 b57f71a2fe3354efdfef1988f1b0da88a630154a
push id20835
push userbgrinstead@mozilla.com
push dateFri, 10 Jun 2016 20:03:23 +0000
reviewerspbro
bugs1266478
milestone50.0a1
Bug 1266478 - Integrate Menu API with inspector;r=pbro This patch converts the inspector's context menu implementation away from directly using XUL menus, and updates a number of tests to match. MozReview-Commit-ID: L8aL23BUmXS
devtools/client/inspector/breadcrumbs.js
devtools/client/inspector/inspector-panel.js
devtools/client/inspector/inspector.xul
devtools/client/inspector/markup/test/browser_markup_copy_image_data.js
devtools/client/inspector/markup/test/browser_markup_links_04.js
devtools/client/inspector/markup/test/browser_markup_links_05.js
devtools/client/inspector/markup/test/browser_markup_links_06.js
devtools/client/inspector/markup/test/head.js
devtools/client/inspector/test/browser_inspector_addNode_01.js
devtools/client/inspector/test/browser_inspector_addNode_02.js
devtools/client/inspector/test/browser_inspector_delete-selected-node-02.js
devtools/client/inspector/test/browser_inspector_expand-collapse.js
devtools/client/inspector/test/browser_inspector_menu-01-sensitivity.js
devtools/client/inspector/test/browser_inspector_menu-02-copy-items.js
devtools/client/inspector/test/browser_inspector_menu-03-paste-items.js
devtools/client/inspector/test/browser_inspector_menu-04-use-in-console.js
devtools/client/inspector/test/browser_inspector_menu-05-attribute-items.js
devtools/client/inspector/test/browser_inspector_menu-06-other.js
devtools/client/inspector/test/browser_inspector_pseudoclass-menu.js
devtools/client/inspector/test/head.js
devtools/client/locales/en-US/inspector.dtd
devtools/client/locales/en-US/inspector.properties
--- a/devtools/client/inspector/breadcrumbs.js
+++ b/devtools/client/inspector/breadcrumbs.js
@@ -810,20 +810,16 @@ HTMLBreadcrumbs.prototype = {
    * @param {Array} mutations An array of mutations in case this was called as
    * the "markupmutation" event listener.
    */
   update: function (reason, mutations) {
     if (this.isDestroyed) {
       return;
     }
 
-    if (reason !== "markupmutation") {
-      this.inspector.hideNodeMenu();
-    }
-
     let hasInterestingMutations = this._hasInterestingMutations(mutations);
     if (reason === "markupmutation" && !hasInterestingMutations) {
       return;
     }
 
     let cmdDispatcher = this.chromeDoc.commandDispatcher;
     this.hadFocus = (cmdDispatcher.focusedElement &&
                      cmdDispatcher.focusedElement.parentNode == this.container);
--- a/devtools/client/inspector/inspector-panel.js
+++ b/devtools/client/inspector/inspector-panel.js
@@ -15,16 +15,19 @@ var promise = require("promise");
 var EventEmitter = require("devtools/shared/event-emitter");
 var clipboard = require("sdk/clipboard");
 const {executeSoon} = require("devtools/shared/DevToolsUtils");
 var {KeyShortcuts} = require("devtools/client/shared/key-shortcuts");
 var {Task} = require("devtools/shared/task");
 const {initCssProperties} = require("devtools/shared/fronts/css-properties");
 const nodeConstants = require("devtools/shared/dom-node-constants");
 
+const Menu = require("devtools/client/framework/menu");
+const MenuItem = require("devtools/client/framework/menu-item");
+
 loader.lazyRequireGetter(this, "CSS", "CSS");
 
 loader.lazyRequireGetter(this, "CommandUtils", "devtools/client/shared/developer-toolbar", true);
 loader.lazyRequireGetter(this, "ComputedViewTool", "devtools/client/inspector/computed/computed", true);
 loader.lazyRequireGetter(this, "FontInspector", "devtools/client/inspector/fonts/fonts", true);
 loader.lazyRequireGetter(this, "HTMLBreadcrumbs", "devtools/client/inspector/breadcrumbs", true);
 loader.lazyRequireGetter(this, "InspectorSearch", "devtools/client/inspector/inspector-search", true);
 loader.lazyRequireGetter(this, "LayoutView", "devtools/client/inspector/layout/layout", true);
@@ -85,33 +88,33 @@ function InspectorPanel(iframeWindow, to
   this.panelDoc = iframeWindow.document;
   this.panelWin = iframeWindow;
   this.panelWin.inspector = this;
 
   this.nodeMenuTriggerInfo = null;
 
   this._onBeforeNavigate = this._onBeforeNavigate.bind(this);
   this.onNewRoot = this.onNewRoot.bind(this);
-  this._setupNodeMenu = this._setupNodeMenu.bind(this);
-  this._resetNodeMenu = this._resetNodeMenu.bind(this);
+  this._onContextMenu = this._onContextMenu.bind(this);
   this._updateSearchResultsLabel = this._updateSearchResultsLabel.bind(this);
   this.onNewSelection = this.onNewSelection.bind(this);
   this.onBeforeNewSelection = this.onBeforeNewSelection.bind(this);
   this.onDetached = this.onDetached.bind(this);
   this.onPaneToggleButtonClicked = this.onPaneToggleButtonClicked.bind(this);
   this._onMarkupFrameLoad = this._onMarkupFrameLoad.bind(this);
 
   let doc = this.panelDoc;
 
   // Handle 'Add Node' toolbar button.
   this.addNode = this.addNode.bind(this);
   this.addNodeButton = doc.getElementById("inspector-element-add-button");
   this.addNodeButton.addEventListener("click", this.addNode);
 
   this._target.on("will-navigate", this._onBeforeNavigate);
+  this._detectingActorFeatures = this._detectActorFeatures();
 
   EventEmitter.decorate(this);
 }
 
 exports.InspectorPanel = InspectorPanel;
 
 InspectorPanel.prototype = {
   /**
@@ -157,26 +160,42 @@ InspectorPanel.prototype = {
   get canGetUsedFontFaces() {
     return this._target.client.traits.getUsedFontFaces;
   },
 
   get canPasteInnerOrAdjacentHTML() {
     return this._target.client.traits.pasteHTML;
   },
 
+  /**
+   * Figure out what features the backend supports
+   */
+  _detectActorFeatures: function () {
+    this._supportsDuplicateNode = false;
+    this._supportsScrollIntoView = false;
+    this._supportsResolveRelativeURL = false;
+
+    return promise.all([
+      this._target.actorHasMethod("domwalker", "duplicateNode").then(value => {
+        this._supportsDuplicateNode = value;
+      }).catch(e => console.error(e)),
+      this._target.actorHasMethod("domnode", "scrollIntoView").then(value => {
+        this._supportsScrollIntoView = value;
+      }).catch(e => console.error(e)),
+      this._target.actorHasMethod("inspector", "resolveRelativeURL").then(value => {
+        this._supportsResolveRelativeURL = value;
+      }).catch(e => console.error(e)),
+    ]);
+  },
+
   _deferredOpen: function (defaultSelection) {
     let deferred = promise.defer();
 
     this.walker.on("new-root", this.onNewRoot);
 
-    this.nodemenu = this.panelDoc.getElementById("inspector-node-popup");
-    this.lastNodemenuItem = this.nodemenu.lastChild;
-    this.nodemenu.addEventListener("popupshowing", this._setupNodeMenu, true);
-    this.nodemenu.addEventListener("popuphiding", this._resetNodeMenu, true);
-
     this.selection.on("new-node-front", this.onNewSelection);
     this.selection.on("before-new-node-front", this.onBeforeNewSelection);
     this.selection.on("detached-front", this.onDetached);
 
     this.breadcrumbs = new HTMLBreadcrumbs(this);
 
     if (this.target.isLocalTab) {
       // Show a warning when the debugger is paused.
@@ -663,353 +682,417 @@ InspectorPanel.prototype = {
       }
     });
 
     this.sidebar.off("select", this._setDefaultSidebar);
     let sidebarDestroyer = this.sidebar.destroy();
     this.sidebar = null;
 
     this.addNodeButton.removeEventListener("click", this.addNode);
-
-    this.nodemenu.removeEventListener("popupshowing", this._setupNodeMenu, true);
-    this.nodemenu.removeEventListener("popuphiding", this._resetNodeMenu, true);
     this.breadcrumbs.destroy();
     this._paneToggleButton.removeEventListener("mousedown",
       this.onPaneToggleButtonClicked);
     this._paneToggleButton = null;
     this.selection.off("new-node-front", this.onNewSelection);
     this.selection.off("before-new-node", this.onBeforeNewSelection);
     this.selection.off("before-new-node-front", this.onBeforeNewSelection);
     this.selection.off("detached-front", this.onDetached);
     let markupDestroyer = this._destroyMarkup();
     this.panelWin.inspector = null;
     this.target = null;
     this.panelDoc = null;
     this.panelWin = null;
     this.breadcrumbs = null;
-    this.lastNodemenuItem = null;
-    this.nodemenu = null;
     this._toolbox = null;
     this.search.destroy();
     this.search = null;
     this.searchBox = null;
 
     this._panelDestroyer = promise.all([
       sidebarDestroyer,
       markupDestroyer,
       cssPropertiesDestroyer
     ]);
 
     return this._panelDestroyer;
   },
 
   /**
-   * Show the node menu.
-   */
-  showNodeMenu: function (button, position, extraItems) {
-    if (extraItems) {
-      for (let item of extraItems) {
-        this.nodemenu.appendChild(item);
-      }
-    }
-    this.nodemenu.openPopup(button, position, 0, 0, true, false);
-  },
-
-  hideNodeMenu: function () {
-    this.nodemenu.hidePopup();
-  },
-
-  /**
    * Returns the clipboard content if it is appropriate for pasting
    * into the current node's outer HTML, otherwise returns null.
    */
   _getClipboardContentForPaste: function () {
     let flavors = clipboard.currentFlavors;
     if (flavors.indexOf("text") != -1 ||
         (flavors.indexOf("html") != -1 && flavors.indexOf("image") == -1)) {
       let content = clipboard.get();
       if (content && content.trim().length > 0) {
         return content;
       }
     }
     return null;
   },
 
-  /**
-   * Update, enable, disable, hide, show any menu item depending on the current
-   * element.
-   */
-  _setupNodeMenu: function (event) {
+  _onContextMenu: function (e) {
+    e.preventDefault();
+    this._openMenu({
+      screenX: e.screenX,
+      screenY: e.screenY,
+      target: e.target,
+    });
+  },
+
+  _openMenu: function ({ target, screenX = 0, screenY = 0 } = { }) {
     let markupContainer = this.markup.getContainer(this.selection.nodeFront);
-    this.nodeMenuTriggerInfo =
-      markupContainer.editor.getInfoAtNode(event.target.triggerNode);
+
+    this.contextMenuTarget = target;
+    this.nodeMenuTriggerInfo = markupContainer &&
+      markupContainer.editor.getInfoAtNode(target);
 
     let isSelectionElement = this.selection.isElementNode() &&
                              !this.selection.isPseudoElementNode();
     let isEditableElement = isSelectionElement &&
                             !this.selection.isAnonymousNode();
     let isDuplicatableElement = isSelectionElement &&
                                 !this.selection.isAnonymousNode() &&
                                 !this.selection.isRoot();
     let isScreenshotable = isSelectionElement &&
                            this.canGetUniqueSelector &&
                            this.selection.nodeFront.isTreeDisplayed;
 
+    let menu = new Menu();
+    menu.append(new MenuItem({
+      id: "node-menu-edithtml",
+      label: strings.GetStringFromName("inspectorHTMLEdit.label"),
+      accesskey: strings.GetStringFromName("inspectorHTMLEdit.accesskey"),
+      disabled: !isEditableElement || !this.isOuterHTMLEditable,
+      click: () => this.editHTML(),
+    }));
+    menu.append(new MenuItem({
+      id: "node-menu-add",
+      label: strings.GetStringFromName("inspectorAddNode.label"),
+      accesskey: strings.GetStringFromName("inspectorAddNode.accesskey"),
+      disabled: !this.canAddHTMLChild(),
+      click: () => this.addNode(),
+    }));
+    menu.append(new MenuItem({
+      id: "node-menu-duplicatenode",
+      label: strings.GetStringFromName("inspectorDuplicateNode.label"),
+      hidden: !this._supportsDuplicateNode,
+      disabled: !isDuplicatableElement,
+      click: () => this.duplicateNode(),
+    }));
+    menu.append(new MenuItem({
+      id: "node-menu-delete",
+      label: strings.GetStringFromName("inspectorHTMLDelete.label"),
+      accesskey: strings.GetStringFromName("inspectorHTMLDelete.accesskey"),
+      disabled: !isEditableElement,
+      click: () => this.deleteNode(),
+    }));
+
+    menu.append(new MenuItem({
+      label: strings.GetStringFromName("inspectorAttributesSubmenu.label"),
+      accesskey:
+        strings.GetStringFromName("inspectorAttributesSubmenu.accesskey"),
+      submenu: this._getAttributesSubmenu(isEditableElement),
+    }));
+
+    menu.append(new MenuItem({
+      type: "separator",
+    }));
+
     // Set the pseudo classes
     for (let name of ["hover", "active", "focus"]) {
-      let menu = this.panelDoc.getElementById("node-menu-pseudo-" + name);
+      let menuitem = new MenuItem({
+        id: "node-menu-pseudo-" + name,
+        label: name,
+        type: "checkbox",
+        click: this.togglePseudoClass.bind(this, ":" + name),
+      });
 
       if (isSelectionElement) {
         let checked = this.selection.nodeFront.hasPseudoClassLock(":" + name);
-        menu.setAttribute("checked", checked);
-        menu.removeAttribute("disabled");
+        menuitem.checked = checked;
       } else {
-        menu.setAttribute("disabled", "true");
+        menuitem.disabled = true;
       }
-    }
-
-    // Disable delete item if needed
-    let deleteNode = this.panelDoc.getElementById("node-menu-delete");
-    if (isEditableElement) {
-      deleteNode.removeAttribute("disabled");
-    } else {
-      deleteNode.setAttribute("disabled", "true");
-    }
-
-    // Disable add item if needed
-    let addNode = this.panelDoc.getElementById("node-menu-add");
-    if (this.canAddHTMLChild()) {
-      addNode.removeAttribute("disabled");
-    } else {
-      addNode.setAttribute("disabled", "true");
-    }
-
-    // Disable / enable "Copy Unique Selector", "Copy inner HTML",
-    // "Copy outer HTML", "Scroll Into View" & "Screenshot Node" as appropriate
-    let unique = this.panelDoc.getElementById("node-menu-copyuniqueselector");
-    let screenshot = this.panelDoc.getElementById("node-menu-screenshotnode");
-    let duplicateNode = this.panelDoc.getElementById("node-menu-duplicatenode");
-    let copyInnerHTML = this.panelDoc.getElementById("node-menu-copyinner");
-    let copyOuterHTML = this.panelDoc.getElementById("node-menu-copyouter");
-    let scrollIntoView = this.panelDoc.getElementById("node-menu-scrollnodeintoview");
-    let expandAll = this.panelDoc.getElementById("node-menu-expand");
-    let collapse = this.panelDoc.getElementById("node-menu-collapse");
-
-    expandAll.setAttribute("disabled", "true");
-    collapse.setAttribute("disabled", "true");
 
-    if (this.selection.isNode() && markupContainer.hasChildren) {
-      if (markupContainer.expanded) {
-        collapse.removeAttribute("disabled");
-      }
-      expandAll.removeAttribute("disabled");
-    }
-
-    this._target.actorHasMethod("domwalker", "duplicateNode").then(value => {
-      duplicateNode.hidden = !value;
-    });
-    this._target.actorHasMethod("domnode", "scrollIntoView").then(value => {
-      scrollIntoView.hidden = !value;
-    });
-
-    if (isDuplicatableElement) {
-      duplicateNode.removeAttribute("disabled");
-    } else {
-      duplicateNode.setAttribute("disabled", "true");
-    }
-
-    if (isSelectionElement) {
-      unique.removeAttribute("disabled");
-      copyInnerHTML.removeAttribute("disabled");
-      copyOuterHTML.removeAttribute("disabled");
-      scrollIntoView.removeAttribute("disabled");
-    } else {
-      unique.setAttribute("disabled", "true");
-      copyInnerHTML.setAttribute("disabled", "true");
-      copyOuterHTML.setAttribute("disabled", "true");
-      scrollIntoView.setAttribute("disabled", "true");
-    }
-    if (!this.canGetUniqueSelector) {
-      unique.hidden = true;
-    }
-
-    if (isScreenshotable) {
-      screenshot.removeAttribute("disabled");
-    } else {
-      screenshot.setAttribute("disabled", "true");
+      menu.append(menuitem);
     }
 
-    // Enable/Disable the link open/copy items.
-    this._setupNodeLinkMenu();
+    menu.append(new MenuItem({
+      type: "separator",
+    }));
 
-    // Enable the "edit HTML" item if the selection is an element and the root
-    // actor has the appropriate trait (isOuterHTMLEditable)
-    let editHTML = this.panelDoc.getElementById("node-menu-edithtml");
-    if (isEditableElement && this.isOuterHTMLEditable) {
-      editHTML.removeAttribute("disabled");
-    } else {
-      editHTML.setAttribute("disabled", "true");
-    }
+    let copySubmenu = new Menu();
+    copySubmenu.append(new MenuItem({
+      id: "node-menu-copyinner",
+      label: strings.GetStringFromName("inspectorCopyInnerHTML.label"),
+      accesskey: strings.GetStringFromName("inspectorCopyInnerHTML.accesskey"),
+      disabled: !isSelectionElement,
+      click: () => this.copyInnerHTML(),
+    }));
+    copySubmenu.append(new MenuItem({
+      id: "node-menu-copyouter",
+      label: strings.GetStringFromName("inspectorCopyOuterHTML.label"),
+      accesskey: strings.GetStringFromName("inspectorCopyOuterHTML.accesskey"),
+      disabled: !isSelectionElement,
+      click: () => this.copyOuterHTML(),
+    }));
+    copySubmenu.append(new MenuItem({
+      id: "node-menu-copyuniqueselector",
+      label: strings.GetStringFromName("inspectorCopyCSSSelector.label"),
+      accesskey:
+        strings.GetStringFromName("inspectorCopyCSSSelector.accesskey"),
+      disabled: !isSelectionElement,
+      hidden: !this.canGetUniqueSelector,
+      click: () => this.copyUniqueSelector(),
+    }));
+    copySubmenu.append(new MenuItem({
+      id: "node-menu-copyimagedatauri",
+      label: strings.GetStringFromName("inspectorImageDataUri.label"),
+      disabled: !isSelectionElement || !markupContainer ||
+                !markupContainer.isPreviewable(),
+      click: () => this.copyImageDataUri(),
+    }));
 
-    let pasteOuterHTML = this.panelDoc.getElementById("node-menu-pasteouterhtml");
-    let pasteInnerHTML = this.panelDoc.getElementById("node-menu-pasteinnerhtml");
-    let pasteBefore = this.panelDoc.getElementById("node-menu-pastebefore");
-    let pasteAfter = this.panelDoc.getElementById("node-menu-pasteafter");
-    let pasteFirstChild = this.panelDoc.getElementById("node-menu-pastefirstchild");
-    let pasteLastChild = this.panelDoc.getElementById("node-menu-pastelastchild");
+    menu.append(new MenuItem({
+      label: strings.GetStringFromName("inspectorCopyHTMLSubmenu.label"),
+      submenu: copySubmenu,
+    }));
+
+    menu.append(new MenuItem({
+      label: strings.GetStringFromName("inspectorPasteHTMLSubmenu.label"),
+      submenu: this._getPasteSubmenu(isEditableElement),
+    }));
+
+    menu.append(new MenuItem({
+      type: "separator",
+    }));
 
-    // Is the clipboard content appropriate? Is the element editable?
-    if (isEditableElement && this._getClipboardContentForPaste()) {
-      pasteInnerHTML.disabled = !this.canPasteInnerOrAdjacentHTML;
-      // Enable the "paste outer HTML" item if the selection is an element and
-      // the root actor has the appropriate trait (isOuterHTMLEditable).
-      pasteOuterHTML.disabled = !this.isOuterHTMLEditable;
-      // Don't paste before / after a root or a BODY or a HEAD element.
-      pasteBefore.disabled = pasteAfter.disabled =
-        !this.canPasteInnerOrAdjacentHTML || this.selection.isRoot() ||
-        this.selection.isBodyNode() || this.selection.isHeadNode();
-      // Don't paste as a first / last child of a HTML document element.
-      pasteFirstChild.disabled = pasteLastChild.disabled =
-        !this.canPasteInnerOrAdjacentHTML || (this.selection.isHTMLNode() &&
-        this.selection.isRoot());
-    } else {
-      pasteOuterHTML.disabled = true;
-      pasteInnerHTML.disabled = true;
-      pasteBefore.disabled = true;
-      pasteAfter.disabled = true;
-      pasteFirstChild.disabled = true;
-      pasteLastChild.disabled = true;
+    let isNodeWithChildren = this.selection.isNode() &&
+                             markupContainer.hasChildren;
+    menu.append(new MenuItem({
+      id: "node-menu-expand",
+      label: strings.GetStringFromName("inspectorExpandNode.label"),
+      disabled: !isNodeWithChildren || markupContainer.expanded,
+      click: () => this.expandNode(),
+    }));
+    menu.append(new MenuItem({
+      id: "node-menu-collapse",
+      label: strings.GetStringFromName("inspectorCollapseNode.label"),
+      disabled: !isNodeWithChildren || !markupContainer.expanded,
+      click: () => this.collapseNode(),
+    }));
+
+    menu.append(new MenuItem({
+      type: "separator",
+    }));
+
+    menu.append(new MenuItem({
+      id: "node-menu-scrollnodeintoview",
+      label: strings.GetStringFromName("inspectorScrollNodeIntoView.label"),
+      accesskey:
+        strings.GetStringFromName("inspectorScrollNodeIntoView.accesskey"),
+      hidden: !this._supportsScrollIntoView,
+      disabled: !isSelectionElement,
+      click: () => this.scrollNodeIntoView(),
+    }));
+    menu.append(new MenuItem({
+      id: "node-menu-screenshotnode",
+      label: strings.GetStringFromName("inspectorScreenshotNode.label"),
+      disabled: !isScreenshotable,
+      click: () => this.screenshotNode(),
+    }));
+    menu.append(new MenuItem({
+      id: "node-menu-useinconsole",
+      label: strings.GetStringFromName("inspectorUseInConsole.label"),
+      click: () => this.useInConsole(),
+    }));
+    menu.append(new MenuItem({
+      id: "node-menu-showdomproperties",
+      label: strings.GetStringFromName("inspectorShowDOMProperties.label"),
+      click: () => this.showDOMProperties(),
+    }));
+
+    let nodeLinkMenuItems = this._getNodeLinkMenuItems();
+    if (nodeLinkMenuItems.length > 0) {
+      menu.append(new MenuItem({
+        id: "node-menu-link-separator",
+        type: "separator",
+      }));
     }
 
-    // Enable the "copy image data-uri" item if the selection is previewable
-    // which essentially checks if it's an image or canvas tag
-    let copyImageData = this.panelDoc.getElementById("node-menu-copyimagedatauri");
-    if (isSelectionElement && markupContainer && markupContainer.isPreviewable()) {
-      copyImageData.removeAttribute("disabled");
-    } else {
-      copyImageData.setAttribute("disabled", "true");
-    }
-
-    // Enable / disable "Add Attribute", "Edit Attribute"
-    // and "Remove Attribute" items
-    this._setupAttributeMenu(isEditableElement);
-  },
-
-  _setupAttributeMenu: function (isEditableElement) {
-    let addAttribute = this.panelDoc.getElementById("node-menu-add-attribute");
-    let editAttribute = this.panelDoc.getElementById("node-menu-edit-attribute");
-    let removeAttribute = this.panelDoc.getElementById("node-menu-remove-attribute");
-    let nodeInfo = this.nodeMenuTriggerInfo;
-
-    // Enable "Add Attribute" for all editable elements
-    if (isEditableElement) {
-      addAttribute.removeAttribute("disabled");
-    } else {
-      addAttribute.setAttribute("disabled", "true");
+    for (let menuitem of nodeLinkMenuItems) {
+      menu.append(menuitem);
     }
 
-    // Enable "Edit Attribute" and "Remove Attribute" only on attribute click
-    if (isEditableElement && nodeInfo && nodeInfo.type === "attribute") {
-      editAttribute.removeAttribute("disabled");
-      editAttribute.setAttribute("label",
-        strings.formatStringFromName(
-          "inspector.menu.editAttribute.label", [`"${nodeInfo.name}"`], 1));
-
-      removeAttribute.removeAttribute("disabled");
-      removeAttribute.setAttribute("label",
-        strings.formatStringFromName(
-          "inspector.menu.removeAttribute.label", [`"${nodeInfo.name}"`], 1));
-    } else {
-      editAttribute.setAttribute("disabled", "true");
-      editAttribute.setAttribute("label",
-        strings.formatStringFromName(
-          "inspector.menu.editAttribute.label", [""], 1));
-
-      removeAttribute.setAttribute("disabled", "true");
-      removeAttribute.setAttribute("label",
-        strings.formatStringFromName(
-          "inspector.menu.removeAttribute.label", [""], 1));
-    }
+    menu.popup(screenX, screenY, this._toolbox);
+    return menu;
   },
 
-  _resetNodeMenu: function () {
-    // Remove any extra items
-    while (this.lastNodemenuItem.nextSibling) {
-      let toDelete = this.lastNodemenuItem.nextSibling;
-      toDelete.parentNode.removeChild(toDelete);
-    }
+  _getPasteSubmenu: function (isEditableElement) {
+    let isPasteable = isEditableElement && this._getClipboardContentForPaste();
+    let disableAdjacentPaste = !isPasteable ||
+          !this.canPasteInnerOrAdjacentHTML || this.selection.isRoot() ||
+          this.selection.isBodyNode() || this.selection.isHeadNode();
+    let disableFirstLastPaste = !isPasteable ||
+          !this.canPasteInnerOrAdjacentHTML || (this.selection.isHTMLNode() &&
+          this.selection.isRoot());
+
+    let pasteSubmenu = new Menu();
+    pasteSubmenu.append(new MenuItem({
+      id: "node-menu-pasteinnerhtml",
+      label: strings.GetStringFromName("inspectorPasteInnerHTML.label"),
+      accesskey: strings.GetStringFromName("inspectorPasteInnerHTML.accesskey"),
+      disabled: !isPasteable || !this.canPasteInnerOrAdjacentHTML,
+      click: () => this.pasteInnerHTML(),
+    }));
+    pasteSubmenu.append(new MenuItem({
+      id: "node-menu-pasteouterhtml",
+      label: strings.GetStringFromName("inspectorPasteOuterHTML.label"),
+      accesskey: strings.GetStringFromName("inspectorPasteOuterHTML.accesskey"),
+      disabled: !isPasteable || !this.isOuterHTMLEditable,
+      click: () => this.pasteOuterHTML(),
+    }));
+    pasteSubmenu.append(new MenuItem({
+      id: "node-menu-pastebefore",
+      label: strings.GetStringFromName("inspectorHTMLPasteBefore.label"),
+      accesskey:
+        strings.GetStringFromName("inspectorHTMLPasteBefore.accesskey"),
+      disabled: disableAdjacentPaste,
+      click: () => this.pasteAdjacentHTML("beforeBegin"),
+    }));
+    pasteSubmenu.append(new MenuItem({
+      id: "node-menu-pasteafter",
+      label: strings.GetStringFromName("inspectorHTMLPasteAfter.label"),
+      accesskey:
+        strings.GetStringFromName("inspectorHTMLPasteAfter.accesskey"),
+      disabled: disableAdjacentPaste,
+      click: () => this.pasteAdjacentHTML("afterEnd"),
+    }));
+    pasteSubmenu.append(new MenuItem({
+      id: "node-menu-pastefirstchild",
+      label: strings.GetStringFromName("inspectorHTMLPasteFirstChild.label"),
+      accesskey:
+        strings.GetStringFromName("inspectorHTMLPasteFirstChild.accesskey"),
+      disabled: disableFirstLastPaste,
+      click: () => this.pasteAdjacentHTML("afterBegin"),
+    }));
+    pasteSubmenu.append(new MenuItem({
+      id: "node-menu-pastelastchild",
+      label: strings.GetStringFromName("inspectorHTMLPasteLastChild.label"),
+      accesskey:
+        strings.GetStringFromName("inspectorHTMLPasteLastChild.accesskey"),
+      disabled: disableFirstLastPaste,
+      click: () => this.pasteAdjacentHTML("beforeEnd"),
+    }));
+
+    return pasteSubmenu;
+  },
+
+  _getAttributesSubmenu: function (isEditableElement) {
+    let attributesSubmenu = new Menu();
+    let nodeInfo = this.nodeMenuTriggerInfo;
+    let isAttributeClicked = isEditableElement && nodeInfo &&
+                              nodeInfo.type === "attribute";
+
+    attributesSubmenu.append(new MenuItem({
+      id: "node-menu-add-attribute",
+      label: strings.GetStringFromName("inspectorAddAttribute.label"),
+      accesskey: strings.GetStringFromName("inspectorAddAttribute.accesskey"),
+      disabled: !isEditableElement,
+      click: () => this.onAddAttribute(),
+    }));
+    attributesSubmenu.append(new MenuItem({
+      id: "node-menu-edit-attribute",
+      label: strings.formatStringFromName("inspectorEditAttribute.label",
+                 [isAttributeClicked ? `"${nodeInfo.name}"` : ""], 1),
+      accesskey: strings.GetStringFromName("inspectorEditAttribute.accesskey"),
+      disabled: !isAttributeClicked,
+      click: () => this.onEditAttribute(),
+    }));
+
+    attributesSubmenu.append(new MenuItem({
+      id: "node-menu-remove-attribute",
+      label: strings.formatStringFromName("inspectorRemoveAttribute.label",
+                [isAttributeClicked ? `"${nodeInfo.name}"` : ""], 1),
+      accesskey:
+        strings.GetStringFromName("inspectorRemoveAttribute.accesskey"),
+      disabled: !isAttributeClicked,
+      click: () => this.onRemoveAttribute(),
+    }));
+
+    return attributesSubmenu;
   },
 
   /**
    * Link menu items can be shown or hidden depending on the context and
    * selected node, and their labels can vary.
+   *
+   * @return {Array} list of visible menu items related to links.
    */
-  _setupNodeLinkMenu: function () {
-    let linkSeparator = this.panelDoc.getElementById("node-menu-link-separator");
-    let linkFollow = this.panelDoc.getElementById("node-menu-link-follow");
-    let linkCopy = this.panelDoc.getElementById("node-menu-link-copy");
-
-    // Hide all by default.
-    linkSeparator.setAttribute("hidden", "true");
-    linkFollow.setAttribute("hidden", "true");
-    linkCopy.setAttribute("hidden", "true");
+  _getNodeLinkMenuItems: function () {
+    let linkFollow = new MenuItem({
+      id: "node-menu-link-follow",
+      visible: false,
+      click: () => this.onFollowLink(),
+    });
+    let linkCopy = new MenuItem({
+      id: "node-menu-link-copy",
+      visible: false,
+      click: () => this.onCopyLink(),
+    });
 
     // Get information about the right-clicked node.
-    let popupNode = this.panelDoc.popupNode;
+    let popupNode = this.contextMenuTarget;
     if (!popupNode || !popupNode.classList.contains("link")) {
-      return;
+      return [linkFollow, linkCopy];
     }
 
     let type = popupNode.dataset.type;
-    if (type === "uri" || type === "cssresource" || type === "jsresource") {
-      // First make sure the target can resolve relative URLs.
-      this.target.actorHasMethod("inspector", "resolveRelativeURL").then(canResolve => {
-        if (!canResolve) {
-          return;
-        }
-
-        linkSeparator.removeAttribute("hidden");
+    if (this._supportsResolveRelativeURL &&
+        (type === "uri" || type === "cssresource" || type === "jsresource")) {
+      // Links can't be opened in new tabs in the browser toolbox.
+      if (type === "uri" && !this.target.chrome) {
+        linkFollow.visible = true;
+        linkFollow.label = strings.GetStringFromName(
+          "inspector.menu.openUrlInNewTab.label");
+      } else if (type === "cssresource") {
+        linkFollow.visible = true;
+        linkFollow.label = toolboxStrings.GetStringFromName(
+          "toolbox.viewCssSourceInStyleEditor.label");
+      } else if (type === "jsresource") {
+        linkFollow.visible = true;
+        linkFollow.label = toolboxStrings.GetStringFromName(
+          "toolbox.viewJsSourceInDebugger.label");
+      }
 
-        // Links can't be opened in new tabs in the browser toolbox.
-        if (type === "uri" && !this.target.chrome) {
-          linkFollow.removeAttribute("hidden");
-          linkFollow.setAttribute("label", strings.GetStringFromName(
-            "inspector.menu.openUrlInNewTab.label"));
-        } else if (type === "cssresource") {
-          linkFollow.removeAttribute("hidden");
-          linkFollow.setAttribute("label", toolboxStrings.GetStringFromName(
-            "toolbox.viewCssSourceInStyleEditor.label"));
-        } else if (type === "jsresource") {
-          linkFollow.removeAttribute("hidden");
-          linkFollow.setAttribute("label", toolboxStrings.GetStringFromName(
-            "toolbox.viewJsSourceInDebugger.label"));
-        }
+      linkCopy.visible = true;
+      linkCopy.label = strings.GetStringFromName(
+        "inspector.menu.copyUrlToClipboard.label");
+    } else if (type === "idref") {
+      linkFollow.visible = true;
+      linkFollow.label = strings.formatStringFromName(
+        "inspector.menu.selectElement.label", [popupNode.dataset.link], 1);
+    }
 
-        linkCopy.removeAttribute("hidden");
-        linkCopy.setAttribute("label", strings.GetStringFromName(
-          "inspector.menu.copyUrlToClipboard.label"));
-      }, console.error);
-    } else if (type === "idref") {
-      linkSeparator.removeAttribute("hidden");
-      linkFollow.removeAttribute("hidden");
-      linkFollow.setAttribute("label", strings.formatStringFromName(
-        "inspector.menu.selectElement.label", [popupNode.dataset.link], 1));
-    }
+    return [linkFollow, linkCopy];
   },
 
   _initMarkup: function () {
     let doc = this.panelDoc;
 
     this._markupBox = doc.getElementById("markup-box");
 
     // create tool iframe
     this._markupFrame = doc.createElement("iframe");
     this._markupFrame.setAttribute("flex", "1");
     this._markupFrame.setAttribute("tooltip", "aHTMLTooltip");
-    this._markupFrame.setAttribute("context", "inspector-node-popup");
+    this._markupFrame.addEventListener("contextmenu", this._onContextMenu, true);
 
     // This is needed to enable tooltips inside the iframe document.
     this._markupFrame.addEventListener("load", this._onMarkupFrameLoad, true);
 
     this._markupBox.setAttribute("collapsed", true);
     this._markupBox.appendChild(this._markupFrame);
     this._markupFrame.setAttribute("src", "chrome://devtools/content/inspector/markup/markup.xhtml");
     this._markupFrame.setAttribute("aria-label",
@@ -1028,16 +1111,17 @@ InspectorPanel.prototype = {
     this.emit("markuploaded");
   },
 
   _destroyMarkup: function () {
     let destroyPromise;
 
     if (this._markupFrame) {
       this._markupFrame.removeEventListener("load", this._onMarkupFrameLoad, true);
+      this._markupFrame.removeEventListener("contextmenu", this._onContextMenu, true);
     }
 
     if (this.markup) {
       destroyPromise = this.markup.destroy();
       this.markup = null;
     } else {
       destroyPromise = promise.resolve();
     }
@@ -1410,35 +1494,35 @@ InspectorPanel.prototype = {
     this.markup.collapseNode(this.selection.nodeFront);
   },
 
   /**
    * This method is here for the benefit of the node-menu-link-follow menu item
    * in the inspector contextual-menu.
    */
   onFollowLink: function () {
-    let type = this.panelDoc.popupNode.dataset.type;
-    let link = this.panelDoc.popupNode.dataset.link;
+    let type = this.contextMenuTarget.dataset.type;
+    let link = this.contextMenuTarget.dataset.link;
 
     this.followAttributeLink(type, link);
   },
 
   /**
    * Given a type and link found in a node's attribute in the markup-view,
    * attempt to follow that link (which may result in opening a new tab, the
    * style editor or debugger).
    */
   followAttributeLink: function (type, link) {
     if (!type || !link) {
       return;
     }
 
     if (type === "uri" || type === "cssresource" || type === "jsresource") {
       // Open link in a new tab.
-      // When the inspector menu was setup on click (see _setupNodeLinkMenu), we
+      // When the inspector menu was setup on click (see _getNodeLinkMenuItems), we
       // already checked that resolveRelativeURL existed.
       this.inspector.resolveRelativeURL(
         link, this.selection.nodeFront).then(url => {
           if (type === "uri") {
             let browserWin = this.target.tab.ownerDocument.defaultView;
             browserWin.openUILinkIn(url, "tab");
           } else if (type === "cssresource") {
             return this.toolbox.viewSourceInStyleEditor(url);
@@ -1461,24 +1545,24 @@ InspectorPanel.prototype = {
     }
   },
 
   /**
    * This method is here for the benefit of the node-menu-link-copy menu item
    * in the inspector contextual-menu.
    */
   onCopyLink: function () {
-    let link = this.panelDoc.popupNode.dataset.link;
+    let link = this.contextMenuTarget.dataset.link;
 
     this.copyAttributeLink(link);
   },
 
   /**
    * This method is here for the benefit of copying links.
    */
   copyAttributeLink: function (link) {
-    // When the inspector menu was setup on click (see _setupNodeLinkMenu), we
+    // When the inspector menu was setup on click (see _getNodeLinkMenuItems), we
     // already checked that resolveRelativeURL existed.
     this.inspector.resolveRelativeURL(link, this.selection.nodeFront).then(url => {
       clipboardHelper.copyString(url);
     }, console.error);
   }
 };
--- a/devtools/client/inspector/inspector.xul
+++ b/devtools/client/inspector/inspector.xul
@@ -19,139 +19,16 @@
   <!ENTITY % layoutviewDTD SYSTEM "chrome://devtools/locale/layoutview.dtd"> %layoutviewDTD;
 ]>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         xmlns:html="http://www.w3.org/1999/xhtml">
 
   <script type="application/javascript;version=1.8"
           src="chrome://devtools/content/shared/theme-switching.js"/>
-
-  <popupset id="inspectorPopupSet">
-    <!-- Used by the Markup Panel and the Highlighter -->
-    <menupopup id="inspector-node-popup">
-      <menuitem id="node-menu-edithtml"
-        label="&inspectorHTMLEdit.label;"
-        accesskey="&inspectorHTMLEdit.accesskey;"
-        oncommand="inspector.editHTML()"/>
-      <menuitem id="node-menu-add"
-        label="&inspectorAddNode.label;"
-        accesskey="&inspectorAddNode.accesskey;"
-        oncommand="inspector.addNode()"/>
-      <menuitem id="node-menu-duplicatenode"
-        label="&inspectorDuplicateNode.label;"
-        oncommand="inspector.duplicateNode()"/>
-      <menuitem id="node-menu-delete"
-        label="&inspectorHTMLDelete.label;"
-        accesskey="&inspectorHTMLDelete.accesskey;"
-        oncommand="inspector.deleteNode()"/>
-      <menu label="&inspectorAttributesSubmenu.label;"
-        accesskey="&inspectorAttributesSubmenu.accesskey;">
-        <menupopup>
-          <menuitem id="node-menu-add-attribute"
-            label="&inspectorAddAttribute.label;"
-            accesskey="&inspectorAddAttribute.accesskey;"
-            oncommand="inspector.onAddAttribute()"/>
-          <menuitem id="node-menu-edit-attribute"
-            label="&inspectorEditAttribute.label;"
-            accesskey="&inspectorEditAttribute.accesskey;"
-            oncommand="inspector.onEditAttribute()"/>
-          <menuitem id="node-menu-remove-attribute"
-            label="&inspectorRemoveAttribute.label;"
-            accesskey="&inspectorRemoveAttribute.accesskey;"
-            oncommand="inspector.onRemoveAttribute()"/>
-        </menupopup>
-      </menu>
-      <menuseparator/>
-      <menuitem id="node-menu-pseudo-hover"
-        label=":hover" type="checkbox"
-        oncommand="inspector.togglePseudoClass(':hover')"/>
-      <menuitem id="node-menu-pseudo-active"
-        label=":active" type="checkbox"
-        oncommand="inspector.togglePseudoClass(':active')"/>
-      <menuitem id="node-menu-pseudo-focus"
-        label=":focus" type="checkbox"
-        oncommand="inspector.togglePseudoClass(':focus')"/>
-      <menuseparator/>
-      <menu label="&inspectorCopyHTMLSubmenu.label;">
-        <menupopup>
-          <menuitem id="node-menu-copyinner"
-            label="&inspectorCopyInnerHTML.label;"
-            accesskey="&inspectorCopyInnerHTML.accesskey;"
-            oncommand="inspector.copyInnerHTML()"/>
-          <menuitem id="node-menu-copyouter"
-            label="&inspectorCopyOuterHTML.label;"
-            accesskey="&inspectorCopyOuterHTML.accesskey;"
-            oncommand="inspector.copyOuterHTML()"/>
-          <menuitem id="node-menu-copyuniqueselector"
-            label="&inspectorCopyCSSSelector.label;"
-            accesskey="&inspectorCopyCSSSelector.accesskey;"
-            oncommand="inspector.copyUniqueSelector()"/>
-          <menuitem id="node-menu-copyimagedatauri"
-            label="&inspectorImageDataUri.label;"
-            oncommand="inspector.copyImageDataUri()"/>
-        </menupopup>
-      </menu>
-      <menu label="&inspectorPasteHTMLSubmenu.label;">
-        <menupopup>
-          <menuitem id="node-menu-pasteinnerhtml"
-            label="&inspectorPasteInnerHTML.label;"
-            accesskey="&inspectorPasteInnerHTML.accesskey;"
-            oncommand="inspector.pasteInnerHTML()"/>
-          <menuitem id="node-menu-pasteouterhtml"
-            label="&inspectorPasteOuterHTML.label;"
-            accesskey="&inspectorPasteOuterHTML.accesskey;"
-            oncommand="inspector.pasteOuterHTML()"/>
-          <menuitem id="node-menu-pastebefore"
-            label="&inspectorHTMLPasteBefore.label;"
-            accesskey="&inspectorHTMLPasteBefore.accesskey;"
-            oncommand="inspector.pasteAdjacentHTML('beforeBegin')"/>
-          <menuitem id="node-menu-pasteafter"
-            label="&inspectorHTMLPasteAfter.label;"
-            accesskey="&inspectorHTMLPasteAfter.accesskey;"
-            oncommand="inspector.pasteAdjacentHTML('afterEnd')"/>
-          <menuitem id="node-menu-pastefirstchild"
-            label="&inspectorHTMLPasteFirstChild.label;"
-            accesskey="&inspectorHTMLPasteFirstChild.accesskey;"
-            oncommand="inspector.pasteAdjacentHTML('afterBegin')"/>
-          <menuitem id="node-menu-pastelastchild"
-            label="&inspectorHTMLPasteLastChild.label;"
-            accesskey="&inspectorHTMLPasteLastChild.accesskey;"
-            oncommand="inspector.pasteAdjacentHTML('beforeEnd')"/>
-        </menupopup>
-      </menu>
-      <menuseparator/>
-      <menuitem id="node-menu-expand"
-        label="&inspectorExpandNode.label;"
-        oncommand="inspector.expandNode()"/>
-      <menuitem id="node-menu-collapse"
-        label="&inspectorCollapseNode.label;"
-        oncommand="inspector.collapseNode()"/>
-      <menuseparator/>
-      <menuitem id="node-menu-scrollnodeintoview"
-        label="&inspectorScrollNodeIntoView.label;"
-        accesskey="&inspectorScrollNodeIntoView.accesskey;"
-        oncommand="inspector.scrollNodeIntoView()"/>
-      <menuitem id="node-menu-screenshotnode"
-        label="&inspectorScreenshotNode.label;"
-        oncommand="inspector.screenshotNode()" />
-      <menuitem id="node-menu-useinconsole"
-        label="&inspectorUseInConsole.label;"
-        oncommand="inspector.useInConsole()"/>
-      <menuitem id="node-menu-showdomproperties"
-        label="&inspectorShowDOMProperties.label;"
-        oncommand="inspector.showDOMProperties()"/>
-      <menuseparator id="node-menu-link-separator"/>
-      <menuitem id="node-menu-link-follow"
-        oncommand="inspector.onFollowLink()"/>
-      <menuitem id="node-menu-link-copy"
-        oncommand="inspector.onCopyLink()"/>
-    </menupopup>
-  </popupset>
-
   <box flex="1" class="devtools-responsive-container theme-body">
     <vbox flex="1" class="devtools-main-content">
       <html:div id="inspector-toolbar"
         class="devtools-toolbar"
         nowindowdrag="true">
         <html:button id="inspector-element-add-button"
           title="&inspectorAddNode.label;"
           class="devtools-button" />
--- a/devtools/client/inspector/markup/test/browser_markup_copy_image_data.js
+++ b/devtools/client/inspector/markup/test/browser_markup_copy_image_data.js
@@ -27,49 +27,29 @@ add_task(function* () {
 
   // Check again that the menu isn't available on the DIV (to make sure our
   // menu updating mechanism works)
   yield selectNode("div", inspector);
   yield assertCopyImageDataNotAvailable(inspector);
 });
 
 function* assertCopyImageDataNotAvailable(inspector) {
-  let menu = yield openNodeMenu(inspector);
+  let allMenuItems = openContextMenuAndGetAllItems(inspector);
+  let item = allMenuItems.find(item => item.id === "node-menu-copyimagedatauri");
 
-  let item = menu.getElementsByAttribute("id", "node-menu-copyimagedatauri")[0];
   ok(item, "The menu item was found in the contextual menu");
-  is(item.getAttribute("disabled"), "true", "The menu item is disabled");
-
-  yield closeNodeMenu(inspector);
+  ok(item.disabled, "The menu item is disabled");
 }
 
 function* assertCopyImageDataAvailable(inspector) {
-  let menu = yield openNodeMenu(inspector);
-
-  let item = menu.getElementsByAttribute("id", "node-menu-copyimagedatauri")[0];
-  ok(item, "The menu item was found in the contextual menu");
-  is(item.getAttribute("disabled"), "", "The menu item is enabled");
-
-  yield closeNodeMenu(inspector);
-}
+  let allMenuItems = openContextMenuAndGetAllItems(inspector);
+  let item = allMenuItems.find(item => item.id === "node-menu-copyimagedatauri");
 
-function* openNodeMenu(inspector) {
-  let onShown = once(inspector.nodemenu, "popupshown", false);
-  inspector.nodemenu.hidden = false;
-  inspector.nodemenu.openPopup();
-  yield onShown;
-  return inspector.nodemenu;
-}
-
-function* closeNodeMenu(inspector) {
-  let onHidden = once(inspector.nodemenu, "popuphidden", false);
-  inspector.nodemenu.hidden = true;
-  inspector.nodemenu.hidePopup();
-  yield onHidden;
-  return inspector.nodemenu;
+  ok(item, "The menu item was found in the contextual menu");
+  ok(!item.disabled, "The menu item is enabled");
 }
 
 function triggerCopyImageUrlAndWaitForClipboard(expected, inspector) {
   let def = promise.defer();
 
   SimpleTest.waitForClipboard(expected, () => {
     inspector.markup.getContainer(inspector.selection.nodeFront)
                     .copyImageDataUri();
--- a/devtools/client/inspector/markup/test/browser_markup_links_04.js
+++ b/devtools/client/inspector/markup/test/browser_markup_links_04.js
@@ -70,47 +70,50 @@ const TEST_DATA = [{
   selector: "p[for]",
   attributeName: "for",
   popupNodeSelector: ".attr-value",
   isLinkFollowItemVisible: false,
   isLinkCopyItemVisible: false
 }];
 
 add_task(function* () {
+
   let {inspector} = yield openInspectorForURL(TEST_URL);
 
-  let linkFollow = inspector.panelDoc.getElementById("node-menu-link-follow");
-  let linkCopy = inspector.panelDoc.getElementById("node-menu-link-copy");
-
   for (let test of TEST_DATA) {
     info("Selecting test node " + test.selector);
     yield selectNode(test.selector, inspector);
 
     info("Finding the popupNode to anchor the context-menu to");
     let {editor} = yield getContainerForSelector(test.selector, inspector);
     let popupNode = editor.attrElements.get(test.attributeName)
                     .querySelector(test.popupNodeSelector);
     ok(popupNode, "Found the popupNode in attribute " + test.attributeName);
 
     info("Simulating a context click on the popupNode");
-    contextMenuClick(popupNode);
+    let allMenuItems = openContextMenuAndGetAllItems(inspector, {
+      target: popupNode,
+    });
+
+    let linkFollow = allMenuItems.find(i => i.id === "node-menu-link-follow");
+    let linkCopy =  allMenuItems.find(i => i.id === "node-menu-link-copy");
 
     // The contextual menu setup is async, because it needs to know if the
     // inspector has the resolveRelativeURL method first. So call actorHasMethod
     // here too to make sure the first call resolves first and the menu is
     // properly setup.
     yield inspector.target.actorHasMethod("inspector", "resolveRelativeURL");
 
-    is(linkFollow.hasAttribute("hidden"), !test.isLinkFollowItemVisible,
+    is(linkFollow.visible, test.isLinkFollowItemVisible,
       "The follow-link item display is correct");
-    is(linkCopy.hasAttribute("hidden"), !test.isLinkCopyItemVisible,
+    is(linkCopy.visible, test.isLinkCopyItemVisible,
       "The copy-link item display is correct");
 
     if (test.isLinkFollowItemVisible) {
-      is(linkFollow.getAttribute("label"), test.linkFollowItemLabel,
+      is(linkFollow.label, test.linkFollowItemLabel,
         "the follow-link label is correct");
     }
     if (test.isLinkCopyItemVisible) {
-      is(linkCopy.getAttribute("label"), test.linkCopyItemLabel,
+      is(linkCopy.label, test.linkCopyItemLabel,
         "the copy-link label is correct");
     }
   }
 });
--- a/devtools/client/inspector/markup/test/browser_markup_links_05.js
+++ b/devtools/client/inspector/markup/test/browser_markup_links_05.js
@@ -12,18 +12,19 @@ const TEST_URL = URL_ROOT + "doc_markup_
 add_task(function* () {
   let {inspector} = yield openInspectorForURL(TEST_URL);
 
   info("Select a node with a URI attribute");
   yield selectNode("video", inspector);
 
   info("Set the popupNode to the node that contains the uri");
   let {editor} = yield getContainerForSelector("video", inspector);
-  let popupNode = editor.attrElements.get("poster").querySelector(".link");
-  inspector.panelDoc.popupNode = popupNode;
+  openContextMenuAndGetAllItems(inspector, {
+    target: editor.attrElements.get("poster").querySelector(".link"),
+  });
 
   info("Follow the link and wait for the new tab to open");
   let onTabOpened = once(gBrowser.tabContainer, "TabOpen");
   inspector.onFollowLink();
   let {target: tab} = yield onTabOpened;
   yield waitForTabLoad(tab);
 
   ok(true, "A new tab opened");
@@ -31,34 +32,36 @@ add_task(function* () {
     "The URL for the new tab is correct");
   gBrowser.removeTab(tab);
 
   info("Select a node with a IDREF attribute");
   yield selectNode("label", inspector);
 
   info("Set the popupNode to the node that contains the ref");
   ({editor} = yield getContainerForSelector("label", inspector));
-  popupNode = editor.attrElements.get("for").querySelector(".link");
-  inspector.panelDoc.popupNode = popupNode;
+  openContextMenuAndGetAllItems(inspector, {
+    target: editor.attrElements.get("for").querySelector(".link"),
+  });
 
   info("Follow the link and wait for the new node to be selected");
   let onSelection = inspector.selection.once("new-node-front");
   inspector.onFollowLink();
   yield onSelection;
 
   ok(true, "A new node was selected");
   is(inspector.selection.nodeFront.id, "name", "The right node was selected");
 
   info("Select a node with an invalid IDREF attribute");
   yield selectNode("output", inspector);
 
   info("Set the popupNode to the node that contains the ref");
   ({editor} = yield getContainerForSelector("output", inspector));
-  popupNode = editor.attrElements.get("for").querySelectorAll(".link")[2];
-  inspector.panelDoc.popupNode = popupNode;
+  openContextMenuAndGetAllItems(inspector, {
+    target: editor.attrElements.get("for").querySelectorAll(".link")[2],
+  });
 
   info("Try to follow the link and check that no new node were selected");
   let onFailed = inspector.once("idref-attribute-link-failed");
   inspector.onFollowLink();
   yield onFailed;
 
   ok(true, "The node selection failed");
   is(inspector.selection.nodeFront.tagName.toLowerCase(), "output",
--- a/devtools/client/inspector/markup/test/browser_markup_links_06.js
+++ b/devtools/client/inspector/markup/test/browser_markup_links_06.js
@@ -12,18 +12,19 @@ const TEST_URL = URL_ROOT + "doc_markup_
 add_task(function* () {
   let {toolbox, inspector} = yield openInspectorForURL(TEST_URL);
 
   info("Select a node with a cssresource attribute");
   yield selectNode("link", inspector);
 
   info("Set the popupNode to the node that contains the uri");
   let {editor} = yield getContainerForSelector("link", inspector);
-  let popupNode = editor.attrElements.get("href").querySelector(".link");
-  inspector.panelDoc.popupNode = popupNode;
+  openContextMenuAndGetAllItems(inspector, {
+    target: editor.attrElements.get("href").querySelector(".link"),
+  });
 
   info("Follow the link and wait for the style-editor to open");
   let onStyleEditorReady = toolbox.once("styleeditor-ready");
   inspector.onFollowLink();
   yield onStyleEditorReady;
 
   // No real need to test that the editor opened on the right file here as this
   // is already tested in /framework/test/browser_toolbox_view_source_*
@@ -32,18 +33,19 @@ add_task(function* () {
   info("Switch back to the inspector");
   yield toolbox.selectTool("inspector");
 
   info("Select a node with a jsresource attribute");
   yield selectNode("script", inspector);
 
   info("Set the popupNode to the node that contains the uri");
   ({editor} = yield getContainerForSelector("script", inspector));
-  popupNode = editor.attrElements.get("src").querySelector(".link");
-  inspector.panelDoc.popupNode = popupNode;
+  openContextMenuAndGetAllItems(inspector, {
+    target: editor.attrElements.get("src").querySelector(".link"),
+  });
 
   info("Follow the link and wait for the debugger to open");
   let onDebuggerReady = toolbox.once("jsdebugger-ready");
   inspector.onFollowLink();
   yield onDebuggerReady;
 
   // No real need to test that the debugger opened on the right file here as
   // this is already tested in /framework/test/browser_toolbox_view_source_*
--- a/devtools/client/inspector/markup/test/head.js
+++ b/devtools/client/inspector/markup/test/head.js
@@ -290,97 +290,71 @@ function wait(ms) {
  * @param {InspectorPanel} inspector
  * @param {Boolean} assert Should this function run assertions inline.
  * @return A promise that resolves with a boolean indicating whether
  *         the menu items are disabled once the menu has been checked.
  */
 var isEditingMenuDisabled = Task.async(
 function* (nodeFront, inspector, assert = true) {
   let doc = inspector.panelDoc;
-  let deleteMenuItem = doc.getElementById("node-menu-delete");
-  let editHTMLMenuItem = doc.getElementById("node-menu-edithtml");
-  let pasteHTMLMenuItem = doc.getElementById("node-menu-pasteouterhtml");
 
   // To ensure clipboard contains something to paste.
   clipboard.set("<p>test</p>", "html");
 
-  let menu = inspector.nodemenu;
   yield selectNode(nodeFront, inspector);
-  yield reopenMenu(menu);
+  let allMenuItems = openContextMenuAndGetAllItems(inspector);
 
-  let isDeleteMenuDisabled = deleteMenuItem.hasAttribute("disabled");
-  let isEditHTMLMenuDisabled = editHTMLMenuItem.hasAttribute("disabled");
-  let isPasteHTMLMenuDisabled = pasteHTMLMenuItem.hasAttribute("disabled");
+  let deleteMenuItem = allMenuItems.find(item => item.id === "node-menu-delete");
+  let editHTMLMenuItem = allMenuItems.find(item => item.id === "node-menu-edithtml");
+  let pasteHTMLMenuItem = allMenuItems.find(item => item.id === "node-menu-pasteouterhtml");
 
   if (assert) {
-    ok(isDeleteMenuDisabled, "Delete menu item is disabled");
-    ok(isEditHTMLMenuDisabled, "Edit HTML menu item is disabled");
-    ok(isPasteHTMLMenuDisabled, "Paste HTML menu item is disabled");
+    ok(deleteMenuItem.disabled, "Delete menu item is disabled");
+    ok(editHTMLMenuItem.disabled, "Edit HTML menu item is disabled");
+    ok(pasteHTMLMenuItem.disabled, "Paste HTML menu item is disabled");
   }
 
-  return isDeleteMenuDisabled &&
-         isEditHTMLMenuDisabled &&
-         isPasteHTMLMenuDisabled;
+  return deleteMenuItem.disabled &&
+         editHTMLMenuItem.disabled &&
+         pasteHTMLMenuItem.disabled;
 });
 
 /**
  * Check to see if the inspector menu items for editing are enabled.
  * Things like Edit As HTML, Delete Node, etc.
  * @param {NodeFront} nodeFront
  * @param {InspectorPanel} inspector
  * @param {Boolean} assert Should this function run assertions inline.
  * @return A promise that resolves with a boolean indicating whether
  *         the menu items are enabled once the menu has been checked.
  */
 var isEditingMenuEnabled = Task.async(
 function* (nodeFront, inspector, assert = true) {
   let doc = inspector.panelDoc;
-  let deleteMenuItem = doc.getElementById("node-menu-delete");
-  let editHTMLMenuItem = doc.getElementById("node-menu-edithtml");
-  let pasteHTMLMenuItem = doc.getElementById("node-menu-pasteouterhtml");
 
   // To ensure clipboard contains something to paste.
   clipboard.set("<p>test</p>", "html");
 
   let menu = inspector.nodemenu;
   yield selectNode(nodeFront, inspector);
-  yield reopenMenu(menu);
+  let allMenuItems = openContextMenuAndGetAllItems(inspector);
 
-  let isDeleteMenuDisabled = deleteMenuItem.hasAttribute("disabled");
-  let isEditHTMLMenuDisabled = editHTMLMenuItem.hasAttribute("disabled");
-  let isPasteHTMLMenuDisabled = pasteHTMLMenuItem.hasAttribute("disabled");
+  let deleteMenuItem = allMenuItems.find(item => item.id === "node-menu-delete");
+  let editHTMLMenuItem = allMenuItems.find(item => item.id === "node-menu-edithtml");
+  let pasteHTMLMenuItem = allMenuItems.find(item => item.id === "node-menu-pasteouterhtml");
 
   if (assert) {
-    ok(!isDeleteMenuDisabled, "Delete menu item is enabled");
-    ok(!isEditHTMLMenuDisabled, "Edit HTML menu item is enabled");
-    ok(!isPasteHTMLMenuDisabled, "Paste HTML menu item is enabled");
+    ok(!deleteMenuItem.disabled, "Delete menu item is enabled");
+    ok(!editHTMLMenuItem.disabled, "Edit HTML menu item is enabled");
+    ok(!pasteHTMLMenuItem.disabled, "Paste HTML menu item is enabled");
   }
 
-  return !isDeleteMenuDisabled &&
-         !isEditHTMLMenuDisabled &&
-         !isPasteHTMLMenuDisabled;
-});
-
-/**
- * Open a menu (closing it first if necessary).
- * @param {DOMNode} menu A menu that implements hidePopup/openPopup
- * @return a promise that resolves once the menu is opened.
- */
-var reopenMenu = Task.async(function* (menu) {
-  // First close it is if it is already opened.
-  if (menu.state == "closing" || menu.state == "open") {
-    let popuphidden = once(menu, "popuphidden", true);
-    menu.hidePopup();
-    yield popuphidden;
-  }
-
-  // Then open it and return once
-  let popupshown = once(menu, "popupshown", true);
-  menu.openPopup();
-  yield popupshown;
+  return !deleteMenuItem.disabled &&
+         !editHTMLMenuItem.disabled &&
+         !pasteHTMLMenuItem.disabled;
 });
 
 /**
  * Wait for all current promises to be resolved. See this as executeSoon that
  * can be used with yield.
  */
 function promiseNextTick() {
   let deferred = promise.defer();
@@ -486,30 +460,16 @@ function createTestHTTPServer() {
     yield destroyed.promise;
   });
 
   server.start(-1);
   return server;
 }
 
 /**
- * A helper that simulates a contextmenu event on the given chrome DOM element.
- */
-function contextMenuClick(element) {
-  let evt = element.ownerDocument.createEvent("MouseEvents");
-  let buttonRight = 2;
-
-  evt.initMouseEvent("contextmenu", true, true,
-    element.ownerDocument.defaultView, 1, 0, 0, 0, 0, false, false, false,
-    false, buttonRight, null);
-
-  element.dispatchEvent(evt);
-}
-
-/**
  * Registers new backend tab actor.
  *
  * @param {DebuggerClient} client RDP client object (toolbox.target.client)
  * @param {Object} options Configuration object with the following options:
  *
  * - moduleUrl {String}: URL of the module that contains actor implementation.
  * - prefix {String}: prefix of the actor.
  * - actorClass {ActorClass}: Constructor object for the actor.
--- a/devtools/client/inspector/test/browser_inspector_addNode_01.js
+++ b/devtools/client/inspector/test/browser_inspector_addNode_01.js
@@ -7,16 +7,16 @@
 // Test that the add node button and context menu items are present in the UI.
 
 const TEST_URL = "data:text/html;charset=utf-8,<h1>Add node</h1>";
 
 add_task(function* () {
   let {inspector} = yield openInspectorForURL(TEST_URL);
   let {panelDoc} = inspector;
 
+  let allMenuItems = openContextMenuAndGetAllItems(inspector);
+  let menuItem = allMenuItems.find(item => item.id === "node-menu-add");
+  ok(menuItem, "The item is in the menu");
+
   let toolbarButton =
     panelDoc.querySelector("#inspector-toolbar #inspector-element-add-button");
-  let menuItem =
-    panelDoc.querySelector("#inspector-node-popup #node-menu-add");
-
   ok(toolbarButton, "The add button is in the toolbar");
-  ok(menuItem, "The item is in the menu");
 });
--- a/devtools/client/inspector/test/browser_inspector_addNode_02.js
+++ b/devtools/client/inspector/test/browser_inspector_addNode_02.js
@@ -44,19 +44,20 @@ add_task(function* () {
   yield selectNode("iframe", inspector);
   assertState(false, inspector,
     "The button and item are disabled on an IFRAME element");
 });
 
 function assertState(isEnabled, inspector, desc) {
   let doc = inspector.panelDoc;
   let btn = doc.querySelector("#inspector-element-add-button");
-  let item = doc.querySelector("#node-menu-add");
 
   // Force an update of the context menu to make sure menu items are updated
   // according to the current selection. This normally happens when the menu is
   // opened, but for the sake of this test's simplicity, we directly call the
   // private update function instead.
-  inspector._setupNodeMenu({target: {}});
+  let allMenuItems = openContextMenuAndGetAllItems(inspector);
+  let menuItem = allMenuItems.find(item => item.id === "node-menu-add");
+  ok(menuItem, "The item is in the menu");
+  is(!menuItem.disabled, isEnabled, desc);
 
   is(!btn.hasAttribute("disabled"), isEnabled, desc);
-  is(!item.hasAttribute("disabled"), isEnabled, desc);
 }
--- a/devtools/client/inspector/test/browser_inspector_delete-selected-node-02.js
+++ b/devtools/client/inspector/test/browser_inspector_delete-selected-node-02.js
@@ -23,26 +23,24 @@ add_task(function* () {
     info("Selecting a node, deleting it via context menu and checking that " +
           "its parent node is selected and breadcrumbs are updated.");
 
     yield selectNode("#deleteManually", inspector);
 
     info("Getting the node container in the markup view.");
     let container = yield getContainerForSelector("#deleteManually", inspector);
 
-    info("Simulating right-click on the markup view container.");
-    EventUtils.synthesizeMouse(container.tagLine, 2, 2,
-      {type: "contextmenu", button: 2}, inspector.panelWin);
-
-    info("Waiting for the context menu to open.");
-    yield once(inspector.panelDoc.getElementById("inspectorPopupSet"),
-               "popupshown");
+    let allMenuItems = openContextMenuAndGetAllItems(inspector, {
+      target: container.tagLine,
+    });
+    let menuItem = allMenuItems.find(item => item.id === "node-menu-delete");
 
     info("Clicking 'Delete Node' in the context menu.");
-    inspector.panelDoc.getElementById("node-menu-delete").click();
+    is(menuItem.disabled, false, "delete menu item is enabled");
+    menuItem.click();
 
     info("Waiting for inspector to update.");
     yield inspector.once("inspector-updated");
 
     info("Inspector updated, performing checks.");
     yield assertNodeSelectedAndPanelsUpdated("#selectedAfterDelete",
                                              "li#selectedAfterDelete");
   }
--- a/devtools/client/inspector/test/browser_inspector_expand-collapse.js
+++ b/devtools/client/inspector/test/browser_inspector_expand-collapse.js
@@ -10,49 +10,52 @@ const TEST_URL = "data:text/html;charset
                  "<div id='parent-node'><div id='child-node'></div></div>";
 
 add_task(function* () {
   // Test is often exceeding time-out threshold, similar to Bug 1137765
   requestLongerTimeout(2);
 
   let {inspector} = yield openInspectorForURL(TEST_URL);
 
-  let nodeMenuCollapseElement = inspector.panelDoc.getElementById(
-    "node-menu-collapse");
-  let nodeMenuExpandElement = inspector.panelDoc.getElementById(
-    "node-menu-expand");
-
   info("Selecting the parent node");
 
   let front = yield getNodeFrontForSelector("#parent-node", inspector);
 
   yield selectNode(front, inspector);
 
   info("Simulating context menu click on the selected node container.");
-  contextMenuClick(getContainerForNodeFront(front, inspector).tagLine);
+  let allMenuItems = openContextMenuAndGetAllItems(inspector, {
+    target: getContainerForNodeFront(front, inspector).tagLine,
+  });
+  let nodeMenuCollapseElement =
+    allMenuItems.find(item => item.id === "node-menu-collapse");
+  let nodeMenuExpandElement =
+    allMenuItems.find(item => item.id === "node-menu-expand");
 
-  ok(nodeMenuCollapseElement.hasAttribute("disabled"),
-     "Collapse option is disabled");
-  ok(!nodeMenuExpandElement.hasAttribute("disabled"),
-     "ExpandAll option is enabled");
+  ok(nodeMenuCollapseElement.disabled, "Collapse option is disabled");
+  ok(!nodeMenuExpandElement.disabled, "ExpandAll option is enabled");
 
   info("Testing whether expansion works properly");
-  dispatchCommandEvent(nodeMenuExpandElement);
+  nodeMenuExpandElement.click();
+
   info("Waiting for expansion to occur");
   yield waitForMultipleChildrenUpdates(inspector);
   let markUpContainer = getContainerForNodeFront(front, inspector);
   ok(markUpContainer.expanded, "node has been successfully expanded");
 
-    // reslecting node after expansion
+  // reselecting node after expansion
   yield selectNode(front, inspector);
 
   info("Testing whether collapse works properly");
   info("Simulating context menu click on the selected node container.");
-  contextMenuClick(getContainerForNodeFront(front, inspector).tagLine);
+  allMenuItems = openContextMenuAndGetAllItems(inspector, {
+    target: getContainerForNodeFront(front, inspector).tagLine,
+  });
+  nodeMenuCollapseElement =
+    allMenuItems.find(item => item.id === "node-menu-collapse");
 
-  ok(!nodeMenuCollapseElement.hasAttribute("disabled"),
-     "Collapse option is enabled");
+  ok(!nodeMenuCollapseElement.disabled, "Collapse option is enabled");
+  nodeMenuCollapseElement.click();
 
-  dispatchCommandEvent(nodeMenuCollapseElement);
   info("Waiting for collapse to occur");
   yield waitForMultipleChildrenUpdates(inspector);
   ok(!markUpContainer.expanded, "node has been successfully collapsed");
 });
--- a/devtools/client/inspector/test/browser_inspector_menu-01-sensitivity.js
+++ b/devtools/client/inspector/test/browser_inspector_menu-01-sensitivity.js
@@ -229,25 +229,26 @@ add_task(function* () {
     yield selectNode(front, inspector);
 
     info("Simulating context menu click on the selected node container.");
     let nodeFrontContainer = getContainerForNodeFront(front, inspector);
     let contextMenuTrigger = attributeTrigger
       ? nodeFrontContainer.tagLine.querySelector(
           `[data-attr="${attributeTrigger}"]`)
       : nodeFrontContainer.tagLine;
-    contextMenuClick(contextMenuTrigger);
+
+    let allMenuItems = openContextMenuAndGetAllItems(inspector, {
+      target: contextMenuTrigger,
+    });
 
-    for (let menuitem of ALL_MENU_ITEMS) {
-      let elt = inspector.panelDoc.getElementById(menuitem);
-      let shouldBeDisabled = disabled.indexOf(menuitem) !== -1;
-      let isDisabled = elt.hasAttribute("disabled");
-
-      is(isDisabled, shouldBeDisabled,
-        `#${menuitem} should be ${shouldBeDisabled ? "disabled" : "enabled"} `);
+    for (let id of ALL_MENU_ITEMS) {
+      let menuItem = allMenuItems.find(item => item.id === id);
+      let shouldBeDisabled = disabled.indexOf(id) !== -1;
+      is(menuItem.disabled, shouldBeDisabled,
+        `#${id} should be ${shouldBeDisabled ? "disabled" : "enabled"} `);
     }
   }
 });
 
 /**
  * A helper that fetches a front for a node that matches the given selector or
  * doctype node if the selector is falsy.
  */
@@ -270,22 +271,8 @@ function setupClipboard(data, type) {
   if (data) {
     info("Populating clipboard with " + type + " data.");
     clipboard.set(data, type);
   } else {
     info("Clearing clipboard.");
     clipboard.set("", "text");
   }
 }
-
-/**
- * A helper that simulates a contextmenu event on the given chrome DOM element.
- */
-function contextMenuClick(element) {
-  let evt = element.ownerDocument.createEvent("MouseEvents");
-  let button = 2;
-
-  evt.initMouseEvent("contextmenu", true, true,
-       element.ownerDocument.defaultView, 1, 0, 0, 0, 0, false,
-       false, false, false, button, null);
-
-  element.dispatchEvent(evt);
-}
--- a/devtools/client/inspector/test/browser_inspector_menu-02-copy-items.js
+++ b/devtools/client/inspector/test/browser_inspector_menu-02-copy-items.js
@@ -35,14 +35,15 @@ const COPY_ITEMS_TEST_DATA = [
 ];
 
 add_task(function* () {
   let { inspector } = yield openInspectorForURL(TEST_URL);
   for (let {desc, id, selector, text} of COPY_ITEMS_TEST_DATA) {
     info("Testing " + desc);
     yield selectNode(selector, inspector);
 
-    let item = inspector.panelDoc.getElementById(id);
+    let allMenuItems = openContextMenuAndGetAllItems(inspector);
+    let item = allMenuItems.find(item => item.id === id);
     ok(item, "The popup has a " + desc + " menu item.");
 
-    yield waitForClipboard(() => item.doCommand(), text);
+    yield waitForClipboard(() => item.click(), text);
   }
 });
--- a/devtools/client/inspector/test/browser_inspector_menu-03-paste-items.js
+++ b/devtools/client/inspector/test/browser_inspector_menu-03-paste-items.js
@@ -44,21 +44,22 @@ add_task(function* () {
   function* testPasteOuterHTMLMenu() {
     info("Testing that 'Paste Outer HTML' menu item works.");
     clipboard.set("this was pasted (outerHTML)");
     let outerHTMLSelector = "#paste-area h1";
 
     let nodeFront = yield getNodeFront(outerHTMLSelector, inspector);
     yield selectNode(nodeFront, inspector);
 
-    contextMenuClick(getContainerForNodeFront(nodeFront, inspector).tagLine);
+    let allMenuItems = openContextMenuAndGetAllItems(inspector, {
+      target: getContainerForNodeFront(nodeFront, inspector).tagLine,
+    });
 
     let onNodeReselected = inspector.markup.once("reselectedonremoved");
-    let menu = inspector.panelDoc.getElementById("node-menu-pasteouterhtml");
-    dispatchCommandEvent(menu);
+    allMenuItems.find(item => item.id === "node-menu-pasteouterhtml").click();
 
     info("Waiting for inspector selection to update");
     yield onNodeReselected;
 
     let outerHTML = yield testActor.getProperty("body", "outerHTML");
     ok(outerHTML.includes(clipboard.get()),
        "Clipboard content was pasted into the node's outer HTML.");
     ok(!(yield testActor.hasNode(outerHTMLSelector)),
@@ -71,22 +72,22 @@ add_task(function* () {
     let innerHTMLSelector = "#paste-area .inner";
     let getInnerHTML = () => testActor.getProperty(innerHTMLSelector,
                                                    "innerHTML");
     let origInnerHTML = yield getInnerHTML();
 
     let nodeFront = yield getNodeFront(innerHTMLSelector, inspector);
     yield selectNode(nodeFront, inspector);
 
-    contextMenuClick(getContainerForNodeFront(nodeFront, inspector).tagLine);
+    let allMenuItems = openContextMenuAndGetAllItems(inspector, {
+      target: getContainerForNodeFront(nodeFront, inspector).tagLine,
+    });
 
     let onMutation = inspector.once("markupmutation");
-    let menu = inspector.panelDoc.getElementById("node-menu-pasteinnerhtml");
-    dispatchCommandEvent(menu);
-
+    allMenuItems.find(item => item.id === "node-menu-pasteinnerhtml").click();
     info("Waiting for mutation to occur");
     yield onMutation;
 
     ok((yield getInnerHTML()) === clipboard.get(),
        "Clipboard content was pasted into the node's inner HTML.");
     ok((yield testActor.hasNode(innerHTMLSelector)),
        "The original node has been preserved.");
     yield undoChange(inspector);
@@ -97,59 +98,31 @@ add_task(function* () {
   function* testPasteAdjacentHTMLMenu() {
     let refSelector = "#paste-area .adjacent .ref";
     let adjacentNodeSelector = "#paste-area .adjacent";
     let nodeFront = yield getNodeFront(refSelector, inspector);
     yield selectNode(nodeFront, inspector);
     let markupTagLine = getContainerForNodeFront(nodeFront, inspector).tagLine;
 
     for (let { clipboardData, menuId } of PASTE_ADJACENT_HTML_DATA) {
-      let menu = inspector.panelDoc.getElementById(menuId);
-      info(`Testing ${getLabelFor(menu)} for ${clipboardData}`);
+      let allMenuItems = openContextMenuAndGetAllItems(inspector, {
+        target: markupTagLine,
+      });
+      info(`Testing ${menuId} for ${clipboardData}`);
       clipboard.set(clipboardData);
 
-      contextMenuClick(markupTagLine);
       let onMutation = inspector.once("markupmutation");
-      dispatchCommandEvent(menu);
-
+      allMenuItems.find(item => item.id === menuId).click();
       info("Waiting for mutation to occur");
       yield onMutation;
     }
 
     let html = yield testActor.getProperty(adjacentNodeSelector, "innerHTML");
     ok(html.trim() === "1<span class=\"ref\">234</span><span>5</span>",
        "The Paste as Last Child / as First Child / Before / After worked as " +
        "expected");
     yield undoChange(inspector);
 
     html = yield testActor.getProperty(adjacentNodeSelector, "innerHTML");
     ok(html.trim() === "1<span class=\"ref\">234</span>",
        "Undo works for paste adjacent HTML");
   }
-
-  function dispatchCommandEvent(node) {
-    info("Dispatching command event on " + node);
-    let commandEvent = document.createEvent("XULCommandEvent");
-    commandEvent.initCommandEvent("command", true, true, window, 0, false,
-                                  false, false, false, null);
-    node.dispatchEvent(commandEvent);
-  }
-
-  function contextMenuClick(element) {
-    info("Simulating contextmenu event on " + element);
-    let evt = element.ownerDocument.createEvent("MouseEvents");
-    let button = 2;
-
-    evt.initMouseEvent("contextmenu", true, true,
-         element.ownerDocument.defaultView, 1, 0, 0, 0, 0, false,
-         false, false, false, button, null);
-
-    element.dispatchEvent(evt);
-  }
-
-  function getLabelFor(elt) {
-    if (typeof elt === "string") {
-      elt = inspector.panelDoc.querySelector(elt);
-    }
-    let isInPasteSubMenu = elt.matches("#node-menu-paste-extra-submenu *");
-    return `"${isInPasteSubMenu ? "Paste > " : ""}${elt.label}"`;
-  }
 });
--- a/devtools/client/inspector/test/browser_inspector_menu-04-use-in-console.js
+++ b/devtools/client/inspector/test/browser_inspector_menu-04-use-in-console.js
@@ -13,35 +13,39 @@ registerCleanupFunction(() => {
 
 add_task(function* () {
   let { inspector, toolbox } = yield openInspectorForURL(TEST_URL);
 
   yield testUseInConsole();
 
   function* testUseInConsole() {
     info("Testing 'Use in Console' menu item.");
-    let useInConsoleNode = inspector.panelDoc.getElementById(
-      "node-menu-useinconsole");
 
     yield selectNode("#console-var", inspector);
-    dispatchCommandEvent(useInConsoleNode);
+    let container = yield getContainerForSelector("#console-var", inspector);
+    let allMenuItems = openContextMenuAndGetAllItems(inspector, {
+      target: container.tagLine,
+    });
+    let menuItem = allMenuItems.find(i => i.id === "node-menu-useinconsole");
+    menuItem.click();
+
     yield inspector.once("console-var-ready");
 
     let hud = toolbox.getPanel("webconsole").hud;
     let jsterm = hud.jsterm;
 
     let jstermInput = jsterm.hud.document.querySelector(".jsterm-input-node");
     is(jstermInput.value, "temp0", "first console variable is named temp0");
 
     let result = yield jsterm.execute();
     isnot(result.textContent.indexOf('<p id="console-var">'), -1,
           "variable temp0 references correct node");
 
     yield selectNode("#console-var-multi", inspector);
-    dispatchCommandEvent(useInConsoleNode);
+    menuItem.click();
     yield inspector.once("console-var-ready");
 
     is(jstermInput.value, "temp1", "second console variable is named temp1");
 
     result = yield jsterm.execute();
     isnot(result.textContent.indexOf('<p id="console-var-multi">'), -1,
           "variable temp1 references correct node");
 
--- a/devtools/client/inspector/test/browser_inspector_menu-05-attribute-items.js
+++ b/devtools/client/inspector/test/browser_inspector_menu-05-attribute-items.js
@@ -11,21 +11,20 @@ add_task(function* () {
   let { inspector, testActor } = yield openInspectorForURL(TEST_URL);
   yield selectNode("#attributes", inspector);
 
   yield testAddAttribute();
   yield testEditAttribute();
   yield testRemoveAttribute();
 
   function* testAddAttribute() {
-    info("Testing 'Add Attribute' menu item");
+    info("Triggering 'Add Attribute' and waiting for mutation to occur");
     let addAttribute = getMenuItem("node-menu-add-attribute");
+    addAttribute.click();
 
-    info("Triggering 'Add Attribute' and waiting for mutation to occur");
-    dispatchCommandEvent(addAttribute);
     EventUtils.synthesizeKey('class="u-hidden"', {});
     let onMutation = inspector.once("markupmutation");
     EventUtils.synthesizeKey("VK_RETURN", {});
     yield onMutation;
 
     let hasAttribute = testActor.hasNode("#attributes.u-hidden");
     ok(hasAttribute, "attribute was successfully added");
   }
@@ -34,17 +33,17 @@ add_task(function* () {
     info("Testing 'Edit Attribute' menu item");
     let editAttribute = getMenuItem("node-menu-edit-attribute");
 
     info("Triggering 'Edit Attribute' and waiting for mutation to occur");
     inspector.nodeMenuTriggerInfo = {
       type: "attribute",
       name: "data-edit"
     };
-    dispatchCommandEvent(editAttribute);
+    editAttribute.click();
     EventUtils.synthesizeKey("data-edit='edited'", {});
     let onMutation = inspector.once("markupmutation");
     EventUtils.synthesizeKey("VK_RETURN", {});
     yield onMutation;
 
     let isAttributeChanged =
       yield testActor.hasNode("#attributes[data-edit='edited']");
     ok(isAttributeChanged, "attribute was successfully edited");
@@ -55,21 +54,26 @@ add_task(function* () {
     let removeAttribute = getMenuItem("node-menu-remove-attribute");
 
     info("Triggering 'Remove Attribute' and waiting for mutation to occur");
     inspector.nodeMenuTriggerInfo = {
       type: "attribute",
       name: "data-remove"
     };
     let onMutation = inspector.once("markupmutation");
-    dispatchCommandEvent(removeAttribute);
+    removeAttribute.click();
     yield onMutation;
 
     let hasAttribute = yield testActor.hasNode("#attributes[data-remove]");
     ok(!hasAttribute, "attribute was successfully removed");
   }
 
   function getMenuItem(id) {
-    let attribute = inspector.panelDoc.getElementById(id);
-    ok(attribute, "Menu item '" + id + "' found");
-    return attribute;
+    let allMenuItems = openContextMenuAndGetAllItems(inspector, {
+      target: getContainerForSelector("#attributes", inspector).tagLine,
+    });
+    let menuItem = allMenuItems.find(i => i.id === id);
+    ok(menuItem, "Menu item '" + id + "' found");
+    // Close the menu so synthesizing future keys won't select menu items.
+    EventUtils.synthesizeKey("VK_ESCAPE", {});
+    return menuItem;
   }
 });
--- a/devtools/client/inspector/test/browser_inspector_menu-06-other.js
+++ b/devtools/client/inspector/test/browser_inspector_menu-06-other.js
@@ -9,76 +9,81 @@ add_task(function* () {
   let { inspector, toolbox, testActor } = yield openInspectorForURL(TEST_URL);
   yield testShowDOMProperties();
   yield testDuplicateNode();
   yield testDeleteNode();
   yield testDeleteRootNode();
   yield testScrollIntoView();
   function* testShowDOMProperties() {
     info("Testing 'Show DOM Properties' menu item.");
-    let showDOMPropertiesNode = inspector.panelDoc.getElementById(
-      "node-menu-showdomproperties");
+    let allMenuItems = openContextMenuAndGetAllItems(inspector);
+    let showDOMPropertiesNode =
+      allMenuItems.find(item => item.id === "node-menu-showdomproperties");
     ok(showDOMPropertiesNode, "the popup menu has a show dom properties item");
 
     let consoleOpened = toolbox.once("webconsole-ready");
 
     info("Triggering 'Show DOM Properties' and waiting for inspector open");
-    dispatchCommandEvent(showDOMPropertiesNode);
+    showDOMPropertiesNode.click();
     yield consoleOpened;
 
     let webconsoleUI = toolbox.getPanel("webconsole").hud.ui;
     let messagesAdded = webconsoleUI.once("new-messages");
     yield messagesAdded;
     info("Checking if 'inspect($0)' was evaluated");
     ok(webconsoleUI.jsterm.history[0] === "inspect($0)");
     yield toolbox.toggleSplitConsole();
   }
   function* testDuplicateNode() {
     info("Testing 'Duplicate Node' menu item for normal elements.");
 
     yield selectNode(".duplicate", inspector);
     is((yield testActor.getNumberOfElementMatches(".duplicate")), 1,
        "There should initially be 1 .duplicate node");
 
-    let menuItem = inspector.panelDoc.getElementById("node-menu-duplicatenode");
+    let allMenuItems = openContextMenuAndGetAllItems(inspector);
+    let menuItem =
+      allMenuItems.find(item => item.id === "node-menu-duplicatenode");
     ok(menuItem, "'Duplicate node' menu item should exist");
 
     info("Triggering 'Duplicate Node' and waiting for inspector to update");
     let updated = inspector.once("markupmutation");
-    dispatchCommandEvent(menuItem);
+    menuItem.click();
     yield updated;
 
     is((yield testActor.getNumberOfElementMatches(".duplicate")), 2,
        "The duplicated node should be in the markup.");
 
     let container = yield getContainerForSelector(".duplicate + .duplicate",
                                                    inspector);
     ok(container, "A MarkupContainer should be created for the new node");
   }
 
   function* testDeleteNode() {
     info("Testing 'Delete Node' menu item for normal elements.");
     yield selectNode("#delete", inspector);
-    let deleteNode = inspector.panelDoc.getElementById("node-menu-delete");
+    let allMenuItems = openContextMenuAndGetAllItems(inspector);
+    let deleteNode = allMenuItems.find(item => item.id === "node-menu-delete");
     ok(deleteNode, "the popup menu has a delete menu item");
     let updated = inspector.once("inspector-updated");
 
     info("Triggering 'Delete Node' and waiting for inspector to update");
-    dispatchCommandEvent(deleteNode);
+    deleteNode.click();
     yield updated;
 
     ok(!(yield testActor.hasNode("#delete")), "Node deleted");
   }
 
   function* testDeleteRootNode() {
     info("Testing 'Delete Node' menu item does not delete root node.");
     yield selectNode("html", inspector);
 
-    let deleteNode = inspector.panelDoc.getElementById("node-menu-delete");
-    dispatchCommandEvent(deleteNode);
+    let allMenuItems = openContextMenuAndGetAllItems(inspector);
+    let deleteNode = allMenuItems.find(item => item.id === "node-menu-delete");
+    deleteNode.click();
 
     let deferred = promise.defer();
     executeSoon(deferred.resolve);
     yield deferred.promise;
 
     ok((yield testActor.eval("!!content.document.documentElement")),
        "Document element still alive.");
   }
--- a/devtools/client/inspector/test/browser_inspector_pseudoclass-menu.js
+++ b/devtools/client/inspector/test/browser_inspector_pseudoclass-menu.js
@@ -10,45 +10,36 @@ const TEST_URI = "data:text/html;charset
                  "pseudo-class lock node menu tests" +
                  "<div>test div</div>";
 const PSEUDOS = ["hover", "active", "focus"];
 
 add_task(function* () {
   let {inspector, testActor} = yield openInspectorForURL(TEST_URI);
   yield selectNode("div", inspector);
 
-  info("Getting the inspector ctx menu and opening it");
-  let menu = inspector.panelDoc.getElementById("inspector-node-popup");
-  yield openMenu(menu);
+  let allMenuItems = openContextMenuAndGetAllItems(inspector);
 
-  yield testMenuItems(testActor, menu, inspector);
-
-  menu.hidePopup();
+  yield testMenuItems(testActor, allMenuItems, inspector);
 });
 
-function openMenu(menu) {
-  let promise = once(menu, "popupshowing", true);
-  menu.openPopup();
-  return promise;
-}
-
-function* testMenuItems(testActor, menu, inspector) {
+function* testMenuItems(testActor, allMenuItems, inspector) {
   for (let pseudo of PSEUDOS) {
-    let menuitem = inspector.panelDoc.getElementById(
-      "node-menu-pseudo-" + pseudo);
-    ok(menuitem, ":" + pseudo + " menuitem exists");
+    let menuItem =
+      allMenuItems.find(item => item.id === "node-menu-pseudo-" + pseudo);
+    ok(menuItem, ":" + pseudo + " menuitem exists");
+    is(menuItem.disabled, false, ":" + pseudo + " menuitem is enabled");
 
     // Give the inspector panels a chance to update when the pseudoclass changes
     let onPseudo = inspector.selection.once("pseudoclass");
     let onRefresh = inspector.once("rule-view-refreshed");
 
     // Walker uses SDK-events so calling walker.once does not return a promise.
     let onMutations = once(inspector.walker, "mutations");
 
-    menuitem.doCommand();
+    menuItem.click();
 
     yield onPseudo;
     yield onRefresh;
     yield onMutations;
 
     let hasLock = yield testActor.hasPseudoClassLock("div", ":" + pseudo);
     ok(hasLock, "pseudo-class lock has been applied");
   }
--- a/devtools/client/inspector/test/head.js
+++ b/devtools/client/inspector/test/head.js
@@ -189,21 +189,24 @@ var openInspectorForURL = Task.async(fun
  */
 var openInspector = Task.async(function* (hostType) {
   info("Opening the inspector");
 
   let toolbox = yield openToolboxForTab(gBrowser.selectedTab, "inspector",
                                         hostType);
   let inspector = toolbox.getPanel("inspector");
 
-  info("Waiting for the inspector to update");
   if (inspector._updateProgress) {
+    info("Need to wait for the inspector to update");
     yield inspector.once("inspector-updated");
   }
 
+  info("Waiting for actor features to be detected");
+  yield inspector._detectingActorFeatures;
+
   yield registerTestActor(toolbox.target.client);
   let testActor = yield getTestActor(toolbox);
 
   return {toolbox, inspector, testActor};
 });
 
 function getActiveInspector() {
   let target = TargetFactory.forTab(gBrowser.selectedTab);
@@ -508,42 +511,16 @@ function redoChange(inspector) {
   }
 
   let mutated = inspector.once("markupmutation");
   inspector.markup.undo.redo();
   return mutated;
 }
 
 /**
- * Dispatch a command event on a node (e.g. click on a contextual menu item).
- * @param {DOMNode} node
- */
-function dispatchCommandEvent(node) {
-  info("Dispatching command event on " + node);
-  let commandEvent = document.createEvent("XULCommandEvent");
-  commandEvent.initCommandEvent("command", true, true, window, 0, false, false,
-                                false, false, null);
-  node.dispatchEvent(commandEvent);
-}
-
-/**
- * A helper that simulates a contextmenu event on the given chrome DOM element.
- */
-function contextMenuClick(element) {
-  let evt = element.ownerDocument.createEvent("MouseEvents");
-  let button = 2;
-
-  evt.initMouseEvent("contextmenu", true, true,
-       element.ownerDocument.defaultView, 1, 0, 0, 0, 0, false,
-       false, false, false, button, null);
-
-  element.dispatchEvent(evt);
-}
-
-/**
  * A helper that fetches a front for a node that matches the given selector or
  * doctype node if the selector is falsy.
  */
 function* getNodeFrontForSelector(selector, inspector) {
   if (selector) {
     info("Retrieving front for selector " + selector);
     return getNodeFront(selector, inspector);
   }
@@ -824,8 +801,28 @@ function isHoverTooltipTarget(tooltip, t
  */
 function assertHoverTooltipOn(tooltip, element) {
   return isHoverTooltipTarget(tooltip, element).then(() => {
     ok(true, "A tooltip is defined on hover of the given element");
   }, () => {
     ok(false, "No tooltip is defined on hover of the given element");
   });
 }
+
+/**
+ * Open the inspector menu and return all of it's items in a flat array
+ * @param {InspectorPanel} inspector
+ * @param {Object} options to pass into openMenu
+ * @return An array of MenuItems
+ */
+function openContextMenuAndGetAllItems(inspector, options) {
+  let menu = inspector._openMenu(options);
+
+  // Flatten all menu items into a single array to make searching through it easier
+  let allItems = [].concat.apply([], menu.items.map(function addItem(item) {
+    if (item.submenu) {
+      return addItem(item.submenu.items);
+    }
+    return item;
+  }));
+
+  return allItems;
+}
--- a/devtools/client/locales/en-US/inspector.dtd
+++ b/devtools/client/locales/en-US/inspector.dtd
@@ -1,179 +1,17 @@
-<!-- LOCALIZATION NOTE (inspectorHTMLEdit.label): This is the label shown
-     in the inspector contextual-menu for the item that lets users edit the
-     (outer) HTML of the current node -->
-<!ENTITY inspectorHTMLEdit.label       "Edit As HTML">
-<!ENTITY inspectorHTMLEdit.accesskey   "E">
-
-<!-- LOCALIZATION NOTE (inspectorCopyInnerHTML.label): This is the label shown
-     in the inspector contextual-menu for the item that lets users copy the
-     inner HTML of the current node -->
-<!ENTITY inspectorCopyInnerHTML.label       "Inner HTML">
-<!ENTITY inspectorCopyInnerHTML.accesskey   "I">
-
-<!-- LOCALIZATION NOTE (inspectorCopyOuterHTML.label): This is the label shown
-     in the inspector contextual-menu for the item that lets users copy the
-     outer HTML of the current node -->
-<!ENTITY inspectorCopyOuterHTML.label       "Outer HTML">
-<!ENTITY inspectorCopyOuterHTML.accesskey   "O">
-
-<!-- LOCALIZATION NOTE (inspectorCopyCSSSelector.label): This is the label
-     shown in the inspector contextual-menu for the item that lets users copy
-     the CSS Selector of the current node -->
-<!ENTITY inspectorCopyCSSSelector.label "CSS Selector">
-<!ENTITY inspectorCopyCSSSelector.accesskey "S">
-
-<!-- LOCALIZATION NOTE (inspectorPasteOuterHTML.label): This is the label shown
-     in the inspector contextual-menu for the item that lets users paste outer
-     HTML in the current node -->
-<!ENTITY inspectorPasteOuterHTML.label      "Outer HTML">
-<!ENTITY inspectorPasteOuterHTML.accesskey  "O">
-
-<!-- LOCALIZATION NOTE (inspectorPasteInnerHTML.label): This is the label shown
-     in the inspector contextual-menu for the item that lets users paste inner
-     HTML in the current node -->
-<!ENTITY inspectorPasteInnerHTML.label      "Inner HTML">
-<!ENTITY inspectorPasteInnerHTML.accesskey  "I">
-
-<!-- LOCALIZATION NOTE (inspectorHTMLPasteExtraSubmenu.label): This is the label
-     shown in the inspector contextual-menu for the sub-menu of the other Paste
-     items, which allow to paste HTML:
-     - before the current node
-     - after the current node
-     - as the first child of the current node
-     - as the last child of the current node -->
-<!ENTITY inspectorHTMLPasteExtraSubmenu.label      "Paste…">
-<!ENTITY inspectorHTMLPasteExtraSubmenu.accesskey  "t">
-
-<!-- LOCALIZATION NOTE (inspectorHTMLPasteBefore.label): This is the label shown
-     in the inspector contextual-menu for the item that lets users paste
-     the HTML before the current node -->
-<!ENTITY inspectorHTMLPasteBefore.label      "Before">
-<!ENTITY inspectorHTMLPasteBefore.accesskey  "B">
-
-<!-- LOCALIZATION NOTE (inspectorHTMLPasteAfter.label): This is the label shown
-     in the inspector contextual-menu for the item that lets users paste
-     the HTML after the current node -->
-<!ENTITY inspectorHTMLPasteAfter.label       "After">
-<!ENTITY inspectorHTMLPasteAfter.accesskey   "A">
-
-<!-- LOCALIZATION NOTE (inspectorHTMLPasteFirstChild.label): This is the label
-     shown in the inspector contextual-menu for the item that lets users paste
-     the HTML as the first child the current node -->
-<!ENTITY inspectorHTMLPasteFirstChild.label      "As First Child">
-<!ENTITY inspectorHTMLPasteFirstChild.accesskey  "F">
-
-<!-- LOCALIZATION NOTE (inspectorHTMLPasteLastChild.label): This is the label
-     shown in the inspector contextual-menu for the item that lets users paste
-     the HTML as the last child the current node -->
-<!ENTITY inspectorHTMLPasteLastChild.label       "As Last Child">
-<!ENTITY inspectorHTMLPasteLastChild.accesskey   "L">
-
-<!-- LOCALIZATION NOTE (inspectorScrollNodeIntoView.label): This is the label
-     shown in the inspector contextual-menu for the item that lets users scroll
-     the current node into view -->
-<!ENTITY inspectorScrollNodeIntoView.label       "Scroll Into View">
-<!ENTITY inspectorScrollNodeIntoView.accesskey   "S">
-
-<!-- LOCALIZATION NOTE (inspectorHTMLDelete.label): This is the label shown in
-     the inspector contextual-menu for the item that lets users delete the
-     current node -->
-<!ENTITY inspectorHTMLDelete.label          "Delete Node">
-<!ENTITY inspectorHTMLDelete.accesskey      "D">
-
-<!-- LOCALIZATION NOTE (inspectorAttributesSubmenu.label): This is the label
-     shown in the inspector contextual-menu for the sub-menu of the other
-     attribute items, which allow to:
-     - add new attribute
-     - edit attribute
-     - remove attribute -->
-<!ENTITY inspectorAttributesSubmenu.label      "Attributes">
-<!ENTITY inspectorAttributesSubmenu.accesskey  "A">
-
-<!-- LOCALIZATION NOTE (inspectorAddAttribute.label): This is the label shown in
-     the inspector contextual-menu for the item that lets users add attribute
-     to current node -->
-<!ENTITY inspectorAddAttribute.label        "Add Attribute">
-<!ENTITY inspectorAddAttribute.accesskey    "A">
-
-<!-- LOCALIZATION NOTE (inspectorEditAttribute.label): This is the label shown in
-     the inspector contextual-menu for the item that lets users edit attribute
-     for current node -->
-<!ENTITY inspectorEditAttribute.label        "Edit Attribute">
-<!ENTITY inspectorEditAttribute.accesskey    "E">
-
 <!-- LOCALIZATION NOTE (inspectorRemoveAttribute.label): This is the label shown in
      the inspector contextual-menu for the item that lets users delete attribute
      from current node -->
 <!ENTITY inspectorRemoveAttribute.label        "Remove Attribute">
 <!ENTITY inspectorRemoveAttribute.accesskey    "R">
 
 <!ENTITY inspector.selectButton.tooltip     "Select element with mouse">
 
 <!-- LOCALIZATION NOTE (inspectorSearchHTML.label3): This is the label that is
      shown as the placeholder for the markup view search in the inspector. -->
 <!ENTITY inspectorSearchHTML.label3 "Search HTML">
 
-<!-- LOCALIZATION NOTE (inspectorImageDataUri.label): This is the label
-     shown in the inspector contextual-menu for the item that lets users copy
-     the URL embedding the image data encoded in Base 64 (what we name
-     here Image Data URL). For more information:
-     https://developer.mozilla.org/en-US/docs/Web/HTTP/data_URIs -->
-<!ENTITY inspectorImageDataUri.label       "Image Data-URL">
-
-<!-- LOCALIZATION NOTE (inspectorShowDOMProperties.label): This is the label
-     shown in the inspector contextual-menu for the item that lets users see
-     the DOM properties of the current node. When triggered, this item
-     opens the split Console and displays the properties in its side panel. -->
-<!ENTITY inspectorShowDOMProperties.label       "Show DOM Properties">
-
-<!-- LOCALIZATION NOTE (inspectorUseInConsole.label): This is the label
-     shown in the inspector contextual-menu for the item that outputs a
-     variable for the current node to the console. When triggered,
-     this item opens the split Console. -->
-<!ENTITY inspectorUseInConsole.label       "Use in Console">
-
-<!-- LOCALIZATION NOTE (inspectorExpandNode.label): This is the label
-     shown in the inspector contextual-menu for recursively expanding
-     mark-up elements -->
-<!ENTITY inspectorExpandNode.label       "Expand All">
-
-<!-- LOCALIZATION NOTE (inspectorCollapseNode.label): This is the label
-     shown in the inspector contextual-menu for recursively collapsing
-     mark-up elements -->
-<!ENTITY inspectorCollapseNode.label       "Collapse">
-
-<!-- LOCALIZATION NOTE (inspectorScreenshotNode.label): This is the label
-     shown in the inspector contextual-menu for the item that lets users take
-     a screenshot of the currently selected node. -->
-<!ENTITY inspectorScreenshotNode.label       "Screenshot Node">
-
-<!-- LOCALIZATION NOTE (inspectorDuplicateNode.label): This is the label
-     shown in the inspector contextual-menu for the item that lets users
-     duplicate the currently selected node. -->
-<!ENTITY inspectorDuplicateNode.label       "Duplicate Node">
-
 <!-- LOCALIZATION NOTE (inspectorAddNode.label): This is the label shown in
      the inspector toolbar for the button that lets users add elements to the
      DOM (as children of the currently selected element). -->
 <!ENTITY inspectorAddNode.label       "Create New Node">
 <!ENTITY inspectorAddNode.accesskey   "C">
-
-<!-- LOCALIZATION NOTE (inspectorCopyHTMLSubmenu.label): This is the label
-     shown in the inspector contextual-menu for the sub-menu of the other
-     copy items, which allow to:
-     - Copy Inner HTML
-     - Copy Outer HTML
-     - Copy Unique selector
-     - Copy Image data URI -->
-<!ENTITY inspectorCopyHTMLSubmenu.label      "Copy">
-
-<!-- LOCALIZATION NOTE (inspectorPasteHTMLSubmenu.label): This is the label
-     shown in the inspector contextual-menu for the sub-menu of the other
-     paste items, which allow to:
-     - Paste Inner HTML
-     - Paste Outer HTML
-     - Before
-     - After
-     - As First Child
-     - As Last Child -->
-<!ENTITY inspectorPasteHTMLSubmenu.label      "Paste">
--- a/devtools/client/locales/en-US/inspector.properties
+++ b/devtools/client/locales/en-US/inspector.properties
@@ -105,27 +105,29 @@ inspector.menu.copyUrlToClipboard.label=
 
 # LOCALIZATION NOTE (inspector.menu.selectElement.label): This is the label of a
 # menu item in the inspector contextual-menu that appears when the user right-
 # clicks on the attribute of a node in the inspector that is the ID of another
 # element in the DOM (like with <label for="input-id">), and that allows to
 # select that element in the inspector.
 inspector.menu.selectElement.label=Select Element #%S
 
-# LOCALIZATION NOTE (inspector.menu.editAttribute.label): This is the label of a
+# LOCALIZATION NOTE (inspectorEditAttribute.label): This is the label of a
 # sub-menu "Attribute" in the inspector contextual-menu that appears
 # when the user right-clicks on the node in the inspector, and that allows
 # to edit an attribute on this node.
-inspector.menu.editAttribute.label=Edit Attribute %S
+inspectorEditAttribute.label=Edit Attribute %S
+inspectorEditAttribute.accesskey=E
 
-# LOCALIZATION NOTE (inspector.menu.removeAttribute.label): This is the label of a
+# LOCALIZATION NOTE (inspectorRemoveAttribute.label): This is the label of a
 # sub-menu "Attribute" in the inspector contextual-menu that appears
 # when the user right-clicks on the attribute of a node in the inspector,
 # and that allows to remove this attribute.
-inspector.menu.removeAttribute.label=Remove Attribute %S
+inspectorRemoveAttribute.label=Remove Attribute %S
+inspectorRemoveAttribute.accesskey=R
 
 # LOCALIZATION NOTE (inspector.nodePreview.selectNodeLabel):
 # This string is displayed in a tooltip that is shown when hovering over a DOM
 # node preview (e.g. something like "div#foo.bar").
 # DOM node previews can be displayed in places like the animation-inspector, the
 # console or the object inspector.
 # The tooltip invites the user to click on the node in order to select it in the
 # inspector panel.
@@ -136,16 +138,188 @@ inspector.nodePreview.selectNodeLabel=Cl
 # inspector icon displayed next to a DOM node preview (e.g. next to something
 # like "div#foo.bar").
 # DOM node previews can be displayed in places like the animation-inspector, the
 # console or the object inspector.
 # The tooltip invites the user to click on the icon in order to highlight the
 # node in the page.
 inspector.nodePreview.highlightNodeLabel=Click to highlight this node in the page
 
+# LOCALIZATION NOTE (inspectorHTMLEdit.label): This is the label shown
+# in the inspector contextual-menu for the item that lets users edit the
+# (outer) HTML of the current node
+inspectorHTMLEdit.label=Edit As HTML
+inspectorHTMLEdit.accesskey=E
+
+# LOCALIZATION NOTE (inspectorCopyInnerHTML.label): This is the label shown
+# in the inspector contextual-menu for the item that lets users copy the
+# inner HTML of the current node
+inspectorCopyInnerHTML.label=Inner HTML
+inspectorCopyInnerHTML.accesskey=I
+
+# LOCALIZATION NOTE (inspectorCopyOuterHTML.label): This is the label shown
+# in the inspector contextual-menu for the item that lets users copy the
+# outer HTML of the current node
+inspectorCopyOuterHTML.label=Outer HTML
+inspectorCopyOuterHTML.accesskey=O
+
+# LOCALIZATION NOTE (inspectorCopyCSSSelector.label): This is the label
+# shown in the inspector contextual-menu for the item that lets users copy
+# the CSS Selector of the current node
+inspectorCopyCSSSelector.label=CSS Selector
+inspectorCopyCSSSelector.accesskey=S
+
+# LOCALIZATION NOTE (inspectorPasteOuterHTML.label): This is the label shown
+# in the inspector contextual-menu for the item that lets users paste outer
+# HTML in the current node
+inspectorPasteOuterHTML.label=Outer HTML
+inspectorPasteOuterHTML.accesskey=O
+
+# LOCALIZATION NOTE (inspectorPasteInnerHTML.label): This is the label shown
+# in the inspector contextual-menu for the item that lets users paste inner
+# HTML in the current node
+inspectorPasteInnerHTML.label=Inner HTML
+inspectorPasteInnerHTML.accesskey=I
+
+# LOCALIZATION NOTE (inspectorHTMLPasteExtraSubmenu.label): This is the label
+# shown in the inspector contextual-menu for the sub-menu of the other Paste
+# items, which allow to paste HTML:
+# - before the current node
+# - after the current node
+# - as the first child of the current node
+# - as the last child of the current node
+inspectorHTMLPasteExtraSubmenu.label=Paste…
+inspectorHTMLPasteExtraSubmenu.accesskey=t
+
+# LOCALIZATION NOTE (inspectorHTMLPasteBefore.label): This is the label shown
+# in the inspector contextual-menu for the item that lets users paste
+# the HTML before the current node
+inspectorHTMLPasteBefore.label=Before
+inspectorHTMLPasteBefore.accesskey=B
+
+# LOCALIZATION NOTE (inspectorHTMLPasteAfter.label): This is the label shown
+# in the inspector contextual-menu for the item that lets users paste
+# the HTML after the current node
+inspectorHTMLPasteAfter.label=After
+inspectorHTMLPasteAfter.accesskey=A
+
+# LOCALIZATION NOTE (inspectorHTMLPasteFirstChild.label): This is the label
+# shown in the inspector contextual-menu for the item that lets users paste
+# the HTML as the first child the current node
+inspectorHTMLPasteFirstChild.label=As First Child
+inspectorHTMLPasteFirstChild.accesskey=F
+
+# LOCALIZATION NOTE (inspectorHTMLPasteLastChild.label): This is the label
+# shown in the inspector contextual-menu for the item that lets users paste
+# the HTML as the last child the current node
+inspectorHTMLPasteLastChild.label=As Last Child
+inspectorHTMLPasteLastChild.accesskey=L
+
+# LOCALIZATION NOTE (inspectorScrollNodeIntoView.label): This is the label
+# shown in the inspector contextual-menu for the item that lets users scroll
+# the current node into view
+inspectorScrollNodeIntoView.label=Scroll Into View
+inspectorScrollNodeIntoView.accesskey=S
+
+# LOCALIZATION NOTE (inspectorHTMLDelete.label): This is the label shown in
+# the inspector contextual-menu for the item that lets users delete the
+# current node
+inspectorHTMLDelete.label=Delete Node
+inspectorHTMLDelete.accesskey=D
+
+# LOCALIZATION NOTE (inspectorAttributesSubmenu.label): This is the label
+# shown in the inspector contextual-menu for the sub-menu of the other
+# attribute items, which allow to:
+# - add new attribute
+# - edit attribute
+# - remove attribute
+inspectorAttributesSubmenu.label=Attributes
+inspectorAttributesSubmenu.accesskey=A
+
+# LOCALIZATION NOTE (inspectorAddAttribute.label): This is the label shown in
+# the inspector contextual-menu for the item that lets users add attribute
+# to current node
+inspectorAddAttribute.label=Add Attribute
+inspectorAddAttribute.accesskey=A
+
+# LOCALIZATION NOTE (inspectorSearchHTML.label2): This is the label shown as
+# the placeholder in inspector search box
+inspectorSearchHTML.label2=Search with CSS Selectors
+inspectorSearchHTML.key=F
+
+# LOCALIZATION NOTE (inspectorSearchHTML.label3): This is the label that is
+# shown as the placeholder for the markup view search in the inspector.
+inspectorSearchHTML.label3=Search HTML
+
+# LOCALIZATION NOTE (inspectorImageDataUri.label): This is the label
+# shown in the inspector contextual-menu for the item that lets users copy
+# the URL embedding the image data encoded in Base 64 (what we name
+# here Image Data URL). For more information:
+# https://developer.mozilla.org/en-US/docs/Web/HTTP/data_URIs
+inspectorImageDataUri.label=Image Data-URL
+
+# LOCALIZATION NOTE (inspectorShowDOMProperties.label): This is the label
+# shown in the inspector contextual-menu for the item that lets users see
+# the DOM properties of the current node. When triggered, this item
+# opens the split Console and displays the properties in its side panel.
+inspectorShowDOMProperties.label=Show DOM Properties
+
+# LOCALIZATION NOTE (inspectorUseInConsole.label): This is the label
+# shown in the inspector contextual-menu for the item that outputs a
+# variable for the current node to the console. When triggered,
+# this item opens the split Console.
+inspectorUseInConsole.label=Use in Console
+
+# LOCALIZATION NOTE (inspectorExpandNode.label): This is the label
+# shown in the inspector contextual-menu for recursively expanding
+# mark-up elements
+inspectorExpandNode.label=Expand All
+
+# LOCALIZATION NOTE (inspectorCollapseNode.label): This is the label
+# shown in the inspector contextual-menu for recursively collapsing
+# mark-up elements
+inspectorCollapseNode.label=Collapse
+
+# LOCALIZATION NOTE (inspectorScreenshotNode.label): This is the label
+# shown in the inspector contextual-menu for the item that lets users take
+# a screenshot of the currently selected node.
+inspectorScreenshotNode.label=Screenshot Node
+
+# LOCALIZATION NOTE (inspectorDuplicateNode.label): This is the label
+# shown in the inspector contextual-menu for the item that lets users
+# duplicate the currently selected node.
+inspectorDuplicateNode.label=Duplicate Node
+
+# LOCALIZATION NOTE (inspectorAddNode.label): This is the label shown in
+# the inspector toolbar for the button that lets users add elements to the
+# DOM (as children of the currently selected element).
+inspectorAddNode.label=Create New Node
+inspectorAddNode.accesskey=C
+
+# LOCALIZATION NOTE (inspectorCopyHTMLSubmenu.label): This is the label
+# shown in the inspector contextual-menu for the sub-menu of the other
+# copy items, which allow to:
+# - Copy Inner HTML
+# - Copy Outer HTML
+# - Copy Unique selector
+# - Copy Image data URI
+inspectorCopyHTMLSubmenu.label=Copy
+
+# LOCALIZATION NOTE (inspectorPasteHTMLSubmenu.label): This is the label
+# shown in the inspector contextual-menu for the sub-menu of the other
+# paste items, which allow to:
+# - Paste Inner HTML
+# - Paste Outer HTML
+# - Before
+# - After
+# - As First Child
+# - As Last Child
+inspectorPasteHTMLSubmenu.label=Paste
+
+
 # LOCALIZATION NOTE (inspector.searchHTML.key):
 # Key shortcut used to focus the DOM element search box on top-right corner of
 # the markup view
 inspector.searchHTML.key=CmdOrCtrl+F
 
 # LOCALIZATION NOTE (markupView.hide.key):
 # Key shortcut used to hide the selected node in the markup view.
 markupView.hide.key=h