summaryrefslogtreecommitdiffstats
path: root/browser/extensions/screenshots/test
diff options
context:
space:
mode:
Diffstat (limited to 'browser/extensions/screenshots/test')
-rw-r--r--browser/extensions/screenshots/test/browser/browser.ini22
-rw-r--r--browser/extensions/screenshots/test/browser/browser_screenshot_button.js75
-rw-r--r--browser/extensions/screenshots/test/browser/browser_screenshots_dimensions.js109
-rw-r--r--browser/extensions/screenshots/test/browser/browser_screenshots_download.js98
-rw-r--r--browser/extensions/screenshots/test/browser/browser_screenshots_injection.js82
-rw-r--r--browser/extensions/screenshots/test/browser/green2vh.html23
-rw-r--r--browser/extensions/screenshots/test/browser/head.js237
-rw-r--r--browser/extensions/screenshots/test/browser/injection-page.html23
8 files changed, 669 insertions, 0 deletions
diff --git a/browser/extensions/screenshots/test/browser/browser.ini b/browser/extensions/screenshots/test/browser/browser.ini
new file mode 100644
index 0000000000..e6cd1e8fe8
--- /dev/null
+++ b/browser/extensions/screenshots/test/browser/browser.ini
@@ -0,0 +1,22 @@
+[DEFAULT]
+prefs =
+ # The Screenshots extension is disabled by default in Mochitests. We re-enable
+ # it here, since it's a more realistic configuration.
+ extensions.screenshots.disabled=false
+[browser_screenshots_injection.js]
+
+support-files =
+ head.js
+ injection-page.html
+ green2vh.html
+
+[browser_screenshot_button.js]
+[browser_screenshots_dimensions.js]
+https_first_disabled = true
+# Bug 1714237 Disabled on tsan due to timeouts interacting with the UI
+# Bug 1714210 Disabled on headless which doesnt support image data on the clipboard
+skip-if =
+ headless || tsan
+ os == 'win' # Bug 1714295
+ os == 'linux' && bits == 64 # Bug 1714295
+[browser_screenshots_download.js]
diff --git a/browser/extensions/screenshots/test/browser/browser_screenshot_button.js b/browser/extensions/screenshots/test/browser/browser_screenshot_button.js
new file mode 100644
index 0000000000..84c5758a1d
--- /dev/null
+++ b/browser/extensions/screenshots/test/browser/browser_screenshot_button.js
@@ -0,0 +1,75 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+"use strict";
+
+add_task(async function testScreenshotButtonDisabled() {
+ info("Test the Screenshots button in the panel");
+
+ CustomizableUI.addWidgetToArea(
+ "screenshot-button",
+ CustomizableUI.AREA_NAVBAR
+ );
+
+ let screenshotBtn = document.getElementById("screenshot-button");
+ Assert.ok(screenshotBtn, "The screenshots button was added to the nav bar");
+
+ await BrowserTestUtils.withNewTab("https://example.com/", () => {
+ Assert.equal(
+ screenshotBtn.disabled,
+ false,
+ "Screenshots button is enabled"
+ );
+ });
+ await BrowserTestUtils.withNewTab("about:home", () => {
+ Assert.equal(
+ screenshotBtn.disabled,
+ true,
+ "Screenshots button is now disabled"
+ );
+ });
+});
+
+add_task(async function test_disabledMultiWindow() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: TEST_GREEN_PAGE,
+ },
+ async browser => {
+ let helper = new ScreenshotsHelper(browser);
+ await helper.triggerUIFromToolbar();
+
+ let screenshotBtn = document.getElementById("screenshot-button");
+ Assert.ok(
+ screenshotBtn,
+ "The screenshots button was added to the nav bar"
+ );
+
+ info("Waiting for the preselect UI");
+ await helper.waitForUIContent(
+ helper.selector.preselectIframe,
+ helper.selector.fullPageButton
+ );
+
+ let newWin = await BrowserTestUtils.openNewBrowserWindow();
+ await BrowserTestUtils.closeWindow(newWin);
+
+ let deactivatedPromise = helper.waitForToolbarButtonDeactivation();
+ await deactivatedPromise;
+ info("Screenshots is deactivated");
+
+ await EventUtils.synthesizeAndWaitKey("VK_ESCAPE", {});
+ await BrowserTestUtils.waitForCondition(() => {
+ return !screenshotBtn.disabled;
+ });
+
+ Assert.equal(
+ screenshotBtn.disabled,
+ false,
+ "Screenshots button is enabled"
+ );
+ }
+ );
+});
diff --git a/browser/extensions/screenshots/test/browser/browser_screenshots_dimensions.js b/browser/extensions/screenshots/test/browser/browser_screenshots_dimensions.js
new file mode 100644
index 0000000000..8b32d7968b
--- /dev/null
+++ b/browser/extensions/screenshots/test/browser/browser_screenshots_dimensions.js
@@ -0,0 +1,109 @@
+add_task(async function test_fullPageScreenshot() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: TEST_GREEN_PAGE,
+ },
+ async browser => {
+ let helper = new ScreenshotsHelper(browser);
+ let contentInfo = await helper.getContentDimensions();
+ ok(contentInfo, "Got dimensions back from the content");
+
+ await helper.triggerUIFromToolbar();
+
+ await helper.clickUIElement(
+ helper.selector.preselectIframe,
+ helper.selector.fullPageButton
+ );
+
+ info("Waiting for the preview UI and the copy button");
+ await helper.waitForUIContent(
+ helper.selector.previewIframe,
+ helper.selector.copyButton
+ );
+
+ let deactivatedPromise = helper.waitForToolbarButtonDeactivation();
+ let clipboardChanged = waitForRawClipboardChange();
+
+ await helper.clickUIElement(
+ helper.selector.previewIframe,
+ helper.selector.copyButton
+ );
+
+ info("Waiting for clipboard change");
+ await clipboardChanged;
+
+ await deactivatedPromise;
+ info("Screenshots is deactivated");
+
+ let result = await getImageSizeFromClipboard(browser);
+ is(
+ result.width,
+ contentInfo.documentWidth,
+ "Got expected screenshot width"
+ );
+ is(
+ result.height,
+ contentInfo.documentHeight,
+ "Got expected screenshot height"
+ );
+ }
+ );
+});
+
+add_task(async function test_visiblePageScreenshot() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: TEST_GREEN_PAGE,
+ },
+ async browser => {
+ const DEVICE_PIXEL_RATIO = window.devicePixelRatio;
+ let helper = new ScreenshotsHelper(browser);
+ let contentInfo = await helper.getContentDimensions();
+ ok(contentInfo, "Got dimensions back from the content");
+
+ await helper.triggerUIFromToolbar();
+
+ await helper.clickUIElement(
+ helper.selector.preselectIframe,
+ helper.selector.visiblePageButton
+ );
+
+ info("Waiting for the preview UI and the copy button");
+ await helper.waitForUIContent(
+ helper.selector.previewIframe,
+ helper.selector.copyButton
+ );
+
+ let deactivatedPromise = helper.waitForToolbarButtonDeactivation();
+ let clipboardChanged = waitForRawClipboardChange();
+
+ await helper.clickUIElement(
+ helper.selector.previewIframe,
+ helper.selector.copyButton
+ );
+
+ info("Waiting for clipboard change");
+ await clipboardChanged;
+
+ await deactivatedPromise;
+ info("Screenshots is deactivated");
+
+ let result = await getImageSizeFromClipboard(browser);
+ info("result: " + JSON.stringify(result, null, 2));
+ info("contentInfo: " + JSON.stringify(contentInfo, null, 2));
+
+ is(
+ result.width,
+ contentInfo.documentWidth * DEVICE_PIXEL_RATIO,
+ "Got expected screenshot width"
+ );
+ is(
+ result.height,
+ contentInfo.clientHeight * DEVICE_PIXEL_RATIO,
+ "Got expected screenshot height"
+ );
+ }
+ );
+});
diff --git a/browser/extensions/screenshots/test/browser/browser_screenshots_download.js b/browser/extensions/screenshots/test/browser/browser_screenshots_download.js
new file mode 100644
index 0000000000..3e32f06da6
--- /dev/null
+++ b/browser/extensions/screenshots/test/browser/browser_screenshots_download.js
@@ -0,0 +1,98 @@
+add_task(async function test_fullPageScreenshot() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: TEST_GREEN_PAGE,
+ },
+
+ async browser => {
+ const tests = [
+ { title: "Green Page", expected: "Green Page.png" },
+ { title: "\tA*B\\/+?<>\u200f\x1fC ", expected: "A B__ C.png" },
+ {
+ title: "簡単".repeat(35),
+ expected: " " + "簡単".repeat(35) + ".png",
+ },
+ {
+ title: "簡単".repeat(36),
+ expected: " " + "簡単".repeat(26) + "[...].png",
+ },
+ {
+ title: "簡単".repeat(56) + "?",
+ expected: " " + "簡単".repeat(26) + "[...].png",
+ },
+ ];
+
+ for (let test of tests) {
+ info("Testing with title " + test.title);
+
+ await SpecialPowers.spawn(browser, [test.title], titleToUse => {
+ content.document.title = titleToUse;
+ });
+
+ let helper = new ScreenshotsHelper(browser);
+ await helper.triggerUIFromToolbar();
+
+ await helper.clickUIElement(
+ helper.selector.preselectIframe,
+ helper.selector.fullPageButton
+ );
+
+ info("Waiting for the preview UI and the download button");
+ await helper.waitForUIContent(
+ helper.selector.previewIframe,
+ helper.selector.downloadButton
+ );
+
+ let publicList = await Downloads.getList(Downloads.PUBLIC);
+ let downloadPromise = new Promise(resolve => {
+ let downloadView = {
+ onDownloadAdded(download) {
+ publicList.removeView(downloadView);
+ resolve(download);
+ },
+ };
+
+ publicList.addView(downloadView);
+ });
+
+ await helper.clickUIElement(
+ helper.selector.previewIframe,
+ helper.selector.downloadButton
+ );
+
+ let download = await downloadPromise;
+ let filename = PathUtils.filename(download.target.path);
+ ok(
+ filename.endsWith(test.expected),
+ "Used correct filename '" +
+ filename +
+ "', expected: '" +
+ test.expected +
+ "'"
+ );
+
+ await task_resetState();
+ }
+ }
+ );
+});
+
+// This is from browser/components/downloads/test/browser/head.js
+async function task_resetState() {
+ let publicList = await Downloads.getList(Downloads.PUBLIC);
+ let downloads = await publicList.getAll();
+ for (let download of downloads) {
+ await publicList.remove(download);
+ await download.finalize(true);
+ if (await IOUtils.exists(download.target.path)) {
+ if (Services.appinfo.OS === "WINNT") {
+ // We need to make the file writable to delete it on Windows.
+ await IOUtils.setPermissions(download.target.path, 0o600);
+ }
+ await IOUtils.remove(download.target.path);
+ }
+ }
+
+ DownloadsPanel.hidePanel();
+}
diff --git a/browser/extensions/screenshots/test/browser/browser_screenshots_injection.js b/browser/extensions/screenshots/test/browser/browser_screenshots_injection.js
new file mode 100644
index 0000000000..56222bf7e3
--- /dev/null
+++ b/browser/extensions/screenshots/test/browser/browser_screenshots_injection.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+);
+
+/**
+ * Check that web content cannot break into screenshots.
+ */
+add_task(async function test_inject_srcdoc() {
+ // If Screenshots was disabled, enable it just for this test.
+ const addon = await AddonManager.getAddonByID("screenshots@mozilla.org");
+ const isEnabled = addon.enabled;
+ if (!isEnabled) {
+ await addon.enable({ allowSystemAddons: true });
+ registerCleanupFunction(async () => {
+ await addon.disable({ allowSystemAddons: true });
+ });
+ }
+
+ await BrowserTestUtils.withNewTab(
+ TEST_PATH + "injection-page.html",
+ async browser => {
+ // Set up the content hijacking. Do this so we can see it without
+ // awaiting - the promise should never resolve.
+ let response = null;
+ let responsePromise = SpecialPowers.spawn(browser, [], () => {
+ return new Promise(resolve => {
+ // We can't pass `resolve` directly because of sandboxing.
+ // `responseHandler` gets invoked from the content page.
+ content.wrappedJSObject.responseHandler = Cu.exportFunction(function(
+ arg
+ ) {
+ resolve(arg);
+ },
+ content);
+ });
+ }).then(
+ r => {
+ ok(false, "Should not have gotten HTML but got: " + r);
+ response = r;
+ },
+ () => {
+ // Do nothing - we expect this to error when the test finishes
+ // and the actor is destroyed, while the promise still hasn't
+ // been resolved. We need to catch it in order not to throw
+ // uncaught rejection errors and inadvertently fail the test.
+ }
+ );
+
+ let error;
+ let errorPromise = new Promise(resolve => {
+ SpecialPowers.registerConsoleListener(msg => {
+ if (
+ msg.message?.match(/iframe URL does not match expected blank.html/)
+ ) {
+ error = msg;
+ resolve();
+ }
+ });
+ });
+
+ // Now try to start the screenshot flow:
+ CustomizableUI.addWidgetToArea(
+ "screenshot-button",
+ CustomizableUI.AREA_NAVBAR
+ );
+
+ let screenshotBtn = document.getElementById("screenshot-button");
+ screenshotBtn.click();
+ await Promise.race([errorPromise, responsePromise]);
+ ok(error, "Should get the relevant error: " + error?.message);
+ ok(!response, "Should not get a response from the webpage.");
+
+ SpecialPowers.postConsoleSentinel();
+ }
+ );
+});
diff --git a/browser/extensions/screenshots/test/browser/green2vh.html b/browser/extensions/screenshots/test/browser/green2vh.html
new file mode 100644
index 0000000000..bb25e2a021
--- /dev/null
+++ b/browser/extensions/screenshots/test/browser/green2vh.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <style>
+ html, body {
+ padding: 0;
+ margin: 0;
+ }
+ body {
+ background: rgb(0,255,0);
+ min-height: 200vh;
+ }
+ </style>
+ <script>
+ window.addEventListener("DOMContentLoaded", () => {
+ console.log(window.screen);
+ });
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/browser/extensions/screenshots/test/browser/head.js b/browser/extensions/screenshots/test/browser/head.js
new file mode 100644
index 0000000000..19c5c2d136
--- /dev/null
+++ b/browser/extensions/screenshots/test/browser/head.js
@@ -0,0 +1,237 @@
+"use strict";
+
+const { AddonManager } = ChromeUtils.import(
+ "resource://gre/modules/AddonManager.jsm"
+);
+const { ContentTaskUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/ContentTaskUtils.sys.mjs"
+);
+
+const TEST_ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+);
+const TEST_GREEN_PAGE = TEST_ROOT + "green2vh.html";
+
+const gScreenshotUISelectors = {
+ preselectIframe: "#firefox-screenshots-preselection-iframe",
+ fullPageButton: "button.full-page",
+ visiblePageButton: "button.visible",
+ previewIframe: "#firefox-screenshots-preview-iframe",
+ copyButton: "button.highlight-button-copy",
+ downloadButton: "button.highlight-button-download",
+};
+
+class ScreenshotsHelper {
+ constructor(browser) {
+ this.browser = browser;
+ this.selector = gScreenshotUISelectors;
+ }
+
+ get toolbarButton() {
+ return document.getElementById("screenshot-button");
+ }
+
+ async triggerUIFromToolbar() {
+ let button = this.toolbarButton;
+ ok(
+ BrowserTestUtils.is_visible(button),
+ "The screenshot toolbar button is visible"
+ );
+ EventUtils.synthesizeMouseAtCenter(button, {});
+ // Make sure the Screenshots UI is loaded before yielding
+ await this.waitForUIContent(
+ this.selector.preselectIframe,
+ this.selector.fullPageButton
+ );
+ }
+
+ async waitForUIContent(iframeSel, elemSel) {
+ await SpecialPowers.spawn(
+ this.browser,
+ [iframeSel, elemSel],
+ async function(iframeSelector, elemSelector) {
+ info(
+ `in waitForUIContent content function, iframeSelector: ${iframeSelector}, elemSelector: ${elemSelector}`
+ );
+ let iframe;
+ await ContentTaskUtils.waitForCondition(() => {
+ iframe = content.document.querySelector(iframeSelector);
+ if (!iframe || !ContentTaskUtils.is_visible(iframe)) {
+ info("in waitForUIContent, no visible iframe yet");
+ return false;
+ }
+ let elem = iframe.contentDocument.querySelector(elemSelector);
+ info(
+ "in waitForUIContent, got visible elem: " +
+ (elem && ContentTaskUtils.is_visible(elem))
+ );
+ return elem && ContentTaskUtils.is_visible(elem);
+ });
+ // wait a frame for the screenshots UI to finish any init
+ await new content.Promise(res => content.requestAnimationFrame(res));
+ }
+ );
+ }
+
+ async clickUIElement(iframeSel, elemSel) {
+ await SpecialPowers.spawn(
+ this.browser,
+ [iframeSel, elemSel],
+ async function(iframeSelector, elemSelector) {
+ info(
+ `in clickScreenshotsUIElement content function, iframeSelector: ${iframeSelector}, elemSelector: ${elemSelector}`
+ );
+ const EventUtils = ContentTaskUtils.getEventUtils(content);
+ let iframe = content.document.querySelector(iframeSelector);
+ let elem = iframe.contentDocument.querySelector(elemSelector);
+ info(`Found the thing to click: ${elemSelector}: ${!!elem}`);
+
+ EventUtils.synthesizeMouseAtCenter(elem, {}, iframe.contentWindow);
+ await new content.Promise(res => content.requestAnimationFrame(res));
+ }
+ );
+ }
+
+ waitForToolbarButtonDeactivation() {
+ return BrowserTestUtils.waitForCondition(() => {
+ return !this.toolbarButton.style.cssText.includes("icon-highlight");
+ });
+ }
+
+ getContentDimensions() {
+ return SpecialPowers.spawn(this.browser, [], async function() {
+ let doc = content.document;
+ let rect = doc.documentElement.getBoundingClientRect();
+ return {
+ clientHeight: doc.documentElement.clientHeight,
+ clientWidth: doc.documentElement.clientWidth,
+ currentURI: doc.documentURI,
+ documentHeight: Math.round(rect.height),
+ documentWidth: Math.round(rect.width),
+ };
+ });
+ }
+}
+
+function waitForRawClipboardChange() {
+ const initialClipboardData = Date.now().toString();
+ SpecialPowers.clipboardCopyString(initialClipboardData);
+
+ let promiseChanged = BrowserTestUtils.waitForCondition(() => {
+ let data;
+ try {
+ data = getRawClipboardData("image/png");
+ } catch (e) {
+ console.log("Failed to get image/png clipboard data:", e);
+ return false;
+ }
+ return data && initialClipboardData !== data;
+ });
+ return promiseChanged;
+}
+
+function getRawClipboardData(flavor) {
+ const whichClipboard = Services.clipboard.kGlobalClipboard;
+ const xferable = Cc["@mozilla.org/widget/transferable;1"].createInstance(
+ Ci.nsITransferable
+ );
+ xferable.init(null);
+ xferable.addDataFlavor(flavor);
+ Services.clipboard.getData(xferable, whichClipboard);
+ let data = {};
+ try {
+ xferable.getTransferData(flavor, data);
+ } catch (e) {}
+ data = data.value || null;
+ return data;
+}
+
+/**
+ * A helper that returns the size of the image that was just put into the clipboard by the
+ * :screenshot command.
+ * @return The {width, height} dimension object.
+ */
+async function getImageSizeFromClipboard(browser) {
+ let flavor = "image/png";
+ let image = getRawClipboardData(flavor);
+ ok(image, "screenshot data exists on the clipboard");
+
+ // 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 or as image container. If it's not possible obtain a
+ // byte stream, the function throws.
+
+ if (image instanceof Ci.imgIContainer) {
+ image = Cc["@mozilla.org/image/tools;1"]
+ .getService(Ci.imgITools)
+ .encodeImage(image, flavor);
+ }
+
+ if (!(image instanceof Ci.nsIInputStream)) {
+ throw new Error("Unable to read image data");
+ }
+
+ const binaryStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
+ Ci.nsIBinaryInputStream
+ );
+ binaryStream.setInputStream(image);
+ const available = binaryStream.available();
+ const buffer = new ArrayBuffer(available);
+ is(
+ binaryStream.readArrayBuffer(available, buffer),
+ available,
+ "Read expected amount of data"
+ );
+
+ // We are going to load the image in the content page to measure its size.
+ // We don't want to insert the image directly in the browser's document
+ // which could mess all sorts of things up
+ return SpecialPowers.spawn(browser, [buffer], async function(_buffer) {
+ const img = content.document.createElement("img");
+ const loaded = new Promise(r => {
+ img.addEventListener("load", r, { once: true });
+ });
+
+ const url = content.URL.createObjectURL(
+ new Blob([_buffer], { type: "image/png" })
+ );
+
+ img.src = url;
+ content.document.documentElement.appendChild(img);
+
+ info("Waiting for the clipboard image to load in the content page");
+ await loaded;
+
+ img.remove();
+ content.URL.revokeObjectURL(url);
+
+ // TODO: could get pixel data as well so we can check colors at specific locations
+ return {
+ width: img.width,
+ height: img.height,
+ };
+ });
+}
+
+add_setup(async function common_initialize() {
+ // Ensure Screenshots is initially enabled for all tests
+ const addon = await AddonManager.getAddonByID("screenshots@mozilla.org");
+ const isEnabled = addon.enabled;
+ if (!isEnabled) {
+ await addon.enable({ allowSystemAddons: true });
+ registerCleanupFunction(async () => {
+ await addon.disable({ allowSystemAddons: true });
+ });
+ }
+ // Add the Screenshots button to the toolbar for all tests
+ CustomizableUI.addWidgetToArea(
+ "screenshot-button",
+ CustomizableUI.AREA_NAVBAR
+ );
+ registerCleanupFunction(() =>
+ CustomizableUI.removeWidgetFromArea("screenshot-button")
+ );
+ let screenshotBtn = document.getElementById("screenshot-button");
+ Assert.ok(screenshotBtn, "The screenshots button was added to the nav bar");
+});
diff --git a/browser/extensions/screenshots/test/browser/injection-page.html b/browser/extensions/screenshots/test/browser/injection-page.html
new file mode 100644
index 0000000000..ffbda3b25b
--- /dev/null
+++ b/browser/extensions/screenshots/test/browser/injection-page.html
@@ -0,0 +1,23 @@
+<body>
+<script>
+let callback = function(mutationsList, observer) {
+ for (let mutation of mutationsList) {
+ let [added] = mutation.addedNodes;
+ if (added instanceof HTMLIFrameElement && added.id == "firefox-screenshots-preview-iframe") {
+ added.srcdoc = "<html></html>";
+ // Now we have to wait for the doc to be populated.
+ let interval = setInterval(() => {
+ console.log(added.contentDocument.innerHTML);
+ if (added.contentDocument.body.innerHTML) {
+ clearInterval(interval);
+ window.responseHandler(added.contentDocument.body.innerHTML);
+ }
+ }, 100);
+ observer.disconnect();
+ }
+ }
+};
+var observer = new MutationObserver(callback);
+observer.observe(document.body, {childList: true});
+</script>
+</body>