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 = {
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,

View file

@ -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", {});
},

View file

@ -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;
}
}

View file

@ -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();