summaryrefslogtreecommitdiffstats
path: root/browser/components/screenshots/tests/browser/head.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/screenshots/tests/browser/head.js')
-rw-r--r--browser/components/screenshots/tests/browser/head.js951
1 files changed, 951 insertions, 0 deletions
diff --git a/browser/components/screenshots/tests/browser/head.js b/browser/components/screenshots/tests/browser/head.js
new file mode 100644
index 0000000000..171e3b8c41
--- /dev/null
+++ b/browser/components/screenshots/tests/browser/head.js
@@ -0,0 +1,951 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+const { UrlbarTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/UrlbarTestUtils.sys.mjs"
+);
+
+const TEST_ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+);
+
+const TEST_PAGE = TEST_ROOT + "test-page.html";
+const SHORT_TEST_PAGE = TEST_ROOT + "short-test-page.html";
+const LARGE_TEST_PAGE = TEST_ROOT + "large-test-page.html";
+const IFRAME_TEST_PAGE = TEST_ROOT + "iframe-test-page.html";
+const RESIZE_TEST_PAGE = TEST_ROOT + "test-page-resize.html";
+
+const { MAX_CAPTURE_DIMENSION, MAX_CAPTURE_AREA } = ChromeUtils.importESModule(
+ "resource:///modules/ScreenshotsUtils.sys.mjs"
+);
+
+const gScreenshotUISelectors = {
+ panel: "#screenshotsPagePanel",
+ fullPageButton: "button.full-page",
+ visiblePageButton: "button.visible-page",
+ copyButton: "button.#copy",
+};
+
+// MouseEvents is for the mouse events on the Anonymous content
+const MouseEvents = {
+ mouse: new Proxy(
+ {},
+ {
+ get: (target, name) =>
+ async function (x, y, options = {}) {
+ if (name === "click") {
+ this.down(x, y, options);
+ this.up(x, y, options);
+ } else {
+ await safeSynthesizeMouseEventInContentPage(":root", x, y, {
+ type: "mouse" + name,
+ ...options,
+ });
+ }
+ },
+ }
+ ),
+};
+
+const { mouse } = MouseEvents;
+
+class ScreenshotsHelper {
+ constructor(browser) {
+ this.browser = browser;
+ this.selector = gScreenshotUISelectors;
+ }
+
+ get toolbarButton() {
+ return this.browser.ownerDocument.getElementById("screenshot-button");
+ }
+
+ get panel() {
+ return this.browser.ownerDocument.querySelector(this.selector.panel);
+ }
+
+ /**
+ * Click the screenshots button in the toolbar
+ */
+ triggerUIFromToolbar() {
+ let button = this.toolbarButton;
+ ok(
+ BrowserTestUtils.isVisible(button),
+ "The screenshot toolbar button is visible"
+ );
+ button.click();
+ }
+
+ async getPanelButton(selector) {
+ let panel = await this.waitForPanel();
+ let screenshotsButtons = panel.querySelector("screenshots-buttons");
+ ok(screenshotsButtons, "Found the screenshots-buttons");
+ let button = screenshotsButtons.shadowRoot.querySelector(selector);
+ ok(button, `Found ${selector} button`);
+ return button;
+ }
+
+ async waitForPanel() {
+ let panel = this.panel;
+ await BrowserTestUtils.waitForCondition(async () => {
+ if (!panel) {
+ panel = this.panel;
+ }
+ return panel && BrowserTestUtils.isVisible(panel);
+ });
+ return panel;
+ }
+
+ async waitForOverlay() {
+ const panel = await this.waitForPanel();
+ ok(BrowserTestUtils.isVisible(panel), "Panel buttons are visible");
+
+ await BrowserTestUtils.waitForCondition(async () => {
+ let init = await this.isOverlayInitialized();
+ return init;
+ });
+ info("Overlay is visible");
+ }
+
+ async waitForPanelClosed() {
+ let panel = this.panel;
+ if (!panel) {
+ info("waitForPanelClosed: Panel doesnt exist");
+ return;
+ }
+ if (panel.hidden) {
+ info("waitForPanelClosed: panel is already hidden");
+ return;
+ }
+ info("waitForPanelClosed: waiting for the panel to become hidden");
+ await BrowserTestUtils.waitForMutationCondition(
+ panel,
+ { attributes: true },
+ () => {
+ return BrowserTestUtils.isHidden(panel);
+ }
+ );
+ ok(BrowserTestUtils.isHidden(panel), "Panel buttons are hidden");
+ info("waitForPanelClosed, panel is hidden: " + panel.hidden);
+ }
+
+ async waitForOverlayClosed() {
+ await this.waitForPanelClosed();
+ await BrowserTestUtils.waitForCondition(async () => {
+ let init = !(await this.isOverlayInitialized());
+ info("Is overlay initialized: " + !init);
+ return init;
+ });
+ info("Overlay is not visible");
+ }
+
+ async isOverlayInitialized() {
+ return SpecialPowers.spawn(this.browser, [], () => {
+ let screenshotsChild = content.windowGlobalChild.getActor(
+ "ScreenshotsComponent"
+ );
+ return screenshotsChild?.overlay?.initialized;
+ });
+ }
+
+ waitForStateChange(newState) {
+ return SpecialPowers.spawn(this.browser, [newState], async state => {
+ let screenshotsChild = content.windowGlobalChild.getActor(
+ "ScreenshotsComponent"
+ );
+
+ await ContentTaskUtils.waitForCondition(() => {
+ info(`got ${screenshotsChild.overlay.state}. expected ${state}`);
+ return screenshotsChild.overlay.state === state;
+ }, `Wait for overlay state to be ${state}`);
+
+ return screenshotsChild.overlay.state;
+ });
+ }
+
+ async assertStateChange(newState) {
+ let currentState = await this.waitForStateChange(newState);
+
+ is(
+ currentState,
+ newState,
+ `The current state is ${currentState}, expected ${newState}`
+ );
+ }
+
+ getHoverElementRect() {
+ return ContentTask.spawn(this.browser, null, async () => {
+ let screenshotsChild = content.windowGlobalChild.getActor(
+ "ScreenshotsComponent"
+ );
+ return screenshotsChild.overlay.hoverElementRegion.dimensions;
+ });
+ }
+
+ isHoverElementRegionValid() {
+ return ContentTask.spawn(this.browser, null, async () => {
+ let screenshotsChild = content.windowGlobalChild.getActor(
+ "ScreenshotsComponent"
+ );
+ return screenshotsChild.overlay.hoverElementRegion.isRegionValid;
+ });
+ }
+
+ async waitForHoverElementRect(expectedWidth, expectedHeight) {
+ return SpecialPowers.spawn(
+ this.browser,
+ [expectedWidth, expectedHeight],
+ async (width, height) => {
+ let screenshotsChild = content.windowGlobalChild.getActor(
+ "ScreenshotsComponent"
+ );
+ let dimensions;
+ await ContentTaskUtils.waitForCondition(() => {
+ dimensions = screenshotsChild.overlay.hoverElementRegion.dimensions;
+ return dimensions.width === width && dimensions.height === height;
+ }, "The hover element region is the expected width and height");
+ return dimensions;
+ }
+ );
+ }
+
+ async waitForSelectionRegionSizeChange(currentWidth) {
+ await ContentTask.spawn(
+ this.browser,
+ [currentWidth],
+ async ([currWidth]) => {
+ let screenshotsChild = content.windowGlobalChild.getActor(
+ "ScreenshotsComponent"
+ );
+
+ let dimensions = screenshotsChild.overlay.selectionRegion.dimensions;
+ await ContentTaskUtils.waitForCondition(() => {
+ dimensions = screenshotsChild.overlay.selectionRegion.dimensions;
+ return dimensions.width !== currWidth;
+ }, "Wait for selection box width change");
+ }
+ );
+ }
+
+ /**
+ * This will drag an overlay starting at the given startX and startY coordinates and ending
+ * at the given endX and endY coordinates.
+ *
+ * endY should be at least 70px from the bottom of window and endX should be at least
+ * 265px from the left of the window. If these requirements are not met then the
+ * overlay buttons (cancel, copy, download) will be positioned different from the default
+ * and the methods to click the overlay buttons will not work unless the updated
+ * position coordinates are supplied.
+ * See https://searchfox.org/mozilla-central/rev/af78418c4b5f2c8721d1a06486cf4cf0b33e1e8d/browser/components/screenshots/ScreenshotsOverlayChild.sys.mjs#1789,1798
+ * for how the overlay buttons are positioned when the overlay rect is near the bottom or
+ * left edge of the window.
+ *
+ * Note: The distance of the rect should be greater than 40 to enter in the "dragging" state.
+ * See https://searchfox.org/mozilla-central/rev/af78418c4b5f2c8721d1a06486cf4cf0b33e1e8d/browser/components/screenshots/ScreenshotsOverlayChild.sys.mjs#809
+ * @param {Number} startX The starting X coordinate. The left edge of the overlay rect.
+ * @param {Number} startY The starting Y coordinate. The top edge of the overlay rect.
+ * @param {Number} endX The end X coordinate. The right edge of the overlay rect.
+ * @param {Number} endY The end Y coordinate. The bottom edge of the overlay rect.
+ */
+ async dragOverlay(
+ startX,
+ startY,
+ endX,
+ endY,
+ expectedStartingState = "crosshairs"
+ ) {
+ await this.assertStateChange(expectedStartingState);
+
+ mouse.down(startX, startY);
+
+ await Promise.any([
+ this.waitForStateChange("draggingReady"),
+ this.waitForStateChange("resizing"),
+ ]);
+ Assert.ok(true, "The overlay is in the draggingReady or resizing state");
+
+ mouse.move(endX, endY);
+
+ await Promise.any([
+ this.waitForStateChange("dragging"),
+ this.waitForStateChange("resizing"),
+ ]);
+ Assert.ok(true, "The overlay is in the dragging or resizing state");
+
+ mouse.up(endX, endY);
+
+ await this.assertStateChange("selected");
+
+ this.endX = endX;
+ this.endY = endY;
+ }
+
+ async moveOverlayViaKeyboard(mover, events) {
+ await SpecialPowers.spawn(
+ this.browser,
+ [mover, events],
+ async (moverToFocus, eventsArr) => {
+ let screenshotsChild = content.windowGlobalChild.getActor(
+ "ScreenshotsComponent"
+ );
+
+ let overlay = screenshotsChild.overlay;
+
+ switch (moverToFocus) {
+ case "highlight":
+ overlay.highlightEl.focus({ focusVisible: true });
+ break;
+ case "mover-bottomLeft":
+ overlay.bottomLeftMover.focus({ focusVisible: true });
+ break;
+ case "mover-bottomRight":
+ overlay.bottomRightMover.focus({ focusVisible: true });
+ break;
+ case "mover-topLeft":
+ overlay.topLeftMover.focus({ focusVisible: true });
+ break;
+ case "mover-topRight":
+ overlay.topRightMover.focus({ focusVisible: true });
+ break;
+ }
+ screenshotsChild.overlay.highlightEl.focus();
+
+ for (let event of eventsArr) {
+ EventUtils.synthesizeKey(
+ event.key,
+ { type: "keydown", ...event.options },
+ content
+ );
+
+ await ContentTaskUtils.waitForCondition(
+ () => overlay.state === "resizing",
+ "Wait for overlay state to be resizing"
+ );
+
+ EventUtils.synthesizeKey(
+ event.key,
+ { type: "keyup", ...event.options },
+ content
+ );
+
+ await ContentTaskUtils.waitForCondition(
+ () => overlay.state === "selected",
+ "Wait for overlay state to be selected"
+ );
+ }
+ }
+ );
+ }
+
+ async scrollContentWindow(x, y) {
+ let promise = BrowserTestUtils.waitForContentEvent(this.browser, "scroll");
+ let contentDims = await this.getContentDimensions();
+ await ContentTask.spawn(
+ this.browser,
+ [x, y, contentDims],
+ async ([xPos, yPos, cDims]) => {
+ content.window.scroll(xPos, yPos);
+
+ info(JSON.stringify(cDims, null, 2));
+ const scrollbarHeight = {};
+ const scrollbarWidth = {};
+ content.window.windowUtils.getScrollbarSize(
+ false,
+ scrollbarWidth,
+ scrollbarHeight
+ );
+
+ await ContentTaskUtils.waitForCondition(() => {
+ function isCloseEnough(a, b, diff) {
+ return Math.abs(a - b) <= diff;
+ }
+
+ info(
+ `scrollbarWidth: ${scrollbarWidth.value}, scrollbarHeight: ${scrollbarHeight.value}`
+ );
+ info(
+ `scrollX: ${content.window.scrollX}, scrollY: ${content.window.scrollY}, scrollMaxX: ${content.window.scrollMaxX}, scrollMaxY: ${content.window.scrollMaxY}`
+ );
+
+ // Sometimes (read intermittently) the scroll width/height will be
+ // off by the width/height of the scrollbar when we are expecting the
+ // page to be scrolled to the very end. To mitigate this, we check
+ // that the below differences are within the scrollbar width/height.
+ return (
+ (content.window.scrollX === xPos ||
+ isCloseEnough(
+ cDims.clientWidth + content.window.scrollX,
+ cDims.scrollWidth,
+ scrollbarWidth.value + 1
+ )) &&
+ (content.window.scrollY === yPos ||
+ isCloseEnough(
+ cDims.clientHeight + content.window.scrollY,
+ cDims.scrollHeight,
+ scrollbarHeight.value + 1
+ ))
+ );
+ }, `Waiting for window to scroll to ${xPos}, ${yPos}`);
+ }
+ );
+ await promise;
+ }
+
+ async waitForScrollTo(x, y) {
+ await ContentTask.spawn(this.browser, [x, y], async ([xPos, yPos]) => {
+ await ContentTaskUtils.waitForCondition(() => {
+ info(
+ `Got scrollX: ${content.window.scrollX}. scrollY: ${content.window.scrollY}`
+ );
+ return (
+ content.window.scrollX === xPos && content.window.scrollY === yPos
+ );
+ }, `Waiting for window to scroll to ${xPos}, ${yPos}`);
+ });
+ }
+
+ async resizeContentWindow(width, height) {
+ this.browser.ownerGlobal.resizeTo(width, height);
+ await TestUtils.waitForCondition(
+ () => window.outerHeight === height && window.outerWidth === width,
+ "Waiting for window to resize"
+ );
+ }
+
+ async clickDownloadButton() {
+ let { centerX: x, centerY: y } = await ContentTask.spawn(
+ this.browser,
+ null,
+ async () => {
+ let screenshotsChild = content.windowGlobalChild.getActor(
+ "ScreenshotsComponent"
+ );
+ let { left, top, width, height } =
+ screenshotsChild.overlay.downloadButton.getBoundingClientRect();
+ let centerX = left + width / 2;
+ let centerY = top + height / 2;
+ return { centerX, centerY };
+ }
+ );
+
+ info(`clicking download button at ${x}, ${y}`);
+ mouse.click(x, y);
+ }
+
+ async clickCopyButton() {
+ let { centerX: x, centerY: y } = await ContentTask.spawn(
+ this.browser,
+ null,
+ async () => {
+ let screenshotsChild = content.windowGlobalChild.getActor(
+ "ScreenshotsComponent"
+ );
+ let { left, top, width, height } =
+ screenshotsChild.overlay.copyButton.getBoundingClientRect();
+ let centerX = left + width / 2;
+ let centerY = top + height / 2;
+ return { centerX, centerY };
+ }
+ );
+
+ info(`clicking copy button at ${x}, ${y}`);
+ mouse.click(x, y);
+ }
+
+ async clickCancelButton() {
+ let { centerX: x, centerY: y } = await ContentTask.spawn(
+ this.browser,
+ null,
+ async () => {
+ let screenshotsChild = content.windowGlobalChild.getActor(
+ "ScreenshotsComponent"
+ );
+ let { left, top, width, height } =
+ screenshotsChild.overlay.cancelButton.getBoundingClientRect();
+ let centerX = left + width / 2;
+ let centerY = top + height / 2;
+ return { centerX, centerY };
+ }
+ );
+
+ info(`clicking cancel button at ${x}, ${y}`);
+ mouse.click(x, y);
+ }
+
+ async clickPreviewCancelButton() {
+ let { centerX: x, centerY: y } = await ContentTask.spawn(
+ this.browser,
+ null,
+ async () => {
+ let screenshotsChild = content.windowGlobalChild.getActor(
+ "ScreenshotsComponent"
+ );
+ let { left, top, width, height } =
+ screenshotsChild.overlay.previewCancelButton.getBoundingClientRect();
+ let centerX = left + width / 2;
+ let centerY = top + height / 2;
+ return { centerX, centerY };
+ }
+ );
+
+ info(`clicking cancel button at ${x}, ${y}`);
+ mouse.click(x, y);
+ }
+
+ escapeKeyInContent() {
+ return SpecialPowers.spawn(this.browser, [], () => {
+ EventUtils.synthesizeKey("KEY_Escape", {}, content);
+ });
+ }
+
+ getTestPageElementRect(elementId = "testPageElement") {
+ return ContentTask.spawn(this.browser, [elementId], async id => {
+ let ele = content.document.getElementById(id);
+ return ele.getBoundingClientRect();
+ });
+ }
+
+ async clickTestPageElement(elementId = "testPageElement") {
+ let rect = await this.getTestPageElementRect(elementId);
+ let dims = await this.getContentDimensions();
+
+ let x = Math.floor(rect.x + dims.scrollX + rect.width / 2);
+ let y = Math.floor(rect.y + dims.scrollY + rect.height / 2);
+
+ mouse.move(x, y);
+ await this.waitForHoverElementRect(rect.width, rect.height);
+ mouse.down(x, y);
+ await this.assertStateChange("draggingReady");
+ mouse.up(x, y);
+ await this.assertStateChange("selected");
+ }
+
+ async zoomBrowser(zoom) {
+ let promise = BrowserTestUtils.waitForContentEvent(this.browser, "resize");
+ await SpecialPowers.spawn(this.browser, [zoom], zoomLevel => {
+ const { Layout } = ChromeUtils.importESModule(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Layout.sys.mjs"
+ );
+ Layout.zoomDocument(content.document, zoomLevel);
+ });
+ await promise;
+ }
+
+ /**
+ * Gets the dialog box
+ * @returns The dialog box
+ */
+ getDialog() {
+ let currDialogBox = this.browser.tabDialogBox;
+ let manager = currDialogBox.getTabDialogManager();
+ let dialogs = manager.hasDialogs && manager.dialogs;
+ return dialogs[0];
+ }
+
+ assertPanelVisible() {
+ info("assertPanelVisible, panel.hidden:" + this.panel?.hidden);
+ Assert.ok(
+ BrowserTestUtils.isVisible(this.panel),
+ "Screenshots panel is visible"
+ );
+ }
+
+ assertPanelNotVisible() {
+ info("assertPanelNotVisible, panel.hidden:" + this.panel?.hidden);
+ Assert.ok(
+ !this.panel || BrowserTestUtils.isHidden(this.panel),
+ "Screenshots panel is not visible"
+ );
+ }
+
+ /**
+ * Copied from screenshots extension
+ * Returns a promise that resolves when the clipboard data has changed
+ * Otherwise rejects
+ */
+ waitForRawClipboardChange(epectedWidth, expectedHeight) {
+ const initialClipboardData = Date.now().toString();
+ SpecialPowers.clipboardCopyString(initialClipboardData);
+
+ return TestUtils.waitForCondition(
+ async () => {
+ let data;
+ try {
+ data = await this.getImageSizeAndColorFromClipboard();
+ } catch (e) {
+ console.log("Failed to get image/png clipboard data:", e);
+ return false;
+ }
+ if (
+ data &&
+ initialClipboardData !== data &&
+ data.height === expectedHeight &&
+ data.width === epectedWidth
+ ) {
+ return data;
+ }
+ return false;
+ },
+ "Waiting for screenshot to copy to clipboard",
+ 200
+ );
+ }
+
+ /**
+ * Gets the client and scroll demensions on the window
+ * @returns { Object }
+ * clientHeight The visible height
+ * clientWidth The visible width
+ * scrollHeight The scrollable height
+ * scrollWidth The scrollable width
+ * scrollX The scroll x position
+ * scrollY The scroll y position
+ */
+ getContentDimensions() {
+ return SpecialPowers.spawn(this.browser, [], async function () {
+ let {
+ innerWidth,
+ innerHeight,
+ scrollMaxX,
+ scrollMaxY,
+ scrollX,
+ scrollY,
+ } = content.window;
+ let width = innerWidth + scrollMaxX;
+ let height = innerHeight + scrollMaxY;
+
+ const scrollbarHeight = {};
+ const scrollbarWidth = {};
+ content.window.windowUtils.getScrollbarSize(
+ false,
+ scrollbarWidth,
+ scrollbarHeight
+ );
+ width -= scrollbarWidth.value;
+ height -= scrollbarHeight.value;
+ innerWidth -= scrollbarWidth.value;
+ innerHeight -= scrollbarHeight.value;
+
+ return {
+ clientHeight: innerHeight,
+ clientWidth: innerWidth,
+ scrollHeight: height,
+ scrollWidth: width,
+ scrollX,
+ scrollY,
+ };
+ });
+ }
+
+ async getScreenshotsOverlayDimensions() {
+ return ContentTask.spawn(this.browser, null, async () => {
+ let screenshotsChild = content.windowGlobalChild.getActor(
+ "ScreenshotsComponent"
+ );
+ Assert.ok(screenshotsChild.overlay.initialized, "The overlay exists");
+
+ let screenshotsContainer = screenshotsChild.overlay.screenshotsContainer;
+
+ await ContentTaskUtils.waitForCondition(() => {
+ return !screenshotsContainer.hasAttribute("resizing");
+ }, "Waiting for overlay to be done resizing");
+
+ info(
+ `${screenshotsContainer.style.width} ${
+ screenshotsContainer.style.height
+ } ${screenshotsContainer.hasAttribute("resizing")}`
+ );
+
+ return {
+ scrollWidth: screenshotsContainer.scrollWidth,
+ scrollHeight: screenshotsContainer.scrollHeight,
+ };
+ });
+ }
+
+ async waitForSelectionLayerDimensionChange(oldWidth, oldHeight) {
+ await ContentTask.spawn(
+ this.browser,
+ [oldWidth, oldHeight],
+ async ([prevWidth, prevHeight]) => {
+ let screenshotsChild = content.windowGlobalChild.getActor(
+ "ScreenshotsComponent"
+ );
+
+ await ContentTaskUtils.waitForCondition(() => {
+ let screenshotsContainer =
+ screenshotsChild.overlay.screenshotsContainer;
+ info(
+ `old height: ${prevHeight}. new height: ${screenshotsContainer.scrollHeight}.\nold width: ${prevWidth}. new width: ${screenshotsContainer.scrollWidth}`
+ );
+ return (
+ screenshotsContainer.scrollHeight !== prevHeight &&
+ screenshotsContainer.scrollWidth !== prevWidth
+ );
+ }, "Wait for selection box width change");
+ }
+ );
+ }
+
+ waitForOverlaySizeChangeTo(width, height) {
+ return ContentTask.spawn(
+ this.browser,
+ [width, height],
+ async ([newWidth, newHeight]) => {
+ await ContentTaskUtils.waitForCondition(() => {
+ let {
+ innerHeight,
+ innerWidth,
+ scrollMaxY,
+ scrollMaxX,
+ scrollMinY,
+ scrollMinX,
+ } = content.window;
+ let scrollWidth = innerWidth + scrollMaxX - scrollMinX;
+ let scrollHeight = innerHeight + scrollMaxY - scrollMinY;
+
+ const scrollbarHeight = {};
+ const scrollbarWidth = {};
+ content.window.windowUtils.getScrollbarSize(
+ false,
+ scrollbarWidth,
+ scrollbarHeight
+ );
+ scrollWidth -= scrollbarWidth.value;
+ scrollHeight -= scrollbarHeight.value;
+ info(
+ `${scrollHeight}, ${newHeight}, ${scrollWidth}, ${newWidth}, ${content.window.scrollMaxX}`
+ );
+ return scrollHeight === newHeight && scrollWidth === newWidth;
+ }, "Wait for document size change");
+ }
+ );
+ }
+
+ getSelectionRegionDimensions() {
+ return ContentTask.spawn(this.browser, null, async () => {
+ let screenshotsChild = content.windowGlobalChild.getActor(
+ "ScreenshotsComponent"
+ );
+ Assert.ok(screenshotsChild.overlay.initialized, "The overlay exists");
+
+ return screenshotsChild.overlay.selectionRegion.dimensions;
+ });
+ }
+
+ /**
+ * Copied from screenshots extension
+ * A helper that returns the size of the image that was just put into the clipboard by the
+ * :screenshot command.
+ * @return The {width, height, color} dimension and color object.
+ */
+ async getImageSizeAndColorFromClipboard() {
+ let flavor = "image/png";
+ let image = getRawClipboardData(flavor);
+ if (!image) {
+ return false;
+ }
+
+ // 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);
+ info(
+ `${binaryStream.readArrayBuffer(
+ available,
+ buffer
+ )} read, ${available} available`
+ );
+
+ // 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(
+ this.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;
+
+ let canvas = content.document.createElementNS(
+ "http://www.w3.org/1999/xhtml",
+ "html:canvas"
+ );
+ let context = canvas.getContext("2d");
+ canvas.width = img.width;
+ canvas.height = img.height;
+ context.drawImage(img, 0, 0);
+ let topLeft = context.getImageData(0, 0, 1, 1);
+ let topRight = context.getImageData(img.width - 1, 0, 1, 1);
+ let bottomLeft = context.getImageData(0, img.height - 1, 1, 1);
+ let bottomRight = context.getImageData(
+ img.width - 1,
+ img.height - 1,
+ 1,
+ 1
+ );
+
+ img.remove();
+ content.URL.revokeObjectURL(url);
+
+ return {
+ width: img.width,
+ height: img.height,
+ color: {
+ topLeft: topLeft.data,
+ topRight: topRight.data,
+ bottomLeft: bottomLeft.data,
+ bottomRight: bottomRight.data,
+ },
+ };
+ }
+ );
+ }
+}
+
+/**
+ * Get the raw clipboard data
+ * @param flavor Type of data to get from clipboard
+ * @returns The data from the clipboard
+ */
+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,
+ SpecialPowers.wrap(window).browsingContext.currentWindowContext
+ );
+ let data = {};
+ try {
+ // xferable.getTransferData(flavor, data);
+ xferable.getAnyTransferData({}, data);
+ info(JSON.stringify(data, null, 2));
+ } catch (e) {
+ info(e);
+ }
+ data = data.value || null;
+ return data;
+}
+
+/**
+ * Synthesize a mouse event on an element, after ensuring that it is visible
+ * in the viewport.
+ *
+ * @param {String} selector: The node selector to get the node target for the event.
+ * @param {number} x
+ * @param {number} y
+ * @param {object} options: Options that will be passed to BrowserTestUtils.synthesizeMouse
+ */
+async function safeSynthesizeMouseEventInContentPage(
+ selector,
+ x,
+ y,
+ options = {}
+) {
+ let context = gBrowser.selectedBrowser.browsingContext;
+ BrowserTestUtils.synthesizeMouse(selector, x, y, options, context);
+}
+
+add_setup(async () => {
+ CustomizableUI.addWidgetToArea(
+ "screenshot-button",
+ CustomizableUI.AREA_NAVBAR,
+ 0
+ );
+ let screenshotBtn = document.getElementById("screenshot-button");
+ Assert.ok(screenshotBtn, "The screenshots button was added to the nav bar");
+});
+
+function getContentDevicePixelRatio(browser) {
+ return SpecialPowers.spawn(browser, [], async function () {
+ return content.window.devicePixelRatio;
+ });
+}
+
+async function clearAllTelemetryEvents() {
+ // Clear everything.
+ info("Clearing all telemetry events");
+ await TestUtils.waitForCondition(() => {
+ Services.telemetry.clearEvents();
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ true
+ );
+ let content = events.content;
+ let parent = events.parent;
+
+ return (!content && !parent) || (!content.length && !parent.length);
+ });
+}
+
+async function waitForScreenshotsEventCount(count, process = "parent") {
+ await TestUtils.waitForCondition(
+ () => {
+ let events = TelemetryTestUtils.getEvents(
+ { category: "screenshots" },
+ { process }
+ );
+
+ info(`Got ${events?.length} event(s)`);
+ info(`Actual events: ${JSON.stringify(events, null, 2)}`);
+ return events.length === count ? events : null;
+ },
+ `Waiting for ${count} ${process} event(s).`,
+ 200,
+ 100
+ );
+}
+
+async function assertScreenshotsEvents(
+ expectedEvents,
+ process = "parent",
+ clearEvents = true
+) {
+ info(`Expected events: ${JSON.stringify(expectedEvents, null, 2)}`);
+ // Make sure we have recorded the correct number of events
+ await waitForScreenshotsEventCount(expectedEvents.length, process);
+
+ TelemetryTestUtils.assertEvents(
+ expectedEvents,
+ { category: "screenshots" },
+ { clear: clearEvents, process }
+ );
+}