Skip to content

Commit 1d596f6

Browse files
miniakalexeykuzmin
authored andcommitted
fix: NativeImage serialization of <webview>.capturePage() result (electron#20825)
1 parent c0657a4 commit 1d596f6

File tree

12 files changed

+67
-44
lines changed

12 files changed

+67
-44
lines changed

filenames.auto.gni

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,10 @@ auto_filenames = {
135135
"lib/common/api/module-list.ts",
136136
"lib/common/api/native-image.js",
137137
"lib/common/api/shell.js",
138-
"lib/common/clipboard-utils.ts",
139138
"lib/common/crash-reporter.js",
140139
"lib/common/define-properties.ts",
141140
"lib/common/electron-binding-setup.ts",
142-
"lib/common/remote/type-utils.ts",
141+
"lib/common/type-utils.ts",
143142
"lib/common/web-view-methods.ts",
144143
"lib/common/webpack-globals-provider.ts",
145144
"lib/renderer/api/context-bridge.ts",
@@ -268,14 +267,13 @@ auto_filenames = {
268267
"lib/common/api/module-list.ts",
269268
"lib/common/api/native-image.js",
270269
"lib/common/api/shell.js",
271-
"lib/common/clipboard-utils.ts",
272270
"lib/common/crash-reporter.js",
273271
"lib/common/define-properties.ts",
274272
"lib/common/electron-binding-setup.ts",
275273
"lib/common/init.ts",
276274
"lib/common/parse-features-string.js",
277-
"lib/common/remote/type-utils.ts",
278275
"lib/common/reset-search-paths.ts",
276+
"lib/common/type-utils.ts",
279277
"lib/common/web-view-methods.ts",
280278
"lib/common/webpack-globals-provider.ts",
281279
"lib/renderer/ipc-renderer-internal-utils.ts",
@@ -292,13 +290,12 @@ auto_filenames = {
292290
"lib/common/api/module-list.ts",
293291
"lib/common/api/native-image.js",
294292
"lib/common/api/shell.js",
295-
"lib/common/clipboard-utils.ts",
296293
"lib/common/crash-reporter.js",
297294
"lib/common/define-properties.ts",
298295
"lib/common/electron-binding-setup.ts",
299296
"lib/common/init.ts",
300-
"lib/common/remote/type-utils.ts",
301297
"lib/common/reset-search-paths.ts",
298+
"lib/common/type-utils.ts",
302299
"lib/common/web-view-methods.ts",
303300
"lib/common/webpack-globals-provider.ts",
304301
"lib/renderer/api/context-bridge.ts",
@@ -342,13 +339,12 @@ auto_filenames = {
342339
"lib/common/api/module-list.ts",
343340
"lib/common/api/native-image.js",
344341
"lib/common/api/shell.js",
345-
"lib/common/clipboard-utils.ts",
346342
"lib/common/crash-reporter.js",
347343
"lib/common/define-properties.ts",
348344
"lib/common/electron-binding-setup.ts",
349345
"lib/common/init.ts",
350-
"lib/common/remote/type-utils.ts",
351346
"lib/common/reset-search-paths.ts",
347+
"lib/common/type-utils.ts",
352348
"lib/common/webpack-globals-provider.ts",
353349
"lib/renderer/api/context-bridge.ts",
354350
"lib/renderer/api/crash-reporter.js",

lib/browser/guest-view-manager.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-interna
55
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils')
66
const parseFeaturesString = require('@electron/internal/common/parse-features-string')
77
const { syncMethods, asyncMethods } = require('@electron/internal/common/web-view-methods')
8+
const { serialize } = require('@electron/internal/common/type-utils')
89

910
// Doesn't exist in early initialization.
1011
let webViewManager = null
@@ -383,6 +384,12 @@ handleMessageSync('ELECTRON_GUEST_VIEW_MANAGER_CALL', function (event, guestInst
383384
return guest[method](...args)
384385
})
385386

387+
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CAPTURE_PAGE', async function (event, guestInstanceId, args) {
388+
const guest = getGuestForWebContents(guestInstanceId, event.sender)
389+
390+
return serialize(await guest.capturePage(...args))
391+
})
392+
386393
// Returns WebContents from its guest id hosted in given webContents.
387394
const getGuestForWebContents = function (guestInstanceId, contents) {
388395
const guest = getGuest(guestInstanceId)

lib/browser/remote/server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as electron from 'electron'
44
import { EventEmitter } from 'events'
55
import objectsRegistry from './objects-registry'
66
import { ipcMainInternal } from '../ipc-main-internal'
7-
import { isPromise, isSerializableObject } from '@electron/internal/common/remote/type-utils'
7+
import { isPromise, isSerializableObject } from '@electron/internal/common/type-utils'
88

99
const v8Util = process.electronBinding('v8_util')
1010
const eventBinding = process.electronBinding('event')

lib/browser/rpc-server.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const { crashReporterInit } = require('@electron/internal/browser/crash-reporter
1111
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal')
1212
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils')
1313
const guestViewManager = require('@electron/internal/browser/guest-view-manager')
14-
const clipboardUtils = require('@electron/internal/common/clipboard-utils')
14+
const typeUtils = require('@electron/internal/common/type-utils')
1515

1616
const emitCustomEvent = function (contents, eventName, ...args) {
1717
const event = eventBinding.createWithSender(contents)
@@ -62,7 +62,7 @@ ipcMainUtils.handleSync('ELECTRON_BROWSER_CLIPBOARD', function (event, method, .
6262
throw new Error(`Invalid method: ${method}`)
6363
}
6464

65-
return clipboardUtils.serialize(electron.clipboard[method](...clipboardUtils.deserialize(args)))
65+
return typeUtils.serialize(electron.clipboard[method](...typeUtils.deserialize(args)))
6666
})
6767

6868
if (features.isDesktopCapturerEnabled()) {

lib/common/api/clipboard.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ const clipboard = process.electronBinding('clipboard')
44

55
if (process.type === 'renderer') {
66
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils')
7-
const clipboardUtils = require('@electron/internal/common/clipboard-utils')
7+
const typeUtils = require('@electron/internal/common/type-utils')
88

99
const makeRemoteMethod = function (method) {
1010
return (...args) => {
11-
args = clipboardUtils.serialize(args)
11+
args = typeUtils.serialize(args)
1212
const result = ipcRendererUtils.invokeSync('ELECTRON_BROWSER_CLIPBOARD', method, ...args)
13-
return clipboardUtils.deserialize(result)
13+
return typeUtils.deserialize(result)
1414
}
1515
}
1616

lib/common/remote/type-utils.ts

Lines changed: 0 additions & 26 deletions
This file was deleted.

lib/common/clipboard-utils.ts renamed to lib/common/type-utils.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,32 @@
11
const { nativeImage, NativeImage } = process.electronBinding('native_image')
22

3+
export function isPromise (val: any) {
4+
return (
5+
val &&
6+
val.then &&
7+
val.then instanceof Function &&
8+
val.constructor &&
9+
val.constructor.reject &&
10+
val.constructor.reject instanceof Function &&
11+
val.constructor.resolve &&
12+
val.constructor.resolve instanceof Function
13+
)
14+
}
15+
16+
const serializableTypes = [
17+
Boolean,
18+
Number,
19+
String,
20+
Date,
21+
Error,
22+
RegExp,
23+
ArrayBuffer
24+
]
25+
26+
export function isSerializableObject (value: any) {
27+
return value === null || ArrayBuffer.isView(value) || serializableTypes.some(type => value instanceof type)
28+
}
29+
330
const objectMap = function (source: Object, mapper: (value: any) => any) {
431
const sourceEntries = Object.entries(source)
532
const targetEntries = sourceEntries.map(([key, val]) => [key, mapper(val)])
@@ -15,7 +42,7 @@ export function serialize (value: any): any {
1542
}
1643
} else if (Array.isArray(value)) {
1744
return value.map(serialize)
18-
} else if (value instanceof Buffer) {
45+
} else if (isSerializableObject(value)) {
1946
return value
2047
} else if (value instanceof Object) {
2148
return objectMap(value, serialize)
@@ -29,7 +56,7 @@ export function deserialize (value: any): any {
2956
return nativeImage.createFromBitmap(value.buffer, value.size)
3057
} else if (Array.isArray(value)) {
3158
return value.map(deserialize)
32-
} else if (value instanceof Buffer) {
59+
} else if (isSerializableObject(value)) {
3360
return value
3461
} else if (value instanceof Object) {
3562
return objectMap(value, deserialize)

lib/common/web-view-methods.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ export const syncMethods = new Set([
5252

5353
export const asyncMethods = new Set([
5454
'loadURL',
55-
'capturePage',
5655
'executeJavaScript',
5756
'insertCSS',
5857
'insertText',

lib/renderer/api/remote.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const v8Util = process.electronBinding('v8_util')
44
const { hasSwitch } = process.electronBinding('command_line')
55

66
const { CallbacksRegistry } = require('@electron/internal/renderer/remote/callbacks-registry')
7-
const { isPromise, isSerializableObject } = require('@electron/internal/common/remote/type-utils')
7+
const { isPromise, isSerializableObject } = require('@electron/internal/common/type-utils')
88
const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal')
99

1010
const callbacksRegistry = new CallbacksRegistry()

lib/renderer/web-view/web-view-impl.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-inte
55
import * as guestViewInternal from '@electron/internal/renderer/web-view/guest-view-internal'
66
import { WEB_VIEW_CONSTANTS } from '@electron/internal/renderer/web-view/web-view-constants'
77
import { syncMethods, asyncMethods } from '@electron/internal/common/web-view-methods'
8+
import { deserialize } from '@electron/internal/common/type-utils'
89
const { webFrame } = electron
910

1011
const v8Util = process.electronBinding('v8_util')
@@ -244,6 +245,10 @@ export const setupMethods = (WebViewElement: typeof ElectronInternal.WebViewElem
244245
for (const method of asyncMethods) {
245246
(WebViewElement.prototype as Record<string, any>)[method] = createNonBlockHandler(method)
246247
}
248+
249+
WebViewElement.prototype.capturePage = async function (...args) {
250+
return deserialize(await ipcRendererInternal.invoke('ELECTRON_GUEST_VIEW_MANAGER_CAPTURE_PAGE', this.getWebContentsId(), args))
251+
}
247252
}
248253

249254
export const webViewImplModule = {

spec/webview-spec.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,6 +1022,20 @@ describe('<webview> tag', function () {
10221022
})
10231023
})
10241024

1025+
describe('<webview>.capturePage()', () => {
1026+
it('returns a Promise with a NativeImage', async () => {
1027+
const src = 'data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E'
1028+
await loadWebView(webview, { src })
1029+
1030+
const image = await webview.capturePage()
1031+
const imgBuffer = image.toPNG()
1032+
1033+
// Check the 25th byte in the PNG.
1034+
// Values can be 0,2,3,4, or 6. We want 6, which is RGB + Alpha
1035+
expect(imgBuffer[25]).to.equal(6)
1036+
})
1037+
})
1038+
10251039
describe('<webview>.printToPDF()', () => {
10261040
before(function () {
10271041
if (!features.isPrintingEnabled()) {

typings/internal-electron.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ declare namespace ElectronInternal {
162162

163163
// Created in web-view-impl
164164
public getWebContentsId(): number;
165+
public capturePage(rect?: Electron.Rectangle): Promise<Electron.NativeImage>;
165166
}
166167
}
167168

0 commit comments

Comments
 (0)