Bug 1208864 - Duplicate node context menu item in markup view. r=pbro
authorTim Nguyen <ntim.bugs@gmail.com>
Mon, 19 Oct 2015 14:04:00 +0200
changeset 303715 c641e850dd9212c24de9f7df2b9f2581b4df8cde
parent 303714 ff2744dd112ad89208864d942677f2699caeba48
child 303716 399f9492ad3c3d7f9ffc4859dfeeeacb5b555ceb
push id1001
push userraliiev@mozilla.com
push dateMon, 18 Jan 2016 19:06:03 +0000
treeherdermozilla-release@8b89261f3ac4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspbro
bugs1208864
milestone44.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1208864 - Duplicate node context menu item in markup view. r=pbro
browser/locales/en-US/chrome/browser/devtools/inspector.dtd
devtools/client/inspector/inspector-panel.js
devtools/client/inspector/inspector.xul
devtools/server/actors/inspector.js
--- a/browser/locales/en-US/chrome/browser/devtools/inspector.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/inspector.dtd
@@ -120,8 +120,13 @@
      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">
--- a/devtools/client/inspector/inspector-panel.js
+++ b/devtools/client/inspector/inspector-panel.js
@@ -649,16 +649,19 @@ InspectorPanel.prototype = {
   /**
    * Disable the delete item if needed. Update the pseudo classes.
    */
   _setupNodeMenu: function() {
     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;
 
     // Set the pseudo classes
     for (let name of ["hover", "active", "focus"]) {
       let menu = this.panelDoc.getElementById("node-menu-pseudo-" + name);
 
@@ -678,16 +681,17 @@ InspectorPanel.prototype = {
     } else {
       deleteNode.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");
@@ -695,20 +699,30 @@ InspectorPanel.prototype = {
     let markUpContainer = this.markup.importNode(this.selection.nodeFront, false);
     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");
@@ -1173,16 +1187,30 @@ InspectorPanel.prototype = {
     if (!this.selection.isNode()) {
       return;
     }
 
     this.selection.nodeFront.scrollIntoView();
   },
 
   /**
+   * Duplicate the selected node
+   */
+  duplicateNode: function() {
+    let selection = this.selection;
+    if (!selection.isElementNode() ||
+        selection.isRoot() ||
+        selection.isAnonymousNode() ||
+        selection.isPseudoElementNode()) {
+      return;
+    }
+    this.walker.duplicateNode(selection.nodeFront).catch(e => console.error(e));
+  },
+
+  /**
    * Delete the selected node.
    */
   deleteNode: function() {
     if (!this.selection.isNode() ||
          this.selection.isRoot()) {
       return;
     }
 
--- a/devtools/client/inspector/inspector.xul
+++ b/devtools/client/inspector/inspector.xul
@@ -98,16 +98,19 @@
       <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-duplicatenode"
+        label="&inspectorDuplicateNode.label;"
+        oncommand="inspector.duplicateNode()"/>
       <menuitem id="node-menu-delete"
         label="&inspectorHTMLDelete.label;"
         accesskey="&inspectorHTMLDelete.accesskey;"
         oncommand="inspector.deleteNode()"/>
       <menuseparator id="node-menu-link-separator"/>
       <menuitem id="node-menu-link-follow"
         oncommand="inspector.onFollowLink()"/>
       <menuitem id="node-menu-link-copy"
--- a/devtools/server/actors/inspector.js
+++ b/devtools/server/actors/inspector.js
@@ -2588,16 +2588,31 @@ var WalkerActor = protocol.ActorClass({
       node: Arg(0, "domnode"),
       position: Arg(1, "string"),
       value: Arg(2, "string")
     },
     response: RetVal("disconnectedNodeArray")
   }),
 
   /**
+   * Duplicate a specified node
+   *
+   * @param {NodeActor} node The node to duplicate.
+   */
+  duplicateNode: method(function({rawNode}) {
+    let clonedNode = rawNode.cloneNode(true);
+    rawNode.parentNode.insertBefore(clonedNode, rawNode.nextSibling);
+  }, {
+    request: {
+      node: Arg(0, "domnode")
+    },
+    response: {}
+  }),
+
+  /**
    * Test whether a node is a document or a document element.
    *
    * @param {NodeActor} node The node to remove.
    * @return {boolean} True if the node is a document or a document element.
    */
   isDocumentOrDocumentElementNode: function(node) {
       return ((node.rawNode.ownerDocument &&
         node.rawNode.ownerDocument.documentElement === this.rawNode) ||