Bug 788890 - GCLI screenshot command with no filename should copy to clipboard, r=jwalker
authorGirish Sharma <scrapmachines@gmail.com>
Fri, 14 Sep 2012 02:26:19 +0530
changeset 106955 c4ebd4e8997544ee3917a0ba1a34488beb1bfcc0
parent 106954 3f7d16ee5712e82211861f50dffbc8c83c76fb20
child 106956 f736813af10ce05788bb1d707ce0e3bd00cff33d
push id1078
push uservporof@mozilla.com
push dateThu, 13 Sep 2012 23:02:22 +0000
treeherderfx-team@dd4d761d670d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwalker
bugs788890
milestone18.0a1
Bug 788890 - GCLI screenshot command with no filename should copy to clipboard, r=jwalker
browser/devtools/commandline/CmdScreenshot.jsm
browser/devtools/commandline/test/Makefile.in
browser/devtools/commandline/test/browser_cmd_screenshot.html
browser/devtools/commandline/test/browser_cmd_screenshot.js
browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
--- a/browser/devtools/commandline/CmdScreenshot.jsm
+++ b/browser/devtools/commandline/CmdScreenshot.jsm
@@ -7,74 +7,94 @@ const { classes: Cc, interfaces: Ci, uti
 let EXPORTED_SYMBOLS = [ ];
 
 Cu.import("resource:///modules/devtools/gcli.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "LayoutHelpers",
                                   "resource:///modules/devtools/LayoutHelpers.jsm");
 
+// String used as an indication to generate default file name in the following
+// format: "Screen Shot yyyy-mm-dd at HH.MM.SS.png"
+const FILENAME_DEFAULT_VALUE = " ";
+
 /**
  * 'screenshot' command
  */
 gcli.addCommand({
   name: "screenshot",
   description: gcli.lookup("screenshotDesc"),
   manual: gcli.lookup("screenshotManual"),
-  returnType: "string",
+  returnType: "html",
   params: [
     {
       name: "filename",
       type: "string",
+      defaultValue: FILENAME_DEFAULT_VALUE,
       description: gcli.lookup("screenshotFilenameDesc"),
       manual: gcli.lookup("screenshotFilenameManual")
     },
     {
-      name: "delay",
-      type: { name: "number", min: 0 },
-      defaultValue: 0,
-      description: gcli.lookup("screenshotDelayDesc"),
-      manual: gcli.lookup("screenshotDelayManual")
-    },
-    {
-      name: "fullpage",
-      type: "boolean",
-      description: gcli.lookup("screenshotFullPageDesc"),
-      manual: gcli.lookup("screenshotFullPageManual")
-    },
-    {
-      name: "selector",
-      type: "node",
-      defaultValue: null,
-      description: gcli.lookup("inspectNodeDesc"),
-      manual: gcli.lookup("inspectNodeManual")
+      group: gcli.lookup("screenshotGroupOptions"),
+      params: [
+        {
+          name: "clipboard",
+          type: "boolean",
+          description: gcli.lookup("screenshotClipboardDesc"),
+          manual: gcli.lookup("screenshotClipboardManual")
+        },
+        {
+          name: "delay",
+          type: { name: "number", min: 0 },
+          defaultValue: 0,
+          description: gcli.lookup("screenshotDelayDesc"),
+          manual: gcli.lookup("screenshotDelayManual")
+        },
+        {
+          name: "fullpage",
+          type: "boolean",
+          description: gcli.lookup("screenshotFullPageDesc"),
+          manual: gcli.lookup("screenshotFullPageManual")
+        },
+        {
+          name: "selector",
+          type: "node",
+          defaultValue: null,
+          description: gcli.lookup("inspectNodeDesc"),
+          manual: gcli.lookup("inspectNodeManual")
+        }
+      ]
     }
   ],
   exec: function Command_screenshot(args, context) {
     var document = context.environment.contentDocument;
     if (args.delay > 0) {
       var promise = context.createPromise();
       document.defaultView.setTimeout(function Command_screenshotDelay() {
-        let reply = this.grabScreen(document, args.filename);
+        let reply = this.grabScreen(document, args.filename, args.clipboard,
+                                    args.fullpage);
         promise.resolve(reply);
       }.bind(this), args.delay * 1000);
       return promise;
     }
     else {
-      return this.grabScreen(document, args.filename, args.fullpage, args.selector);
+      return this.grabScreen(document, args.filename, args.clipboard,
+                             args.fullpage, args.selector);
     }
   },
   grabScreen:
-  function Command_screenshotGrabScreen(document, filename, fullpage, node) {
+  function Command_screenshotGrabScreen(document, filename, clipboard,
+                                        fullpage, node) {
     let window = document.defaultView;
     let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
     let left = 0;
     let top = 0;
     let width;
     let height;
+    let div = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
 
     if (!fullpage) {
       if (!node) {
         left = window.scrollX;
         top = window.scrollY;
         width = window.innerWidth;
         height = window.innerHeight;
       } else {
@@ -88,47 +108,105 @@ gcli.addCommand({
       width = window.innerWidth + window.scrollMaxX;
       height = window.innerHeight + window.scrollMaxY;
     }
     canvas.width = width;
     canvas.height = height;
 
     let ctx = canvas.getContext("2d");
     ctx.drawWindow(window, left, top, width, height, "#fff");
+    let data = canvas.toDataURL("image/png", "");
 
-    let data = canvas.toDataURL("image/png", "");
+    try {
+      if (clipboard) {
+        let io = Cc["@mozilla.org/network/io-service;1"]
+                   .getService(Ci.nsIIOService);
+        let channel = io.newChannel(data, null, null);
+        let input = channel.open();
+        let imgTools = Cc["@mozilla.org/image/tools;1"]
+                         .getService(Ci.imgITools);
+
+        let container = {};
+        imgTools.decodeImageData(input, channel.contentType, container);
+
+        let wrapped = Cc["@mozilla.org/supports-interface-pointer;1"]
+                        .createInstance(Ci.nsISupportsInterfacePointer);
+        wrapped.data = container.value;
+
+        let trans = Cc["@mozilla.org/widget/transferable;1"]
+                      .createInstance(Ci.nsITransferable);
+        if ("init" in trans) {
+          trans.init(null);
+        }
+        trans.addDataFlavor(channel.contentType);
+        trans.setTransferData(channel.contentType, wrapped, -1);
+
+        let clipid = Ci.nsIClipboard;
+        let clip = Cc["@mozilla.org/widget/clipboard;1"].getService(clipid);
+        clip.setData(trans, null, clipid.kGlobalClipboard);
+        div.textContent = gcli.lookup("screenshotCopied");
+        return div;
+      }
+    }
+    catch (ex) {
+      div.textContent = gcli.lookup("screenshotErrorCopying");
+      return div;
+    }
+
     let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
 
+    // Create a name for the file if not present
+    if (filename == FILENAME_DEFAULT_VALUE) {
+      let date = new Date();
+      let dateString = date.getFullYear() + "-" + (date.getMonth() + 1) +
+                       "-" + date.getDate();
+      dateString = dateString.split("-").map(function(part) {
+        if (part.length == 1) {
+          part = "0" + part;
+        }
+        return part;
+      }).join("-");
+      let timeString = date.toTimeString().replace(/:/g, ".").split(" ")[0];
+      filename = gcli.lookupFormat("screenshotGeneratedFilename",
+                                   [dateString, timeString]) + ".png";
+    }
     // Check there is a .png extension to filename
-    if (!filename.match(/.png$/i)) {
+    else if (!filename.match(/.png$/i)) {
       filename += ".png";
     }
 
     // If the filename is relative, tack it onto the download directory
     if (!filename.match(/[\\\/]/)) {
       let downloadMgr = Cc["@mozilla.org/download-manager;1"]
-        .getService(Ci.nsIDownloadManager);
+                          .getService(Ci.nsIDownloadManager);
       let tempfile = downloadMgr.userDownloadsDirectory;
       tempfile.append(filename);
       filename = tempfile.path;
     }
 
     try {
       file.initWithPath(filename);
     } catch (ex) {
-      return "Error saving to " + filename;
+      div.textContent = gcli.lookup("screenshotErrorSavingToFile") + " " + filename;
+      return div;
     }
 
     let ioService = Cc["@mozilla.org/network/io-service;1"]
-      .getService(Ci.nsIIOService);
+                      .getService(Ci.nsIIOService);
 
     let Persist = Ci.nsIWebBrowserPersist;
     let persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
-      .createInstance(Persist);
+                    .createInstance(Persist);
     persist.persistFlags = Persist.PERSIST_FLAGS_REPLACE_EXISTING_FILES |
                            Persist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
 
     let source = ioService.newURI(data, "UTF8", null);
     persist.saveURI(source, null, null, null, null, file);
 
-    return "Saved to " + filename;
+    div.textContent = gcli.lookup("screenshotSavedToFile") + " " + filename;
+    div.addEventListener("click", function openFile() {
+      div.removeEventListener("click", openFile);
+      file.reveal();
+    });
+    div.style.cursor = "pointer";
+    return div;
   }
-});
+});
\ No newline at end of file
--- a/browser/devtools/commandline/test/Makefile.in
+++ b/browser/devtools/commandline/test/Makefile.in
@@ -19,22 +19,24 @@ MOCHITEST_BROWSER_FILES = \
   browser_cmd_calllog_chrome.js \
   browser_cmd_commands.js \
   browser_cmd_cookie.js \
   browser_cmd_integrate.js \
   browser_cmd_jsb.js \
   browser_cmd_pagemod_export.js \
   browser_cmd_pref.js \
   browser_cmd_restart.js \
+  browser_cmd_screenshot.js \
   browser_cmd_settings.js \
   browser_gcli_web.js \
   head.js \
   helpers.js \
   $(NULL)
 
 MOCHITEST_BROWSER_FILES += \
   browser_dbg_cmd_break.html \
   browser_dbg_cmd.html \
+  browser_cmd_screenshot.html \
   browser_cmd_pagemod_export.html \
   browser_cmd_jsb_script.jsi \
   $(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/test/browser_cmd_screenshot.html
@@ -0,0 +1,6 @@
+<html>
+  <head></head>
+  <body>
+    <img id="testImage" ></img>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/test/browser_cmd_screenshot.js
@@ -0,0 +1,146 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that screenshot command works properly
+const TEST_URI = "http://example.com/browser/browser/devtools/commandline/" +
+                 "test/browser_cmd_screenshot.html";
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+let tempScope = {};
+Cu.import("resource://gre/modules/FileUtils.jsm", tempScope);
+let FileUtils = tempScope.FileUtils;
+
+function test() {
+  DeveloperToolbarTest.test(TEST_URI, [ testInput, testCapture ]);
+}
+
+function testInput() {
+  helpers.setInput('screenshot');
+  helpers.check({
+    input:  'screenshot',
+    markup: 'VVVVVVVVVV',
+    status: 'VALID',
+    args: {
+    }
+  });
+
+  helpers.setInput('screenshot abc.png');
+  helpers.check({
+    input:  'screenshot abc.png',
+    markup: 'VVVVVVVVVVVVVVVVVV',
+    status: 'VALID',
+    args: {
+      filename: { value: "abc.png"},
+    }
+  });
+
+  helpers.setInput('screenshot --fullpage');
+  helpers.check({
+    input:  'screenshot --fullpage',
+    markup: 'VVVVVVVVVVVVVVVVVVVVV',
+    status: 'VALID',
+    args: {
+      fullpage: { value: true},
+    }
+  });
+
+  helpers.setInput('screenshot abc --delay 5');
+  helpers.check({
+    input:  'screenshot abc --delay 5',
+    markup: 'VVVVVVVVVVVVVVVVVVVVVVVV',
+    status: 'VALID',
+    args: {
+      filename: { value: "abc"},
+      delay: { value: "5"},
+    }
+  });
+
+  helpers.setInput('screenshot --selector img#testImage');
+  helpers.check({
+    input:  'screenshot --selector img#testImage',
+    markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV',
+    status: 'VALID',
+    args: {
+      selector: { value: content.document.getElementById("testImage")},
+    }
+  });
+}
+
+function testCapture() {
+  function checkTemporaryFile() {
+    // Create a temporary file.
+    let gFile = FileUtils.getFile("TmpD", ["TestScreenshotFile.png"]);
+    if (gFile.exists()) {
+      gFile.remove(false);
+      return true;
+    }
+    else {
+      return false;
+    }
+  }
+
+  function clearClipboard() {
+    let clipid = Ci.nsIClipboard;
+    let clip = Cc["@mozilla.org/widget/clipboard;1"].getService(clipid);
+    clip.emptyClipboard(clipid.kGlobalClipboard);
+  }
+
+  function checkClipboard() {
+    try {
+      let clipid = Ci.nsIClipboard;
+      let clip = Cc["@mozilla.org/widget/clipboard;1"].getService(clipid);
+      let trans = Cc["@mozilla.org/widget/transferable;1"]
+                    .createInstance(Ci.nsITransferable);
+      if ("init" in trans) {
+        trans.init(null);
+      }
+      let io = Cc["@mozilla.org/network/io-service;1"]
+                 .getService(Ci.nsIIOService);
+      let contentType = io.newChannel("", null, null).contentType;
+      trans.addDataFlavor(contentType);
+      clip.getData(trans, clipid.kGlobalClipboard);
+      let str = new Object();
+      let strLength = new Object();
+      trans.getTransferData(contentType, str, strLength);
+      if (str && strLength > 0) {
+        clip.emptyClipboard(clipid.kGlobalClipboard);
+        return true;
+      }
+    }
+    catch (ex) {}
+    return false;
+  }
+
+  let path = FileUtils.getFile("TmpD", ["TestScreenshotFile.png"]).path;
+
+  DeveloperToolbarTest.exec({
+    typed: "screenshot " + path,
+    args: {
+      delay: 0,
+      filename: "" + path,
+      fullpage: false,
+      clipboard: false,
+      node: null,
+    },
+    outputMatch: new RegExp("^Saved to "),
+  });
+
+  ok(checkTemporaryFile, "Screenshot got created");
+
+  clearClipboard();
+
+  DeveloperToolbarTest.exec({
+    typed: "screenshot --fullpage --clipboard",
+    args: {
+      delay: 0,
+      filename: " ",
+      fullpage: true,
+      clipboard: true,
+      node: null,
+    },
+    outputMatch: new RegExp("^Copied to clipboard.$"),
+  });
+
+  ok(checkClipboard, "Screenshot got created and copied");
+}
+
--- a/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
@@ -55,16 +55,30 @@ screenshotManual=Save a PNG image of the
 # a dialog when the user is using this command.
 screenshotFilenameDesc=Destination filename
 
 # LOCALIZATION NOTE (screenshotFilenameManual) A fuller description of the
 # 'filename' parameter to the 'screenshot' command, displayed when the user
 # asks for help on what it does.
 screenshotFilenameManual=The name of the file (should have a '.png' extension) to which we write the screenshot.
 
+# LOCALIZATION NOTE (screenshotClipboardDesc) A very short string to describe
+# the 'clipboard' parameter to the 'screenshot' command, which is displayed in
+# a dialog when the user is using this command.
+screenshotClipboardDesc=Copy screenshot to clipboard? (true/false)
+
+# LOCALIZATION NOTE (screenshotClipboardManual) A fuller description of the
+# 'clipboard' parameter to the 'screenshot' command, displayed when the user
+# asks for help on what it does.
+screenshotClipboardManual=True if you want to copy the screenshot instead of saving it to a file.
+
+# LOCALIZATION NOTE (screenshotGroupOptions) A label for the optional options of
+# the screenshot command.
+screenshotGroupOptions=Options
+
 # LOCALIZATION NOTE (screenshotDelayDesc) A very short string to describe
 # the 'delay' parameter to the 'screenshot' command, which is displayed in
 # a dialog when the user is using this command.
 screenshotDelayDesc=Delay (seconds)
 
 # LOCALIZATION NOTE (screenshotDelayManual) A fuller description of the
 # 'delay' parameter to the 'screenshot' command, displayed when the user
 # asks for help on what it does.
@@ -75,16 +89,38 @@ screenshotDelayManual=The time to wait (
 # a dialog when the user is using this command.
 screenshotFullPageDesc=Entire webpage? (true/false)
 
 # LOCALIZATION NOTE (screenshotFullscreenManual) A fuller description of the
 # 'fullscreen' parameter to the 'screenshot' command, displayed when the user
 # asks for help on what it does.
 screenshotFullPageManual=True if the screenshot should also include parts of the webpage which are outside the current scrolled bounds.
 
+# LOCALIZATION NOTE (screenshotGeneratedFilename) The auto generated filename
+# when no file name is provided. the first argument (%1$S) is the date string
+# in yyyy-mm-dd format and the second argument (%2$S) is the time string
+# in HH.MM.SS format. Please don't add the extension here.
+screenshotGeneratedFilename=Screen Shot %1$S at %2$S
+
+# LOCALIZATION NOTE (screenshotErrorSavingToFile) Text displayed to user upon
+# encountering error while saving the screenshot to the file specified.
+screenshotErrorSavingToFile=Error saving to
+
+# LOCALIZATION NOTE (screenshotSavedToFile) Text displayed to user when the
+# screenshot is successfully saved to the file specified.
+screenshotSavedToFile=Saved to
+
+# LOCALIZATION NOTE (screenshotErrorCopying) Text displayed to user upon
+# encountering error while copying the screenshot to clipboard.
+screenshotErrorCopying=Error occurred while copying to clipboard.
+
+# LOCALIZATION NOTE (screenshotCopied) Text displayed to user when the
+# screenshot is successfully copied to the clipboard.
+screenshotCopied=Copied to clipboard.
+
 # LOCALIZATION NOTE (restartFirefoxDesc) A very short description of the
 # 'restart' command. This string is designed to be shown in a menu alongside the
 # command name, which is why it should be as short as possible.
 restartFirefoxDesc=Restart Firefox
 
 # LOCALIZATION NOTE (restartFirefoxNocacheDesc) A very short string to
 # describe the 'nocache' parameter to the 'restart' command, which is
 # displayed in a dialog when the user is using this command.