browser(firefox): move screenshots to browser-side (#15230)

* `clip` option is always passed from the client code
* with this change, we can no longer capture screenshot of a blinking
  caret; the browser-side API doesn't have this capability.
This commit is contained in:
Andrey Lushnikov 2022-07-06 15:02:48 -07:00 committed by GitHub
parent 4e46ac2191
commit e9d66535ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 98 additions and 84 deletions

View File

@ -1,2 +1,2 @@
1334
Changed: lushnikov@chromium.org Wed Jul 6 01:35:56 MSK 2022
1335
Changed: lushnikov@chromium.org Wed Jul 6 20:30:28 MSK 2022

View File

@ -148,7 +148,6 @@ class PageAgent {
insertText: this._insertText.bind(this),
navigate: this._navigate.bind(this),
reload: this._reload.bind(this),
screenshot: this._screenshot.bind(this),
scrollIntoViewIfNeeded: this._scrollIntoViewIfNeeded.bind(this),
setCacheDisabled: this._setCacheDisabled.bind(this),
setFileInputFiles: this._setFileInputFiles.bind(this),
@ -520,16 +519,6 @@ class PageAgent {
return {x: x1, y: y1, width: x2 - x1, height: y2 - y1};
}
async _screenshot({mimeType, clip, omitDeviceScaleFactor}) {
const content = this._messageManager.content;
if (clip) {
const data = takeScreenshot(content, clip.x, clip.y, clip.width, clip.height, mimeType, omitDeviceScaleFactor);
return {data};
}
const data = takeScreenshot(content, content.scrollX, content.scrollY, content.innerWidth, content.innerHeight, mimeType, omitDeviceScaleFactor);
return {data};
}
async _dispatchKeyEvent({type, keyCode, code, key, repeat, location, text}) {
// key events don't fire if we are dragging.
if (this._dragging) {
@ -900,31 +889,6 @@ class PageAgent {
}
}
function takeScreenshot(win, left, top, width, height, mimeType, omitDeviceScaleFactor) {
const MAX_SKIA_DIMENSIONS = 32767;
// `win.devicePixelRatio` returns a non-overriden value to priveleged code.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1761032
// See https://phabricator.services.mozilla.com/D141323
const devicePixelRatio = win.browsingContext.overrideDPPX || win.devicePixelRatio;
const scale = omitDeviceScaleFactor ? 1 : devicePixelRatio;
const canvasWidth = width * scale;
const canvasHeight = height * scale;
if (canvasWidth > MAX_SKIA_DIMENSIONS || canvasHeight > MAX_SKIA_DIMENSIONS)
throw new Error('Cannot take screenshot larger than ' + MAX_SKIA_DIMENSIONS);
const canvas = win.document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas');
canvas.width = canvasWidth;
canvas.height = canvasHeight;
let ctx = canvas.getContext('2d');
ctx.scale(scale, scale);
ctx.drawWindow(win, left, top, width, height, 'rgb(255,255,255)', ctx.DRAWWINDOW_DRAW_CARET);
const dataURL = canvas.toDataURL(mimeType);
return dataURL.substring(dataURL.indexOf(',') + 1);
};
var EXPORTED_SYMBOLS = ['PageAgent'];
this.PageAgent = PageAgent;

View File

@ -8,6 +8,7 @@ const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
const {NetworkObserver, PageNetwork} = ChromeUtils.import('chrome://juggler/content/NetworkObserver.js');
const {PageTarget} = ChromeUtils.import('chrome://juggler/content/TargetRegistry.js');
const {setTimeout} = ChromeUtils.import('resource://gre/modules/Timer.jsm');
const Cc = Components.classes;
const Ci = Components.interfaces;
@ -302,8 +303,51 @@ class PageHandler {
return await this._contentPage.send('adoptNode', options);
}
async ['Page.screenshot'](options) {
return await this._contentPage.send('screenshot', options);
async ['Page.screenshot']({ mimeType, clip, omitDeviceScaleFactor }) {
const rect = new DOMRect(clip.x, clip.y, clip.width, clip.height);
const browsingContext = this._pageTarget.linkedBrowser().browsingContext;
// `win.devicePixelRatio` returns a non-overriden value to priveleged code.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1761032
// See https://phabricator.services.mozilla.com/D141323
const devicePixelRatio = browsingContext.overrideDPPX || this._pageTarget._window.devicePixelRatio;
const scale = omitDeviceScaleFactor ? 1 : devicePixelRatio;
const canvasWidth = rect.width * scale;
const canvasHeight = rect.height * scale;
const MAX_CANVAS_DIMENSIONS = 32767;
const MAX_CANVAS_AREA = 472907776;
if (canvasWidth > MAX_CANVAS_DIMENSIONS || canvasHeight > MAX_CANVAS_DIMENSIONS)
throw new Error('Cannot take screenshot larger than ' + MAX_CANVAS_DIMENSIONS);
if (canvasWidth * canvasHeight > MAX_CANVAS_AREA)
throw new Error('Cannot take screenshot with more than ' + MAX_CANVAS_AREA + ' pixels');
let snapshot;
while (!snapshot) {
try {
//TODO(fission): browsingContext will change in case of cross-group navigation.
snapshot = await browsingContext.currentWindowGlobal.drawSnapshot(
rect,
scale,
"rgb(255,255,255)"
);
} catch (e) {
// The currentWindowGlobal.drawSnapshot might throw
// NS_ERROR_LOSS_OF_SIGNIFICANT_DATA if called during navigation.
// wait a little and re-try.
await new Promise(x => setTimeout(x, 50));
}
}
const win = browsingContext.topChromeWindow.ownerGlobal;
const canvas = win.document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas');
canvas.width = canvasWidth;
canvas.height = canvasHeight;
let ctx = canvas.getContext('2d');
ctx.drawImage(snapshot, 0, 0);
snapshot.close();
const dataURL = canvas.toDataURL(mimeType);
return { data: dataURL.substring(dataURL.indexOf(',') + 1) };
}
async ['Page.getContentQuads'](options) {

View File

@ -857,7 +857,7 @@ const Page = {
'screenshot': {
params: {
mimeType: t.Enum(['image/png', 'image/jpeg']),
clip: t.Optional(pageTypes.Clip),
clip: pageTypes.Clip,
omitDeviceScaleFactor: t.Optional(t.Boolean),
},
returns: {

View File

@ -1,2 +1,2 @@
1334
Changed: lushnikov@chromium.org Wed Jul 6 00:25:34 MSK 2022
1335
Changed: lushnikov@chromium.org Wed Jul 6 20:30:28 MSK 2022

View File

@ -148,7 +148,6 @@ class PageAgent {
insertText: this._insertText.bind(this),
navigate: this._navigate.bind(this),
reload: this._reload.bind(this),
screenshot: this._screenshot.bind(this),
scrollIntoViewIfNeeded: this._scrollIntoViewIfNeeded.bind(this),
setCacheDisabled: this._setCacheDisabled.bind(this),
setFileInputFiles: this._setFileInputFiles.bind(this),
@ -520,16 +519,6 @@ class PageAgent {
return {x: x1, y: y1, width: x2 - x1, height: y2 - y1};
}
async _screenshot({mimeType, clip, omitDeviceScaleFactor}) {
const content = this._messageManager.content;
if (clip) {
const data = takeScreenshot(content, clip.x, clip.y, clip.width, clip.height, mimeType, omitDeviceScaleFactor);
return {data};
}
const data = takeScreenshot(content, content.scrollX, content.scrollY, content.innerWidth, content.innerHeight, mimeType, omitDeviceScaleFactor);
return {data};
}
async _dispatchKeyEvent({type, keyCode, code, key, repeat, location, text}) {
// key events don't fire if we are dragging.
if (this._dragging) {
@ -900,31 +889,6 @@ class PageAgent {
}
}
function takeScreenshot(win, left, top, width, height, mimeType, omitDeviceScaleFactor) {
const MAX_SKIA_DIMENSIONS = 32767;
// `win.devicePixelRatio` returns a non-overriden value to priveleged code.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1761032
// See https://phabricator.services.mozilla.com/D141323
const devicePixelRatio = win.browsingContext.overrideDPPX || win.devicePixelRatio;
const scale = omitDeviceScaleFactor ? 1 : devicePixelRatio;
const canvasWidth = width * scale;
const canvasHeight = height * scale;
if (canvasWidth > MAX_SKIA_DIMENSIONS || canvasHeight > MAX_SKIA_DIMENSIONS)
throw new Error('Cannot take screenshot larger than ' + MAX_SKIA_DIMENSIONS);
const canvas = win.document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas');
canvas.width = canvasWidth;
canvas.height = canvasHeight;
let ctx = canvas.getContext('2d');
ctx.scale(scale, scale);
ctx.drawWindow(win, left, top, width, height, 'rgb(255,255,255)', ctx.DRAWWINDOW_DRAW_CARET);
const dataURL = canvas.toDataURL(mimeType);
return dataURL.substring(dataURL.indexOf(',') + 1);
};
var EXPORTED_SYMBOLS = ['PageAgent'];
this.PageAgent = PageAgent;

View File

@ -8,6 +8,7 @@ const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
const {NetworkObserver, PageNetwork} = ChromeUtils.import('chrome://juggler/content/NetworkObserver.js');
const {PageTarget} = ChromeUtils.import('chrome://juggler/content/TargetRegistry.js');
const {setTimeout} = ChromeUtils.import('resource://gre/modules/Timer.jsm');
const Cc = Components.classes;
const Ci = Components.interfaces;
@ -302,8 +303,51 @@ class PageHandler {
return await this._contentPage.send('adoptNode', options);
}
async ['Page.screenshot'](options) {
return await this._contentPage.send('screenshot', options);
async ['Page.screenshot']({ mimeType, clip, omitDeviceScaleFactor }) {
const rect = new DOMRect(clip.x, clip.y, clip.width, clip.height);
const browsingContext = this._pageTarget.linkedBrowser().browsingContext;
// `win.devicePixelRatio` returns a non-overriden value to priveleged code.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1761032
// See https://phabricator.services.mozilla.com/D141323
const devicePixelRatio = browsingContext.overrideDPPX || this._pageTarget._window.devicePixelRatio;
const scale = omitDeviceScaleFactor ? 1 : devicePixelRatio;
const canvasWidth = rect.width * scale;
const canvasHeight = rect.height * scale;
const MAX_CANVAS_DIMENSIONS = 32767;
const MAX_CANVAS_AREA = 472907776;
if (canvasWidth > MAX_CANVAS_DIMENSIONS || canvasHeight > MAX_CANVAS_DIMENSIONS)
throw new Error('Cannot take screenshot larger than ' + MAX_CANVAS_DIMENSIONS);
if (canvasWidth * canvasHeight > MAX_CANVAS_AREA)
throw new Error('Cannot take screenshot with more than ' + MAX_CANVAS_AREA + ' pixels');
let snapshot;
while (!snapshot) {
try {
//TODO(fission): browsingContext will change in case of cross-group navigation.
snapshot = await browsingContext.currentWindowGlobal.drawSnapshot(
rect,
scale,
"rgb(255,255,255)"
);
} catch (e) {
// The currentWindowGlobal.drawSnapshot might throw
// NS_ERROR_LOSS_OF_SIGNIFICANT_DATA if called during navigation.
// wait a little and re-try.
await new Promise(x => setTimeout(x, 50));
}
}
const win = browsingContext.topChromeWindow.ownerGlobal;
const canvas = win.document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas');
canvas.width = canvasWidth;
canvas.height = canvasHeight;
let ctx = canvas.getContext('2d');
ctx.drawImage(snapshot, 0, 0);
snapshot.close();
const dataURL = canvas.toDataURL(mimeType);
return { data: dataURL.substring(dataURL.indexOf(',') + 1) };
}
async ['Page.getContentQuads'](options) {

View File

@ -857,7 +857,7 @@ const Page = {
'screenshot': {
params: {
mimeType: t.Enum(['image/png', 'image/jpeg']),
clip: t.Optional(pageTypes.Clip),
clip: pageTypes.Clip,
omitDeviceScaleFactor: t.Optional(t.Boolean),
},
returns: {

View File

@ -433,8 +433,6 @@ export class FFPage implements PageDelegate {
height: viewportRect!.height,
};
}
// TODO: remove fullPage option from Page.screenshot.
// TODO: remove Page.getBoundingBox method.
progress.throwIfAborted();
const { data } = await this._session.send('Page.screenshot', {
mimeType: ('image/' + format) as ('image/png' | 'image/jpeg'),