From e9d66535bae2d32525e3a784c283b036677b261c Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Wed, 6 Jul 2022 15:02:48 -0700 Subject: [PATCH] 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. --- browser_patches/firefox-beta/BUILD_NUMBER | 4 +- .../firefox-beta/juggler/content/PageAgent.js | 36 -------------- .../juggler/protocol/PageHandler.js | 48 ++++++++++++++++++- .../firefox-beta/juggler/protocol/Protocol.js | 2 +- browser_patches/firefox/BUILD_NUMBER | 4 +- .../firefox/juggler/content/PageAgent.js | 36 -------------- .../firefox/juggler/protocol/PageHandler.js | 48 ++++++++++++++++++- .../firefox/juggler/protocol/Protocol.js | 2 +- .../src/server/firefox/ffPage.ts | 2 - 9 files changed, 98 insertions(+), 84 deletions(-) diff --git a/browser_patches/firefox-beta/BUILD_NUMBER b/browser_patches/firefox-beta/BUILD_NUMBER index d1bec215b9..1be8652038 100644 --- a/browser_patches/firefox-beta/BUILD_NUMBER +++ b/browser_patches/firefox-beta/BUILD_NUMBER @@ -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 diff --git a/browser_patches/firefox-beta/juggler/content/PageAgent.js b/browser_patches/firefox-beta/juggler/content/PageAgent.js index 1fd9c0b05a..a4bf7e9205 100644 --- a/browser_patches/firefox-beta/juggler/content/PageAgent.js +++ b/browser_patches/firefox-beta/juggler/content/PageAgent.js @@ -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; diff --git a/browser_patches/firefox-beta/juggler/protocol/PageHandler.js b/browser_patches/firefox-beta/juggler/protocol/PageHandler.js index 4648476169..41878434bf 100644 --- a/browser_patches/firefox-beta/juggler/protocol/PageHandler.js +++ b/browser_patches/firefox-beta/juggler/protocol/PageHandler.js @@ -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) { diff --git a/browser_patches/firefox-beta/juggler/protocol/Protocol.js b/browser_patches/firefox-beta/juggler/protocol/Protocol.js index 90d4d99828..be0f15af0d 100644 --- a/browser_patches/firefox-beta/juggler/protocol/Protocol.js +++ b/browser_patches/firefox-beta/juggler/protocol/Protocol.js @@ -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: { diff --git a/browser_patches/firefox/BUILD_NUMBER b/browser_patches/firefox/BUILD_NUMBER index 4e7ef4d9d5..1be8652038 100644 --- a/browser_patches/firefox/BUILD_NUMBER +++ b/browser_patches/firefox/BUILD_NUMBER @@ -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 diff --git a/browser_patches/firefox/juggler/content/PageAgent.js b/browser_patches/firefox/juggler/content/PageAgent.js index 1fd9c0b05a..a4bf7e9205 100644 --- a/browser_patches/firefox/juggler/content/PageAgent.js +++ b/browser_patches/firefox/juggler/content/PageAgent.js @@ -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; diff --git a/browser_patches/firefox/juggler/protocol/PageHandler.js b/browser_patches/firefox/juggler/protocol/PageHandler.js index 4648476169..41878434bf 100644 --- a/browser_patches/firefox/juggler/protocol/PageHandler.js +++ b/browser_patches/firefox/juggler/protocol/PageHandler.js @@ -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) { diff --git a/browser_patches/firefox/juggler/protocol/Protocol.js b/browser_patches/firefox/juggler/protocol/Protocol.js index 90d4d99828..be0f15af0d 100644 --- a/browser_patches/firefox/juggler/protocol/Protocol.js +++ b/browser_patches/firefox/juggler/protocol/Protocol.js @@ -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: { diff --git a/packages/playwright-core/src/server/firefox/ffPage.ts b/packages/playwright-core/src/server/firefox/ffPage.ts index 5c2a4f6ffa..4e079fe07b 100644 --- a/packages/playwright-core/src/server/firefox/ffPage.ts +++ b/packages/playwright-core/src/server/firefox/ffPage.ts @@ -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'),