From 6e5b9a5a1edab13a1b2e2e90944b6e06b4d8149c Mon Sep 17 00:00:00 2001 From: Niklas Baumgardner Date: Thu, 12 Oct 2023 16:15:41 +0000 Subject: [PATCH] Bug 1854953 - Improve performance when taking large screenshots. r=sfoster,desktop-theme-reviewers Differential Revision: https://phabricator.services.mozilla.com/D189158 --- .../actors/ScreenshotsComponentChild.sys.mjs | 4 + .../screenshots/ScreenshotsUtils.sys.mjs | 77 ++++++++++++------- .../components/screenshots/overlayHelpers.mjs | 16 ++-- ...ser_screenshots_test_screenshot_too_big.js | 22 +++--- 4 files changed, 75 insertions(+), 44 deletions(-) diff --git a/browser/actors/ScreenshotsComponentChild.sys.mjs b/browser/actors/ScreenshotsComponentChild.sys.mjs index 2b4c4e4cf524..0ca824933e06 100644 --- a/browser/actors/ScreenshotsComponentChild.sys.mjs +++ b/browser/actors/ScreenshotsComponentChild.sys.mjs @@ -256,6 +256,8 @@ export class ScreenshotsComponentChild extends JSWindowActorChild { let rect = { left: scrollMinX, top: scrollMinY, + right: scrollWidth, + bottom: scrollHeight, width: scrollWidth, height: scrollHeight, devicePixelRatio: this.contentWindow.devicePixelRatio, @@ -292,6 +294,8 @@ export class ScreenshotsComponentChild extends JSWindowActorChild { let rect = { left: scrollX, top: scrollY, + right: scrollX + clientWidth, + bottom: scrollY + clientHeight, width: clientWidth, height: clientHeight, devicePixelRatio: this.contentWindow.devicePixelRatio, diff --git a/browser/components/screenshots/ScreenshotsUtils.sys.mjs b/browser/components/screenshots/ScreenshotsUtils.sys.mjs index 3b536b16e587..13c20edb5ce3 100644 --- a/browser/components/screenshots/ScreenshotsUtils.sys.mjs +++ b/browser/components/screenshots/ScreenshotsUtils.sys.mjs @@ -24,11 +24,12 @@ ChromeUtils.defineLazyGetter(lazy, "screenshotsLocalization", () => { const PanelPosition = "bottomright topright"; const PanelOffsetX = -33; const PanelOffsetY = -8; -// The max dimension for a canvas is defined https://searchfox.org/mozilla-central/rev/f40d29a11f2eb4685256b59934e637012ea6fb78/gfx/cairo/cairo/src/cairo-image-surface.c#62. -// The max number of pixels for a canvas is 124925329 or 11177 x 11177. +// The max dimension for a canvas is 32,767 https://searchfox.org/mozilla-central/rev/f40d29a11f2eb4685256b59934e637012ea6fb78/gfx/cairo/cairo/src/cairo-image-surface.c#62. +// The max number of pixels for a canvas is 472,907,776 pixels (i.e., 22,528 x 20,992) https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas#maximum_canvas_size // We have to limit screenshots to these dimensions otherwise it will cause an error. export const MAX_CAPTURE_DIMENSION = 32766; -export const MAX_CAPTURE_AREA = 124925329; +export const MAX_CAPTURE_AREA = 472907776; +export const MAX_SNAPSHOT_DIMENSION = 1024; export class ScreenshotsComponentParent extends JSWindowActorParent { async receiveMessage(message) { @@ -544,6 +545,8 @@ export var ScreenshotsUtils = { rect.width = Math.floor(width / rect.devicePixelRatio); rect.height = Math.floor(height / rect.devicePixelRatio); + rect.right = rect.left + rect.width; + rect.bottom = rect.top + rect.height; if (cropped) { let [errorTitle, errorMessage] = @@ -586,7 +589,7 @@ export var ScreenshotsUtils = { * @param rect DOMRect containing bounds of the screenshot. */ async takeScreenshot(browser, dialog, rect) { - let { canvas, snapshot } = await this.createCanvas(rect, browser); + let canvas = await this.createCanvas(rect, browser); let newImg = dialog._frame.contentDocument.createElement("img"); let url = canvas.toDataURL(); @@ -601,47 +604,69 @@ export var ScreenshotsUtils = { if (Cu.isInAutomation) { Services.obs.notifyObservers(null, "screenshots-preview-ready"); } - - snapshot.close(); }, /** * Creates a canvas and draws a snapshot of the screenshot on the canvas * @param region The bounds of screenshots * @param browser The current browser - * @returns The canvas and snapshot in an object + * @returns The canvas */ async createCanvas(region, browser) { this.cropScreenshotRectIfNeeded(region); - let rect = new DOMRect( - region.left, - region.top, - region.width, - region.height - ); let { devicePixelRatio } = region; let browsingContext = BrowsingContext.get(browser.browsingContext.id); - let snapshot = await browsingContext.currentWindowGlobal.drawSnapshot( - rect, - devicePixelRatio, - "rgb(255,255,255)" - ); - let canvas = browser.ownerDocument.createElementNS( "http://www.w3.org/1999/xhtml", "html:canvas" ); let context = canvas.getContext("2d"); - canvas.width = snapshot.width; - canvas.height = snapshot.height; + canvas.width = region.width * devicePixelRatio; + canvas.height = region.height * devicePixelRatio; - context.drawImage(snapshot, 0, 0); + for ( + let startLeft = region.left; + startLeft < region.right; + startLeft += MAX_SNAPSHOT_DIMENSION + ) { + for ( + let startTop = region.top; + startTop < region.bottom; + startTop += MAX_SNAPSHOT_DIMENSION + ) { + let height = + startTop + MAX_SNAPSHOT_DIMENSION > region.bottom + ? region.bottom - startTop + : MAX_SNAPSHOT_DIMENSION; + let width = + startLeft + MAX_SNAPSHOT_DIMENSION > region.right + ? region.right - startLeft + : MAX_SNAPSHOT_DIMENSION; + let rect = new DOMRect(startLeft, startTop, width, height); - return { canvas, snapshot }; + let snapshot = await browsingContext.currentWindowGlobal.drawSnapshot( + rect, + devicePixelRatio, + "rgb(255,255,255)" + ); + + context.drawImage( + snapshot, + (startLeft - region.left) * devicePixelRatio, + (startTop - region.top) * devicePixelRatio, + width * devicePixelRatio, + height * devicePixelRatio + ); + + snapshot.close(); + } + } + + return canvas; }, /** @@ -650,11 +675,10 @@ export var ScreenshotsUtils = { * @param browser The current browser */ async copyScreenshotFromRegion(region, browser) { - let { canvas, snapshot } = await this.createCanvas(region, browser); + let canvas = await this.createCanvas(region, browser); let url = canvas.toDataURL(); this.copyScreenshot(url, browser); - snapshot.close(); this.recordTelemetryEvent("copy", "overlay_copy", {}); }, @@ -706,11 +730,10 @@ export var ScreenshotsUtils = { * @param browser The current browser */ async downloadScreenshotFromRegion(title, region, browser) { - let { canvas, snapshot } = await this.createCanvas(region, browser); + let canvas = await this.createCanvas(region, browser); let dataUrl = canvas.toDataURL(); await this.downloadScreenshot(title, dataUrl, browser); - snapshot.close(); this.recordTelemetryEvent("download", "overlay_download", {}); }, diff --git a/browser/components/screenshots/overlayHelpers.mjs b/browser/components/screenshots/overlayHelpers.mjs index 1e5a2200efa4..af145f57ba1f 100644 --- a/browser/components/screenshots/overlayHelpers.mjs +++ b/browser/components/screenshots/overlayHelpers.mjs @@ -341,28 +341,28 @@ export class WindowDimensions { #scrollMinY = null; set dimensions(dimensions) { - if (dimensions.clientHeight) { + if (dimensions.clientHeight != null) { this.#clientHeight = dimensions.clientHeight; } - if (dimensions.clientWidth) { + if (dimensions.clientWidth != null) { this.#clientWidth = dimensions.clientWidth; } - if (dimensions.scrollHeight) { + if (dimensions.scrollHeight != null) { this.#scrollHeight = dimensions.scrollHeight; } - if (dimensions.scrollWidth) { + if (dimensions.scrollWidth != null) { this.#scrollWidth = dimensions.scrollWidth; } - if (dimensions.scrollX) { + if (dimensions.scrollX != null) { this.#scrollX = dimensions.scrollX; } - if (dimensions.scrollY) { + if (dimensions.scrollY != null) { this.#scrollY = dimensions.scrollY; } - if (dimensions.scrollMinX) { + if (dimensions.scrollMinX != null) { this.#scrollMinX = dimensions.scrollMinX; } - if (dimensions.scrollMinY) { + if (dimensions.scrollMinY != null) { this.#scrollMinY = dimensions.scrollMinY; } } diff --git a/browser/components/screenshots/tests/browser/browser_screenshots_test_screenshot_too_big.js b/browser/components/screenshots/tests/browser/browser_screenshots_test_screenshot_too_big.js index 9a6201dea60f..4e2da3d4940c 100644 --- a/browser/components/screenshots/tests/browser/browser_screenshots_test_screenshot_too_big.js +++ b/browser/components/screenshots/tests/browser/browser_screenshots_test_screenshot_too_big.js @@ -39,11 +39,15 @@ add_task(async function test_screenshot_too_large_cropped() { ScreenshotsUtils.cropScreenshotRectIfNeeded(rect); - is(rect.width, MAX_CAPTURE_DIMENSION, "The width is 32766"); + is( + rect.width, + MAX_CAPTURE_DIMENSION, + `The width is ${MAX_CAPTURE_DIMENSION}` + ); is( rect.height, Math.floor(MAX_CAPTURE_AREA / MAX_CAPTURE_DIMENSION), - "The height is 124925329 / 32766" + `The height is ${MAX_CAPTURE_AREA} / ${MAX_CAPTURE_DIMENSION}` ); rect.width = 40000; @@ -54,7 +58,7 @@ add_task(async function test_screenshot_too_large_cropped() { is( rect.width, MAX_CAPTURE_DIMENSION, - "The width was cropped to the max capture dimension (32766)." + `The width was cropped to the max capture dimension (${MAX_CAPTURE_DIMENSION}).` ); rect.width = 1; @@ -65,19 +69,19 @@ add_task(async function test_screenshot_too_large_cropped() { is( rect.height, MAX_CAPTURE_DIMENSION, - "The height was cropped to the max capture dimension (32766)." + `The height was cropped to the max capture dimension (${MAX_CAPTURE_DIMENSION}).` ); - rect.width = 12345; - rect.height = 12345; + rect.width = 25000; + rect.height = 25000; ScreenshotsUtils.cropScreenshotRectIfNeeded(rect); - is(rect.width, 12345, "The width was not cropped"); + is(rect.width, 25000, "The width was not cropped"); is( rect.height, - Math.floor(MAX_CAPTURE_AREA / 12345), - "The height was cropped to 10119" + Math.floor(MAX_CAPTURE_AREA / 25000), + `The height was cropped to ${MAX_CAPTURE_AREA / 25000}` ); showAlertMessageStub.restore();