Bug 709460 - GCLI needs a screenshot command; r=jwalker
authorVictor Porof <vporof@mozilla.com>
Thu, 24 May 2012 17:50:13 +0300
changeset 99444 68600a8af079a8a3ebbddd09880c3766b7f1d911
parent 99443 3e0e827bd2364b9d45172f7de13a6bb665e0409e
child 99445 a24414165cd43dfd58c55388e544ba95472a9060
push idunknown
push userunknown
push dateunknown
reviewersjwalker
bugs709460
milestone15.0a1
Bug 709460 - GCLI needs a screenshot command; r=jwalker
browser/devtools/commandline/GcliCommands.jsm
browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
--- a/browser/devtools/commandline/GcliCommands.jsm
+++ b/browser/devtools/commandline/GcliCommands.jsm
@@ -1,19 +1,32 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 let EXPORTED_SYMBOLS = [ ];
 
-Components.utils.import("resource:///modules/devtools/gcli.jsm");
-Components.utils.import("resource:///modules/HUDService.jsm");
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+Cu.import("resource:///modules/devtools/gcli.jsm");
+Cu.import("resource:///modules/HUDService.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+Cu.import("resource:///modules/devtools/GcliTiltCommands.jsm", {});
 
-Components.utils.import("resource:///modules/devtools/GcliTiltCommands.jsm", {});
+XPCOMUtils.defineLazyGetter(this, "Services", function () {
+  var obj = {};
+  Cu.import("resource://gre/modules/Services.jsm", obj);
+  return obj.Services;
+});
+XPCOMUtils.defineLazyGetter(this, "LayoutHelpers", function () {
+  var obj = {};
+  Cu.import("resource:///modules/devtools/LayoutHelpers.jsm", obj);
+  return obj.LayoutHelpers;
+});
 
 
 /**
  * 'echo' command
  */
 gcli.addCommand({
   name: "echo",
   description: gcli.lookup("echoDesc"),
@@ -27,16 +40,139 @@ gcli.addCommand({
   returnType: "string",
   exec: function Command_echo(args, context) {
     return args.message;
   }
 });
 
 
 /**
+ * 'screenshot' command
+ */
+gcli.addCommand({
+  name: "screenshot",
+  description: gcli.lookup("screenshotDesc"),
+  manual: gcli.lookup("screenshotManual"),
+  returnType: "string",
+  params: [
+    {
+      name: "filename",
+      type: "string",
+      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",
+      defaultValue: false,
+      description: gcli.lookup("screenshotFullPageDesc"),
+      manual: gcli.lookup("screenshotFullPageManual")
+    },
+    {
+      name: "node",
+      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);
+        promise.resolve(reply);
+      }.bind(this), args.delay * 1000);
+      return promise;
+    }
+    else {
+      return this.grabScreen(document, args.filename, args.fullpage, args.node);
+    }
+  },
+  grabScreen:
+  function Command_screenshotGrabScreen(document, filename, 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;
+
+    if (!fullpage) {
+      if (!node) {
+        left = window.scrollX;
+        top = window.scrollY;
+        width = window.innerWidth;
+        height = window.innerHeight;
+      } else {
+        let rect = LayoutHelpers.getRect(node, window);
+        top = rect.top;
+        left = rect.left;
+        width = rect.width;
+        height = rect.height;
+      }
+    } else {
+      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 file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+
+    // Check there is a .png extension to filename
+    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);
+      let tempfile = downloadMgr.userDownloadsDirectory;
+      tempfile.append(filename);
+      filename = tempfile.path;
+    }
+
+    try {
+      file.initWithPath(filename);
+    } catch (ex) {
+      return "Error saving to " + filename;
+    }
+
+    let ioService = Cc["@mozilla.org/network/io-service;1"]
+      .getService(Ci.nsIIOService);
+
+    let Persist = Ci.nsIWebBrowserPersist;
+    let persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
+      .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;
+  }
+});
+
+
+/**
  * 'console' command
  */
 gcli.addCommand({
   name: "console",
   description: gcli.lookup("consoleDesc"),
   manual: gcli.lookup("consoleManual")
 });
 
@@ -46,23 +182,23 @@ gcli.addCommand({
 gcli.addCommand({
   name: "console clear",
   description: gcli.lookup("consoleclearDesc"),
   exec: function Command_consoleClear(args, context) {
     let window = context.environment.chromeDocument.defaultView;
     let hud = HUDService.getHudReferenceById(context.environment.hudId);
 
     // Use a timeout so we also clear the reporting of the clear command
-    let threadManager = Components.classes["@mozilla.org/thread-manager;1"]
-        .getService(Components.interfaces.nsIThreadManager);
+    let threadManager = Cc["@mozilla.org/thread-manager;1"]
+        .getService(Ci.nsIThreadManager);
     threadManager.mainThread.dispatch({
       run: function() {
         hud.gcliterm.clearOutput();
       }
-    }, Components.interfaces.nsIThread.DISPATCH_NORMAL);
+    }, Ci.nsIThread.DISPATCH_NORMAL);
   }
 });
 
 
 /**
  * 'console close' command
  */
 gcli.addCommand({
--- a/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
@@ -35,16 +35,56 @@ consoleDesc=Commands to control the cons
 # LOCALIZATION NOTE (consoleManual) A longer description describing the
 # set of commands that control the console.
 consoleManual=Filter, clear and close the web console
 
 # LOCALIZATION NOTE (consoleclearDesc) A very short string used to describe the
 # function of the 'console clear' command.
 consoleclearDesc=Clear the console
 
+# LOCALIZATION NOTE (screenshotDesc) A very short description of the
+# 'screenshot' command. See screenshotManual for a fuller description of what
+# it does. This string is designed to be shown in a menu alongside the
+# command name, which is why it should be as short as possible.
+screenshotDesc=Save an image of the page
+
+# LOCALIZATION NOTE (screenshotManual) A fuller description of the 'screenshot'
+# command, displayed when the user asks for help on what it does.
+screenshotManual=Save an PNG image of the entire visible window (optionally after a delay)
+
+# LOCALIZATION NOTE (screenshotFilenameDesc) A very short string to describe
+# the 'filename' parameter to the 'screenshot' command, which is displayed in
+# 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 (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.
+screenshotDelayManual=The time to wait (in seconds) before the screenshot is taken
+
+# LOCALIZATION NOTE (screenshotFullscreenDesc) A very short string to describe
+# the 'fullscreen' parameter to the 'screenshot' command, which is displayed in
+# 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 (inspectDesc) A very short description of the 'inspect'
 # command. See inspectManual for a fuller description of what it does. This
 # string is designed to be shown in a menu alongside the command name, which
 # is why it should be as short as possible.
 inspectDesc=Inspect a node
 
 # LOCALIZATION NOTE (inspectManual) A fuller description of the 'inspect'
 # command, displayed when the user asks for help on what it does.