From 1d596f616d3c8ccdb428724e8cbdc0af1f4b1bf4 Mon Sep 17 00:00:00 2001 From: Milan Burda Date: Tue, 12 Nov 2019 21:56:17 +0100 Subject: [PATCH] fix: NativeImage serialization of .capturePage() result (#20825) --- filenames.auto.gni | 12 +++---- lib/browser/guest-view-manager.js | 7 +++++ lib/browser/remote/server.ts | 2 +- lib/browser/rpc-server.js | 4 +-- lib/common/api/clipboard.js | 6 ++-- lib/common/remote/type-utils.ts | 26 ---------------- .../{clipboard-utils.ts => type-utils.ts} | 31 +++++++++++++++++-- lib/common/web-view-methods.ts | 1 - lib/renderer/api/remote.js | 2 +- lib/renderer/web-view/web-view-impl.ts | 5 +++ spec/webview-spec.js | 14 +++++++++ typings/internal-electron.d.ts | 1 + 12 files changed, 67 insertions(+), 44 deletions(-) delete mode 100644 lib/common/remote/type-utils.ts rename lib/common/{clipboard-utils.ts => type-utils.ts} (62%) diff --git a/filenames.auto.gni b/filenames.auto.gni index 854d68d43939..1eee5335546e 100644 --- a/filenames.auto.gni +++ b/filenames.auto.gni @@ -135,11 +135,10 @@ auto_filenames = { "lib/common/api/module-list.ts", "lib/common/api/native-image.js", "lib/common/api/shell.js", - "lib/common/clipboard-utils.ts", "lib/common/crash-reporter.js", "lib/common/define-properties.ts", "lib/common/electron-binding-setup.ts", - "lib/common/remote/type-utils.ts", + "lib/common/type-utils.ts", "lib/common/web-view-methods.ts", "lib/common/webpack-globals-provider.ts", "lib/renderer/api/context-bridge.ts", @@ -268,14 +267,13 @@ auto_filenames = { "lib/common/api/module-list.ts", "lib/common/api/native-image.js", "lib/common/api/shell.js", - "lib/common/clipboard-utils.ts", "lib/common/crash-reporter.js", "lib/common/define-properties.ts", "lib/common/electron-binding-setup.ts", "lib/common/init.ts", "lib/common/parse-features-string.js", - "lib/common/remote/type-utils.ts", "lib/common/reset-search-paths.ts", + "lib/common/type-utils.ts", "lib/common/web-view-methods.ts", "lib/common/webpack-globals-provider.ts", "lib/renderer/ipc-renderer-internal-utils.ts", @@ -292,13 +290,12 @@ auto_filenames = { "lib/common/api/module-list.ts", "lib/common/api/native-image.js", "lib/common/api/shell.js", - "lib/common/clipboard-utils.ts", "lib/common/crash-reporter.js", "lib/common/define-properties.ts", "lib/common/electron-binding-setup.ts", "lib/common/init.ts", - "lib/common/remote/type-utils.ts", "lib/common/reset-search-paths.ts", + "lib/common/type-utils.ts", "lib/common/web-view-methods.ts", "lib/common/webpack-globals-provider.ts", "lib/renderer/api/context-bridge.ts", @@ -342,13 +339,12 @@ auto_filenames = { "lib/common/api/module-list.ts", "lib/common/api/native-image.js", "lib/common/api/shell.js", - "lib/common/clipboard-utils.ts", "lib/common/crash-reporter.js", "lib/common/define-properties.ts", "lib/common/electron-binding-setup.ts", "lib/common/init.ts", - "lib/common/remote/type-utils.ts", "lib/common/reset-search-paths.ts", + "lib/common/type-utils.ts", "lib/common/webpack-globals-provider.ts", "lib/renderer/api/context-bridge.ts", "lib/renderer/api/crash-reporter.js", diff --git a/lib/browser/guest-view-manager.js b/lib/browser/guest-view-manager.js index f55f071ed42e..272cdd663b42 100644 --- a/lib/browser/guest-view-manager.js +++ b/lib/browser/guest-view-manager.js @@ -5,6 +5,7 @@ const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-interna const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils') const parseFeaturesString = require('@electron/internal/common/parse-features-string') const { syncMethods, asyncMethods } = require('@electron/internal/common/web-view-methods') +const { serialize } = require('@electron/internal/common/type-utils') // Doesn't exist in early initialization. let webViewManager = null @@ -383,6 +384,12 @@ handleMessageSync('ELECTRON_GUEST_VIEW_MANAGER_CALL', function (event, guestInst return guest[method](...args) }) +handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CAPTURE_PAGE', async function (event, guestInstanceId, args) { + const guest = getGuestForWebContents(guestInstanceId, event.sender) + + return serialize(await guest.capturePage(...args)) +}) + // Returns WebContents from its guest id hosted in given webContents. const getGuestForWebContents = function (guestInstanceId, contents) { const guest = getGuest(guestInstanceId) diff --git a/lib/browser/remote/server.ts b/lib/browser/remote/server.ts index 5e0b511f23f9..4c49385c380d 100644 --- a/lib/browser/remote/server.ts +++ b/lib/browser/remote/server.ts @@ -4,7 +4,7 @@ import * as electron from 'electron' import { EventEmitter } from 'events' import objectsRegistry from './objects-registry' import { ipcMainInternal } from '../ipc-main-internal' -import { isPromise, isSerializableObject } from '@electron/internal/common/remote/type-utils' +import { isPromise, isSerializableObject } from '@electron/internal/common/type-utils' const v8Util = process.electronBinding('v8_util') const eventBinding = process.electronBinding('event') diff --git a/lib/browser/rpc-server.js b/lib/browser/rpc-server.js index 2dd61e05b489..cfcbaa09926c 100644 --- a/lib/browser/rpc-server.js +++ b/lib/browser/rpc-server.js @@ -11,7 +11,7 @@ const { crashReporterInit } = require('@electron/internal/browser/crash-reporter const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal') const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils') const guestViewManager = require('@electron/internal/browser/guest-view-manager') -const clipboardUtils = require('@electron/internal/common/clipboard-utils') +const typeUtils = require('@electron/internal/common/type-utils') const emitCustomEvent = function (contents, eventName, ...args) { const event = eventBinding.createWithSender(contents) @@ -62,7 +62,7 @@ ipcMainUtils.handleSync('ELECTRON_BROWSER_CLIPBOARD', function (event, method, . throw new Error(`Invalid method: ${method}`) } - return clipboardUtils.serialize(electron.clipboard[method](...clipboardUtils.deserialize(args))) + return typeUtils.serialize(electron.clipboard[method](...typeUtils.deserialize(args))) }) if (features.isDesktopCapturerEnabled()) { diff --git a/lib/common/api/clipboard.js b/lib/common/api/clipboard.js index 99b4644935ea..50763dd5a570 100644 --- a/lib/common/api/clipboard.js +++ b/lib/common/api/clipboard.js @@ -4,13 +4,13 @@ const clipboard = process.electronBinding('clipboard') if (process.type === 'renderer') { const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils') - const clipboardUtils = require('@electron/internal/common/clipboard-utils') + const typeUtils = require('@electron/internal/common/type-utils') const makeRemoteMethod = function (method) { return (...args) => { - args = clipboardUtils.serialize(args) + args = typeUtils.serialize(args) const result = ipcRendererUtils.invokeSync('ELECTRON_BROWSER_CLIPBOARD', method, ...args) - return clipboardUtils.deserialize(result) + return typeUtils.deserialize(result) } } diff --git a/lib/common/remote/type-utils.ts b/lib/common/remote/type-utils.ts deleted file mode 100644 index e90c64fcc4ac..000000000000 --- a/lib/common/remote/type-utils.ts +++ /dev/null @@ -1,26 +0,0 @@ -export function isPromise (val: any) { - return ( - val && - val.then && - val.then instanceof Function && - val.constructor && - val.constructor.reject && - val.constructor.reject instanceof Function && - val.constructor.resolve && - val.constructor.resolve instanceof Function - ) -} - -const serializableTypes = [ - Boolean, - Number, - String, - Date, - Error, - RegExp, - ArrayBuffer -] - -export function isSerializableObject (value: any) { - return value === null || ArrayBuffer.isView(value) || serializableTypes.some(type => value instanceof type) -} diff --git a/lib/common/clipboard-utils.ts b/lib/common/type-utils.ts similarity index 62% rename from lib/common/clipboard-utils.ts rename to lib/common/type-utils.ts index 166bf74be562..d5536db080af 100644 --- a/lib/common/clipboard-utils.ts +++ b/lib/common/type-utils.ts @@ -1,5 +1,32 @@ const { nativeImage, NativeImage } = process.electronBinding('native_image') +export function isPromise (val: any) { + return ( + val && + val.then && + val.then instanceof Function && + val.constructor && + val.constructor.reject && + val.constructor.reject instanceof Function && + val.constructor.resolve && + val.constructor.resolve instanceof Function + ) +} + +const serializableTypes = [ + Boolean, + Number, + String, + Date, + Error, + RegExp, + ArrayBuffer +] + +export function isSerializableObject (value: any) { + return value === null || ArrayBuffer.isView(value) || serializableTypes.some(type => value instanceof type) +} + const objectMap = function (source: Object, mapper: (value: any) => any) { const sourceEntries = Object.entries(source) const targetEntries = sourceEntries.map(([key, val]) => [key, mapper(val)]) @@ -15,7 +42,7 @@ export function serialize (value: any): any { } } else if (Array.isArray(value)) { return value.map(serialize) - } else if (value instanceof Buffer) { + } else if (isSerializableObject(value)) { return value } else if (value instanceof Object) { return objectMap(value, serialize) @@ -29,7 +56,7 @@ export function deserialize (value: any): any { return nativeImage.createFromBitmap(value.buffer, value.size) } else if (Array.isArray(value)) { return value.map(deserialize) - } else if (value instanceof Buffer) { + } else if (isSerializableObject(value)) { return value } else if (value instanceof Object) { return objectMap(value, deserialize) diff --git a/lib/common/web-view-methods.ts b/lib/common/web-view-methods.ts index f3b6c3c0de31..3ddd6e2fa08a 100644 --- a/lib/common/web-view-methods.ts +++ b/lib/common/web-view-methods.ts @@ -52,7 +52,6 @@ export const syncMethods = new Set([ export const asyncMethods = new Set([ 'loadURL', - 'capturePage', 'executeJavaScript', 'insertCSS', 'insertText', diff --git a/lib/renderer/api/remote.js b/lib/renderer/api/remote.js index c337d903391f..618233e5c0d4 100644 --- a/lib/renderer/api/remote.js +++ b/lib/renderer/api/remote.js @@ -4,7 +4,7 @@ const v8Util = process.electronBinding('v8_util') const { hasSwitch } = process.electronBinding('command_line') const { CallbacksRegistry } = require('@electron/internal/renderer/remote/callbacks-registry') -const { isPromise, isSerializableObject } = require('@electron/internal/common/remote/type-utils') +const { isPromise, isSerializableObject } = require('@electron/internal/common/type-utils') const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal') const callbacksRegistry = new CallbacksRegistry() diff --git a/lib/renderer/web-view/web-view-impl.ts b/lib/renderer/web-view/web-view-impl.ts index 5b25695e7357..40f67a54874d 100644 --- a/lib/renderer/web-view/web-view-impl.ts +++ b/lib/renderer/web-view/web-view-impl.ts @@ -5,6 +5,7 @@ import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-inte import * as guestViewInternal from '@electron/internal/renderer/web-view/guest-view-internal' import { WEB_VIEW_CONSTANTS } from '@electron/internal/renderer/web-view/web-view-constants' import { syncMethods, asyncMethods } from '@electron/internal/common/web-view-methods' +import { deserialize } from '@electron/internal/common/type-utils' const { webFrame } = electron const v8Util = process.electronBinding('v8_util') @@ -244,6 +245,10 @@ export const setupMethods = (WebViewElement: typeof ElectronInternal.WebViewElem for (const method of asyncMethods) { (WebViewElement.prototype as Record)[method] = createNonBlockHandler(method) } + + WebViewElement.prototype.capturePage = async function (...args) { + return deserialize(await ipcRendererInternal.invoke('ELECTRON_GUEST_VIEW_MANAGER_CAPTURE_PAGE', this.getWebContentsId(), args)) + } } export const webViewImplModule = { diff --git a/spec/webview-spec.js b/spec/webview-spec.js index add3e81e25da..e1a8139aca36 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -1022,6 +1022,20 @@ describe(' tag', function () { }) }) + describe('.capturePage()', () => { + it('returns a Promise with a NativeImage', async () => { + const src = 'data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E' + await loadWebView(webview, { src }) + + const image = await webview.capturePage() + const imgBuffer = image.toPNG() + + // Check the 25th byte in the PNG. + // Values can be 0,2,3,4, or 6. We want 6, which is RGB + Alpha + expect(imgBuffer[25]).to.equal(6) + }) + }) + describe('.printToPDF()', () => { before(function () { if (!features.isPrintingEnabled()) { diff --git a/typings/internal-electron.d.ts b/typings/internal-electron.d.ts index 7ee80c8ee207..0b9265da255e 100644 --- a/typings/internal-electron.d.ts +++ b/typings/internal-electron.d.ts @@ -162,6 +162,7 @@ declare namespace ElectronInternal { // Created in web-view-impl public getWebContentsId(): number; + public capturePage(rect?: Electron.Rectangle): Promise; } }