new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_screenshot_command.js
@@ -0,0 +1,219 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* global helpers, btoa, whenDelayedStartupFinished, OpenBrowserWindow */
+
+// Test that screenshot command works properly
+
+"use strict";
+
+const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
+ "test/mochitest/browser_cmd_screenshot.html";
+
+const FileUtils = (ChromeUtils.import("resource://gre/modules/FileUtils.jsm", {})).FileUtils;
+const browser = "?";
+
+add_task(async function() {
+ await addTab(TEST_URI);
+
+ const opened = waitForBrowserConsole();
+
+ let hud = HUDService.getBrowserConsole();
+ ok(!hud, "browser console is not open");
+ info("wait for the browser console to open with ctrl-shift-j");
+ EventUtils.synthesizeKey("j", { accelKey: true, shiftKey: true }, window);
+
+ hud = await opened;
+ ok(hud, "browser console opened");
+
+ await testFile(hud);
+ await testClipboard(hud);
+ await testFullpageClipboard(hud);
+ await testSelectorClipboard(hud);
+
+ const scrollbarSize = await createScrollbarOverflow();
+ await testClipboardScrollbar(hud, scrollbarSize);
+ await testFullpageClipboardScrollbar(hud, scrollbarSize);
+});
+
+async function testFile(hud) {
+ // Test capture to file
+ const file = FileUtils.getFile("TmpD", [ "TestScreenshotFile.png" ]);
+ const command = `:screenshot ${file.path}`;
+ hud.jsterm.execute(command);
+ await waitForMessage("Saved to TmpD/TestScreenshotFile.png");
+ // Bug 849168: screenshot command tests fail in try but not locally
+ ok(file.exists(), "Screenshot file exists");
+
+ if (file.exists()) {
+ file.remove(false);
+ }
+}
+
+async function testClipboard(hud) {
+ const command = `:screenshot --clipboard`;
+ hud.jsterm.execute(command);
+ await waitForMessage("Copied to clipboard.");
+ const imgSize1 = await getImageSizeFromClipboard();
+ await ContentTask.spawn(browser, imgSize1, function* (imgSize) {
+ Assert.equal(imgSize.width, content.innerWidth,
+ "Image width matches window size");
+ Assert.equal(imgSize.height, content.innerHeight,
+ "Image height matches window size");
+ });
+}
+
+async function testFullpageClipboard(hud) {
+ const command = `:screenshot --fullpage --clipboard`;
+ hud.jsterm.execute(command);
+ await waitForMessage("Copied to clipboard.");
+ const imgSize1 = await getImageSizeFromClipboard();
+ await ContentTask.spawn(browser, imgSize1, function* (imgSize) {
+ Assert.equal(imgSize.width,
+ content.innerWidth + content.scrollMaxX - content.scrollMinX,
+ "Image width matches page size");
+ Assert.equal(imgSize.height,
+ content.innerHeight + content.scrollMaxY - content.scrollMinY,
+ "Image height matches page size");
+ });
+}
+
+async function testSelectorClipboard(hud) {
+ const command = `:screenshot --selector img#testImage --clipboard`;
+ hud.jsterm.execute(command);
+ await waitForMessage("Copied to clipboard.");
+ const imgSize1 = await getImageSizeFromClipboard();
+ await ContentTask.spawn(browser, imgSize1, function* (imgSize) {
+ const img = content.document.querySelector("img#testImage");
+ Assert.equal(imgSize.width, img.clientWidth,
+ "Image width matches element size");
+ Assert.equal(imgSize.height, img.clientHeight,
+ "Image height matches element size");
+ });
+}
+
+async function createScrollbarOverflow() {
+ // Trigger scrollbars by forcing document to overflow
+ // This only affects results on OSes with scrollbars that reduce document size
+ // (non-floating scrollbars). With default OS settings, this means Windows
+ // and Linux are affected, but Mac is not. For Mac to exhibit this behavior,
+ // change System Preferences -> General -> Show scroll bars to Always.
+ await ContentTask.spawn(browser, {}, function* () {
+ content.document.body.classList.add("overflow");
+ });
+
+ const scrollbarSize = await ContentTask.spawn(browser, {}, function* () {
+ const winUtils = content.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ const scrollbarHeight = {};
+ const scrollbarWidth = {};
+ winUtils.getScrollbarSize(true, scrollbarWidth, scrollbarHeight);
+ return {
+ width: scrollbarWidth.value,
+ height: scrollbarHeight.value,
+ };
+ });
+
+ info(`Scrollbar size: ${scrollbarSize.width}x${scrollbarSize.height}`);
+ return scrollbarSize;
+}
+
+async function testClipboardScrollbar(hud, scrollbarSize) {
+ const command = `:screenshot --clipboard`;
+ hud.jsterm.execute(command);
+ await waitForMessage("Copied to clipboard.");
+ const imgSize1 = await getImageSizeFromClipboard();
+ imgSize1.scrollbarWidth = scrollbarSize.width;
+ imgSize1.scrollbarHeight = scrollbarSize.height;
+ await ContentTask.spawn(browser, imgSize1, function* (imgSize) {
+ Assert.equal(imgSize.width, content.innerWidth - imgSize.scrollbarWidth,
+ "Image width matches window size minus scrollbar size");
+ Assert.equal(imgSize.height, content.innerHeight - imgSize.scrollbarHeight,
+ "Image height matches window size minus scrollbar size");
+ });
+}
+
+async function testFullpageClipboardScrollbar(hud, scrollbarSize) {
+ const command = `:screenshot --fullpage --clipboard`;
+ hud.jsterm.execute(command);
+ await waitForMessage("Copied to clipboard.");
+ const imgSize1 = await getImageSizeFromClipboard();
+ imgSize1.scrollbarWidth = scrollbarSize.width;
+ imgSize1.scrollbarHeight = scrollbarSize.height;
+ await ContentTask.spawn(browser, imgSize1, function* (imgSize) {
+ Assert.equal(imgSize.width,
+ (content.innerWidth + content.scrollMaxX -
+ content.scrollMinX) - imgSize.scrollbarWidth,
+ "Image width matches page size minus scrollbar size");
+ Assert.equal(imgSize.height,
+ (content.innerHeight + content.scrollMaxY -
+ content.scrollMinY) - imgSize.scrollbarHeight,
+ "Image height matches page size minus scrollbar size");
+ });
+}
+
+async function getImageSizeFromClipboard() {
+ const clipid = Ci.nsIClipboard;
+ const clip = Cc["@mozilla.org/widget/clipboard;1"].getService(clipid);
+ const trans = Cc["@mozilla.org/widget/transferable;1"]
+ .createInstance(Ci.nsITransferable);
+ const flavor = "image/png";
+ trans.init(null);
+ trans.addDataFlavor(flavor);
+
+ clip.getData(trans, clipid.kGlobalClipboard);
+ const data = new Object();
+ const dataLength = new Object();
+ trans.getTransferData(flavor, data, dataLength);
+
+ ok(data.value, "screenshot exists");
+ ok(dataLength.value > 0, "screenshot has length");
+
+ let image = data.value;
+ let dataURI = `data:${flavor};base64,`;
+
+ // Due to the differences in how images could be stored in the clipboard the
+ // checks below are needed. The clipboard could already provide the image as
+ // byte streams, but also as pointer, or as image container. If it's not
+ // possible obtain a byte stream, the function returns `null`.
+ if (image instanceof Ci.nsISupportsInterfacePointer) {
+ image = image.data;
+ }
+
+ if (image instanceof Ci.imgIContainer) {
+ image = Cc["@mozilla.org/image/tools;1"]
+ .getService(Ci.imgITools)
+ .encodeImage(image, flavor);
+ }
+
+ if (image instanceof Ci.nsIInputStream) {
+ const binaryStream = Cc["@mozilla.org/binaryinputstream;1"]
+ .createInstance(Ci.nsIBinaryInputStream);
+ binaryStream.setInputStream(image);
+ const rawData = binaryStream.readBytes(binaryStream.available());
+ const charCodes = Array.from(rawData, c => c.charCodeAt(0) & 0xff);
+ let encodedData = String.fromCharCode(...charCodes);
+ encodedData = btoa(encodedData);
+ dataURI = dataURI + encodedData;
+ } else {
+ throw new Error("Unable to read image data");
+ }
+
+ const img = document.createElementNS("http://www.w3.org/1999/xhtml", "img");
+
+ const loaded = new Promise(resolve => {
+ img.addEventListener("load", function() {
+ resolve();
+ }, {once: true});
+ });
+
+ img.src = dataURI;
+ document.documentElement.appendChild(img);
+ await loaded;
+ img.remove();
+
+ return {
+ width: img.width,
+ height: img.height,
+ };
+}