Bug 1163332 - Add a 'screenshot this node' button to the inspector popup menu. r=pbrosset
authorLéon McGregor <lemcgregor3@outlook.com>
Tue, 09 Jun 2015 03:58:00 -0400
changeset 248139 846253a39b05f5449f8750d92dcbdb7a480388d4
parent 248138 d94caa738b3fb71b96630ff4c87841c21f3033d8
child 248140 1bd6254c5f41d21145696dc6c7496a45d73fab6a
push id60888
push userkwierso@gmail.com
push dateThu, 11 Jun 2015 01:38:38 +0000
treeherdermozilla-inbound@39e638ed06bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspbrosset
bugs1163332
milestone41.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 1163332 - Add a 'screenshot this node' button to the inspector popup menu. r=pbrosset
browser/devtools/inspector/inspector-panel.js
browser/devtools/inspector/inspector.xul
browser/devtools/inspector/test/browser_inspector_menu-01-sensitivity.js
browser/devtools/inspector/test/doc_inspector_menu.html
browser/locales/en-US/chrome/browser/devtools/inspector.dtd
toolkit/devtools/server/actors/inspector.js
--- a/browser/devtools/inspector/inspector-panel.js
+++ b/browser/devtools/inspector/inspector-panel.js
@@ -23,16 +23,18 @@ loader.lazyGetter(this, "strings", () =>
 });
 loader.lazyGetter(this, "toolboxStrings", () => {
   return Services.strings.createBundle("chrome://browser/locale/devtools/toolbox.properties");
 });
 loader.lazyGetter(this, "clipboardHelper", () => {
   return Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
 });
 
+loader.lazyImporter(this, "CommandUtils", "resource:///modules/devtools/DeveloperToolbar.jsm");
+
 const LAYOUT_CHANGE_TIMER = 250;
 
 /**
  * Represents an open instance of the Inspector for a tab.
  * The inspector controls the breadcrumbs, the markup view, and the sidebar
  * (computed view, rule view, font view and layout view).
  *
  * Events:
@@ -647,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 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);
 
       if (isSelectionElement) {
         let checked = this.selection.nodeFront.hasPseudoClassLock(":" + name);
         menu.setAttribute("checked", checked);
@@ -670,18 +675,19 @@ InspectorPanel.prototype = {
     let deleteNode = this.panelDoc.getElementById("node-menu-delete");
     if (isEditableElement) {
       deleteNode.removeAttribute("disabled");
     } else {
       deleteNode.setAttribute("disabled", "true");
     }
 
     // Disable / enable "Copy Unique Selector", "Copy inner HTML",
-    // "Copy outer HTML" & "Scroll Into View" as appropriate
+    // "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 copyInnerHTML = this.panelDoc.getElementById("node-menu-copyinner");
     let copyOuterHTML = this.panelDoc.getElementById("node-menu-copyouter");
     let scrollIntoView = this.panelDoc.getElementById("node-menu-scrollnodeintoview");
 
     this._target.actorHasMethod("domnode", "scrollIntoView").then(value => {
       scrollIntoView.hidden = !value;
     });
 
@@ -695,16 +701,22 @@ InspectorPanel.prototype = {
       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");
+    }
+
     // Enable/Disable the link open/copy items.
     this._setupNodeLinkMenu();
 
     // 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");
@@ -1064,16 +1076,27 @@ InspectorPanel.prototype = {
     }
 
     this.selection.nodeFront.getUniqueSelector().then((selector) => {
       clipboardHelper.copyString(selector);
     }).then(null, console.error);
   },
 
   /**
+   * Initiate gcli screenshot command on selected node
+   */
+  screenshotNode: function() {
+    CommandUtils.createRequisition(this._target, {
+      environment: CommandUtils.createEnvironment(this, '_target')
+    }).then(requisition => {
+      requisition.updateExec("screenshot --selector " + this.selectionCssSelector);
+    });
+  },
+
+  /**
    * Scroll the node into view.
    */
   scrollNodeIntoView: function() {
     if (!this.selection.isNode()) {
       return;
     }
 
     this.selection.nodeFront.scrollIntoView();
--- a/browser/devtools/inspector/inspector.xul
+++ b/browser/devtools/inspector/inspector.xul
@@ -86,16 +86,19 @@
             oncommand="inspector.pasteAdjacentHTML('beforeEnd')"/>
         </menupopup>
       </menu>
       <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-delete"
         label="&inspectorHTMLDelete.label;"
         accesskey="&inspectorHTMLDelete.accesskey;"
         oncommand="inspector.deleteNode()"/>
       <menuseparator id="node-menu-link-separator"/>
       <menuitem id="node-menu-link-follow"
         oncommand="inspector.followAttributeLink()"/>
       <menuitem id="node-menu-link-copy"
--- a/browser/devtools/inspector/test/browser_inspector_menu-01-sensitivity.js
+++ b/browser/devtools/inspector/test/browser_inspector_menu-01-sensitivity.js
@@ -22,17 +22,18 @@ const ALL_MENU_ITEMS = [
   "node-menu-copyouter",
   "node-menu-copyuniqueselector",
   "node-menu-copyimagedatauri",
   "node-menu-showdomproperties",
   "node-menu-delete",
   "node-menu-pseudo-hover",
   "node-menu-pseudo-active",
   "node-menu-pseudo-focus",
-  "node-menu-scrollnodeintoview"
+  "node-menu-scrollnodeintoview",
+  "node-menu-screenshotnode"
 ].concat(PASTE_MENU_ITEMS);
 
 const ITEMS_WITHOUT_SHOWDOMPROPS =
   ALL_MENU_ITEMS.filter(item => item != "node-menu-showdomproperties");
 
 const TEST_CASES = [
   {
     desc: "doctype node with empty clipboard",
@@ -88,17 +89,26 @@ const TEST_CASES = [
     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",
+    ],
+  },
+  {
+    desc: "<head> with no html on clipboard",
+    selector: "head",
+    disabled: PASTE_MENU_ITEMS.concat([
+      "node-menu-copyimagedatauri",
+      "node-menu-screenshotnode",
+    ]),
   },
   {
     desc: "<element> with text on clipboard",
     clipboardData: "some text",
     clipboardDataType: undefined,
     selector: "#paste-area",
     disabled: ["node-menu-copyimagedatauri"],
   },
@@ -120,16 +130,32 @@ const TEST_CASES = [
   },
   {
     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"]),
   },
+  {
+    desc: "<element> that isn't visible on the page, empty clipboard",
+    selector: "#hiddenElement",
+    disabled: PASTE_MENU_ITEMS.concat([
+      "node-menu-copyimagedatauri",
+      "node-menu-screenshotnode",
+    ]),
+  },
+  {
+    desc: "<element> nested in another hidden element, empty clipboard",
+    selector: "#nestedHiddenElement",
+    disabled: PASTE_MENU_ITEMS.concat([
+      "node-menu-copyimagedatauri",
+      "node-menu-screenshotnode",
+    ]),
+  }
 ];
 
 let clipboard = require("sdk/clipboard");
 registerCleanupFunction(() => {
   clipboard = null;
 });
 
 add_task(function *() {
--- a/browser/devtools/inspector/test/doc_inspector_menu.html
+++ b/browser/devtools/inspector/test/doc_inspector_menu.html
@@ -12,11 +12,14 @@
         <p class="adjacent">
           <span class="ref">3</span>
         </p>
       </div>
       <p data-id="copy">Paragraph for testing copy</p>
       <p id="sensitivity">Paragraph for sensitivity</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>
     </div>
   </body>
 </html>
--- a/browser/locales/en-US/chrome/browser/devtools/inspector.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/inspector.dtd
@@ -99,8 +99,13 @@
      https://developer.mozilla.org/en-US/docs/Web/HTTP/data_URIs -->
 <!ENTITY inspectorCopyImageDataUri.label       "Copy 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 (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">
--- a/toolkit/devtools/server/actors/inspector.js
+++ b/toolkit/devtools/server/actors/inspector.js
@@ -930,16 +930,27 @@ let NodeFront = protocol.FrontClass(Node
   },
 
   get isDisplayed() {
     // The NodeActor's form contains the isDisplayed information as a boolean
     // starting from FF32. Before that, the property is missing
     return "isDisplayed" in this._form ? this._form.isDisplayed : true;
   },
 
+  get isTreeDisplayed() {
+    let parent = this;
+    while (parent) {
+      if (!parent.isDisplayed) {
+        return false;
+      }
+      parent = parent.parentNode();
+    }
+    return true;
+  },
+
   getNodeValue: protocol.custom(function() {
     if (!this.incompleteValue) {
       return delayedResolve(new ShortLongString(this.shortValue));
     } else {
       return this._getNodeValue();
     }
   }, {
     impl: "_getNodeValue"