fix: NativeImage serialization of <webview>.capturePage() result (#20825)
This commit is contained in:
parent
c0657a4ca7
commit
1d596f616d
12 changed files with 67 additions and 44 deletions
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
|
@ -52,7 +52,6 @@ export const syncMethods = new Set([
|
|||
|
||||
export const asyncMethods = new Set([
|
||||
'loadURL',
|
||||
'capturePage',
|
||||
'executeJavaScript',
|
||||
'insertCSS',
|
||||
'insertText',
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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<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 = {
|
||||
|
|
|
@ -1022,6 +1022,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()) {
|
||||
|
|
1
typings/internal-electron.d.ts
vendored
1
typings/internal-electron.d.ts
vendored
|
@ -162,6 +162,7 @@ declare namespace ElectronInternal {
|
|||
|
||||
// Created in web-view-impl
|
||||
public getWebContentsId(): number;
|
||||
public capturePage(rect?: Electron.Rectangle): Promise<Electron.NativeImage>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue