/* 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/commandline/" + "test/browser_cmd_screenshot.html"; var FileUtils = (Cu.import("resource://gre/modules/FileUtils.jsm", {})).FileUtils; function test() { // This test gets bombarded by a cascade of GCs and often takes 50s so lets be // safe and give the test 90s to run. requestLongerTimeout(3); return Task.spawn(spawnTest).then(finish, helpers.handleError); } function* spawnTest() { waitForExplicitFinish(); info("RUN TEST: non-private window"); let normWin = yield addWindow({ private: false }); yield addTabWithToolbarRunTests(normWin); normWin.close(); info("RUN TEST: private window"); let pbWin = yield addWindow({ private: true }); yield addTabWithToolbarRunTests(pbWin); pbWin.close(); } function* addTabWithToolbarRunTests(win) { let options = yield helpers.openTab(TEST_URI, { chromeWindow: win }); let browser = options.browser; yield helpers.openToolbar(options); // Test input status yield helpers.audit(options, [ { setup: "screenshot", check: { input: "screenshot", markup: "VVVVVVVVVV", status: "VALID", args: { } }, }, { setup: "screenshot abc.png", check: { input: "screenshot abc.png", markup: "VVVVVVVVVVVVVVVVVV", status: "VALID", args: { filename: { value: "abc.png"}, } }, }, { setup: "screenshot --fullpage", check: { input: "screenshot --fullpage", markup: "VVVVVVVVVVVVVVVVVVVVV", status: "VALID", args: { fullpage: { value: true}, } }, }, { setup: "screenshot abc --delay 5", check: { input: "screenshot abc --delay 5", markup: "VVVVVVVVVVVVVVVVVVVVVVVV", status: "VALID", args: { filename: { value: "abc"}, delay: { value: 5 }, } }, }, { setup: "screenshot --selector img#testImage", check: { input: "screenshot --selector img#testImage", markup: "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV", status: "VALID", }, }, ]); // Test capture to file let file = FileUtils.getFile("TmpD", [ "TestScreenshotFile.png" ]); yield helpers.audit(options, [ { setup: "screenshot " + file.path, check: { args: { filename: { value: "" + file.path }, fullpage: { value: false }, clipboard: { value: false }, }, }, exec: { output: new RegExp("^Saved to "), }, post: function () { // Bug 849168: screenshot command tests fail in try but not locally // ok(file.exists(), "Screenshot file exists"); if (file.exists()) { file.remove(false); } } }, ]); // Test capture to clipboard yield helpers.audit(options, [ { setup: "screenshot --clipboard", check: { args: { clipboard: { value: true }, }, }, exec: { output: new RegExp("^Copied to clipboard.$"), }, post: Task.async(function* () { let imgSize1 = yield getImageSizeFromClipboard(); yield 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"); }); }) }, { setup: "screenshot --fullpage --clipboard", check: { args: { fullpage: { value: true }, clipboard: { value: true }, }, }, exec: { output: new RegExp("^Copied to clipboard.$"), }, post: Task.async(function* () { let imgSize1 = yield getImageSizeFromClipboard(); yield 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"); }); }) }, { setup: "screenshot --selector img#testImage --clipboard", check: { args: { clipboard: { value: true }, }, }, exec: { output: new RegExp("^Copied to clipboard.$"), }, post: Task.async(function* () { let imgSize1 = yield getImageSizeFromClipboard(); yield ContentTask.spawn(browser, imgSize1, function* (imgSize) { let 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"); }); }) }, ]); // 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. yield ContentTask.spawn(browser, {}, function* () { content.document.body.classList.add("overflow"); }); let scrollbarSize = yield ContentTask.spawn(browser, {}, function* () { const winUtils = content.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindowUtils); let scrollbarHeight = {}; let scrollbarWidth = {}; winUtils.getScrollbarSize(true, scrollbarWidth, scrollbarHeight); return { width: scrollbarWidth.value, height: scrollbarHeight.value, }; }); info(`Scrollbar size: ${scrollbarSize.width}x${scrollbarSize.height}`); // Test capture to clipboard in presence of scrollbars yield helpers.audit(options, [ { setup: "screenshot --clipboard", check: { args: { clipboard: { value: true }, }, }, exec: { output: new RegExp("^Copied to clipboard.$"), }, post: Task.async(function* () { let imgSize1 = yield getImageSizeFromClipboard(); imgSize1.scrollbarWidth = scrollbarSize.width; imgSize1.scrollbarHeight = scrollbarSize.height; yield 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"); }); }) }, { setup: "screenshot --fullpage --clipboard", check: { args: { fullpage: { value: true }, clipboard: { value: true }, }, }, exec: { output: new RegExp("^Copied to clipboard.$"), }, post: Task.async(function* () { let imgSize1 = yield getImageSizeFromClipboard(); imgSize1.scrollbarWidth = scrollbarSize.width; imgSize1.scrollbarHeight = scrollbarSize.height; yield 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"); }); }) }, { setup: "screenshot --selector img#testImage --clipboard", check: { args: { clipboard: { value: true }, }, }, exec: { output: new RegExp("^Copied to clipboard.$"), }, post: Task.async(function* () { let imgSize1 = yield getImageSizeFromClipboard(); yield ContentTask.spawn(browser, imgSize1, function* (imgSize) { let 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"); }); }) }, ]); yield helpers.closeToolbar(options); yield helpers.closeTab(options); } function addWindow(windowOptions) { return new Promise(resolve => { let win = OpenBrowserWindow(windowOptions); // This feels hacky, we should refactor it whenDelayedStartupFinished(win, () => { // Would like to get rid of this executeSoon, but without it the url // (TEST_URI) provided in addTabWithToolbarRunTests hasn't loaded executeSoon(() => { resolve(win); }); }); }); } let getImageSizeFromClipboard = Task.async(function* () { 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); let flavor = "image/png"; trans.init(null); trans.addDataFlavor(flavor); clip.getData(trans, clipid.kGlobalClipboard); let data = new Object(); let 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) { let binaryStream = Cc["@mozilla.org/binaryinputstream;1"] .createInstance(Ci.nsIBinaryInputStream); binaryStream.setInputStream(image); let rawData = binaryStream.readBytes(binaryStream.available()); let 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"); } let img = document.createElementNS("http://www.w3.org/1999/xhtml", "img"); let loaded = new Promise(resolve => { img.addEventListener("load", function onLoad() { img.removeEventListener("load", onLoad); resolve(); }); }); img.src = dataURI; document.documentElement.appendChild(img); yield loaded; img.remove(); return { width: img.width, height: img.height, }; });