|
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/. */ |
|
4 |
|
5 "use strict"; |
|
6 |
|
7 const { Cc, Ci, Cu } = require("chrome"); |
|
8 const gcli = require("gcli/index"); |
|
9 |
|
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"); |
|
14 |
|
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"); |
|
19 |
|
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 = " "; |
|
23 |
|
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 } |
|
93 |
|
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"); |
|
106 |
|
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; |
|
127 |
|
128 let ctx = canvas.getContext("2d"); |
|
129 ctx.drawWindow(window, left, top, width, height, "#fff"); |
|
130 let data = canvas.toDataURL("image/png", ""); |
|
131 |
|
132 let loadContext = document.defaultView |
|
133 .QueryInterface(Ci.nsIInterfaceRequestor) |
|
134 .getInterface(Ci.nsIWebNavigation) |
|
135 .QueryInterface(Ci.nsILoadContext); |
|
136 |
|
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); |
|
145 |
|
146 let container = {}; |
|
147 imgTools.decodeImageData(input, channel.contentType, container); |
|
148 |
|
149 let wrapped = Cc["@mozilla.org/supports-interface-pointer;1"] |
|
150 .createInstance(Ci.nsISupportsInterfacePointer); |
|
151 wrapped.data = container.value; |
|
152 |
|
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); |
|
158 |
|
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 } |
|
169 |
|
170 let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); |
|
171 |
|
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 } |
|
196 |
|
197 try { |
|
198 file.initWithPath(filename); |
|
199 } catch (ex) { |
|
200 div.textContent = gcli.lookup("screenshotErrorSavingToFile") + " " + filename; |
|
201 throw new Task.Result(div); |
|
202 } |
|
203 |
|
204 let ioService = Cc["@mozilla.org/network/io-service;1"] |
|
205 .getService(Ci.nsIIOService); |
|
206 |
|
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; |
|
212 |
|
213 let source = ioService.newURI(data, "UTF8", null); |
|
214 persist.saveURI(source, null, null, null, null, file, loadContext); |
|
215 |
|
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 ]; |