michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const { Cc, Ci, Cu } = require("chrome"); michael@0: const gcli = require("gcli/index"); michael@0: michael@0: loader.lazyImporter(this, "Downloads", "resource://gre/modules/Downloads.jsm"); michael@0: loader.lazyImporter(this, "LayoutHelpers", "resource://gre/modules/devtools/LayoutHelpers.jsm"); michael@0: loader.lazyImporter(this, "Task", "resource://gre/modules/Task.jsm"); michael@0: loader.lazyImporter(this, "OS", "resource://gre/modules/osfile.jsm"); michael@0: michael@0: const BRAND_SHORT_NAME = Cc["@mozilla.org/intl/stringbundle;1"] michael@0: .getService(Ci.nsIStringBundleService) michael@0: .createBundle("chrome://branding/locale/brand.properties") michael@0: .GetStringFromName("brandShortName"); michael@0: michael@0: // String used as an indication to generate default file name in the following michael@0: // format: "Screen Shot yyyy-mm-dd at HH.MM.SS.png" michael@0: const FILENAME_DEFAULT_VALUE = " "; michael@0: michael@0: exports.items = [ michael@0: { michael@0: name: "screenshot", michael@0: description: gcli.lookup("screenshotDesc"), michael@0: manual: gcli.lookup("screenshotManual"), michael@0: returnType: "dom", michael@0: params: [ michael@0: { michael@0: name: "filename", michael@0: type: "string", michael@0: defaultValue: FILENAME_DEFAULT_VALUE, michael@0: description: gcli.lookup("screenshotFilenameDesc"), michael@0: manual: gcli.lookup("screenshotFilenameManual") michael@0: }, michael@0: { michael@0: group: gcli.lookup("screenshotGroupOptions"), michael@0: params: [ michael@0: { michael@0: name: "clipboard", michael@0: type: "boolean", michael@0: description: gcli.lookup("screenshotClipboardDesc"), michael@0: manual: gcli.lookup("screenshotClipboardManual") michael@0: }, michael@0: { michael@0: name: "chrome", michael@0: type: "boolean", michael@0: description: gcli.lookupFormat("screenshotChromeDesc2", [BRAND_SHORT_NAME]), michael@0: manual: gcli.lookupFormat("screenshotChromeManual2", [BRAND_SHORT_NAME]) michael@0: }, michael@0: { michael@0: name: "delay", michael@0: type: { name: "number", min: 0 }, michael@0: defaultValue: 0, michael@0: description: gcli.lookup("screenshotDelayDesc"), michael@0: manual: gcli.lookup("screenshotDelayManual") michael@0: }, michael@0: { michael@0: name: "fullpage", michael@0: type: "boolean", michael@0: description: gcli.lookup("screenshotFullPageDesc"), michael@0: manual: gcli.lookup("screenshotFullPageManual") michael@0: }, michael@0: { michael@0: name: "selector", michael@0: type: "node", michael@0: defaultValue: null, michael@0: description: gcli.lookup("inspectNodeDesc"), michael@0: manual: gcli.lookup("inspectNodeManual") michael@0: } michael@0: ] michael@0: } michael@0: ], michael@0: exec: function(args, context) { michael@0: if (args.chrome && args.selector) { michael@0: // Node screenshot with chrome option does not work as intended michael@0: // Refer https://bugzilla.mozilla.org/show_bug.cgi?id=659268#c7 michael@0: // throwing for now. michael@0: throw new Error(gcli.lookup("screenshotSelectorChromeConflict")); michael@0: } michael@0: var document = args.chrome? context.environment.chromeDocument michael@0: : context.environment.document; michael@0: if (args.delay > 0) { michael@0: var deferred = context.defer(); michael@0: document.defaultView.setTimeout(() => { michael@0: this.grabScreen(document, args.filename, args.clipboard, michael@0: args.fullpage).then(deferred.resolve, deferred.reject); michael@0: }, args.delay * 1000); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: return this.grabScreen(document, args.filename, args.clipboard, michael@0: args.fullpage, args.selector); michael@0: }, michael@0: grabScreen: function(document, filename, clipboard, fullpage, node) { michael@0: return Task.spawn(function() { michael@0: let window = document.defaultView; michael@0: let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); michael@0: let left = 0; michael@0: let top = 0; michael@0: let width; michael@0: let height; michael@0: let div = document.createElementNS("http://www.w3.org/1999/xhtml", "div"); michael@0: michael@0: if (!fullpage) { michael@0: if (!node) { michael@0: left = window.scrollX; michael@0: top = window.scrollY; michael@0: width = window.innerWidth; michael@0: height = window.innerHeight; michael@0: } else { michael@0: let lh = new LayoutHelpers(window); michael@0: let rect = lh.getRect(node, window); michael@0: top = rect.top; michael@0: left = rect.left; michael@0: width = rect.width; michael@0: height = rect.height; michael@0: } michael@0: } else { michael@0: width = window.innerWidth + window.scrollMaxX; michael@0: height = window.innerHeight + window.scrollMaxY; michael@0: } michael@0: canvas.width = width; michael@0: canvas.height = height; michael@0: michael@0: let ctx = canvas.getContext("2d"); michael@0: ctx.drawWindow(window, left, top, width, height, "#fff"); michael@0: let data = canvas.toDataURL("image/png", ""); michael@0: michael@0: let loadContext = document.defaultView michael@0: .QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsILoadContext); michael@0: michael@0: if (clipboard) { michael@0: try { michael@0: let io = Cc["@mozilla.org/network/io-service;1"] michael@0: .getService(Ci.nsIIOService); michael@0: let channel = io.newChannel(data, null, null); michael@0: let input = channel.open(); michael@0: let imgTools = Cc["@mozilla.org/image/tools;1"] michael@0: .getService(Ci.imgITools); michael@0: michael@0: let container = {}; michael@0: imgTools.decodeImageData(input, channel.contentType, container); michael@0: michael@0: let wrapped = Cc["@mozilla.org/supports-interface-pointer;1"] michael@0: .createInstance(Ci.nsISupportsInterfacePointer); michael@0: wrapped.data = container.value; michael@0: michael@0: let trans = Cc["@mozilla.org/widget/transferable;1"] michael@0: .createInstance(Ci.nsITransferable); michael@0: trans.init(loadContext); michael@0: trans.addDataFlavor(channel.contentType); michael@0: trans.setTransferData(channel.contentType, wrapped, -1); michael@0: michael@0: let clipid = Ci.nsIClipboard; michael@0: let clip = Cc["@mozilla.org/widget/clipboard;1"].getService(clipid); michael@0: clip.setData(trans, null, clipid.kGlobalClipboard); michael@0: div.textContent = gcli.lookup("screenshotCopied"); michael@0: } michael@0: catch (ex) { michael@0: div.textContent = gcli.lookup("screenshotErrorCopying"); michael@0: } michael@0: throw new Task.Result(div); michael@0: } michael@0: michael@0: let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); michael@0: michael@0: // Create a name for the file if not present michael@0: if (filename == FILENAME_DEFAULT_VALUE) { michael@0: let date = new Date(); michael@0: let dateString = date.getFullYear() + "-" + (date.getMonth() + 1) + michael@0: "-" + date.getDate(); michael@0: dateString = dateString.split("-").map(function(part) { michael@0: if (part.length == 1) { michael@0: part = "0" + part; michael@0: } michael@0: return part; michael@0: }).join("-"); michael@0: let timeString = date.toTimeString().replace(/:/g, ".").split(" ")[0]; michael@0: filename = gcli.lookupFormat("screenshotGeneratedFilename", michael@0: [dateString, timeString]) + ".png"; michael@0: } michael@0: // Check there is a .png extension to filename michael@0: else if (!filename.match(/.png$/i)) { michael@0: filename += ".png"; michael@0: } michael@0: // If the filename is relative, tack it onto the download directory michael@0: if (!filename.match(/[\\\/]/)) { michael@0: let preferredDir = yield Downloads.getPreferredDownloadsDirectory(); michael@0: filename = OS.Path.join(preferredDir, filename); michael@0: } michael@0: michael@0: try { michael@0: file.initWithPath(filename); michael@0: } catch (ex) { michael@0: div.textContent = gcli.lookup("screenshotErrorSavingToFile") + " " + filename; michael@0: throw new Task.Result(div); michael@0: } michael@0: michael@0: let ioService = Cc["@mozilla.org/network/io-service;1"] michael@0: .getService(Ci.nsIIOService); michael@0: michael@0: let Persist = Ci.nsIWebBrowserPersist; michael@0: let persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] michael@0: .createInstance(Persist); michael@0: persist.persistFlags = Persist.PERSIST_FLAGS_REPLACE_EXISTING_FILES | michael@0: Persist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION; michael@0: michael@0: let source = ioService.newURI(data, "UTF8", null); michael@0: persist.saveURI(source, null, null, null, null, file, loadContext); michael@0: michael@0: div.textContent = gcli.lookup("screenshotSavedToFile") + " \"" + filename + michael@0: "\""; michael@0: div.addEventListener("click", function openFile() { michael@0: div.removeEventListener("click", openFile); michael@0: file.reveal(); michael@0: }); michael@0: div.style.cursor = "pointer"; michael@0: let image = document.createElement("div"); michael@0: let previewHeight = parseInt(256*height/width); michael@0: image.setAttribute("style", michael@0: "width:256px; height:" + previewHeight + "px;" + michael@0: "max-height: 256px;" + michael@0: "background-image: url('" + data + "');" + michael@0: "background-size: 256px " + previewHeight + "px;" + michael@0: "margin: 4px; display: block"); michael@0: div.appendChild(image); michael@0: throw new Task.Result(div); michael@0: }); michael@0: } michael@0: } michael@0: ];