зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1563746 - [remote] Implement Page.captureScreenshot. r=remote-protocol-reviewers,maja_zf,ato
Differential Revision: https://phabricator.services.mozilla.com/D49203 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
2bf032d442
Коммит
3dce432d4f
|
@ -58,6 +58,15 @@ class Page extends ContentProcessDomain {
|
|||
}
|
||||
}
|
||||
|
||||
_viewportRect() {
|
||||
return new DOMRect(
|
||||
this.content.pageXOffset,
|
||||
this.content.pageYOffset,
|
||||
this.content.innerWidth,
|
||||
this.content.innerHeight
|
||||
);
|
||||
}
|
||||
|
||||
async navigate({ url, referrer, transitionType, frameId } = {}) {
|
||||
if (frameId && frameId != this.content.windowUtils.outerWindowID) {
|
||||
throw new UnsupportedError("frameId not supported");
|
||||
|
|
|
@ -12,6 +12,9 @@ const { DialogHandler } = ChromeUtils.import(
|
|||
const { Domain } = ChromeUtils.import(
|
||||
"chrome://remote/content/domains/Domain.jsm"
|
||||
);
|
||||
const { UnsupportedError } = ChromeUtils.import(
|
||||
"chrome://remote/content/Error.jsm"
|
||||
);
|
||||
|
||||
class Page extends Domain {
|
||||
constructor(session) {
|
||||
|
@ -32,6 +35,88 @@ class Page extends Domain {
|
|||
|
||||
// commands
|
||||
|
||||
/**
|
||||
* Capture page screenshot.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {Viewport=} options.clip (not supported)
|
||||
* Capture the screenshot of a given region only.
|
||||
* @param {string=} options.format (not supported)
|
||||
* Image compression format. Defaults to "png".
|
||||
* @param {number=} options.quality (not supported)
|
||||
* Compression quality from range [0..100] (jpeg only). Defaults to 100.
|
||||
*
|
||||
* @return {string}
|
||||
* Base64-encoded image data.
|
||||
*/
|
||||
async captureScreenshot(options = {}) {
|
||||
if (options.clip) {
|
||||
throw new UnsupportedError("clip not supported");
|
||||
}
|
||||
if (options.format) {
|
||||
throw new UnsupportedError("format not supported");
|
||||
}
|
||||
if (options.fromSurface) {
|
||||
throw new UnsupportedError("fromSurface not supported");
|
||||
}
|
||||
if (options.quality) {
|
||||
throw new UnsupportedError("quality not supported");
|
||||
}
|
||||
|
||||
const MAX_CANVAS_DIMENSION = 32767;
|
||||
const MAX_CANVAS_AREA = 472907776;
|
||||
|
||||
// Retrieve the browsing context of the content browser
|
||||
const { browsingContext, window } = this.session.target;
|
||||
const scale = window.devicePixelRatio;
|
||||
|
||||
const rect = await this.executeInChild("_viewportRect");
|
||||
|
||||
let canvasWidth = rect.width * scale;
|
||||
let canvasHeight = rect.height * scale;
|
||||
|
||||
// Cap the screenshot size based on maximum allowed canvas sizes.
|
||||
// Using higher dimensions would trigger exceptions in Gecko.
|
||||
//
|
||||
// See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas#Maximum_canvas_size
|
||||
if (canvasWidth > MAX_CANVAS_DIMENSION) {
|
||||
rect.width = Math.floor(MAX_CANVAS_DIMENSION / scale);
|
||||
canvasWidth = rect.width * scale;
|
||||
}
|
||||
if (canvasHeight > MAX_CANVAS_DIMENSION) {
|
||||
rect.height = Math.floor(MAX_CANVAS_DIMENSION / scale);
|
||||
canvasHeight = rect.height * scale;
|
||||
}
|
||||
// If the area is larger, reduce the height to keep the full width.
|
||||
if (canvasWidth * canvasHeight > MAX_CANVAS_AREA) {
|
||||
rect.height = Math.floor(MAX_CANVAS_AREA / (canvasWidth * scale));
|
||||
canvasHeight = rect.height * scale;
|
||||
}
|
||||
|
||||
const snapshot = await browsingContext.currentWindowGlobal.drawSnapshot(
|
||||
rect,
|
||||
scale,
|
||||
"rgb(255,255,255)"
|
||||
);
|
||||
|
||||
const canvas = window.document.createElementNS(
|
||||
"http://www.w3.org/1999/xhtml",
|
||||
"canvas"
|
||||
);
|
||||
canvas.width = canvasWidth;
|
||||
canvas.height = canvasHeight;
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.drawImage(snapshot, 0, 0);
|
||||
|
||||
// Bug 1574935 - Huge dimensions can trigger an OOM because multiple copies
|
||||
// of the bitmap will exist in memory. Force the removal of the snapshot
|
||||
// because it is no longer needed.
|
||||
snapshot.close();
|
||||
|
||||
return canvas.toDataURL();
|
||||
}
|
||||
|
||||
async enable() {
|
||||
if (this.enabled) {
|
||||
return;
|
||||
|
|
|
@ -16,6 +16,7 @@ support-files =
|
|||
[browser_main_target.js]
|
||||
[browser_network_requestWillBeSent.js]
|
||||
[browser_page_bringToFront.js]
|
||||
[browser_page_captureScreenshot.js]
|
||||
[browser_page_frameNavigated.js]
|
||||
[browser_page_frameNavigated_iframe.js]
|
||||
[browser_page_javascriptDialog_alert.js]
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
async function getDevicePixelRatio() {
|
||||
return ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
|
||||
return content.devicePixelRatio;
|
||||
});
|
||||
}
|
||||
|
||||
async function getImageDetails(client, image) {
|
||||
return ContentTask.spawn(gBrowser.selectedBrowser, image, async function(
|
||||
image
|
||||
) {
|
||||
let infoPromise = new Promise(resolve => {
|
||||
const img = new content.Image();
|
||||
img.addEventListener(
|
||||
"load",
|
||||
() => {
|
||||
resolve({
|
||||
width: img.width,
|
||||
height: img.height,
|
||||
});
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
img.src = image;
|
||||
});
|
||||
return infoPromise;
|
||||
});
|
||||
}
|
||||
|
||||
async function getViewportRect() {
|
||||
return ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
|
||||
return {
|
||||
left: content.pageXOffset,
|
||||
top: content.pageYOffset,
|
||||
width: content.innerWidth,
|
||||
height: content.innerHeight,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function testScreenshotWithDocumentSmallerThanViewport() {
|
||||
const doc = toDataURL("<div>Hello world");
|
||||
const { client, tab } = await setupForURL(doc);
|
||||
|
||||
const { Page } = client;
|
||||
info("Check that captureScreenshot() captures the viewport by default");
|
||||
const screenshot = await Page.captureScreenshot();
|
||||
|
||||
const scale = await getDevicePixelRatio();
|
||||
const viewportRect = await getViewportRect();
|
||||
const { width, height } = await getImageDetails(client, screenshot);
|
||||
|
||||
is(width, (viewportRect.width - viewportRect.left) * scale);
|
||||
is(height, (viewportRect.height - viewportRect.top) * scale);
|
||||
|
||||
await client.close();
|
||||
ok(true, "The client is closed");
|
||||
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
|
||||
await RemoteAgent.close();
|
||||
});
|
||||
|
||||
add_task(async function testScreenshotWithDocumentLargerThanViewport() {
|
||||
const doc = toDataURL("<div style='margin: 100vh 100vw'>Hello world");
|
||||
const { client, tab } = await setupForURL(doc);
|
||||
|
||||
const { Page } = client;
|
||||
info("Check that captureScreenshot() captures the viewport by default");
|
||||
const screenshot = await Page.captureScreenshot();
|
||||
|
||||
const scale = await getDevicePixelRatio();
|
||||
const viewportRect = await getViewportRect();
|
||||
const { width, height } = await getImageDetails(client, screenshot);
|
||||
|
||||
is(width, (viewportRect.width - viewportRect.left) * scale);
|
||||
is(height, (viewportRect.height - viewportRect.top) * scale);
|
||||
|
||||
await client.close();
|
||||
ok(true, "The client is closed");
|
||||
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
|
||||
await RemoteAgent.close();
|
||||
});
|
Загрузка…
Ссылка в новой задаче