Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 const { Cc, Ci, Cu } = require("chrome");
8 const gcli = require("gcli/index");
10 loader.lazyImporter(this, "Downloads", "resource://gre/modules/Downloads.jsm");
11 loader.lazyImporter(this, "LayoutHelpers", "resource://gre/modules/devtools/LayoutHelpers.jsm");
12 loader.lazyImporter(this, "Task", "resource://gre/modules/Task.jsm");
13 loader.lazyImporter(this, "OS", "resource://gre/modules/osfile.jsm");
15 const BRAND_SHORT_NAME = Cc["@mozilla.org/intl/stringbundle;1"]
16 .getService(Ci.nsIStringBundleService)
17 .createBundle("chrome://branding/locale/brand.properties")
18 .GetStringFromName("brandShortName");
20 // String used as an indication to generate default file name in the following
21 // format: "Screen Shot yyyy-mm-dd at HH.MM.SS.png"
22 const FILENAME_DEFAULT_VALUE = " ";
24 exports.items = [
25 {
26 name: "screenshot",
27 description: gcli.lookup("screenshotDesc"),
28 manual: gcli.lookup("screenshotManual"),
29 returnType: "dom",
30 params: [
31 {
32 name: "filename",
33 type: "string",
34 defaultValue: FILENAME_DEFAULT_VALUE,
35 description: gcli.lookup("screenshotFilenameDesc"),
36 manual: gcli.lookup("screenshotFilenameManual")
37 },
38 {
39 group: gcli.lookup("screenshotGroupOptions"),
40 params: [
41 {
42 name: "clipboard",
43 type: "boolean",
44 description: gcli.lookup("screenshotClipboardDesc"),
45 manual: gcli.lookup("screenshotClipboardManual")
46 },
47 {
48 name: "chrome",
49 type: "boolean",
50 description: gcli.lookupFormat("screenshotChromeDesc2", [BRAND_SHORT_NAME]),
51 manual: gcli.lookupFormat("screenshotChromeManual2", [BRAND_SHORT_NAME])
52 },
53 {
54 name: "delay",
55 type: { name: "number", min: 0 },
56 defaultValue: 0,
57 description: gcli.lookup("screenshotDelayDesc"),
58 manual: gcli.lookup("screenshotDelayManual")
59 },
60 {
61 name: "fullpage",
62 type: "boolean",
63 description: gcli.lookup("screenshotFullPageDesc"),
64 manual: gcli.lookup("screenshotFullPageManual")
65 },
66 {
67 name: "selector",
68 type: "node",
69 defaultValue: null,
70 description: gcli.lookup("inspectNodeDesc"),
71 manual: gcli.lookup("inspectNodeManual")
72 }
73 ]
74 }
75 ],
76 exec: function(args, context) {
77 if (args.chrome && args.selector) {
78 // Node screenshot with chrome option does not work as intended
79 // Refer https://bugzilla.mozilla.org/show_bug.cgi?id=659268#c7
80 // throwing for now.
81 throw new Error(gcli.lookup("screenshotSelectorChromeConflict"));
82 }
83 var document = args.chrome? context.environment.chromeDocument
84 : context.environment.document;
85 if (args.delay > 0) {
86 var deferred = context.defer();
87 document.defaultView.setTimeout(() => {
88 this.grabScreen(document, args.filename, args.clipboard,
89 args.fullpage).then(deferred.resolve, deferred.reject);
90 }, args.delay * 1000);
91 return deferred.promise;
92 }
94 return this.grabScreen(document, args.filename, args.clipboard,
95 args.fullpage, args.selector);
96 },
97 grabScreen: function(document, filename, clipboard, fullpage, node) {
98 return Task.spawn(function() {
99 let window = document.defaultView;
100 let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
101 let left = 0;
102 let top = 0;
103 let width;
104 let height;
105 let div = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
107 if (!fullpage) {
108 if (!node) {
109 left = window.scrollX;
110 top = window.scrollY;
111 width = window.innerWidth;
112 height = window.innerHeight;
113 } else {
114 let lh = new LayoutHelpers(window);
115 let rect = lh.getRect(node, window);
116 top = rect.top;
117 left = rect.left;
118 width = rect.width;
119 height = rect.height;
120 }
121 } else {
122 width = window.innerWidth + window.scrollMaxX;
123 height = window.innerHeight + window.scrollMaxY;
124 }
125 canvas.width = width;
126 canvas.height = height;
128 let ctx = canvas.getContext("2d");
129 ctx.drawWindow(window, left, top, width, height, "#fff");
130 let data = canvas.toDataURL("image/png", "");
132 let loadContext = document.defaultView
133 .QueryInterface(Ci.nsIInterfaceRequestor)
134 .getInterface(Ci.nsIWebNavigation)
135 .QueryInterface(Ci.nsILoadContext);
137 if (clipboard) {
138 try {
139 let io = Cc["@mozilla.org/network/io-service;1"]
140 .getService(Ci.nsIIOService);
141 let channel = io.newChannel(data, null, null);
142 let input = channel.open();
143 let imgTools = Cc["@mozilla.org/image/tools;1"]
144 .getService(Ci.imgITools);
146 let container = {};
147 imgTools.decodeImageData(input, channel.contentType, container);
149 let wrapped = Cc["@mozilla.org/supports-interface-pointer;1"]
150 .createInstance(Ci.nsISupportsInterfacePointer);
151 wrapped.data = container.value;
153 let trans = Cc["@mozilla.org/widget/transferable;1"]
154 .createInstance(Ci.nsITransferable);
155 trans.init(loadContext);
156 trans.addDataFlavor(channel.contentType);
157 trans.setTransferData(channel.contentType, wrapped, -1);
159 let clipid = Ci.nsIClipboard;
160 let clip = Cc["@mozilla.org/widget/clipboard;1"].getService(clipid);
161 clip.setData(trans, null, clipid.kGlobalClipboard);
162 div.textContent = gcli.lookup("screenshotCopied");
163 }
164 catch (ex) {
165 div.textContent = gcli.lookup("screenshotErrorCopying");
166 }
167 throw new Task.Result(div);
168 }
170 let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
172 // Create a name for the file if not present
173 if (filename == FILENAME_DEFAULT_VALUE) {
174 let date = new Date();
175 let dateString = date.getFullYear() + "-" + (date.getMonth() + 1) +
176 "-" + date.getDate();
177 dateString = dateString.split("-").map(function(part) {
178 if (part.length == 1) {
179 part = "0" + part;
180 }
181 return part;
182 }).join("-");
183 let timeString = date.toTimeString().replace(/:/g, ".").split(" ")[0];
184 filename = gcli.lookupFormat("screenshotGeneratedFilename",
185 [dateString, timeString]) + ".png";
186 }
187 // Check there is a .png extension to filename
188 else if (!filename.match(/.png$/i)) {
189 filename += ".png";
190 }
191 // If the filename is relative, tack it onto the download directory
192 if (!filename.match(/[\\\/]/)) {
193 let preferredDir = yield Downloads.getPreferredDownloadsDirectory();
194 filename = OS.Path.join(preferredDir, filename);
195 }
197 try {
198 file.initWithPath(filename);
199 } catch (ex) {
200 div.textContent = gcli.lookup("screenshotErrorSavingToFile") + " " + filename;
201 throw new Task.Result(div);
202 }
204 let ioService = Cc["@mozilla.org/network/io-service;1"]
205 .getService(Ci.nsIIOService);
207 let Persist = Ci.nsIWebBrowserPersist;
208 let persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
209 .createInstance(Persist);
210 persist.persistFlags = Persist.PERSIST_FLAGS_REPLACE_EXISTING_FILES |
211 Persist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
213 let source = ioService.newURI(data, "UTF8", null);
214 persist.saveURI(source, null, null, null, null, file, loadContext);
216 div.textContent = gcli.lookup("screenshotSavedToFile") + " \"" + filename +
217 "\"";
218 div.addEventListener("click", function openFile() {
219 div.removeEventListener("click", openFile);
220 file.reveal();
221 });
222 div.style.cursor = "pointer";
223 let image = document.createElement("div");
224 let previewHeight = parseInt(256*height/width);
225 image.setAttribute("style",
226 "width:256px; height:" + previewHeight + "px;" +
227 "max-height: 256px;" +
228 "background-image: url('" + data + "');" +
229 "background-size: 256px " + previewHeight + "px;" +
230 "margin: 4px; display: block");
231 div.appendChild(image);
232 throw new Task.Result(div);
233 });
234 }
235 }
236 ];