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 107063 c4ebd4e8997544ee3917a0ba1a34488beb1bfcc0
parent 107062 3f7d16ee5712e82211861f50dffbc8c83c76fb20
child 107064 f736813af10ce05788bb1d707ce0e3bd00cff33d
push id23468
push userttaubert@mozilla.com
push dateFri, 14 Sep 2012 20:14:18 +0000
treeherdermozilla-central@c66a8c55e6dd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwalker
bugs788890
milestone18.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 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.