Skip to content

Commit

Permalink
fix: NativeImage serialization of <webview>.capturePage() result (#21103
Browse files Browse the repository at this point in the history
)

* refactor: add Error to isSerializableObject() (#20886)

* fix: NativeImage serialization of <webview>.capturePage() result (#20825)
  • Loading branch information
miniak authored and codebytere committed Nov 14, 2019
1 parent 8d67f16 commit a62a367
Show file tree
Hide file tree
Showing 12 changed files with 67 additions and 48 deletions.
12 changes: 4 additions & 8 deletions filenames.auto.gni
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
7 changes: 7 additions & 0 deletions lib/browser/guest-view-manager.js
Expand Up @@ -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
Expand Down Expand Up @@ -387,6 +388,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)
Expand Down
2 changes: 1 addition & 1 deletion lib/browser/remote/server.ts
Expand Up @@ -5,7 +5,7 @@ import { EventEmitter } from 'events'
import objectsRegistry from './objects-registry'
import { ipcMainInternal } from '../ipc-main-internal'
import * as guestViewManager from '@electron/internal/browser/guest-view-manager'
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')
Expand Down
4 changes: 2 additions & 2 deletions lib/browser/rpc-server.js
Expand Up @@ -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)
Expand Down Expand Up @@ -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()) {
Expand Down
6 changes: 3 additions & 3 deletions lib/common/api/clipboard.js
Expand Up @@ -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)
}
}

Expand Down
25 changes: 0 additions & 25 deletions lib/common/remote/type-utils.ts

This file was deleted.

31 changes: 29 additions & 2 deletions lib/common/clipboard-utils.ts → 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)])
Expand All @@ -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)
Expand All @@ -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)
Expand Down
1 change: 0 additions & 1 deletion lib/common/web-view-methods.ts
Expand Up @@ -52,7 +52,6 @@ export const syncMethods = new Set([

export const asyncMethods = new Set([
'loadURL',
'capturePage',
'executeJavaScript',
'insertCSS',
'insertText',
Expand Down
7 changes: 1 addition & 6 deletions lib/renderer/api/remote.js
Expand Up @@ -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()
Expand Down Expand Up @@ -64,11 +64,6 @@ function wrapArgs (args, visited = new Set()) {
type: 'remote-object',
id: v8Util.getHiddenValue(value, 'atomId')
}
} else if (value instanceof Error) {
return {
type: 'value',
value
}
}

const meta = {
Expand Down
5 changes: 5 additions & 0 deletions lib/renderer/web-view/web-view-impl.ts
Expand Up @@ -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')
Expand Down Expand Up @@ -269,6 +270,10 @@ export const setupMethods = (WebViewElement: typeof ElectronInternal.WebViewElem
for (const method of asyncMethods) {
(WebViewElement.prototype as Record<string, any>)[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 = {
Expand Down
14 changes: 14 additions & 0 deletions spec/webview-spec.js
Expand Up @@ -1055,6 +1055,20 @@ describe('<webview> tag', function () {
})
})

describe('<webview>.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('<webview>.printToPDF()', () => {
before(function () {
if (!features.isPrintingEnabled()) {
Expand Down
1 change: 1 addition & 0 deletions typings/internal-electron.d.ts
Expand Up @@ -167,6 +167,7 @@ declare namespace ElectronInternal {
// Created in web-view-impl
public getWebContents(): Electron.WebContents;
public getWebContentsId(): number;
public capturePage(rect?: Electron.Rectangle): Promise<Electron.NativeImage>;
}
}

Expand Down

0 comments on commit a62a367

Please sign in to comment.