1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/devtools/gcli/commands/screenshot.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,236 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +const { Cc, Ci, Cu } = require("chrome"); 1.11 +const gcli = require("gcli/index"); 1.12 + 1.13 +loader.lazyImporter(this, "Downloads", "resource://gre/modules/Downloads.jsm"); 1.14 +loader.lazyImporter(this, "LayoutHelpers", "resource://gre/modules/devtools/LayoutHelpers.jsm"); 1.15 +loader.lazyImporter(this, "Task", "resource://gre/modules/Task.jsm"); 1.16 +loader.lazyImporter(this, "OS", "resource://gre/modules/osfile.jsm"); 1.17 + 1.18 +const BRAND_SHORT_NAME = Cc["@mozilla.org/intl/stringbundle;1"] 1.19 + .getService(Ci.nsIStringBundleService) 1.20 + .createBundle("chrome://branding/locale/brand.properties") 1.21 + .GetStringFromName("brandShortName"); 1.22 + 1.23 +// String used as an indication to generate default file name in the following 1.24 +// format: "Screen Shot yyyy-mm-dd at HH.MM.SS.png" 1.25 +const FILENAME_DEFAULT_VALUE = " "; 1.26 + 1.27 +exports.items = [ 1.28 + { 1.29 + name: "screenshot", 1.30 + description: gcli.lookup("screenshotDesc"), 1.31 + manual: gcli.lookup("screenshotManual"), 1.32 + returnType: "dom", 1.33 + params: [ 1.34 + { 1.35 + name: "filename", 1.36 + type: "string", 1.37 + defaultValue: FILENAME_DEFAULT_VALUE, 1.38 + description: gcli.lookup("screenshotFilenameDesc"), 1.39 + manual: gcli.lookup("screenshotFilenameManual") 1.40 + }, 1.41 + { 1.42 + group: gcli.lookup("screenshotGroupOptions"), 1.43 + params: [ 1.44 + { 1.45 + name: "clipboard", 1.46 + type: "boolean", 1.47 + description: gcli.lookup("screenshotClipboardDesc"), 1.48 + manual: gcli.lookup("screenshotClipboardManual") 1.49 + }, 1.50 + { 1.51 + name: "chrome", 1.52 + type: "boolean", 1.53 + description: gcli.lookupFormat("screenshotChromeDesc2", [BRAND_SHORT_NAME]), 1.54 + manual: gcli.lookupFormat("screenshotChromeManual2", [BRAND_SHORT_NAME]) 1.55 + }, 1.56 + { 1.57 + name: "delay", 1.58 + type: { name: "number", min: 0 }, 1.59 + defaultValue: 0, 1.60 + description: gcli.lookup("screenshotDelayDesc"), 1.61 + manual: gcli.lookup("screenshotDelayManual") 1.62 + }, 1.63 + { 1.64 + name: "fullpage", 1.65 + type: "boolean", 1.66 + description: gcli.lookup("screenshotFullPageDesc"), 1.67 + manual: gcli.lookup("screenshotFullPageManual") 1.68 + }, 1.69 + { 1.70 + name: "selector", 1.71 + type: "node", 1.72 + defaultValue: null, 1.73 + description: gcli.lookup("inspectNodeDesc"), 1.74 + manual: gcli.lookup("inspectNodeManual") 1.75 + } 1.76 + ] 1.77 + } 1.78 + ], 1.79 + exec: function(args, context) { 1.80 + if (args.chrome && args.selector) { 1.81 + // Node screenshot with chrome option does not work as intended 1.82 + // Refer https://bugzilla.mozilla.org/show_bug.cgi?id=659268#c7 1.83 + // throwing for now. 1.84 + throw new Error(gcli.lookup("screenshotSelectorChromeConflict")); 1.85 + } 1.86 + var document = args.chrome? context.environment.chromeDocument 1.87 + : context.environment.document; 1.88 + if (args.delay > 0) { 1.89 + var deferred = context.defer(); 1.90 + document.defaultView.setTimeout(() => { 1.91 + this.grabScreen(document, args.filename, args.clipboard, 1.92 + args.fullpage).then(deferred.resolve, deferred.reject); 1.93 + }, args.delay * 1000); 1.94 + return deferred.promise; 1.95 + } 1.96 + 1.97 + return this.grabScreen(document, args.filename, args.clipboard, 1.98 + args.fullpage, args.selector); 1.99 + }, 1.100 + grabScreen: function(document, filename, clipboard, fullpage, node) { 1.101 + return Task.spawn(function() { 1.102 + let window = document.defaultView; 1.103 + let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); 1.104 + let left = 0; 1.105 + let top = 0; 1.106 + let width; 1.107 + let height; 1.108 + let div = document.createElementNS("http://www.w3.org/1999/xhtml", "div"); 1.109 + 1.110 + if (!fullpage) { 1.111 + if (!node) { 1.112 + left = window.scrollX; 1.113 + top = window.scrollY; 1.114 + width = window.innerWidth; 1.115 + height = window.innerHeight; 1.116 + } else { 1.117 + let lh = new LayoutHelpers(window); 1.118 + let rect = lh.getRect(node, window); 1.119 + top = rect.top; 1.120 + left = rect.left; 1.121 + width = rect.width; 1.122 + height = rect.height; 1.123 + } 1.124 + } else { 1.125 + width = window.innerWidth + window.scrollMaxX; 1.126 + height = window.innerHeight + window.scrollMaxY; 1.127 + } 1.128 + canvas.width = width; 1.129 + canvas.height = height; 1.130 + 1.131 + let ctx = canvas.getContext("2d"); 1.132 + ctx.drawWindow(window, left, top, width, height, "#fff"); 1.133 + let data = canvas.toDataURL("image/png", ""); 1.134 + 1.135 + let loadContext = document.defaultView 1.136 + .QueryInterface(Ci.nsIInterfaceRequestor) 1.137 + .getInterface(Ci.nsIWebNavigation) 1.138 + .QueryInterface(Ci.nsILoadContext); 1.139 + 1.140 + if (clipboard) { 1.141 + try { 1.142 + let io = Cc["@mozilla.org/network/io-service;1"] 1.143 + .getService(Ci.nsIIOService); 1.144 + let channel = io.newChannel(data, null, null); 1.145 + let input = channel.open(); 1.146 + let imgTools = Cc["@mozilla.org/image/tools;1"] 1.147 + .getService(Ci.imgITools); 1.148 + 1.149 + let container = {}; 1.150 + imgTools.decodeImageData(input, channel.contentType, container); 1.151 + 1.152 + let wrapped = Cc["@mozilla.org/supports-interface-pointer;1"] 1.153 + .createInstance(Ci.nsISupportsInterfacePointer); 1.154 + wrapped.data = container.value; 1.155 + 1.156 + let trans = Cc["@mozilla.org/widget/transferable;1"] 1.157 + .createInstance(Ci.nsITransferable); 1.158 + trans.init(loadContext); 1.159 + trans.addDataFlavor(channel.contentType); 1.160 + trans.setTransferData(channel.contentType, wrapped, -1); 1.161 + 1.162 + let clipid = Ci.nsIClipboard; 1.163 + let clip = Cc["@mozilla.org/widget/clipboard;1"].getService(clipid); 1.164 + clip.setData(trans, null, clipid.kGlobalClipboard); 1.165 + div.textContent = gcli.lookup("screenshotCopied"); 1.166 + } 1.167 + catch (ex) { 1.168 + div.textContent = gcli.lookup("screenshotErrorCopying"); 1.169 + } 1.170 + throw new Task.Result(div); 1.171 + } 1.172 + 1.173 + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); 1.174 + 1.175 + // Create a name for the file if not present 1.176 + if (filename == FILENAME_DEFAULT_VALUE) { 1.177 + let date = new Date(); 1.178 + let dateString = date.getFullYear() + "-" + (date.getMonth() + 1) + 1.179 + "-" + date.getDate(); 1.180 + dateString = dateString.split("-").map(function(part) { 1.181 + if (part.length == 1) { 1.182 + part = "0" + part; 1.183 + } 1.184 + return part; 1.185 + }).join("-"); 1.186 + let timeString = date.toTimeString().replace(/:/g, ".").split(" ")[0]; 1.187 + filename = gcli.lookupFormat("screenshotGeneratedFilename", 1.188 + [dateString, timeString]) + ".png"; 1.189 + } 1.190 + // Check there is a .png extension to filename 1.191 + else if (!filename.match(/.png$/i)) { 1.192 + filename += ".png"; 1.193 + } 1.194 + // If the filename is relative, tack it onto the download directory 1.195 + if (!filename.match(/[\\\/]/)) { 1.196 + let preferredDir = yield Downloads.getPreferredDownloadsDirectory(); 1.197 + filename = OS.Path.join(preferredDir, filename); 1.198 + } 1.199 + 1.200 + try { 1.201 + file.initWithPath(filename); 1.202 + } catch (ex) { 1.203 + div.textContent = gcli.lookup("screenshotErrorSavingToFile") + " " + filename; 1.204 + throw new Task.Result(div); 1.205 + } 1.206 + 1.207 + let ioService = Cc["@mozilla.org/network/io-service;1"] 1.208 + .getService(Ci.nsIIOService); 1.209 + 1.210 + let Persist = Ci.nsIWebBrowserPersist; 1.211 + let persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] 1.212 + .createInstance(Persist); 1.213 + persist.persistFlags = Persist.PERSIST_FLAGS_REPLACE_EXISTING_FILES | 1.214 + Persist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION; 1.215 + 1.216 + let source = ioService.newURI(data, "UTF8", null); 1.217 + persist.saveURI(source, null, null, null, null, file, loadContext); 1.218 + 1.219 + div.textContent = gcli.lookup("screenshotSavedToFile") + " \"" + filename + 1.220 + "\""; 1.221 + div.addEventListener("click", function openFile() { 1.222 + div.removeEventListener("click", openFile); 1.223 + file.reveal(); 1.224 + }); 1.225 + div.style.cursor = "pointer"; 1.226 + let image = document.createElement("div"); 1.227 + let previewHeight = parseInt(256*height/width); 1.228 + image.setAttribute("style", 1.229 + "width:256px; height:" + previewHeight + "px;" + 1.230 + "max-height: 256px;" + 1.231 + "background-image: url('" + data + "');" + 1.232 + "background-size: 256px " + previewHeight + "px;" + 1.233 + "margin: 4px; display: block"); 1.234 + div.appendChild(image); 1.235 + throw new Task.Result(div); 1.236 + }); 1.237 + } 1.238 + } 1.239 +];