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:
Henrik Skupin 2019-10-16 19:36:56 +00:00
Родитель 2bf032d442
Коммит 3dce432d4f
4 изменённых файлов: 184 добавлений и 0 удалений

Просмотреть файл

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