diff --git a/browser/devtools/commandline/CmdScreenshot.jsm b/browser/devtools/commandline/CmdScreenshot.jsm index 4f74cc3d126..e94edfcac6a 100644 --- a/browser/devtools/commandline/CmdScreenshot.jsm +++ b/browser/devtools/commandline/CmdScreenshot.jsm @@ -12,6 +12,10 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "LayoutHelpers", "resource:///modules/devtools/LayoutHelpers.jsm"); +// String used as an indication to generate default file name in the following +// format: "Screen Shot yyyy-mm-dd at HH.MM.SS.png" +const FILENAME_DEFAULT_VALUE = " "; + /** * 'screenshot' command */ @@ -19,57 +23,86 @@ gcli.addCommand({ name: "screenshot", description: gcli.lookup("screenshotDesc"), manual: gcli.lookup("screenshotManual"), - returnType: "string", + returnType: "html", params: [ { name: "filename", type: "string", + defaultValue: FILENAME_DEFAULT_VALUE, 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", - description: gcli.lookup("screenshotFullPageDesc"), - manual: gcli.lookup("screenshotFullPageManual") - }, - { - name: "selector", - type: "node", - defaultValue: null, - description: gcli.lookup("inspectNodeDesc"), - manual: gcli.lookup("inspectNodeManual") + group: gcli.lookup("screenshotGroupOptions"), + params: [ + { + name: "clipboard", + type: "boolean", + description: gcli.lookup("screenshotClipboardDesc"), + manual: gcli.lookup("screenshotClipboardManual") + }, + { + name: "chrome", + type: "boolean", + description: gcli.lookup("screenshotChromeDesc"), + manual: gcli.lookup("screenshotChromeManual") + }, + { + name: "delay", + type: { name: "number", min: 0 }, + defaultValue: 0, + description: gcli.lookup("screenshotDelayDesc"), + manual: gcli.lookup("screenshotDelayManual") + }, + { + name: "fullpage", + type: "boolean", + description: gcli.lookup("screenshotFullPageDesc"), + manual: gcli.lookup("screenshotFullPageManual") + }, + { + name: "selector", + 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.chrome && args.selector) { + // Node screenshot with chrome option does not work as inteded + // Refer https://bugzilla.mozilla.org/show_bug.cgi?id=659268#c7 + // throwing for now. + throw new Error(gcli.lookup("screenshotSelectorChromeConflict")); + } + var document = args.chrome? context.environment.chromeDocument + : context.environment.contentDocument; if (args.delay > 0) { var promise = context.createPromise(); document.defaultView.setTimeout(function Command_screenshotDelay() { - let reply = this.grabScreen(document, args.filename); + let reply = this.grabScreen(document, args.filename, args.clipboard, + args.fullpage); promise.resolve(reply); }.bind(this), args.delay * 1000); return promise; } else { - return this.grabScreen(document, args.filename, args.fullpage, args.selector); + return this.grabScreen(document, args.filename, args.clipboard, + args.fullpage, args.selector); } }, grabScreen: - function Command_screenshotGrabScreen(document, filename, fullpage, node) { + function Command_screenshotGrabScreen(document, filename, clipboard, + 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; + let div = document.createElementNS("http://www.w3.org/1999/xhtml", "div"); if (!fullpage) { if (!node) { @@ -93,19 +126,70 @@ gcli.addCommand({ let ctx = canvas.getContext("2d"); ctx.drawWindow(window, left, top, width, height, "#fff"); - let data = canvas.toDataURL("image/png", ""); + + try { + if (clipboard) { + let io = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService); + let channel = io.newChannel(data, null, null); + let input = channel.open(); + let imgTools = Cc["@mozilla.org/image/tools;1"] + .getService(Ci.imgITools); + + let container = {}; + imgTools.decodeImageData(input, channel.contentType, container); + + let wrapped = Cc["@mozilla.org/supports-interface-pointer;1"] + .createInstance(Ci.nsISupportsInterfacePointer); + wrapped.data = container.value; + + let trans = Cc["@mozilla.org/widget/transferable;1"] + .createInstance(Ci.nsITransferable); + if ("init" in trans) { + trans.init(null); + } + trans.addDataFlavor(channel.contentType); + trans.setTransferData(channel.contentType, wrapped, -1); + + let clipid = Ci.nsIClipboard; + let clip = Cc["@mozilla.org/widget/clipboard;1"].getService(clipid); + clip.setData(trans, null, clipid.kGlobalClipboard); + div.textContent = gcli.lookup("screenshotCopied"); + return div; + } + } + catch (ex) { + div.textContent = gcli.lookup("screenshotErrorCopying"); + return div; + } + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); + // Create a name for the file if not present + if (filename == FILENAME_DEFAULT_VALUE) { + let date = new Date(); + let dateString = date.getFullYear() + "-" + (date.getMonth() + 1) + + "-" + date.getDate(); + dateString = dateString.split("-").map(function(part) { + if (part.length == 1) { + part = "0" + part; + } + return part; + }).join("-"); + let timeString = date.toTimeString().replace(/:/g, ".").split(" ")[0]; + filename = gcli.lookupFormat("screenshotGeneratedFilename", + [dateString, timeString]) + ".png"; + } // Check there is a .png extension to filename - if (!filename.match(/.png$/i)) { + else 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); + .getService(Ci.nsIDownloadManager); let tempfile = downloadMgr.userDownloadsDirectory; tempfile.append(filename); filename = tempfile.path; @@ -114,21 +198,36 @@ gcli.addCommand({ try { file.initWithPath(filename); } catch (ex) { - return "Error saving to " + filename; + div.textContent = gcli.lookup("screenshotErrorSavingToFile") + " " + filename; + return div; } let ioService = Cc["@mozilla.org/network/io-service;1"] - .getService(Ci.nsIIOService); + .getService(Ci.nsIIOService); let Persist = Ci.nsIWebBrowserPersist; let persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] - .createInstance(Persist); + .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; + div.textContent = gcli.lookup("screenshotSavedToFile") + " " + filename; + div.addEventListener("click", function openFile() { + div.removeEventListener("click", openFile); + file.reveal(); + }); + div.style.cursor = "pointer"; + let image = document.createElement("div"); + let previewHeight = parseInt(256*height/width); + image.setAttribute("style", + "width:256px; height:" + previewHeight + "px;" + + "background-image: url('" + data + "');" + + "background-size: 256px " + previewHeight + "px;" + + "margin: 4px; display: block"); + div.appendChild(image); + return div; } -}); +}); \ No newline at end of file diff --git a/browser/devtools/commandline/test/Makefile.in b/browser/devtools/commandline/test/Makefile.in index bbedf69dd47..73857850274 100644 --- a/browser/devtools/commandline/test/Makefile.in +++ b/browser/devtools/commandline/test/Makefile.in @@ -24,6 +24,7 @@ MOCHITEST_BROWSER_FILES = \ browser_cmd_pagemod_export.js \ browser_cmd_pref.js \ browser_cmd_restart.js \ + browser_cmd_screenshot.js \ browser_cmd_settings.js \ browser_gcli_web.js \ head.js \ @@ -33,6 +34,7 @@ MOCHITEST_BROWSER_FILES = \ MOCHITEST_BROWSER_FILES += \ browser_dbg_cmd_break.html \ browser_dbg_cmd.html \ + browser_cmd_screenshot.html \ browser_cmd_pagemod_export.html \ browser_cmd_jsb_script.jsi \ $(NULL) diff --git a/browser/devtools/commandline/test/browser_cmd_screenshot.html b/browser/devtools/commandline/test/browser_cmd_screenshot.html new file mode 100644 index 00000000000..8e30016f1f9 --- /dev/null +++ b/browser/devtools/commandline/test/browser_cmd_screenshot.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/browser/devtools/commandline/test/browser_cmd_screenshot.js b/browser/devtools/commandline/test/browser_cmd_screenshot.js new file mode 100644 index 00000000000..bcb6c45c348 --- /dev/null +++ b/browser/devtools/commandline/test/browser_cmd_screenshot.js @@ -0,0 +1,148 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that screenshot command works properly +const TEST_URI = "http://example.com/browser/browser/devtools/commandline/" + + "test/browser_cmd_screenshot.html"; + +const { classes: Cc, interfaces: Ci, utils: Cu } = Components; +let tempScope = {}; +Cu.import("resource://gre/modules/FileUtils.jsm", tempScope); +let FileUtils = tempScope.FileUtils; + +function test() { + DeveloperToolbarTest.test(TEST_URI, [ testInput, testCapture ]); +} + +function testInput() { + helpers.setInput('screenshot'); + helpers.check({ + input: 'screenshot', + markup: 'VVVVVVVVVV', + status: 'VALID', + args: { + } + }); + + helpers.setInput('screenshot abc.png'); + helpers.check({ + input: 'screenshot abc.png', + markup: 'VVVVVVVVVVVVVVVVVV', + status: 'VALID', + args: { + filename: { value: "abc.png"}, + } + }); + + helpers.setInput('screenshot --fullpage'); + helpers.check({ + input: 'screenshot --fullpage', + markup: 'VVVVVVVVVVVVVVVVVVVVV', + status: 'VALID', + args: { + fullpage: { value: true}, + } + }); + + helpers.setInput('screenshot abc --delay 5'); + helpers.check({ + input: 'screenshot abc --delay 5', + markup: 'VVVVVVVVVVVVVVVVVVVVVVVV', + status: 'VALID', + args: { + filename: { value: "abc"}, + delay: { value: "5"}, + } + }); + + helpers.setInput('screenshot --selector img#testImage'); + helpers.check({ + input: 'screenshot --selector img#testImage', + markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV', + status: 'VALID', + args: { + selector: { value: content.document.getElementById("testImage")}, + } + }); +} + +function testCapture() { + function checkTemporaryFile() { + // Create a temporary file. + let gFile = FileUtils.getFile("TmpD", ["TestScreenshotFile.png"]); + if (gFile.exists()) { + gFile.remove(false); + return true; + } + else { + return false; + } + } + + function clearClipboard() { + let clipid = Ci.nsIClipboard; + let clip = Cc["@mozilla.org/widget/clipboard;1"].getService(clipid); + clip.emptyClipboard(clipid.kGlobalClipboard); + } + + function checkClipboard() { + try { + let clipid = Ci.nsIClipboard; + let clip = Cc["@mozilla.org/widget/clipboard;1"].getService(clipid); + let trans = Cc["@mozilla.org/widget/transferable;1"] + .createInstance(Ci.nsITransferable); + if ("init" in trans) { + trans.init(null); + } + let io = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService); + let contentType = io.newChannel("", null, null).contentType; + trans.addDataFlavor(contentType); + clip.getData(trans, clipid.kGlobalClipboard); + let str = new Object(); + let strLength = new Object(); + trans.getTransferData(contentType, str, strLength); + if (str && strLength > 0) { + clip.emptyClipboard(clipid.kGlobalClipboard); + return true; + } + } + catch (ex) {} + return false; + } + + let path = FileUtils.getFile("TmpD", ["TestScreenshotFile.png"]).path; + + DeveloperToolbarTest.exec({ + typed: "screenshot " + path, + args: { + delay: 0, + filename: "" + path, + fullpage: false, + clipboard: false, + node: null, + chrome: false, + }, + outputMatch: new RegExp("^Saved to "), + }); + + ok(checkTemporaryFile, "Screenshot got created"); + + clearClipboard(); + + DeveloperToolbarTest.exec({ + typed: "screenshot --fullpage --clipboard", + args: { + delay: 0, + filename: " ", + fullpage: true, + clipboard: true, + node: null, + chrome: false, + }, + outputMatch: new RegExp("^Copied to clipboard.$"), + }); + + ok(checkClipboard, "Screenshot got created and copied"); +} + diff --git a/browser/devtools/debugger/DebuggerUI.jsm b/browser/devtools/debugger/DebuggerUI.jsm index 217a5213750..678e9cb47e2 100644 --- a/browser/devtools/debugger/DebuggerUI.jsm +++ b/browser/devtools/debugger/DebuggerUI.jsm @@ -245,7 +245,7 @@ DebuggerPane.prototype = { _initServer: function DP__initServer() { if (!DebuggerServer.initialized) { // Always allow connections from nsIPipe transports. - DebuggerServer.init(function () { return true; }); + DebuggerServer.init(function() true); DebuggerServer.addBrowserActors(); } }, diff --git a/browser/devtools/debugger/debugger-controller.js b/browser/devtools/debugger/debugger-controller.js index 8485b4e80ef..171cd0cb798 100644 --- a/browser/devtools/debugger/debugger-controller.js +++ b/browser/devtools/debugger/debugger-controller.js @@ -248,23 +248,6 @@ let DebuggerController = { }.bind(this)); }, - /** - * Returns true if this is a remote debugger instance. - * @return boolean - */ - get _isRemoteDebugger() { - return window._remoteFlag; - }, - - /** - * Returns true if this is a chrome debugger instance. - * @return boolean - */ - get _isChromeDebugger() { - // Directly accessing window.parent.content may throw in some cases. - return !("content" in window.parent) && !this._isRemoteDebugger; - }, - /** * Attempts to quit the current process if allowed. */ @@ -297,6 +280,26 @@ let DebuggerController = { } }; +/** + * Returns true if this is a remote debugger instance. + * @return boolean + */ +XPCOMUtils.defineLazyGetter(DebuggerController, "_isRemoteDebugger", function() { + // We're inside a single top level XUL window, not an iframe container. + return !(window.frameElement instanceof XULElement) && + !!window._remoteFlag; +}); + +/** + * Returns true if this is a chrome debugger instance. + * @return boolean + */ +XPCOMUtils.defineLazyGetter(DebuggerController, "_isChromeDebugger", function() { + // We're inside a single top level XUL window, but not a remote debugger. + return !(window.frameElement instanceof XULElement) && + !window._remoteFlag; +}); + /** * ThreadState keeps the UI up to date with the state of the * thread (paused/attached/etc.). diff --git a/browser/devtools/debugger/debugger-view.js b/browser/devtools/debugger/debugger-view.js index f0f0b5c2b3c..d082ef0162f 100644 --- a/browser/devtools/debugger/debugger-view.js +++ b/browser/devtools/debugger/debugger-view.js @@ -59,9 +59,12 @@ let DebuggerView = { this._stepInButton = document.getElementById("step-in"); this._stepOutButton = document.getElementById("step-out"); this._scriptsSearchbox = document.getElementById("scripts-search"); - this._globalOperatorButton = document.getElementById("global-operator"); - this._tokenOperatorButton = document.getElementById("token-operator"); - this._lineOperatorButton = document.getElementById("line-operator"); + this._globalOperatorLabel = document.getElementById("global-operator-label"); + this._globalOperatorButton = document.getElementById("global-operator-button"); + this._tokenOperatorLabel = document.getElementById("token-operator-label"); + this._tokenOperatorButton = document.getElementById("token-operator-button"); + this._lineOperatorLabel = document.getElementById("line-operator-label"); + this._lineOperatorButton = document.getElementById("line-operator-button"); }, /** @@ -79,12 +82,16 @@ let DebuggerView = { this._scriptsSearchbox.setAttribute("placeholder", L10N.getFormatStr("emptyFilterText", [LayoutHelpers.prettyKey(this._fileSearchKey)])); - this._globalOperatorButton.setAttribute("value", + this._globalOperatorLabel.setAttribute("value", L10N.getFormatStr("searchPanelGlobal", [LayoutHelpers.prettyKey(this._globalSearchKey)])); - this._tokenOperatorButton.setAttribute("value", + this._tokenOperatorLabel.setAttribute("value", L10N.getFormatStr("searchPanelToken", [LayoutHelpers.prettyKey(this._tokenSearchKey)])); - this._lineOperatorButton.setAttribute("value", + this._lineOperatorLabel.setAttribute("value", L10N.getFormatStr("searchPanelLine", [LayoutHelpers.prettyKey(this._lineSearchKey)])); + + this._globalOperatorButton.setAttribute("label", SEARCH_GLOBAL_FLAG); + this._tokenOperatorButton.setAttribute("label", SEARCH_TOKEN_FLAG); + this._lineOperatorButton.setAttribute("label", SEARCH_LINE_FLAG); }, /** @@ -255,8 +262,11 @@ let DebuggerView = { _stepInButton: null, _stepOutButton: null, _scriptsSearchbox: null, + _globalOperatorLabel: null, _globalOperatorButton: null, + _tokenOperatorLabel: null, _tokenOperatorButton: null, + _lineOperatorLabel: null, _lineOperatorButton: null }; diff --git a/browser/devtools/debugger/debugger.xul b/browser/devtools/debugger/debugger.xul index 908f162ff31..7f7a8e63ef6 100644 --- a/browser/devtools/debugger/debugger.xul +++ b/browser/devtools/debugger/debugger.xul @@ -105,19 +105,19 @@