Bug 1231932 - Added copy attribute value context menu to elements in the DOM Inspector; r=gl
authordjmdev <djmdeveloper060796@gmail.com>
Thu, 16 Feb 2017 16:00:41 +0530
changeset 372824 80e5378cc1c819fa92099e28a5a6629d8a949e52
parent 372823 5d3d0bd5aff5a74390e16856089315b205e0454e
child 372825 6af6e3b0247f89ab98d04f16f294c6b21b9730cc
push id10863
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 23:02:23 +0000
treeherdermozilla-aurora@0931190cd725 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgl
bugs1231932
milestone54.0a1
Bug 1231932 - Added copy attribute value context menu to elements in the DOM Inspector; r=gl Added a new attributes submenu option to copy value of an attribute to clipboard. Also updated the corresponding test files.
devtools/client/inspector/inspector.js
devtools/client/inspector/test/browser_inspector_menu-01-sensitivity.js
devtools/client/inspector/test/browser_inspector_menu-05-attribute-items.js
devtools/client/inspector/test/doc_inspector_menu.html
devtools/client/locales/en-US/inspector.properties
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -1279,30 +1279,36 @@ Inspector.prototype = {
     attributesSubmenu.append(new MenuItem({
       id: "node-menu-add-attribute",
       label: INSPECTOR_L10N.getStr("inspectorAddAttribute.label"),
       accesskey: INSPECTOR_L10N.getStr("inspectorAddAttribute.accesskey"),
       disabled: !isEditableElement,
       click: () => this.onAddAttribute(),
     }));
     attributesSubmenu.append(new MenuItem({
+      id: "node-menu-copy-attribute",
+      label: INSPECTOR_L10N.getFormatStr("inspectorCopyAttributeValue.label",
+                                        isAttributeClicked ? `"${nodeInfo.value}"` : ""),
+      accesskey: INSPECTOR_L10N.getStr("inspectorCopyAttributeValue.accesskey"),
+      disabled: !isAttributeClicked,
+      click: () => this.onCopyAttributeValue(),
+    }));
+    attributesSubmenu.append(new MenuItem({
       id: "node-menu-edit-attribute",
       label: INSPECTOR_L10N.getFormatStr("inspectorEditAttribute.label",
                                         isAttributeClicked ? `"${nodeInfo.name}"` : ""),
       accesskey: INSPECTOR_L10N.getStr("inspectorEditAttribute.accesskey"),
       disabled: !isAttributeClicked,
       click: () => this.onEditAttribute(),
     }));
-
     attributesSubmenu.append(new MenuItem({
       id: "node-menu-remove-attribute",
       label: INSPECTOR_L10N.getFormatStr("inspectorRemoveAttribute.label",
                                         isAttributeClicked ? `"${nodeInfo.name}"` : ""),
-      accesskey:
-        INSPECTOR_L10N.getStr("inspectorRemoveAttribute.accesskey"),
+      accesskey: INSPECTOR_L10N.getStr("inspectorRemoveAttribute.accesskey"),
       disabled: !isAttributeClicked,
       click: () => this.onRemoveAttribute(),
     }));
 
     return attributesSubmenu;
   },
 
   /**
@@ -1813,16 +1819,24 @@ Inspector.prototype = {
    * Used for node context menu and shouldn't be called directly.
    */
   onAddAttribute: function () {
     let container = this.markup.getContainer(this.selection.nodeFront);
     container.addAttribute();
   },
 
   /**
+   * Copy attribute value for node.
+   * Used for node context menu and shouldn't be called directly.
+   */
+  onCopyAttributeValue: function () {
+    clipboardHelper.copyString(this.nodeMenuTriggerInfo.value);
+  },
+
+  /**
    * Edit attribute for node.
    * Used for node context menu and shouldn't be called directly.
    */
   onEditAttribute: function () {
     let container = this.markup.getContainer(this.selection.nodeFront);
     container.editAttribute(this.nodeMenuTriggerInfo.name);
   },
 
--- a/devtools/client/inspector/test/browser_inspector_menu-01-sensitivity.js
+++ b/devtools/client/inspector/test/browser_inspector_menu-01-sensitivity.js
@@ -30,16 +30,17 @@ const ALL_MENU_ITEMS = [
   "node-menu-copyimagedatauri",
   "node-menu-delete",
   "node-menu-pseudo-hover",
   "node-menu-pseudo-active",
   "node-menu-pseudo-focus",
   "node-menu-scrollnodeintoview",
   "node-menu-screenshotnode",
   "node-menu-add-attribute",
+  "node-menu-copy-attribute",
   "node-menu-edit-attribute",
   "node-menu-remove-attribute"
 ].concat(PASTE_MENU_ITEMS, ACTIVE_ON_DOCTYPE_ITEMS);
 
 const INACTIVE_ON_DOCTYPE_ITEMS =
   ALL_MENU_ITEMS.filter(item => ACTIVE_ON_DOCTYPE_ITEMS.indexOf(item) === -1);
 
 /**
@@ -65,145 +66,157 @@ const TEST_CASES = [
     disabled: INACTIVE_ON_DOCTYPE_ITEMS,
   },
   {
     desc: "element node HTML on the clipboard",
     clipboardData: "<p>some text</p>",
     clipboardDataType: "html",
     disabled: [
       "node-menu-copyimagedatauri",
+      "node-menu-copy-attribute",
       "node-menu-edit-attribute",
       "node-menu-remove-attribute"
     ],
     selector: "#sensitivity",
   },
   {
     desc: "<html> element",
     clipboardData: "<p>some text</p>",
     clipboardDataType: "html",
     selector: "html",
     disabled: [
       "node-menu-copyimagedatauri",
       "node-menu-pastebefore",
       "node-menu-pasteafter",
       "node-menu-pastefirstchild",
       "node-menu-pastelastchild",
+      "node-menu-copy-attribute",
       "node-menu-edit-attribute",
       "node-menu-remove-attribute"
     ],
   },
   {
     desc: "<body> with HTML on clipboard",
     clipboardData: "<p>some text</p>",
     clipboardDataType: "html",
     selector: "body",
     disabled: [
       "node-menu-copyimagedatauri",
       "node-menu-pastebefore",
       "node-menu-pasteafter",
+      "node-menu-copy-attribute",
       "node-menu-edit-attribute",
       "node-menu-remove-attribute"
     ]
   },
   {
     desc: "<img> with HTML on clipboard",
     clipboardData: "<p>some text</p>",
     clipboardDataType: "html",
     selector: "img",
     disabled: [
+      "node-menu-copy-attribute",
       "node-menu-edit-attribute",
       "node-menu-remove-attribute"
     ]
   },
   {
     desc: "<head> with HTML on clipboard",
     clipboardData: "<p>some text</p>",
     clipboardDataType: "html",
     selector: "head",
     disabled: [
       "node-menu-copyimagedatauri",
       "node-menu-pastebefore",
       "node-menu-pasteafter",
       "node-menu-screenshotnode",
+      "node-menu-copy-attribute",
       "node-menu-edit-attribute",
       "node-menu-remove-attribute"
     ],
   },
   {
     desc: "<head> with no html on clipboard",
     selector: "head",
     disabled: PASTE_MENU_ITEMS.concat([
       "node-menu-copyimagedatauri",
       "node-menu-screenshotnode",
+      "node-menu-copy-attribute",
       "node-menu-edit-attribute",
       "node-menu-remove-attribute"
     ]),
   },
   {
     desc: "<element> with text on clipboard",
     clipboardData: "some text",
     clipboardDataType: undefined,
     selector: "#paste-area",
     disabled: [
       "node-menu-copyimagedatauri",
+      "node-menu-copy-attribute",
       "node-menu-edit-attribute",
       "node-menu-remove-attribute"
     ]
   },
   {
     desc: "<element> with base64 encoded image data uri on clipboard",
     clipboardData:
       "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABC" +
       "AAAAAA6fptVAAAACklEQVQYV2P4DwABAQEAWk1v8QAAAABJRU5ErkJggg==",
     clipboardDataType: undefined,
     selector: "#paste-area",
     disabled: PASTE_MENU_ITEMS.concat([
       "node-menu-copyimagedatauri",
+      "node-menu-copy-attribute",
       "node-menu-edit-attribute",
       "node-menu-remove-attribute"
     ]),
   },
   {
     desc: "<element> with empty string on clipboard",
     clipboardData: "",
     clipboardDataType: undefined,
     selector: "#paste-area",
     disabled: PASTE_MENU_ITEMS.concat([
       "node-menu-copyimagedatauri",
+      "node-menu-copy-attribute",
       "node-menu-edit-attribute",
       "node-menu-remove-attribute"
     ]),
   },
   {
     desc: "<element> with whitespace only on clipboard",
     clipboardData: " \n\n\t\n\n  \n",
     clipboardDataType: undefined,
     selector: "#paste-area",
     disabled: PASTE_MENU_ITEMS.concat([
       "node-menu-copyimagedatauri",
+      "node-menu-copy-attribute",
       "node-menu-edit-attribute",
       "node-menu-remove-attribute"
     ]),
   },
   {
     desc: "<element> that isn't visible on the page, empty clipboard",
     selector: "#hiddenElement",
     disabled: PASTE_MENU_ITEMS.concat([
       "node-menu-copyimagedatauri",
       "node-menu-screenshotnode",
+      "node-menu-copy-attribute",
       "node-menu-edit-attribute",
       "node-menu-remove-attribute"
     ]),
   },
   {
     desc: "<element> nested in another hidden element, empty clipboard",
     selector: "#nestedHiddenElement",
     disabled: PASTE_MENU_ITEMS.concat([
       "node-menu-copyimagedatauri",
       "node-menu-screenshotnode",
+      "node-menu-copy-attribute",
       "node-menu-edit-attribute",
       "node-menu-remove-attribute"
     ]),
   },
   {
     desc: "<element> with context menu triggered on attribute, empty clipboard",
     selector: "#attributes",
     disabled: PASTE_MENU_ITEMS.concat(["node-menu-copyimagedatauri"]),
--- a/devtools/client/inspector/test/browser_inspector_menu-05-attribute-items.js
+++ b/devtools/client/inspector/test/browser_inspector_menu-05-attribute-items.js
@@ -7,16 +7,17 @@ http://creativecommons.org/publicdomain/
 
 const TEST_URL = URL_ROOT + "doc_inspector_menu.html";
 
 add_task(function* () {
   let { inspector, testActor } = yield openInspectorForURL(TEST_URL);
   yield selectNode("#attributes", inspector);
 
   yield testAddAttribute();
+  yield testCopyAttributeValue();
   yield testEditAttribute();
   yield testRemoveAttribute();
 
   function* testAddAttribute() {
     info("Triggering 'Add Attribute' and waiting for mutation to occur");
     let addAttribute = getMenuItem("node-menu-add-attribute");
     addAttribute.click();
 
@@ -24,16 +25,30 @@ add_task(function* () {
     let onMutation = inspector.once("markupmutation");
     EventUtils.synthesizeKey("VK_RETURN", {});
     yield onMutation;
 
     let hasAttribute = testActor.hasNode("#attributes.u-hidden");
     ok(hasAttribute, "attribute was successfully added");
   }
 
+  function* testCopyAttributeValue() {
+    info("Testing 'Copy Attribute Value' and waiting for clipboard promise to resolve");
+    let copyAttributeValue = getMenuItem("node-menu-copy-attribute");
+
+    info("Triggering 'Copy Attribute Value' and waiting for clipboard to copy the value");
+    inspector.nodeMenuTriggerInfo = {
+      type: "attribute",
+      name: "data-edit",
+      value: "the"
+    };
+
+    yield waitForClipboardPromise(() => copyAttributeValue.click(), "the");
+  }
+
   function* testEditAttribute() {
     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"
--- a/devtools/client/inspector/test/doc_inspector_menu.html
+++ b/devtools/client/inspector/test/doc_inspector_menu.html
@@ -18,12 +18,12 @@
       <p class="duplicate">This will be duplicated</p>
       <p id="delete">This has to be deleted</p>
       <img id="copyimage" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQYV2P4DwABAQEAWk1v8QAAAABJRU5ErkJggg==" />
       <div id="hiddenElement" style="display: none;">
         <p id="nestedHiddenElement">Visible element nested inside a non-visible element</p>
       </div>
       <p id="console-var">Paragraph for testing console variables</p>
       <p id="console-var-multi">Paragraph for testing multiple console variables</p>
-      <p id="attributes" data-edit="original" data-remove="thing">Attributes are going to be changed here</p>
+      <p id="attributes" data-copy="the" data-edit="original" data-remove="thing">Attributes are going to be changed here</p>
     </div>
   </body>
 </html>
--- a/devtools/client/locales/en-US/inspector.properties
+++ b/devtools/client/locales/en-US/inspector.properties
@@ -112,16 +112,23 @@ inspectorEditAttribute.accesskey=E
 
 # 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.
 inspectorRemoveAttribute.label=Remove Attribute %S
 inspectorRemoveAttribute.accesskey=R
 
+# LOCALIZATION NOTE (inspectorCopyAttributeValue.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 copy the attribute value to clipboard.
+inspectorCopyAttributeValue.label=Copy Attribute Value %S
+inspectorCopyAttributeValue.accesskey=V
+
 # 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.
 inspector.nodePreview.selectNodeLabel=Click to select this node in the Inspector