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/module-list.ts",
|
||||||
"lib/common/api/native-image.js",
|
"lib/common/api/native-image.js",
|
||||||
"lib/common/api/shell.js",
|
"lib/common/api/shell.js",
|
||||||
"lib/common/clipboard-utils.ts",
|
|
||||||
"lib/common/crash-reporter.js",
|
"lib/common/crash-reporter.js",
|
||||||
"lib/common/define-properties.ts",
|
"lib/common/define-properties.ts",
|
||||||
"lib/common/electron-binding-setup.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/web-view-methods.ts",
|
||||||
"lib/common/webpack-globals-provider.ts",
|
"lib/common/webpack-globals-provider.ts",
|
||||||
"lib/renderer/api/context-bridge.ts",
|
"lib/renderer/api/context-bridge.ts",
|
||||||
|
@ -268,14 +267,13 @@ auto_filenames = {
|
||||||
"lib/common/api/module-list.ts",
|
"lib/common/api/module-list.ts",
|
||||||
"lib/common/api/native-image.js",
|
"lib/common/api/native-image.js",
|
||||||
"lib/common/api/shell.js",
|
"lib/common/api/shell.js",
|
||||||
"lib/common/clipboard-utils.ts",
|
|
||||||
"lib/common/crash-reporter.js",
|
"lib/common/crash-reporter.js",
|
||||||
"lib/common/define-properties.ts",
|
"lib/common/define-properties.ts",
|
||||||
"lib/common/electron-binding-setup.ts",
|
"lib/common/electron-binding-setup.ts",
|
||||||
"lib/common/init.ts",
|
"lib/common/init.ts",
|
||||||
"lib/common/parse-features-string.js",
|
"lib/common/parse-features-string.js",
|
||||||
"lib/common/remote/type-utils.ts",
|
|
||||||
"lib/common/reset-search-paths.ts",
|
"lib/common/reset-search-paths.ts",
|
||||||
|
"lib/common/type-utils.ts",
|
||||||
"lib/common/web-view-methods.ts",
|
"lib/common/web-view-methods.ts",
|
||||||
"lib/common/webpack-globals-provider.ts",
|
"lib/common/webpack-globals-provider.ts",
|
||||||
"lib/renderer/ipc-renderer-internal-utils.ts",
|
"lib/renderer/ipc-renderer-internal-utils.ts",
|
||||||
|
@ -292,13 +290,12 @@ auto_filenames = {
|
||||||
"lib/common/api/module-list.ts",
|
"lib/common/api/module-list.ts",
|
||||||
"lib/common/api/native-image.js",
|
"lib/common/api/native-image.js",
|
||||||
"lib/common/api/shell.js",
|
"lib/common/api/shell.js",
|
||||||
"lib/common/clipboard-utils.ts",
|
|
||||||
"lib/common/crash-reporter.js",
|
"lib/common/crash-reporter.js",
|
||||||
"lib/common/define-properties.ts",
|
"lib/common/define-properties.ts",
|
||||||
"lib/common/electron-binding-setup.ts",
|
"lib/common/electron-binding-setup.ts",
|
||||||
"lib/common/init.ts",
|
"lib/common/init.ts",
|
||||||
"lib/common/remote/type-utils.ts",
|
|
||||||
"lib/common/reset-search-paths.ts",
|
"lib/common/reset-search-paths.ts",
|
||||||
|
"lib/common/type-utils.ts",
|
||||||
"lib/common/web-view-methods.ts",
|
"lib/common/web-view-methods.ts",
|
||||||
"lib/common/webpack-globals-provider.ts",
|
"lib/common/webpack-globals-provider.ts",
|
||||||
"lib/renderer/api/context-bridge.ts",
|
"lib/renderer/api/context-bridge.ts",
|
||||||
|
@ -342,13 +339,12 @@ auto_filenames = {
|
||||||
"lib/common/api/module-list.ts",
|
"lib/common/api/module-list.ts",
|
||||||
"lib/common/api/native-image.js",
|
"lib/common/api/native-image.js",
|
||||||
"lib/common/api/shell.js",
|
"lib/common/api/shell.js",
|
||||||
"lib/common/clipboard-utils.ts",
|
|
||||||
"lib/common/crash-reporter.js",
|
"lib/common/crash-reporter.js",
|
||||||
"lib/common/define-properties.ts",
|
"lib/common/define-properties.ts",
|
||||||
"lib/common/electron-binding-setup.ts",
|
"lib/common/electron-binding-setup.ts",
|
||||||
"lib/common/init.ts",
|
"lib/common/init.ts",
|
||||||
"lib/common/remote/type-utils.ts",
|
|
||||||
"lib/common/reset-search-paths.ts",
|
"lib/common/reset-search-paths.ts",
|
||||||
|
"lib/common/type-utils.ts",
|
||||||
"lib/common/webpack-globals-provider.ts",
|
"lib/common/webpack-globals-provider.ts",
|
||||||
"lib/renderer/api/context-bridge.ts",
|
"lib/renderer/api/context-bridge.ts",
|
||||||
"lib/renderer/api/crash-reporter.js",
|
"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 ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils')
|
||||||
const parseFeaturesString = require('@electron/internal/common/parse-features-string')
|
const parseFeaturesString = require('@electron/internal/common/parse-features-string')
|
||||||
const { syncMethods, asyncMethods } = require('@electron/internal/common/web-view-methods')
|
const { syncMethods, asyncMethods } = require('@electron/internal/common/web-view-methods')
|
||||||
|
const { serialize } = require('@electron/internal/common/type-utils')
|
||||||
|
|
||||||
// Doesn't exist in early initialization.
|
// Doesn't exist in early initialization.
|
||||||
let webViewManager = null
|
let webViewManager = null
|
||||||
|
@ -383,6 +384,12 @@ handleMessageSync('ELECTRON_GUEST_VIEW_MANAGER_CALL', function (event, guestInst
|
||||||
return guest[method](...args)
|
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.
|
// Returns WebContents from its guest id hosted in given webContents.
|
||||||
const getGuestForWebContents = function (guestInstanceId, contents) {
|
const getGuestForWebContents = function (guestInstanceId, contents) {
|
||||||
const guest = getGuest(guestInstanceId)
|
const guest = getGuest(guestInstanceId)
|
||||||
|
|
|
@ -4,7 +4,7 @@ import * as electron from 'electron'
|
||||||
import { EventEmitter } from 'events'
|
import { EventEmitter } from 'events'
|
||||||
import objectsRegistry from './objects-registry'
|
import objectsRegistry from './objects-registry'
|
||||||
import { ipcMainInternal } from '../ipc-main-internal'
|
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 v8Util = process.electronBinding('v8_util')
|
||||||
const eventBinding = process.electronBinding('event')
|
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 { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal')
|
||||||
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils')
|
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils')
|
||||||
const guestViewManager = require('@electron/internal/browser/guest-view-manager')
|
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 emitCustomEvent = function (contents, eventName, ...args) {
|
||||||
const event = eventBinding.createWithSender(contents)
|
const event = eventBinding.createWithSender(contents)
|
||||||
|
@ -62,7 +62,7 @@ ipcMainUtils.handleSync('ELECTRON_BROWSER_CLIPBOARD', function (event, method, .
|
||||||
throw new Error(`Invalid method: ${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()) {
|
if (features.isDesktopCapturerEnabled()) {
|
||||||
|
|
|
@ -4,13 +4,13 @@ const clipboard = process.electronBinding('clipboard')
|
||||||
|
|
||||||
if (process.type === 'renderer') {
|
if (process.type === 'renderer') {
|
||||||
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils')
|
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) {
|
const makeRemoteMethod = function (method) {
|
||||||
return (...args) => {
|
return (...args) => {
|
||||||
args = clipboardUtils.serialize(args)
|
args = typeUtils.serialize(args)
|
||||||
const result = ipcRendererUtils.invokeSync('ELECTRON_BROWSER_CLIPBOARD', method, ...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')
|
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 objectMap = function (source: Object, mapper: (value: any) => any) {
|
||||||
const sourceEntries = Object.entries(source)
|
const sourceEntries = Object.entries(source)
|
||||||
const targetEntries = sourceEntries.map(([key, val]) => [key, mapper(val)])
|
const targetEntries = sourceEntries.map(([key, val]) => [key, mapper(val)])
|
||||||
|
@ -15,7 +42,7 @@ export function serialize (value: any): any {
|
||||||
}
|
}
|
||||||
} else if (Array.isArray(value)) {
|
} else if (Array.isArray(value)) {
|
||||||
return value.map(serialize)
|
return value.map(serialize)
|
||||||
} else if (value instanceof Buffer) {
|
} else if (isSerializableObject(value)) {
|
||||||
return value
|
return value
|
||||||
} else if (value instanceof Object) {
|
} else if (value instanceof Object) {
|
||||||
return objectMap(value, serialize)
|
return objectMap(value, serialize)
|
||||||
|
@ -29,7 +56,7 @@ export function deserialize (value: any): any {
|
||||||
return nativeImage.createFromBitmap(value.buffer, value.size)
|
return nativeImage.createFromBitmap(value.buffer, value.size)
|
||||||
} else if (Array.isArray(value)) {
|
} else if (Array.isArray(value)) {
|
||||||
return value.map(deserialize)
|
return value.map(deserialize)
|
||||||
} else if (value instanceof Buffer) {
|
} else if (isSerializableObject(value)) {
|
||||||
return value
|
return value
|
||||||
} else if (value instanceof Object) {
|
} else if (value instanceof Object) {
|
||||||
return objectMap(value, deserialize)
|
return objectMap(value, deserialize)
|
|
@ -52,7 +52,6 @@ export const syncMethods = new Set([
|
||||||
|
|
||||||
export const asyncMethods = new Set([
|
export const asyncMethods = new Set([
|
||||||
'loadURL',
|
'loadURL',
|
||||||
'capturePage',
|
|
||||||
'executeJavaScript',
|
'executeJavaScript',
|
||||||
'insertCSS',
|
'insertCSS',
|
||||||
'insertText',
|
'insertText',
|
||||||
|
|
|
@ -4,7 +4,7 @@ const v8Util = process.electronBinding('v8_util')
|
||||||
const { hasSwitch } = process.electronBinding('command_line')
|
const { hasSwitch } = process.electronBinding('command_line')
|
||||||
|
|
||||||
const { CallbacksRegistry } = require('@electron/internal/renderer/remote/callbacks-registry')
|
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 { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal')
|
||||||
|
|
||||||
const callbacksRegistry = new CallbacksRegistry()
|
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 * 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 { WEB_VIEW_CONSTANTS } from '@electron/internal/renderer/web-view/web-view-constants'
|
||||||
import { syncMethods, asyncMethods } from '@electron/internal/common/web-view-methods'
|
import { syncMethods, asyncMethods } from '@electron/internal/common/web-view-methods'
|
||||||
|
import { deserialize } from '@electron/internal/common/type-utils'
|
||||||
const { webFrame } = electron
|
const { webFrame } = electron
|
||||||
|
|
||||||
const v8Util = process.electronBinding('v8_util')
|
const v8Util = process.electronBinding('v8_util')
|
||||||
|
@ -244,6 +245,10 @@ export const setupMethods = (WebViewElement: typeof ElectronInternal.WebViewElem
|
||||||
for (const method of asyncMethods) {
|
for (const method of asyncMethods) {
|
||||||
(WebViewElement.prototype as Record<string, any>)[method] = createNonBlockHandler(method)
|
(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 = {
|
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()', () => {
|
describe('<webview>.printToPDF()', () => {
|
||||||
before(function () {
|
before(function () {
|
||||||
if (!features.isPrintingEnabled()) {
|
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
|
// Created in web-view-impl
|
||||||
public getWebContentsId(): number;
|
public getWebContentsId(): number;
|
||||||
|
public capturePage(rect?: Electron.Rectangle): Promise<Electron.NativeImage>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue