Bug 1854953 - Improve performance when taking large screenshots. r=sfoster,desktop-theme-reviewers

Differential Revision: https://phabricator.services.mozilla.com/D189158
This commit is contained in:
Niklas Baumgardner 2023-10-12 16:15:41 +00:00
parent 4dee6bf753
commit 6e5b9a5a1e
4 changed files with 75 additions and 44 deletions

View file

@ -256,6 +256,8 @@ export class ScreenshotsComponentChild extends JSWindowActorChild {
let rect = { let rect = {
left: scrollMinX, left: scrollMinX,
top: scrollMinY, top: scrollMinY,
right: scrollWidth,
bottom: scrollHeight,
width: scrollWidth, width: scrollWidth,
height: scrollHeight, height: scrollHeight,
devicePixelRatio: this.contentWindow.devicePixelRatio, devicePixelRatio: this.contentWindow.devicePixelRatio,
@ -292,6 +294,8 @@ export class ScreenshotsComponentChild extends JSWindowActorChild {
let rect = { let rect = {
left: scrollX, left: scrollX,
top: scrollY, top: scrollY,
right: scrollX + clientWidth,
bottom: scrollY + clientHeight,
width: clientWidth, width: clientWidth,
height: clientHeight, height: clientHeight,
devicePixelRatio: this.contentWindow.devicePixelRatio, devicePixelRatio: this.contentWindow.devicePixelRatio,

View file

@ -24,11 +24,12 @@ ChromeUtils.defineLazyGetter(lazy, "screenshotsLocalization", () => {
const PanelPosition = "bottomright topright"; const PanelPosition = "bottomright topright";
const PanelOffsetX = -33; const PanelOffsetX = -33;
const PanelOffsetY = -8; 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 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 124925329 or 11177 x 11177. // 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. // We have to limit screenshots to these dimensions otherwise it will cause an error.
export const MAX_CAPTURE_DIMENSION = 32766; 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 { export class ScreenshotsComponentParent extends JSWindowActorParent {
async receiveMessage(message) { async receiveMessage(message) {
@ -544,6 +545,8 @@ export var ScreenshotsUtils = {
rect.width = Math.floor(width / rect.devicePixelRatio); rect.width = Math.floor(width / rect.devicePixelRatio);
rect.height = Math.floor(height / rect.devicePixelRatio); rect.height = Math.floor(height / rect.devicePixelRatio);
rect.right = rect.left + rect.width;
rect.bottom = rect.top + rect.height;
if (cropped) { if (cropped) {
let [errorTitle, errorMessage] = let [errorTitle, errorMessage] =
@ -586,7 +589,7 @@ export var ScreenshotsUtils = {
* @param rect DOMRect containing bounds of the screenshot. * @param rect DOMRect containing bounds of the screenshot.
*/ */
async takeScreenshot(browser, dialog, rect) { 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 newImg = dialog._frame.contentDocument.createElement("img");
let url = canvas.toDataURL(); let url = canvas.toDataURL();
@ -601,47 +604,69 @@ export var ScreenshotsUtils = {
if (Cu.isInAutomation) { if (Cu.isInAutomation) {
Services.obs.notifyObservers(null, "screenshots-preview-ready"); Services.obs.notifyObservers(null, "screenshots-preview-ready");
} }
snapshot.close();
}, },
/** /**
* Creates a canvas and draws a snapshot of the screenshot on the canvas * Creates a canvas and draws a snapshot of the screenshot on the canvas
* @param region The bounds of screenshots * @param region The bounds of screenshots
* @param browser The current browser * @param browser The current browser
* @returns The canvas and snapshot in an object * @returns The canvas
*/ */
async createCanvas(region, browser) { async createCanvas(region, browser) {
this.cropScreenshotRectIfNeeded(region); this.cropScreenshotRectIfNeeded(region);
let rect = new DOMRect(
region.left,
region.top,
region.width,
region.height
);
let { devicePixelRatio } = region; let { devicePixelRatio } = region;
let browsingContext = BrowsingContext.get(browser.browsingContext.id); let browsingContext = BrowsingContext.get(browser.browsingContext.id);
let snapshot = await browsingContext.currentWindowGlobal.drawSnapshot(
rect,
devicePixelRatio,
"rgb(255,255,255)"
);
let canvas = browser.ownerDocument.createElementNS( let canvas = browser.ownerDocument.createElementNS(
"http://www.w3.org/1999/xhtml", "http://www.w3.org/1999/xhtml",
"html:canvas" "html:canvas"
); );
let context = canvas.getContext("2d"); let context = canvas.getContext("2d");
canvas.width = snapshot.width; canvas.width = region.width * devicePixelRatio;
canvas.height = snapshot.height; 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 * @param browser The current browser
*/ */
async copyScreenshotFromRegion(region, browser) { async copyScreenshotFromRegion(region, browser) {
let { canvas, snapshot } = await this.createCanvas(region, browser); let canvas = await this.createCanvas(region, browser);
let url = canvas.toDataURL(); let url = canvas.toDataURL();
this.copyScreenshot(url, browser); this.copyScreenshot(url, browser);
snapshot.close();
this.recordTelemetryEvent("copy", "overlay_copy", {}); this.recordTelemetryEvent("copy", "overlay_copy", {});
}, },
@ -706,11 +730,10 @@ export var ScreenshotsUtils = {
* @param browser The current browser * @param browser The current browser
*/ */
async downloadScreenshotFromRegion(title, region, 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(); let dataUrl = canvas.toDataURL();
await this.downloadScreenshot(title, dataUrl, browser); await this.downloadScreenshot(title, dataUrl, browser);
snapshot.close();
this.recordTelemetryEvent("download", "overlay_download", {}); this.recordTelemetryEvent("download", "overlay_download", {});
}, },

View file

@ -341,28 +341,28 @@ export class WindowDimensions {
#scrollMinY = null; #scrollMinY = null;
set dimensions(dimensions) { set dimensions(dimensions) {
if (dimensions.clientHeight) { if (dimensions.clientHeight != null) {
this.#clientHeight = dimensions.clientHeight; this.#clientHeight = dimensions.clientHeight;
} }
if (dimensions.clientWidth) { if (dimensions.clientWidth != null) {
this.#clientWidth = dimensions.clientWidth; this.#clientWidth = dimensions.clientWidth;
} }
if (dimensions.scrollHeight) { if (dimensions.scrollHeight != null) {
this.#scrollHeight = dimensions.scrollHeight; this.#scrollHeight = dimensions.scrollHeight;
} }
if (dimensions.scrollWidth) { if (dimensions.scrollWidth != null) {
this.#scrollWidth = dimensions.scrollWidth; this.#scrollWidth = dimensions.scrollWidth;
} }
if (dimensions.scrollX) { if (dimensions.scrollX != null) {
this.#scrollX = dimensions.scrollX; this.#scrollX = dimensions.scrollX;
} }
if (dimensions.scrollY) { if (dimensions.scrollY != null) {
this.#scrollY = dimensions.scrollY; this.#scrollY = dimensions.scrollY;
} }
if (dimensions.scrollMinX) { if (dimensions.scrollMinX != null) {
this.#scrollMinX = dimensions.scrollMinX; this.#scrollMinX = dimensions.scrollMinX;
} }
if (dimensions.scrollMinY) { if (dimensions.scrollMinY != null) {
this.#scrollMinY = dimensions.scrollMinY; this.#scrollMinY = dimensions.scrollMinY;
} }
} }

View file

@ -39,11 +39,15 @@ add_task(async function test_screenshot_too_large_cropped() {
ScreenshotsUtils.cropScreenshotRectIfNeeded(rect); 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( is(
rect.height, rect.height,
Math.floor(MAX_CAPTURE_AREA / MAX_CAPTURE_DIMENSION), 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; rect.width = 40000;
@ -54,7 +58,7 @@ add_task(async function test_screenshot_too_large_cropped() {
is( is(
rect.width, rect.width,
MAX_CAPTURE_DIMENSION, 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; rect.width = 1;
@ -65,19 +69,19 @@ add_task(async function test_screenshot_too_large_cropped() {
is( is(
rect.height, rect.height,
MAX_CAPTURE_DIMENSION, 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.width = 25000;
rect.height = 12345; rect.height = 25000;
ScreenshotsUtils.cropScreenshotRectIfNeeded(rect); ScreenshotsUtils.cropScreenshotRectIfNeeded(rect);
is(rect.width, 12345, "The width was not cropped"); is(rect.width, 25000, "The width was not cropped");
is( is(
rect.height, rect.height,
Math.floor(MAX_CAPTURE_AREA / 12345), Math.floor(MAX_CAPTURE_AREA / 25000),
"The height was cropped to 10119" `The height was cropped to ${MAX_CAPTURE_AREA / 25000}`
); );
showAlertMessageStub.restore(); showAlertMessageStub.restore();