feat: MessagePorts in the main process (#22404)
This commit is contained in:
parent
c4c0888972
commit
b4d07f76d3
34 changed files with 1316 additions and 113 deletions
|
@ -57,7 +57,7 @@ Removes all listeners, or those of the specified `channel`.
|
||||||
|
|
||||||
Send an asynchronous message to the main process via `channel`, along with
|
Send an asynchronous message to the main process via `channel`, along with
|
||||||
arguments. Arguments will be serialized with the [Structured Clone
|
arguments. Arguments will be serialized with the [Structured Clone
|
||||||
Algorithm][SCA], just like [`postMessage`][], so prototype chains will not be
|
Algorithm][SCA], just like [`window.postMessage`][], so prototype chains will not be
|
||||||
included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will
|
included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will
|
||||||
throw an exception.
|
throw an exception.
|
||||||
|
|
||||||
|
@ -68,6 +68,10 @@ throw an exception.
|
||||||
The main process handles it by listening for `channel` with the
|
The main process handles it by listening for `channel` with the
|
||||||
[`ipcMain`](ipc-main.md) module.
|
[`ipcMain`](ipc-main.md) module.
|
||||||
|
|
||||||
|
If you need to transfer a [`MessagePort`][] to the main process, use [`ipcRenderer.postMessage`](#ipcrendererpostmessagechannel-message-transfer).
|
||||||
|
|
||||||
|
If you want to receive a single response from the main process, like the result of a method call, consider using [`ipcRenderer.invoke`](#ipcrendererinvokechannel-args).
|
||||||
|
|
||||||
### `ipcRenderer.invoke(channel, ...args)`
|
### `ipcRenderer.invoke(channel, ...args)`
|
||||||
|
|
||||||
* `channel` String
|
* `channel` String
|
||||||
|
@ -77,7 +81,7 @@ Returns `Promise<any>` - Resolves with the response from the main process.
|
||||||
|
|
||||||
Send a message to the main process via `channel` and expect a result
|
Send a message to the main process via `channel` and expect a result
|
||||||
asynchronously. Arguments will be serialized with the [Structured Clone
|
asynchronously. Arguments will be serialized with the [Structured Clone
|
||||||
Algorithm][SCA], just like [`postMessage`][], so prototype chains will not be
|
Algorithm][SCA], just like [`window.postMessage`][], so prototype chains will not be
|
||||||
included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will
|
included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will
|
||||||
throw an exception.
|
throw an exception.
|
||||||
|
|
||||||
|
@ -102,6 +106,10 @@ ipcMain.handle('some-name', async (event, someArgument) => {
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you need to transfer a [`MessagePort`][] to the main process, use [`ipcRenderer.postMessage`](#ipcrendererpostmessagechannel-message-transfer).
|
||||||
|
|
||||||
|
If you do not need a respons to the message, consider using [`ipcRenderer.send`](#ipcrenderersendchannel-args).
|
||||||
|
|
||||||
### `ipcRenderer.sendSync(channel, ...args)`
|
### `ipcRenderer.sendSync(channel, ...args)`
|
||||||
|
|
||||||
* `channel` String
|
* `channel` String
|
||||||
|
@ -111,7 +119,7 @@ Returns `any` - The value sent back by the [`ipcMain`](ipc-main.md) handler.
|
||||||
|
|
||||||
Send a message to the main process via `channel` and expect a result
|
Send a message to the main process via `channel` and expect a result
|
||||||
synchronously. Arguments will be serialized with the [Structured Clone
|
synchronously. Arguments will be serialized with the [Structured Clone
|
||||||
Algorithm][SCA], just like [`postMessage`][], so prototype chains will not be
|
Algorithm][SCA], just like [`window.postMessage`][], so prototype chains will not be
|
||||||
included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will
|
included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will
|
||||||
throw an exception.
|
throw an exception.
|
||||||
|
|
||||||
|
@ -127,6 +135,35 @@ and replies by setting `event.returnValue`.
|
||||||
> last resort. It's much better to use the asynchronous version,
|
> last resort. It's much better to use the asynchronous version,
|
||||||
> [`invoke()`](ipc-renderer.md#ipcrendererinvokechannel-args).
|
> [`invoke()`](ipc-renderer.md#ipcrendererinvokechannel-args).
|
||||||
|
|
||||||
|
### `ipcRenderer.postMessage(channel, message, [transfer])`
|
||||||
|
|
||||||
|
* `channel` String
|
||||||
|
* `message` any
|
||||||
|
* `transfer` MessagePort[] (optional)
|
||||||
|
|
||||||
|
Send a message to the main process, optionally transferring ownership of zero
|
||||||
|
or more [`MessagePort`][] objects.
|
||||||
|
|
||||||
|
The transferred `MessagePort` objects will be available in the main process as
|
||||||
|
[`MessagePortMain`](message-port-main.md) objects by accessing the `ports`
|
||||||
|
property of the emitted event.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
```js
|
||||||
|
// Renderer process
|
||||||
|
const { port1, port2 } = new MessageChannel()
|
||||||
|
ipcRenderer.postMessage('port', { message: 'hello' }, [port1])
|
||||||
|
|
||||||
|
// Main process
|
||||||
|
ipcMain.on('port', (e, msg) => {
|
||||||
|
const [port] = e.ports
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
For more information on using `MessagePort` and `MessageChannel`, see the [MDN
|
||||||
|
documentation](https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel).
|
||||||
|
|
||||||
### `ipcRenderer.sendTo(webContentsId, channel, ...args)`
|
### `ipcRenderer.sendTo(webContentsId, channel, ...args)`
|
||||||
|
|
||||||
* `webContentsId` Number
|
* `webContentsId` Number
|
||||||
|
@ -150,4 +187,5 @@ in the [`ipc-renderer-event`](structures/ipc-renderer-event.md) structure docs.
|
||||||
|
|
||||||
[event-emitter]: https://nodejs.org/api/events.html#events_class_eventemitter
|
[event-emitter]: https://nodejs.org/api/events.html#events_class_eventemitter
|
||||||
[SCA]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
|
[SCA]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
|
||||||
[`postMessage`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
|
[`window.postMessage`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
|
||||||
|
[`MessagePort`]: https://developer.mozilla.org/en-US/docs/Web/API/MessagePort
|
||||||
|
|
30
docs/api/message-channel-main.md
Normal file
30
docs/api/message-channel-main.md
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# MessageChannelMain
|
||||||
|
|
||||||
|
`MessageChannelMain` is the main-process-side equivalent of the DOM
|
||||||
|
[`MessageChannel`][] object. Its singular function is to create a pair of
|
||||||
|
connected [`MessagePortMain`](message-port-main.md) objects.
|
||||||
|
|
||||||
|
See the [Channel Messaging API][] documentation for more information on using
|
||||||
|
channel messaging.
|
||||||
|
|
||||||
|
## Class: MessageChannelMain
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```js
|
||||||
|
const { port1, port2 } = new MessageChannelMain()
|
||||||
|
w.webContents.postMessage('port', null, [port2])
|
||||||
|
port1.postMessage({ some: 'message' })
|
||||||
|
```
|
||||||
|
|
||||||
|
### Instance Properties
|
||||||
|
|
||||||
|
#### `channel.port1`
|
||||||
|
|
||||||
|
A [`MessagePortMain`](message-port-main.md) property.
|
||||||
|
|
||||||
|
#### `channel.port2`
|
||||||
|
|
||||||
|
A [`MessagePortMain`](message-port-main.md) property.
|
||||||
|
|
||||||
|
[`MessageChannel`]: https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel
|
||||||
|
[Channel Messaging API]: https://developer.mozilla.org/en-US/docs/Web/API/Channel_Messaging_API
|
49
docs/api/message-port-main.md
Normal file
49
docs/api/message-port-main.md
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
# MessagePortMain
|
||||||
|
|
||||||
|
`MessagePortMain` is the main-process-side equivalent of the DOM
|
||||||
|
[`MessagePort`][] object. It behaves similarly to the DOM version, with the
|
||||||
|
exception that it uses the Node.js `EventEmitter` event system, instead of the
|
||||||
|
DOM `EventTarget` system. This means you should use `port.on('message', ...)`
|
||||||
|
to listen for events, instead of `port.onmessage = ...` or
|
||||||
|
`port.addEventListener('message', ...)`
|
||||||
|
|
||||||
|
See the [Channel Messaging API][] documentation for more information on using
|
||||||
|
channel messaging.
|
||||||
|
|
||||||
|
`MessagePortMain` is an [EventEmitter][event-emitter].
|
||||||
|
|
||||||
|
## Class: MessagePortMain
|
||||||
|
|
||||||
|
### Instance Methods
|
||||||
|
|
||||||
|
#### `port.postMessage(message, [transfer])`
|
||||||
|
|
||||||
|
* `message` any
|
||||||
|
* `transfer` MessagePortMain[] (optional)
|
||||||
|
|
||||||
|
Sends a message from the port, and optionally, transfers ownership of objects
|
||||||
|
to other browsing contexts.
|
||||||
|
|
||||||
|
#### `port.start()`
|
||||||
|
|
||||||
|
Starts the sending of messages queued on the port. Messages will be queued
|
||||||
|
until this method is called.
|
||||||
|
|
||||||
|
#### `port.close()`
|
||||||
|
|
||||||
|
Disconnects the port, so it is no longer active.
|
||||||
|
|
||||||
|
### Instance Events
|
||||||
|
|
||||||
|
#### Event: 'message'
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
* `messageEvent` Object
|
||||||
|
* `data` any
|
||||||
|
* `ports` MessagePortMain[]
|
||||||
|
|
||||||
|
Emitted when a MessagePortMain object receives a message.
|
||||||
|
|
||||||
|
[`MessagePort`]: https://developer.mozilla.org/en-US/docs/Web/API/MessagePort
|
||||||
|
[Channel Messaging API]: https://developer.mozilla.org/en-US/docs/Web/API/Channel_Messaging_API
|
|
@ -3,6 +3,7 @@
|
||||||
* `frameId` Integer - The ID of the renderer frame that sent this message
|
* `frameId` Integer - The ID of the renderer frame that sent this message
|
||||||
* `returnValue` any - Set this to the value to be returned in a synchronous message
|
* `returnValue` any - Set this to the value to be returned in a synchronous message
|
||||||
* `sender` WebContents - Returns the `webContents` that sent the message
|
* `sender` WebContents - Returns the `webContents` that sent the message
|
||||||
|
* `ports` MessagePortMain[] - A list of MessagePorts that were transferred with this message
|
||||||
* `reply` Function - A function that will send an IPC message to the renderer frame that sent the original message that you are currently handling. You should use this method to "reply" to the sent message in order to guarantee the reply will go to the correct process and frame.
|
* `reply` Function - A function that will send an IPC message to the renderer frame that sent the original message that you are currently handling. You should use this method to "reply" to the sent message in order to guarantee the reply will go to the correct process and frame.
|
||||||
* `channel` String
|
* `channel` String
|
||||||
* `...args` any[]
|
* `...args` any[]
|
||||||
|
|
|
@ -2,5 +2,6 @@
|
||||||
|
|
||||||
* `sender` IpcRenderer - The `IpcRenderer` instance that emitted the event originally
|
* `sender` IpcRenderer - The `IpcRenderer` instance that emitted the event originally
|
||||||
* `senderId` Integer - The `webContents.id` that sent the message, you can call `event.sender.sendTo(event.senderId, ...)` to reply to the message, see [ipcRenderer.sendTo][ipc-renderer-sendto] for more information. This only applies to messages sent from a different renderer. Messages sent directly from the main process set `event.senderId` to `0`.
|
* `senderId` Integer - The `webContents.id` that sent the message, you can call `event.sender.sendTo(event.senderId, ...)` to reply to the message, see [ipcRenderer.sendTo][ipc-renderer-sendto] for more information. This only applies to messages sent from a different renderer. Messages sent directly from the main process set `event.senderId` to `0`.
|
||||||
|
* `ports` MessagePort[] - A list of MessagePorts that were transferred with this message
|
||||||
|
|
||||||
[ipc-renderer-sendto]: #ipcrenderersendtowindowid-channel--arg1-arg2-
|
[ipc-renderer-sendto]: #ipcrenderersendtowindowid-channel--arg1-arg2-
|
||||||
|
|
|
@ -1593,6 +1593,32 @@ ipcMain.on('ping', (event) => {
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### `contents.postMessage(channel, message, [transfer])`
|
||||||
|
|
||||||
|
* `channel` String
|
||||||
|
* `message` any
|
||||||
|
* `transfer` MessagePortMain[] (optional)
|
||||||
|
|
||||||
|
Send a message to the renderer process, optionally transferring ownership of
|
||||||
|
zero or more [`MessagePortMain`][] objects.
|
||||||
|
|
||||||
|
The transferred `MessagePortMain` objects will be available in the renderer
|
||||||
|
process by accessing the `ports` property of the emitted event. When they
|
||||||
|
arrive in the renderer, they will be native DOM `MessagePort` objects.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
```js
|
||||||
|
// Main process
|
||||||
|
const { port1, port2 } = new MessageChannelMain()
|
||||||
|
webContents.postMessage('port', { message: 'hello' }, [port1])
|
||||||
|
|
||||||
|
// Renderer process
|
||||||
|
ipcRenderer.on('port', (e, msg) => {
|
||||||
|
const [port] = e.ports
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
#### `contents.enableDeviceEmulation(parameters)`
|
#### `contents.enableDeviceEmulation(parameters)`
|
||||||
|
|
||||||
* `parameters` Object
|
* `parameters` Object
|
||||||
|
|
|
@ -32,6 +32,8 @@ auto_filenames = {
|
||||||
"docs/api/locales.md",
|
"docs/api/locales.md",
|
||||||
"docs/api/menu-item.md",
|
"docs/api/menu-item.md",
|
||||||
"docs/api/menu.md",
|
"docs/api/menu.md",
|
||||||
|
"docs/api/message-channel-main.md",
|
||||||
|
"docs/api/message-port-main.md",
|
||||||
"docs/api/modernization",
|
"docs/api/modernization",
|
||||||
"docs/api/native-image.md",
|
"docs/api/native-image.md",
|
||||||
"docs/api/native-theme.md",
|
"docs/api/native-theme.md",
|
||||||
|
@ -224,6 +226,7 @@ auto_filenames = {
|
||||||
"lib/browser/api/menu-item.js",
|
"lib/browser/api/menu-item.js",
|
||||||
"lib/browser/api/menu-utils.js",
|
"lib/browser/api/menu-utils.js",
|
||||||
"lib/browser/api/menu.js",
|
"lib/browser/api/menu.js",
|
||||||
|
"lib/browser/api/message-channel.ts",
|
||||||
"lib/browser/api/module-list.ts",
|
"lib/browser/api/module-list.ts",
|
||||||
"lib/browser/api/native-theme.ts",
|
"lib/browser/api/native-theme.ts",
|
||||||
"lib/browser/api/net-log.js",
|
"lib/browser/api/net-log.js",
|
||||||
|
@ -260,6 +263,7 @@ auto_filenames = {
|
||||||
"lib/browser/ipc-main-impl.ts",
|
"lib/browser/ipc-main-impl.ts",
|
||||||
"lib/browser/ipc-main-internal-utils.ts",
|
"lib/browser/ipc-main-internal-utils.ts",
|
||||||
"lib/browser/ipc-main-internal.ts",
|
"lib/browser/ipc-main-internal.ts",
|
||||||
|
"lib/browser/message-port-main.ts",
|
||||||
"lib/browser/navigation-controller.js",
|
"lib/browser/navigation-controller.js",
|
||||||
"lib/browser/remote/objects-registry.ts",
|
"lib/browser/remote/objects-registry.ts",
|
||||||
"lib/browser/remote/server.ts",
|
"lib/browser/remote/server.ts",
|
||||||
|
|
|
@ -121,6 +121,8 @@ filenames = {
|
||||||
"shell/browser/api/gpu_info_enumerator.h",
|
"shell/browser/api/gpu_info_enumerator.h",
|
||||||
"shell/browser/api/gpuinfo_manager.cc",
|
"shell/browser/api/gpuinfo_manager.cc",
|
||||||
"shell/browser/api/gpuinfo_manager.h",
|
"shell/browser/api/gpuinfo_manager.h",
|
||||||
|
"shell/browser/api/message_port.cc",
|
||||||
|
"shell/browser/api/message_port.h",
|
||||||
"shell/browser/api/process_metric.cc",
|
"shell/browser/api/process_metric.cc",
|
||||||
"shell/browser/api/process_metric.h",
|
"shell/browser/api/process_metric.h",
|
||||||
"shell/browser/api/save_page_handler.cc",
|
"shell/browser/api/save_page_handler.cc",
|
||||||
|
@ -513,6 +515,7 @@ filenames = {
|
||||||
"shell/common/gin_helper/event_emitter_caller.h",
|
"shell/common/gin_helper/event_emitter_caller.h",
|
||||||
"shell/common/gin_helper/function_template.cc",
|
"shell/common/gin_helper/function_template.cc",
|
||||||
"shell/common/gin_helper/function_template.h",
|
"shell/common/gin_helper/function_template.h",
|
||||||
|
"shell/common/gin_helper/function_template_extensions.h",
|
||||||
"shell/common/gin_helper/locker.cc",
|
"shell/common/gin_helper/locker.cc",
|
||||||
"shell/common/gin_helper/locker.h",
|
"shell/common/gin_helper/locker.h",
|
||||||
"shell/common/gin_helper/object_template_builder.cc",
|
"shell/common/gin_helper/object_template_builder.cc",
|
||||||
|
@ -558,6 +561,8 @@ filenames = {
|
||||||
"shell/common/skia_util.h",
|
"shell/common/skia_util.h",
|
||||||
"shell/common/v8_value_converter.cc",
|
"shell/common/v8_value_converter.cc",
|
||||||
"shell/common/v8_value_converter.h",
|
"shell/common/v8_value_converter.h",
|
||||||
|
"shell/common/v8_value_serializer.cc",
|
||||||
|
"shell/common/v8_value_serializer.h",
|
||||||
"shell/common/world_ids.h",
|
"shell/common/world_ids.h",
|
||||||
"shell/renderer/api/context_bridge/object_cache.cc",
|
"shell/renderer/api/context_bridge/object_cache.cc",
|
||||||
"shell/renderer/api/context_bridge/object_cache.h",
|
"shell/renderer/api/context_bridge/object_cache.h",
|
||||||
|
|
12
lib/browser/api/message-channel.ts
Normal file
12
lib/browser/api/message-channel.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { MessagePortMain } from '@electron/internal/browser/message-port-main'
|
||||||
|
const { createPair } = process.electronBinding('message_port')
|
||||||
|
|
||||||
|
export default class MessageChannelMain {
|
||||||
|
port1: MessagePortMain;
|
||||||
|
port2: MessagePortMain;
|
||||||
|
constructor () {
|
||||||
|
const { port1, port2 } = createPair()
|
||||||
|
this.port1 = new MessagePortMain(port1)
|
||||||
|
this.port2 = new MessagePortMain(port2)
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,7 @@ module.exports = [
|
||||||
{ name: 'nativeTheme' },
|
{ name: 'nativeTheme' },
|
||||||
{ name: 'net' },
|
{ name: 'net' },
|
||||||
{ name: 'netLog' },
|
{ name: 'netLog' },
|
||||||
|
{ name: 'MessageChannelMain' },
|
||||||
{ name: 'Notification' },
|
{ name: 'Notification' },
|
||||||
{ name: 'powerMonitor' },
|
{ name: 'powerMonitor' },
|
||||||
{ name: 'powerSaveBlocker' },
|
{ name: 'powerSaveBlocker' },
|
||||||
|
|
|
@ -16,6 +16,7 @@ export const browserModuleList: ElectronInternal.ModuleEntry[] = [
|
||||||
{ name: 'inAppPurchase', loader: () => require('./in-app-purchase') },
|
{ name: 'inAppPurchase', loader: () => require('./in-app-purchase') },
|
||||||
{ name: 'Menu', loader: () => require('./menu') },
|
{ name: 'Menu', loader: () => require('./menu') },
|
||||||
{ name: 'MenuItem', loader: () => require('./menu-item') },
|
{ name: 'MenuItem', loader: () => require('./menu-item') },
|
||||||
|
{ name: 'MessageChannelMain', loader: () => require('./message-channel') },
|
||||||
{ name: 'nativeTheme', loader: () => require('./native-theme') },
|
{ name: 'nativeTheme', loader: () => require('./native-theme') },
|
||||||
{ name: 'net', loader: () => require('./net') },
|
{ name: 'net', loader: () => require('./net') },
|
||||||
{ name: 'netLog', loader: () => require('./net-log') },
|
{ name: 'netLog', loader: () => require('./net-log') },
|
||||||
|
|
|
@ -11,6 +11,7 @@ const { internalWindowOpen } = require('@electron/internal/browser/guest-window-
|
||||||
const NavigationController = require('@electron/internal/browser/navigation-controller')
|
const NavigationController = require('@electron/internal/browser/navigation-controller')
|
||||||
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 { MessagePortMain } = require('@electron/internal/browser/message-port-main')
|
||||||
|
|
||||||
// session is not used here, the purpose is to make sure session is initalized
|
// session is not used here, the purpose is to make sure session is initalized
|
||||||
// before the webContents module.
|
// before the webContents module.
|
||||||
|
@ -115,6 +116,13 @@ WebContents.prototype.send = function (channel, ...args) {
|
||||||
return this._send(internal, sendToAll, channel, args)
|
return this._send(internal, sendToAll, channel, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WebContents.prototype.postMessage = function (...args) {
|
||||||
|
if (Array.isArray(args[2])) {
|
||||||
|
args[2] = args[2].map(o => o instanceof MessagePortMain ? o._internalPort : o)
|
||||||
|
}
|
||||||
|
this._postMessage(...args)
|
||||||
|
}
|
||||||
|
|
||||||
WebContents.prototype.sendToAll = function (channel, ...args) {
|
WebContents.prototype.sendToAll = function (channel, ...args) {
|
||||||
if (typeof channel !== 'string') {
|
if (typeof channel !== 'string') {
|
||||||
throw new Error('Missing required channel argument')
|
throw new Error('Missing required channel argument')
|
||||||
|
@ -472,6 +480,11 @@ WebContents.prototype._init = function () {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.on('-ipc-ports', function (event, internal, channel, message, ports) {
|
||||||
|
event.ports = ports.map(p => new MessagePortMain(p))
|
||||||
|
ipcMain.emit(channel, event, message)
|
||||||
|
})
|
||||||
|
|
||||||
// Handle context menu action request from pepper plugin.
|
// Handle context menu action request from pepper plugin.
|
||||||
this.on('pepper-context-menu', function (event, params, callback) {
|
this.on('pepper-context-menu', function (event, params, callback) {
|
||||||
// Access Menu via electron.Menu to prevent circular require.
|
// Access Menu via electron.Menu to prevent circular require.
|
||||||
|
|
25
lib/browser/message-port-main.ts
Normal file
25
lib/browser/message-port-main.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { EventEmitter } from 'events'
|
||||||
|
|
||||||
|
export class MessagePortMain extends EventEmitter {
|
||||||
|
_internalPort: any
|
||||||
|
constructor (internalPort: any) {
|
||||||
|
super()
|
||||||
|
this._internalPort = internalPort
|
||||||
|
this._internalPort.emit = (channel: string, event: {ports: any[]}) => {
|
||||||
|
if (channel === 'message') { event = { ...event, ports: event.ports.map(p => new MessagePortMain(p)) } }
|
||||||
|
this.emit(channel, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
start () {
|
||||||
|
return this._internalPort.start()
|
||||||
|
}
|
||||||
|
close () {
|
||||||
|
return this._internalPort.close()
|
||||||
|
}
|
||||||
|
postMessage (...args: any[]) {
|
||||||
|
if (Array.isArray(args[1])) {
|
||||||
|
args[1] = args[1].map((o: any) => o instanceof MessagePortMain ? o._internalPort : o)
|
||||||
|
}
|
||||||
|
return this._internalPort.postMessage(...args)
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,4 +29,8 @@ ipcRenderer.invoke = async function (channel, ...args) {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ipcRenderer.postMessage = function (channel: string, message: any, transferables: any) {
|
||||||
|
return ipc.postMessage(channel, message, transferables)
|
||||||
|
}
|
||||||
|
|
||||||
export default ipcRenderer
|
export default ipcRenderer
|
||||||
|
|
|
@ -45,9 +45,9 @@ v8Util.setHiddenValue(global, 'ipc', ipcEmitter)
|
||||||
v8Util.setHiddenValue(global, 'ipc-internal', ipcInternalEmitter)
|
v8Util.setHiddenValue(global, 'ipc-internal', ipcInternalEmitter)
|
||||||
|
|
||||||
v8Util.setHiddenValue(global, 'ipcNative', {
|
v8Util.setHiddenValue(global, 'ipcNative', {
|
||||||
onMessage (internal: boolean, channel: string, args: any[], senderId: number) {
|
onMessage (internal: boolean, channel: string, ports: any[], args: any[], senderId: number) {
|
||||||
const sender = internal ? ipcInternalEmitter : ipcEmitter
|
const sender = internal ? ipcInternalEmitter : ipcEmitter
|
||||||
sender.emit(channel, { sender, senderId }, ...args)
|
sender.emit(channel, { sender, senderId, ports }, ...args)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -54,9 +54,9 @@ const loadedModules = new Map([
|
||||||
// ElectronApiServiceImpl will look for the "ipcNative" hidden object when
|
// ElectronApiServiceImpl will look for the "ipcNative" hidden object when
|
||||||
// invoking the 'onMessage' callback.
|
// invoking the 'onMessage' callback.
|
||||||
v8Util.setHiddenValue(global, 'ipcNative', {
|
v8Util.setHiddenValue(global, 'ipcNative', {
|
||||||
onMessage (internal, channel, args, senderId) {
|
onMessage (internal, channel, ports, args, senderId) {
|
||||||
const sender = internal ? ipcRendererInternal : electron.ipcRenderer
|
const sender = internal ? ipcRendererInternal : electron.ipcRenderer
|
||||||
sender.emit(channel, { sender, senderId }, ...args)
|
sender.emit(channel, { sender, senderId, ports }, ...args)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -73,6 +73,7 @@ expose_setuseragent_on_networkcontext.patch
|
||||||
feat_add_set_theme_source_to_allow_apps_to.patch
|
feat_add_set_theme_source_to_allow_apps_to.patch
|
||||||
revert_cleanup_remove_menu_subtitles_sublabels.patch
|
revert_cleanup_remove_menu_subtitles_sublabels.patch
|
||||||
export_fetchapi_mojo_traits_to_fix_component_build.patch
|
export_fetchapi_mojo_traits_to_fix_component_build.patch
|
||||||
|
add_webmessageportconverter_entangleandinjectmessageportchannel.patch
|
||||||
revert_remove_contentrendererclient_shouldfork.patch
|
revert_remove_contentrendererclient_shouldfork.patch
|
||||||
ignore_rc_check.patch
|
ignore_rc_check.patch
|
||||||
remove_usage_of_incognito_apis_in_the_spellchecker.patch
|
remove_usage_of_incognito_apis_in_the_spellchecker.patch
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Jeremy Apthorp <nornagon@nornagon.net>
|
||||||
|
Date: Fri, 25 Oct 2019 11:23:03 -0700
|
||||||
|
Subject: add WebMessagePortConverter::EntangleAndInjectMessagePortChannel
|
||||||
|
|
||||||
|
This adds a method to the public Blink API that would otherwise require
|
||||||
|
accessing Blink internals. Its inverse, which already exists, is used in
|
||||||
|
Android WebView.
|
||||||
|
|
||||||
|
diff --git a/third_party/blink/public/web/web_message_port_converter.h b/third_party/blink/public/web/web_message_port_converter.h
|
||||||
|
index ad603aa7c557bfd4f571a541d70e2edf9ae757d9..d4b0bf8f5e8f3af9328b0099b65d9963414dfcc1 100644
|
||||||
|
--- a/third_party/blink/public/web/web_message_port_converter.h
|
||||||
|
+++ b/third_party/blink/public/web/web_message_port_converter.h
|
||||||
|
@@ -13,6 +13,7 @@ class Isolate;
|
||||||
|
template <class T>
|
||||||
|
class Local;
|
||||||
|
class Value;
|
||||||
|
+class Context;
|
||||||
|
} // namespace v8
|
||||||
|
|
||||||
|
namespace blink {
|
||||||
|
@@ -25,6 +26,9 @@ class WebMessagePortConverter {
|
||||||
|
// neutered, it will return nullopt.
|
||||||
|
BLINK_EXPORT static base::Optional<MessagePortChannel>
|
||||||
|
DisentangleAndExtractMessagePortChannel(v8::Isolate*, v8::Local<v8::Value>);
|
||||||
|
+
|
||||||
|
+ BLINK_EXPORT static v8::Local<v8::Value>
|
||||||
|
+ EntangleAndInjectMessagePortChannel(v8::Local<v8::Context>, MessagePortChannel);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace blink
|
||||||
|
diff --git a/third_party/blink/renderer/core/exported/web_message_port_converter.cc b/third_party/blink/renderer/core/exported/web_message_port_converter.cc
|
||||||
|
index 333760d667f6b98b3e7674bf9082f999743dadfa..fc2f517de1951380482fbfa92c038041e15d9c3e 100644
|
||||||
|
--- a/third_party/blink/renderer/core/exported/web_message_port_converter.cc
|
||||||
|
+++ b/third_party/blink/renderer/core/exported/web_message_port_converter.cc
|
||||||
|
@@ -21,4 +21,15 @@ WebMessagePortConverter::DisentangleAndExtractMessagePortChannel(
|
||||||
|
return port->Disentangle();
|
||||||
|
}
|
||||||
|
|
||||||
|
+v8::Local<v8::Value>
|
||||||
|
+WebMessagePortConverter::EntangleAndInjectMessagePortChannel(
|
||||||
|
+ v8::Local<v8::Context> context,
|
||||||
|
+ MessagePortChannel port_channel) {
|
||||||
|
+ auto* execution_context = ToExecutionContext(context);
|
||||||
|
+ CHECK(execution_context);
|
||||||
|
+ auto* port = MakeGarbageCollected<MessagePort>(*execution_context);
|
||||||
|
+ port->Entangle(std::move(port_channel));
|
||||||
|
+ return ToV8(port, context->Global(), context->GetIsolate());
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
} // namespace blink
|
|
@ -7,6 +7,7 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <unordered_set>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
@ -44,12 +45,17 @@
|
||||||
#include "content/public/common/context_menu_params.h"
|
#include "content/public/common/context_menu_params.h"
|
||||||
#include "electron/buildflags/buildflags.h"
|
#include "electron/buildflags/buildflags.h"
|
||||||
#include "electron/shell/common/api/api.mojom.h"
|
#include "electron/shell/common/api/api.mojom.h"
|
||||||
|
#include "gin/data_object_builder.h"
|
||||||
|
#include "gin/handle.h"
|
||||||
|
#include "gin/object_template_builder.h"
|
||||||
|
#include "gin/wrappable.h"
|
||||||
#include "mojo/public/cpp/bindings/associated_remote.h"
|
#include "mojo/public/cpp/bindings/associated_remote.h"
|
||||||
#include "mojo/public/cpp/system/platform_handle.h"
|
#include "mojo/public/cpp/system/platform_handle.h"
|
||||||
#include "ppapi/buildflags/buildflags.h"
|
#include "ppapi/buildflags/buildflags.h"
|
||||||
#include "shell/browser/api/electron_api_browser_window.h"
|
#include "shell/browser/api/electron_api_browser_window.h"
|
||||||
#include "shell/browser/api/electron_api_debugger.h"
|
#include "shell/browser/api/electron_api_debugger.h"
|
||||||
#include "shell/browser/api/electron_api_session.h"
|
#include "shell/browser/api/electron_api_session.h"
|
||||||
|
#include "shell/browser/api/message_port.h"
|
||||||
#include "shell/browser/browser.h"
|
#include "shell/browser/browser.h"
|
||||||
#include "shell/browser/child_web_contents_tracker.h"
|
#include "shell/browser/child_web_contents_tracker.h"
|
||||||
#include "shell/browser/electron_autofill_driver_factory.h"
|
#include "shell/browser/electron_autofill_driver_factory.h"
|
||||||
|
@ -84,11 +90,14 @@
|
||||||
#include "shell/common/mouse_util.h"
|
#include "shell/common/mouse_util.h"
|
||||||
#include "shell/common/node_includes.h"
|
#include "shell/common/node_includes.h"
|
||||||
#include "shell/common/options_switches.h"
|
#include "shell/common/options_switches.h"
|
||||||
|
#include "shell/common/v8_value_serializer.h"
|
||||||
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
|
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
|
||||||
#include "third_party/blink/public/common/input/web_input_event.h"
|
#include "third_party/blink/public/common/input/web_input_event.h"
|
||||||
|
#include "third_party/blink/public/common/messaging/transferable_message_mojom_traits.h"
|
||||||
#include "third_party/blink/public/common/page/page_zoom.h"
|
#include "third_party/blink/public/common/page/page_zoom.h"
|
||||||
#include "third_party/blink/public/mojom/frame/find_in_page.mojom.h"
|
#include "third_party/blink/public/mojom/frame/find_in_page.mojom.h"
|
||||||
#include "third_party/blink/public/mojom/frame/fullscreen.mojom.h"
|
#include "third_party/blink/public/mojom/frame/fullscreen.mojom.h"
|
||||||
|
#include "third_party/blink/public/mojom/messaging/transferable_message.mojom.h"
|
||||||
#include "ui/base/cursor/cursor.h"
|
#include "ui/base/cursor/cursor.h"
|
||||||
#include "ui/base/mojom/cursor_type.mojom-shared.h"
|
#include "ui/base/mojom/cursor_type.mojom-shared.h"
|
||||||
#include "ui/display/screen.h"
|
#include "ui/display/screen.h"
|
||||||
|
@ -1088,6 +1097,49 @@ void WebContents::Invoke(bool internal,
|
||||||
std::move(callback), internal, channel, std::move(arguments));
|
std::move(callback), internal, channel, std::move(arguments));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WebContents::ReceivePostMessage(const std::string& channel,
|
||||||
|
blink::TransferableMessage message) {
|
||||||
|
v8::HandleScope handle_scope(isolate());
|
||||||
|
auto wrapped_ports =
|
||||||
|
MessagePort::EntanglePorts(isolate(), std::move(message.ports));
|
||||||
|
v8::Local<v8::Value> message_value =
|
||||||
|
electron::DeserializeV8Value(isolate(), message);
|
||||||
|
EmitWithSender("-ipc-ports", bindings_.dispatch_context(), InvokeCallback(),
|
||||||
|
false, channel, message_value, std::move(wrapped_ports));
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebContents::PostMessage(const std::string& channel,
|
||||||
|
v8::Local<v8::Value> message_value,
|
||||||
|
base::Optional<v8::Local<v8::Value>> transfer) {
|
||||||
|
blink::TransferableMessage transferable_message;
|
||||||
|
if (!electron::SerializeV8Value(isolate(), message_value,
|
||||||
|
&transferable_message)) {
|
||||||
|
// SerializeV8Value sets an exception.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<gin::Handle<MessagePort>> wrapped_ports;
|
||||||
|
if (transfer) {
|
||||||
|
if (!gin::ConvertFromV8(isolate(), *transfer, &wrapped_ports)) {
|
||||||
|
isolate()->ThrowException(v8::Exception::Error(
|
||||||
|
gin::StringToV8(isolate(), "Invalid value for transfer")));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool threw_exception = false;
|
||||||
|
transferable_message.ports =
|
||||||
|
MessagePort::DisentanglePorts(isolate(), wrapped_ports, &threw_exception);
|
||||||
|
if (threw_exception)
|
||||||
|
return;
|
||||||
|
|
||||||
|
content::RenderFrameHost* frame_host = web_contents()->GetMainFrame();
|
||||||
|
mojo::AssociatedRemote<mojom::ElectronRenderer> electron_renderer;
|
||||||
|
frame_host->GetRemoteAssociatedInterfaces()->GetInterface(&electron_renderer);
|
||||||
|
electron_renderer->ReceivePostMessage(channel,
|
||||||
|
std::move(transferable_message));
|
||||||
|
}
|
||||||
|
|
||||||
void WebContents::MessageSync(bool internal,
|
void WebContents::MessageSync(bool internal,
|
||||||
const std::string& channel,
|
const std::string& channel,
|
||||||
blink::CloneableMessage arguments,
|
blink::CloneableMessage arguments,
|
||||||
|
@ -2663,6 +2715,7 @@ void WebContents::BuildPrototype(v8::Isolate* isolate,
|
||||||
.SetMethod("isFocused", &WebContents::IsFocused)
|
.SetMethod("isFocused", &WebContents::IsFocused)
|
||||||
.SetMethod("tabTraverse", &WebContents::TabTraverse)
|
.SetMethod("tabTraverse", &WebContents::TabTraverse)
|
||||||
.SetMethod("_send", &WebContents::SendIPCMessage)
|
.SetMethod("_send", &WebContents::SendIPCMessage)
|
||||||
|
.SetMethod("_postMessage", &WebContents::PostMessage)
|
||||||
.SetMethod("_sendToFrame", &WebContents::SendIPCMessageToFrame)
|
.SetMethod("_sendToFrame", &WebContents::SendIPCMessageToFrame)
|
||||||
.SetMethod("sendInputEvent", &WebContents::SendInputEvent)
|
.SetMethod("sendInputEvent", &WebContents::SendInputEvent)
|
||||||
.SetMethod("beginFrameSubscription", &WebContents::BeginFrameSubscription)
|
.SetMethod("beginFrameSubscription", &WebContents::BeginFrameSubscription)
|
||||||
|
|
|
@ -256,6 +256,10 @@ class WebContents : public gin_helper::TrackableObject<WebContents>,
|
||||||
const std::string& channel,
|
const std::string& channel,
|
||||||
v8::Local<v8::Value> args);
|
v8::Local<v8::Value> args);
|
||||||
|
|
||||||
|
void PostMessage(const std::string& channel,
|
||||||
|
v8::Local<v8::Value> message,
|
||||||
|
base::Optional<v8::Local<v8::Value>> transfer);
|
||||||
|
|
||||||
// Send WebInputEvent to the page.
|
// Send WebInputEvent to the page.
|
||||||
void SendInputEvent(v8::Isolate* isolate, v8::Local<v8::Value> input_event);
|
void SendInputEvent(v8::Isolate* isolate, v8::Local<v8::Value> input_event);
|
||||||
|
|
||||||
|
@ -525,6 +529,8 @@ class WebContents : public gin_helper::TrackableObject<WebContents>,
|
||||||
const std::string& channel,
|
const std::string& channel,
|
||||||
blink::CloneableMessage arguments,
|
blink::CloneableMessage arguments,
|
||||||
InvokeCallback callback) override;
|
InvokeCallback callback) override;
|
||||||
|
void ReceivePostMessage(const std::string& channel,
|
||||||
|
blink::TransferableMessage message) override;
|
||||||
void MessageSync(bool internal,
|
void MessageSync(bool internal,
|
||||||
const std::string& channel,
|
const std::string& channel,
|
||||||
blink::CloneableMessage arguments,
|
blink::CloneableMessage arguments,
|
||||||
|
|
276
shell/browser/api/message_port.cc
Normal file
276
shell/browser/api/message_port.cc
Normal file
|
@ -0,0 +1,276 @@
|
||||||
|
// Copyright (c) 2020 Slack Technologies, Inc.
|
||||||
|
// Use of this source code is governed by the MIT license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
#include "shell/browser/api/message_port.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "base/strings/string_number_conversions.h"
|
||||||
|
#include "gin/arguments.h"
|
||||||
|
#include "gin/data_object_builder.h"
|
||||||
|
#include "gin/handle.h"
|
||||||
|
#include "gin/object_template_builder.h"
|
||||||
|
#include "shell/common/gin_helper/dictionary.h"
|
||||||
|
#include "shell/common/gin_helper/error_thrower.h"
|
||||||
|
#include "shell/common/gin_helper/event_emitter_caller.h"
|
||||||
|
#include "shell/common/node_includes.h"
|
||||||
|
#include "shell/common/v8_value_serializer.h"
|
||||||
|
#include "third_party/blink/public/common/messaging/transferable_message.h"
|
||||||
|
#include "third_party/blink/public/common/messaging/transferable_message_mojom_traits.h"
|
||||||
|
#include "third_party/blink/public/mojom/messaging/transferable_message.mojom.h"
|
||||||
|
|
||||||
|
namespace electron {
|
||||||
|
|
||||||
|
gin::WrapperInfo MessagePort::kWrapperInfo = {gin::kEmbedderNativeGin};
|
||||||
|
|
||||||
|
MessagePort::MessagePort() = default;
|
||||||
|
MessagePort::~MessagePort() = default;
|
||||||
|
|
||||||
|
// static
|
||||||
|
gin::Handle<MessagePort> MessagePort::Create(v8::Isolate* isolate) {
|
||||||
|
return gin::CreateHandle(isolate, new MessagePort());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessagePort::PostMessage(gin::Arguments* args) {
|
||||||
|
if (!IsEntangled())
|
||||||
|
return;
|
||||||
|
DCHECK(!IsNeutered());
|
||||||
|
|
||||||
|
blink::TransferableMessage transferable_message;
|
||||||
|
|
||||||
|
v8::Local<v8::Value> message_value;
|
||||||
|
if (!args->GetNext(&message_value)) {
|
||||||
|
args->ThrowTypeError("Expected at least one argument to postMessage");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
electron::SerializeV8Value(args->isolate(), message_value,
|
||||||
|
&transferable_message);
|
||||||
|
|
||||||
|
v8::Local<v8::Value> transferables;
|
||||||
|
std::vector<gin::Handle<MessagePort>> wrapped_ports;
|
||||||
|
if (args->GetNext(&transferables)) {
|
||||||
|
if (!gin::ConvertFromV8(args->isolate(), transferables, &wrapped_ports)) {
|
||||||
|
args->ThrowError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we aren't connected to any of the passed-in ports.
|
||||||
|
for (unsigned i = 0; i < wrapped_ports.size(); ++i) {
|
||||||
|
if (wrapped_ports[i].get() == this) {
|
||||||
|
gin_helper::ErrorThrower(args->isolate())
|
||||||
|
.ThrowError("Port at index " + base::NumberToString(i) +
|
||||||
|
" contains the source port.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool threw_exception = false;
|
||||||
|
transferable_message.ports = MessagePort::DisentanglePorts(
|
||||||
|
args->isolate(), wrapped_ports, &threw_exception);
|
||||||
|
if (threw_exception)
|
||||||
|
return;
|
||||||
|
|
||||||
|
mojo::Message mojo_message = blink::mojom::TransferableMessage::WrapAsMessage(
|
||||||
|
std::move(transferable_message));
|
||||||
|
connector_->Accept(&mojo_message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessagePort::Start() {
|
||||||
|
if (!IsEntangled())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (started_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
started_ = true;
|
||||||
|
if (HasPendingActivity())
|
||||||
|
Pin();
|
||||||
|
connector_->ResumeIncomingMethodCallProcessing();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessagePort::Close() {
|
||||||
|
if (closed_)
|
||||||
|
return;
|
||||||
|
if (!IsNeutered()) {
|
||||||
|
connector_ = nullptr;
|
||||||
|
Entangle(mojo::MessagePipe().handle0);
|
||||||
|
}
|
||||||
|
closed_ = true;
|
||||||
|
if (!HasPendingActivity())
|
||||||
|
Unpin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessagePort::Entangle(mojo::ScopedMessagePipeHandle handle) {
|
||||||
|
DCHECK(handle.is_valid());
|
||||||
|
DCHECK(!connector_);
|
||||||
|
connector_ = std::make_unique<mojo::Connector>(
|
||||||
|
std::move(handle), mojo::Connector::SINGLE_THREADED_SEND,
|
||||||
|
base::ThreadTaskRunnerHandle::Get());
|
||||||
|
connector_->PauseIncomingMethodCallProcessing();
|
||||||
|
connector_->set_incoming_receiver(this);
|
||||||
|
connector_->set_connection_error_handler(
|
||||||
|
base::Bind(&MessagePort::Close, weak_factory_.GetWeakPtr()));
|
||||||
|
if (HasPendingActivity())
|
||||||
|
Pin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessagePort::Entangle(blink::MessagePortChannel channel) {
|
||||||
|
Entangle(channel.ReleaseHandle());
|
||||||
|
}
|
||||||
|
|
||||||
|
blink::MessagePortChannel MessagePort::Disentangle() {
|
||||||
|
DCHECK(!IsNeutered());
|
||||||
|
auto result = blink::MessagePortChannel(connector_->PassMessagePipe());
|
||||||
|
connector_ = nullptr;
|
||||||
|
if (!HasPendingActivity())
|
||||||
|
Unpin();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MessagePort::HasPendingActivity() const {
|
||||||
|
// The spec says that entangled message ports should always be treated as if
|
||||||
|
// they have a strong reference.
|
||||||
|
// We'll also stipulate that the queue needs to be open (if the app drops its
|
||||||
|
// reference to the port before start()-ing it, then it's not really entangled
|
||||||
|
// as it's unreachable).
|
||||||
|
return started_ && IsEntangled();
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
std::vector<gin::Handle<MessagePort>> MessagePort::EntanglePorts(
|
||||||
|
v8::Isolate* isolate,
|
||||||
|
std::vector<blink::MessagePortChannel> channels) {
|
||||||
|
std::vector<gin::Handle<MessagePort>> wrapped_ports;
|
||||||
|
for (auto& port : channels) {
|
||||||
|
auto wrapped_port = MessagePort::Create(isolate);
|
||||||
|
wrapped_port->Entangle(std::move(port));
|
||||||
|
wrapped_ports.emplace_back(wrapped_port);
|
||||||
|
}
|
||||||
|
return wrapped_ports;
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
std::vector<blink::MessagePortChannel> MessagePort::DisentanglePorts(
|
||||||
|
v8::Isolate* isolate,
|
||||||
|
const std::vector<gin::Handle<MessagePort>>& ports,
|
||||||
|
bool* threw_exception) {
|
||||||
|
if (!ports.size())
|
||||||
|
return std::vector<blink::MessagePortChannel>();
|
||||||
|
|
||||||
|
std::unordered_set<MessagePort*> visited;
|
||||||
|
|
||||||
|
// Walk the incoming array - if there are any duplicate ports, or null ports
|
||||||
|
// or cloned ports, throw an error (per section 8.3.3 of the HTML5 spec).
|
||||||
|
for (unsigned i = 0; i < ports.size(); ++i) {
|
||||||
|
auto* port = ports[i].get();
|
||||||
|
if (!port || port->IsNeutered() || visited.find(port) != visited.end()) {
|
||||||
|
std::string type;
|
||||||
|
if (!port)
|
||||||
|
type = "null";
|
||||||
|
else if (port->IsNeutered())
|
||||||
|
type = "already neutered";
|
||||||
|
else
|
||||||
|
type = "a duplicate";
|
||||||
|
gin_helper::ErrorThrower(isolate).ThrowError(
|
||||||
|
"Port at index " + base::NumberToString(i) + " is " + type + ".");
|
||||||
|
*threw_exception = true;
|
||||||
|
return std::vector<blink::MessagePortChannel>();
|
||||||
|
}
|
||||||
|
visited.insert(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Passed-in ports passed validity checks, so we can disentangle them.
|
||||||
|
std::vector<blink::MessagePortChannel> channels;
|
||||||
|
channels.reserve(ports.size());
|
||||||
|
for (unsigned i = 0; i < ports.size(); ++i)
|
||||||
|
channels.push_back(ports[i]->Disentangle());
|
||||||
|
return channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessagePort::Pin() {
|
||||||
|
if (!pinned_.IsEmpty())
|
||||||
|
return;
|
||||||
|
v8::Isolate* isolate = v8::Isolate::GetCurrent();
|
||||||
|
v8::Local<v8::Value> self;
|
||||||
|
if (GetWrapper(isolate).ToLocal(&self)) {
|
||||||
|
pinned_.Reset(isolate, self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessagePort::Unpin() {
|
||||||
|
pinned_.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MessagePort::Accept(mojo::Message* mojo_message) {
|
||||||
|
blink::TransferableMessage message;
|
||||||
|
if (!blink::mojom::TransferableMessage::DeserializeFromMessage(
|
||||||
|
std::move(*mojo_message), &message)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
v8::Isolate* isolate = v8::Isolate::GetCurrent();
|
||||||
|
v8::HandleScope scope(isolate);
|
||||||
|
|
||||||
|
auto ports = EntanglePorts(isolate, std::move(message.ports));
|
||||||
|
|
||||||
|
v8::Local<v8::Value> message_value = DeserializeV8Value(isolate, message);
|
||||||
|
|
||||||
|
v8::Local<v8::Object> self;
|
||||||
|
if (!GetWrapper(isolate).ToLocal(&self))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto event = gin::DataObjectBuilder(isolate)
|
||||||
|
.Set("data", message_value)
|
||||||
|
.Set("ports", ports)
|
||||||
|
.Build();
|
||||||
|
gin_helper::EmitEvent(isolate, self, "message", event);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
gin::ObjectTemplateBuilder MessagePort::GetObjectTemplateBuilder(
|
||||||
|
v8::Isolate* isolate) {
|
||||||
|
return gin::Wrappable<MessagePort>::GetObjectTemplateBuilder(isolate)
|
||||||
|
.SetMethod("postMessage", &MessagePort::PostMessage)
|
||||||
|
.SetMethod("start", &MessagePort::Start)
|
||||||
|
.SetMethod("close", &MessagePort::Close);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* MessagePort::GetTypeName() {
|
||||||
|
return "MessagePort";
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace electron
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using electron::MessagePort;
|
||||||
|
|
||||||
|
v8::Local<v8::Value> CreatePair(v8::Isolate* isolate) {
|
||||||
|
auto port1 = MessagePort::Create(isolate);
|
||||||
|
auto port2 = MessagePort::Create(isolate);
|
||||||
|
mojo::MessagePipe pipe;
|
||||||
|
port1->Entangle(std::move(pipe.handle0));
|
||||||
|
port2->Entangle(std::move(pipe.handle1));
|
||||||
|
return gin::DataObjectBuilder(isolate)
|
||||||
|
.Set("port1", port1)
|
||||||
|
.Set("port2", port2)
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Initialize(v8::Local<v8::Object> exports,
|
||||||
|
v8::Local<v8::Value> unused,
|
||||||
|
v8::Local<v8::Context> context,
|
||||||
|
void* priv) {
|
||||||
|
v8::Isolate* isolate = context->GetIsolate();
|
||||||
|
gin_helper::Dictionary dict(isolate, exports);
|
||||||
|
dict.SetMethod("createPair", &CreatePair);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
NODE_LINKED_MODULE_CONTEXT_AWARE(electron_browser_message_port, Initialize)
|
86
shell/browser/api/message_port.h
Normal file
86
shell/browser/api/message_port.h
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
// Copyright (c) 2020 Slack Technologies, Inc.
|
||||||
|
// Use of this source code is governed by the MIT license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
#ifndef SHELL_BROWSER_API_MESSAGE_PORT_H_
|
||||||
|
#define SHELL_BROWSER_API_MESSAGE_PORT_H_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "gin/wrappable.h"
|
||||||
|
#include "mojo/public/cpp/bindings/connector.h"
|
||||||
|
#include "mojo/public/cpp/bindings/message.h"
|
||||||
|
#include "third_party/blink/public/common/messaging/message_port_channel.h"
|
||||||
|
|
||||||
|
namespace gin {
|
||||||
|
class Arguments;
|
||||||
|
template <typename T>
|
||||||
|
class Handle;
|
||||||
|
} // namespace gin
|
||||||
|
|
||||||
|
namespace electron {
|
||||||
|
|
||||||
|
// A non-blink version of blink::MessagePort.
|
||||||
|
class MessagePort : public gin::Wrappable<MessagePort>, mojo::MessageReceiver {
|
||||||
|
public:
|
||||||
|
~MessagePort() override;
|
||||||
|
static gin::Handle<MessagePort> Create(v8::Isolate* isolate);
|
||||||
|
|
||||||
|
void PostMessage(gin::Arguments* args);
|
||||||
|
void Start();
|
||||||
|
void Close();
|
||||||
|
|
||||||
|
void Entangle(mojo::ScopedMessagePipeHandle handle);
|
||||||
|
void Entangle(blink::MessagePortChannel channel);
|
||||||
|
|
||||||
|
blink::MessagePortChannel Disentangle();
|
||||||
|
|
||||||
|
bool IsEntangled() const { return !closed_ && !IsNeutered(); }
|
||||||
|
bool IsNeutered() const { return !connector_ || !connector_->is_valid(); }
|
||||||
|
|
||||||
|
static std::vector<gin::Handle<MessagePort>> EntanglePorts(
|
||||||
|
v8::Isolate* isolate,
|
||||||
|
std::vector<blink::MessagePortChannel> channels);
|
||||||
|
|
||||||
|
static std::vector<blink::MessagePortChannel> DisentanglePorts(
|
||||||
|
v8::Isolate* isolate,
|
||||||
|
const std::vector<gin::Handle<MessagePort>>& ports,
|
||||||
|
bool* threw_exception);
|
||||||
|
|
||||||
|
// gin::Wrappable
|
||||||
|
gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
|
||||||
|
v8::Isolate* isolate) override;
|
||||||
|
static gin::WrapperInfo kWrapperInfo;
|
||||||
|
const char* GetTypeName() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
MessagePort();
|
||||||
|
|
||||||
|
// The blink version of MessagePort uses the very nice "ActiveScriptWrapper"
|
||||||
|
// class, which keeps the object alive through the V8 embedder hooks into the
|
||||||
|
// GC lifecycle: see
|
||||||
|
// https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/platform/heap/thread_state.cc;l=258;drc=b892cf58e162a8f66cd76d7472f129fe0fb6a7d1
|
||||||
|
// We do not have that luxury, so we brutishly use v8::Global to accomplish
|
||||||
|
// something similar. Critically, whenever the value of
|
||||||
|
// "HasPendingActivity()" changes, we must call Pin() or Unpin() as
|
||||||
|
// appropriate.
|
||||||
|
bool HasPendingActivity() const;
|
||||||
|
void Pin();
|
||||||
|
void Unpin();
|
||||||
|
|
||||||
|
// mojo::MessageReceiver
|
||||||
|
bool Accept(mojo::Message* mojo_message) override;
|
||||||
|
|
||||||
|
std::unique_ptr<mojo::Connector> connector_;
|
||||||
|
bool started_ = false;
|
||||||
|
bool closed_ = false;
|
||||||
|
|
||||||
|
v8::Global<v8::Value> pinned_;
|
||||||
|
|
||||||
|
base::WeakPtrFactory<MessagePort> weak_factory_{this};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace electron
|
||||||
|
|
||||||
|
#endif // SHELL_BROWSER_API_MESSAGE_PORT_H_
|
|
@ -3,6 +3,7 @@ module electron.mojom;
|
||||||
import "mojo/public/mojom/base/string16.mojom";
|
import "mojo/public/mojom/base/string16.mojom";
|
||||||
import "ui/gfx/geometry/mojom/geometry.mojom";
|
import "ui/gfx/geometry/mojom/geometry.mojom";
|
||||||
import "third_party/blink/public/mojom/messaging/cloneable_message.mojom";
|
import "third_party/blink/public/mojom/messaging/cloneable_message.mojom";
|
||||||
|
import "third_party/blink/public/mojom/messaging/transferable_message.mojom";
|
||||||
|
|
||||||
interface ElectronRenderer {
|
interface ElectronRenderer {
|
||||||
Message(
|
Message(
|
||||||
|
@ -12,6 +13,8 @@ interface ElectronRenderer {
|
||||||
blink.mojom.CloneableMessage arguments,
|
blink.mojom.CloneableMessage arguments,
|
||||||
int32 sender_id);
|
int32 sender_id);
|
||||||
|
|
||||||
|
ReceivePostMessage(string channel, blink.mojom.TransferableMessage message);
|
||||||
|
|
||||||
UpdateCrashpadPipeName(string pipe_name);
|
UpdateCrashpadPipeName(string pipe_name);
|
||||||
|
|
||||||
// This is an API specific to the "remote" module, and will ultimately be
|
// This is an API specific to the "remote" module, and will ultimately be
|
||||||
|
@ -53,6 +56,8 @@ interface ElectronBrowser {
|
||||||
string channel,
|
string channel,
|
||||||
blink.mojom.CloneableMessage arguments) => (blink.mojom.CloneableMessage result);
|
blink.mojom.CloneableMessage arguments) => (blink.mojom.CloneableMessage result);
|
||||||
|
|
||||||
|
ReceivePostMessage(string channel, blink.mojom.TransferableMessage message);
|
||||||
|
|
||||||
// Emits an event on |channel| from the ipcMain JavaScript object in the main
|
// Emits an event on |channel| from the ipcMain JavaScript object in the main
|
||||||
// process, and waits synchronously for a response.
|
// process, and waits synchronously for a response.
|
||||||
[Sync]
|
[Sync]
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include "shell/common/gin_converters/value_converter.h"
|
#include "shell/common/gin_converters/value_converter.h"
|
||||||
#include "shell/common/gin_helper/dictionary.h"
|
#include "shell/common/gin_helper/dictionary.h"
|
||||||
#include "shell/common/keyboard_util.h"
|
#include "shell/common/keyboard_util.h"
|
||||||
|
#include "shell/common/v8_value_serializer.h"
|
||||||
#include "third_party/blink/public/common/context_menu_data/edit_flags.h"
|
#include "third_party/blink/public/common/context_menu_data/edit_flags.h"
|
||||||
#include "third_party/blink/public/common/input/web_input_event.h"
|
#include "third_party/blink/public/common/input/web_input_event.h"
|
||||||
#include "third_party/blink/public/common/input/web_keyboard_event.h"
|
#include "third_party/blink/public/common/input/web_keyboard_event.h"
|
||||||
|
@ -481,98 +482,16 @@ bool Converter<network::mojom::ReferrerPolicy>::FromV8(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
class V8Serializer : public v8::ValueSerializer::Delegate {
|
|
||||||
public:
|
|
||||||
explicit V8Serializer(v8::Isolate* isolate)
|
|
||||||
: isolate_(isolate), serializer_(isolate, this) {}
|
|
||||||
~V8Serializer() override = default;
|
|
||||||
|
|
||||||
bool Serialize(v8::Local<v8::Value> value, blink::CloneableMessage* out) {
|
|
||||||
serializer_.WriteHeader();
|
|
||||||
bool wrote_value;
|
|
||||||
if (!serializer_.WriteValue(isolate_->GetCurrentContext(), value)
|
|
||||||
.To(&wrote_value)) {
|
|
||||||
isolate_->ThrowException(v8::Exception::Error(
|
|
||||||
StringToV8(isolate_, "An object could not be cloned.")));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
DCHECK(wrote_value);
|
|
||||||
|
|
||||||
std::pair<uint8_t*, size_t> buffer = serializer_.Release();
|
|
||||||
DCHECK_EQ(buffer.first, data_.data());
|
|
||||||
out->encoded_message = base::make_span(buffer.first, buffer.second);
|
|
||||||
out->owned_encoded_message = std::move(data_);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// v8::ValueSerializer::Delegate
|
|
||||||
void* ReallocateBufferMemory(void* old_buffer,
|
|
||||||
size_t size,
|
|
||||||
size_t* actual_size) override {
|
|
||||||
DCHECK_EQ(old_buffer, data_.data());
|
|
||||||
data_.resize(size);
|
|
||||||
*actual_size = data_.capacity();
|
|
||||||
return data_.data();
|
|
||||||
}
|
|
||||||
|
|
||||||
void FreeBufferMemory(void* buffer) override {
|
|
||||||
DCHECK_EQ(buffer, data_.data());
|
|
||||||
data_ = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
void ThrowDataCloneError(v8::Local<v8::String> message) override {
|
|
||||||
isolate_->ThrowException(v8::Exception::Error(message));
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
v8::Isolate* isolate_;
|
|
||||||
std::vector<uint8_t> data_;
|
|
||||||
v8::ValueSerializer serializer_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class V8Deserializer : public v8::ValueDeserializer::Delegate {
|
|
||||||
public:
|
|
||||||
V8Deserializer(v8::Isolate* isolate, const blink::CloneableMessage& message)
|
|
||||||
: isolate_(isolate),
|
|
||||||
deserializer_(isolate,
|
|
||||||
message.encoded_message.data(),
|
|
||||||
message.encoded_message.size(),
|
|
||||||
this) {}
|
|
||||||
|
|
||||||
v8::Local<v8::Value> Deserialize() {
|
|
||||||
v8::EscapableHandleScope scope(isolate_);
|
|
||||||
auto context = isolate_->GetCurrentContext();
|
|
||||||
bool read_header;
|
|
||||||
if (!deserializer_.ReadHeader(context).To(&read_header))
|
|
||||||
return v8::Null(isolate_);
|
|
||||||
DCHECK(read_header);
|
|
||||||
v8::Local<v8::Value> value;
|
|
||||||
if (!deserializer_.ReadValue(context).ToLocal(&value)) {
|
|
||||||
return v8::Null(isolate_);
|
|
||||||
}
|
|
||||||
return scope.Escape(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
v8::Isolate* isolate_;
|
|
||||||
v8::ValueDeserializer deserializer_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
v8::Local<v8::Value> Converter<blink::CloneableMessage>::ToV8(
|
v8::Local<v8::Value> Converter<blink::CloneableMessage>::ToV8(
|
||||||
v8::Isolate* isolate,
|
v8::Isolate* isolate,
|
||||||
const blink::CloneableMessage& in) {
|
const blink::CloneableMessage& in) {
|
||||||
return V8Deserializer(isolate, in).Deserialize();
|
return electron::DeserializeV8Value(isolate, in);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Converter<blink::CloneableMessage>::FromV8(v8::Isolate* isolate,
|
bool Converter<blink::CloneableMessage>::FromV8(v8::Isolate* isolate,
|
||||||
v8::Handle<v8::Value> val,
|
v8::Handle<v8::Value> val,
|
||||||
blink::CloneableMessage* out) {
|
blink::CloneableMessage* out) {
|
||||||
return V8Serializer(isolate).Serialize(val, out);
|
return electron::SerializeV8Value(isolate, val, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace gin
|
} // namespace gin
|
||||||
|
|
|
@ -37,7 +37,7 @@ v8::Persistent<v8::FunctionTemplate> g_call_translater;
|
||||||
void CallTranslater(v8::Local<v8::External> external,
|
void CallTranslater(v8::Local<v8::External> external,
|
||||||
v8::Local<v8::Object> state,
|
v8::Local<v8::Object> state,
|
||||||
gin::Arguments* args) {
|
gin::Arguments* args) {
|
||||||
// Whether the callback should only be called for once.
|
// Whether the callback should only be called once.
|
||||||
v8::Isolate* isolate = args->isolate();
|
v8::Isolate* isolate = args->isolate();
|
||||||
auto context = isolate->GetCurrentContext();
|
auto context = isolate->GetCurrentContext();
|
||||||
bool one_time =
|
bool one_time =
|
||||||
|
@ -47,7 +47,7 @@ void CallTranslater(v8::Local<v8::External> external,
|
||||||
if (one_time) {
|
if (one_time) {
|
||||||
auto called_symbol = gin::StringToSymbol(isolate, "called");
|
auto called_symbol = gin::StringToSymbol(isolate, "called");
|
||||||
if (state->Has(context, called_symbol).ToChecked()) {
|
if (state->Has(context, called_symbol).ToChecked()) {
|
||||||
args->ThrowTypeError("callback can only be called for once");
|
args->ThrowTypeError("One-time callback was called more than once");
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
state->Set(context, called_symbol, v8::Boolean::New(isolate, true))
|
state->Set(context, called_symbol, v8::Boolean::New(isolate, true))
|
||||||
|
|
42
shell/common/gin_helper/function_template_extensions.h
Normal file
42
shell/common/gin_helper/function_template_extensions.h
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
// Copyright 2020 Slack Technologies, Inc.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE.chromium file.
|
||||||
|
|
||||||
|
#ifndef SHELL_COMMON_GIN_HELPER_FUNCTION_TEMPLATE_EXTENSIONS_H_
|
||||||
|
#define SHELL_COMMON_GIN_HELPER_FUNCTION_TEMPLATE_EXTENSIONS_H_
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "gin/function_template.h"
|
||||||
|
#include "shell/common/gin_helper/error_thrower.h"
|
||||||
|
|
||||||
|
// This extends the functionality in //gin/function_template.h for "special"
|
||||||
|
// arguments to gin-bound methods.
|
||||||
|
// It's the counterpart to function_template.h, which includes these methods
|
||||||
|
// in the gin_helper namespace.
|
||||||
|
namespace gin {
|
||||||
|
|
||||||
|
// Support base::Optional as an argument.
|
||||||
|
template <typename T>
|
||||||
|
bool GetNextArgument(Arguments* args,
|
||||||
|
const InvokerOptions& invoker_options,
|
||||||
|
bool is_first,
|
||||||
|
base::Optional<T>* result) {
|
||||||
|
T converted;
|
||||||
|
// Use gin::Arguments::GetNext which always advances |next| counter.
|
||||||
|
if (args->GetNext(&converted))
|
||||||
|
result->emplace(std::move(converted));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool GetNextArgument(Arguments* args,
|
||||||
|
const InvokerOptions& invoker_options,
|
||||||
|
bool is_first,
|
||||||
|
gin_helper::ErrorThrower* result) {
|
||||||
|
*result = gin_helper::ErrorThrower(args->isolate());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace gin
|
||||||
|
|
||||||
|
#endif // SHELL_COMMON_GIN_HELPER_FUNCTION_TEMPLATE_EXTENSIONS_H_
|
|
@ -44,6 +44,7 @@
|
||||||
V(electron_browser_global_shortcut) \
|
V(electron_browser_global_shortcut) \
|
||||||
V(electron_browser_in_app_purchase) \
|
V(electron_browser_in_app_purchase) \
|
||||||
V(electron_browser_menu) \
|
V(electron_browser_menu) \
|
||||||
|
V(electron_browser_message_port) \
|
||||||
V(electron_browser_net) \
|
V(electron_browser_net) \
|
||||||
V(electron_browser_power_monitor) \
|
V(electron_browser_power_monitor) \
|
||||||
V(electron_browser_power_save_blocker) \
|
V(electron_browser_power_save_blocker) \
|
||||||
|
|
147
shell/common/v8_value_serializer.cc
Normal file
147
shell/common/v8_value_serializer.cc
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
// Copyright (c) 2020 Slack Technologies, Inc.
|
||||||
|
// Use of this source code is governed by the MIT license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
#include "shell/common/v8_value_serializer.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "gin/converter.h"
|
||||||
|
#include "third_party/blink/public/common/messaging/cloneable_message.h"
|
||||||
|
#include "v8/include/v8.h"
|
||||||
|
|
||||||
|
namespace electron {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
const uint8_t kVersionTag = 0xFF;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
class V8Serializer : public v8::ValueSerializer::Delegate {
|
||||||
|
public:
|
||||||
|
explicit V8Serializer(v8::Isolate* isolate)
|
||||||
|
: isolate_(isolate), serializer_(isolate, this) {}
|
||||||
|
~V8Serializer() override = default;
|
||||||
|
|
||||||
|
bool Serialize(v8::Local<v8::Value> value, blink::CloneableMessage* out) {
|
||||||
|
WriteBlinkEnvelope(19);
|
||||||
|
|
||||||
|
serializer_.WriteHeader();
|
||||||
|
bool wrote_value;
|
||||||
|
if (!serializer_.WriteValue(isolate_->GetCurrentContext(), value)
|
||||||
|
.To(&wrote_value)) {
|
||||||
|
isolate_->ThrowException(v8::Exception::Error(
|
||||||
|
gin::StringToV8(isolate_, "An object could not be cloned.")));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DCHECK(wrote_value);
|
||||||
|
|
||||||
|
std::pair<uint8_t*, size_t> buffer = serializer_.Release();
|
||||||
|
DCHECK_EQ(buffer.first, data_.data());
|
||||||
|
out->encoded_message = base::make_span(buffer.first, buffer.second);
|
||||||
|
out->owned_encoded_message = std::move(data_);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// v8::ValueSerializer::Delegate
|
||||||
|
void* ReallocateBufferMemory(void* old_buffer,
|
||||||
|
size_t size,
|
||||||
|
size_t* actual_size) override {
|
||||||
|
DCHECK_EQ(old_buffer, data_.data());
|
||||||
|
data_.resize(size);
|
||||||
|
*actual_size = data_.capacity();
|
||||||
|
return data_.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FreeBufferMemory(void* buffer) override {
|
||||||
|
DCHECK_EQ(buffer, data_.data());
|
||||||
|
data_ = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void ThrowDataCloneError(v8::Local<v8::String> message) override {
|
||||||
|
isolate_->ThrowException(v8::Exception::Error(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void WriteTag(uint8_t tag) { serializer_.WriteRawBytes(&tag, 1); }
|
||||||
|
|
||||||
|
void WriteBlinkEnvelope(uint32_t blink_version) {
|
||||||
|
// Write a dummy blink version envelope for compatibility with
|
||||||
|
// blink::V8ScriptValueSerializer
|
||||||
|
WriteTag(kVersionTag);
|
||||||
|
serializer_.WriteUint32(blink_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
v8::Isolate* isolate_;
|
||||||
|
std::vector<uint8_t> data_;
|
||||||
|
v8::ValueSerializer serializer_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class V8Deserializer : public v8::ValueDeserializer::Delegate {
|
||||||
|
public:
|
||||||
|
V8Deserializer(v8::Isolate* isolate, base::span<const uint8_t> data)
|
||||||
|
: isolate_(isolate),
|
||||||
|
deserializer_(isolate, data.data(), data.size(), this) {}
|
||||||
|
V8Deserializer(v8::Isolate* isolate, const blink::CloneableMessage& message)
|
||||||
|
: V8Deserializer(isolate, message.encoded_message) {}
|
||||||
|
|
||||||
|
v8::Local<v8::Value> Deserialize() {
|
||||||
|
v8::EscapableHandleScope scope(isolate_);
|
||||||
|
auto context = isolate_->GetCurrentContext();
|
||||||
|
|
||||||
|
uint32_t blink_version;
|
||||||
|
if (!ReadBlinkEnvelope(&blink_version))
|
||||||
|
return v8::Null(isolate_);
|
||||||
|
|
||||||
|
bool read_header;
|
||||||
|
if (!deserializer_.ReadHeader(context).To(&read_header))
|
||||||
|
return v8::Null(isolate_);
|
||||||
|
DCHECK(read_header);
|
||||||
|
v8::Local<v8::Value> value;
|
||||||
|
if (!deserializer_.ReadValue(context).ToLocal(&value))
|
||||||
|
return v8::Null(isolate_);
|
||||||
|
return scope.Escape(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool ReadTag(uint8_t* tag) {
|
||||||
|
const void* tag_bytes = nullptr;
|
||||||
|
if (!deserializer_.ReadRawBytes(1, &tag_bytes))
|
||||||
|
return false;
|
||||||
|
*tag = *reinterpret_cast<const uint8_t*>(tag_bytes);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ReadBlinkEnvelope(uint32_t* blink_version) {
|
||||||
|
// Read a dummy blink version envelope for compatibility with
|
||||||
|
// blink::V8ScriptValueDeserializer
|
||||||
|
uint8_t tag = 0;
|
||||||
|
if (!ReadTag(&tag) || tag != kVersionTag)
|
||||||
|
return false;
|
||||||
|
if (!deserializer_.ReadUint32(blink_version))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
v8::Isolate* isolate_;
|
||||||
|
v8::ValueDeserializer deserializer_;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool SerializeV8Value(v8::Isolate* isolate,
|
||||||
|
v8::Local<v8::Value> value,
|
||||||
|
blink::CloneableMessage* out) {
|
||||||
|
return V8Serializer(isolate).Serialize(value, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
v8::Local<v8::Value> DeserializeV8Value(v8::Isolate* isolate,
|
||||||
|
const blink::CloneableMessage& in) {
|
||||||
|
return V8Deserializer(isolate, in).Deserialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
v8::Local<v8::Value> DeserializeV8Value(v8::Isolate* isolate,
|
||||||
|
base::span<const uint8_t> data) {
|
||||||
|
return V8Deserializer(isolate, data).Deserialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace electron
|
33
shell/common/v8_value_serializer.h
Normal file
33
shell/common/v8_value_serializer.h
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright (c) 2020 Slack Technologies, Inc.
|
||||||
|
// Use of this source code is governed by the MIT license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
#ifndef SHELL_COMMON_V8_VALUE_SERIALIZER_H_
|
||||||
|
#define SHELL_COMMON_V8_VALUE_SERIALIZER_H_
|
||||||
|
|
||||||
|
#include "base/containers/span.h"
|
||||||
|
|
||||||
|
namespace v8 {
|
||||||
|
class Isolate;
|
||||||
|
template <class T>
|
||||||
|
class Local;
|
||||||
|
class Value;
|
||||||
|
} // namespace v8
|
||||||
|
|
||||||
|
namespace blink {
|
||||||
|
struct CloneableMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace electron {
|
||||||
|
|
||||||
|
bool SerializeV8Value(v8::Isolate* isolate,
|
||||||
|
v8::Local<v8::Value> value,
|
||||||
|
blink::CloneableMessage* out);
|
||||||
|
v8::Local<v8::Value> DeserializeV8Value(v8::Isolate* isolate,
|
||||||
|
const blink::CloneableMessage& in);
|
||||||
|
v8::Local<v8::Value> DeserializeV8Value(v8::Isolate* isolate,
|
||||||
|
base::span<const uint8_t> data);
|
||||||
|
|
||||||
|
} // namespace electron
|
||||||
|
|
||||||
|
#endif // SHELL_COMMON_V8_VALUE_SERIALIZER_H_
|
|
@ -15,10 +15,13 @@
|
||||||
#include "shell/common/api/api.mojom.h"
|
#include "shell/common/api/api.mojom.h"
|
||||||
#include "shell/common/gin_converters/blink_converter.h"
|
#include "shell/common/gin_converters/blink_converter.h"
|
||||||
#include "shell/common/gin_converters/value_converter.h"
|
#include "shell/common/gin_converters/value_converter.h"
|
||||||
|
#include "shell/common/gin_helper/function_template_extensions.h"
|
||||||
#include "shell/common/gin_helper/promise.h"
|
#include "shell/common/gin_helper/promise.h"
|
||||||
#include "shell/common/node_bindings.h"
|
#include "shell/common/node_bindings.h"
|
||||||
#include "shell/common/node_includes.h"
|
#include "shell/common/node_includes.h"
|
||||||
|
#include "shell/common/v8_value_serializer.h"
|
||||||
#include "third_party/blink/public/web/web_local_frame.h"
|
#include "third_party/blink/public/web/web_local_frame.h"
|
||||||
|
#include "third_party/blink/public/web/web_message_port_converter.h"
|
||||||
|
|
||||||
using blink::WebLocalFrame;
|
using blink::WebLocalFrame;
|
||||||
using content::RenderFrame;
|
using content::RenderFrame;
|
||||||
|
@ -57,7 +60,8 @@ class IPCRenderer : public gin::Wrappable<IPCRenderer> {
|
||||||
.SetMethod("sendSync", &IPCRenderer::SendSync)
|
.SetMethod("sendSync", &IPCRenderer::SendSync)
|
||||||
.SetMethod("sendTo", &IPCRenderer::SendTo)
|
.SetMethod("sendTo", &IPCRenderer::SendTo)
|
||||||
.SetMethod("sendToHost", &IPCRenderer::SendToHost)
|
.SetMethod("sendToHost", &IPCRenderer::SendToHost)
|
||||||
.SetMethod("invoke", &IPCRenderer::Invoke);
|
.SetMethod("invoke", &IPCRenderer::Invoke)
|
||||||
|
.SetMethod("postMessage", &IPCRenderer::PostMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* GetTypeName() override { return "IPCRenderer"; }
|
const char* GetTypeName() override { return "IPCRenderer"; }
|
||||||
|
@ -68,7 +72,7 @@ class IPCRenderer : public gin::Wrappable<IPCRenderer> {
|
||||||
const std::string& channel,
|
const std::string& channel,
|
||||||
v8::Local<v8::Value> arguments) {
|
v8::Local<v8::Value> arguments) {
|
||||||
blink::CloneableMessage message;
|
blink::CloneableMessage message;
|
||||||
if (!gin::ConvertFromV8(isolate, arguments, &message)) {
|
if (!electron::SerializeV8Value(isolate, arguments, &message)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
electron_browser_ptr_->Message(internal, channel, std::move(message));
|
electron_browser_ptr_->Message(internal, channel, std::move(message));
|
||||||
|
@ -79,7 +83,7 @@ class IPCRenderer : public gin::Wrappable<IPCRenderer> {
|
||||||
const std::string& channel,
|
const std::string& channel,
|
||||||
v8::Local<v8::Value> arguments) {
|
v8::Local<v8::Value> arguments) {
|
||||||
blink::CloneableMessage message;
|
blink::CloneableMessage message;
|
||||||
if (!gin::ConvertFromV8(isolate, arguments, &message)) {
|
if (!electron::SerializeV8Value(isolate, arguments, &message)) {
|
||||||
return v8::Local<v8::Promise>();
|
return v8::Local<v8::Promise>();
|
||||||
}
|
}
|
||||||
gin_helper::Promise<blink::CloneableMessage> p(isolate);
|
gin_helper::Promise<blink::CloneableMessage> p(isolate);
|
||||||
|
@ -95,6 +99,43 @@ class IPCRenderer : public gin::Wrappable<IPCRenderer> {
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PostMessage(v8::Isolate* isolate,
|
||||||
|
gin_helper::ErrorThrower thrower,
|
||||||
|
const std::string& channel,
|
||||||
|
v8::Local<v8::Value> message_value,
|
||||||
|
base::Optional<v8::Local<v8::Value>> transfer) {
|
||||||
|
blink::TransferableMessage transferable_message;
|
||||||
|
if (!electron::SerializeV8Value(isolate, message_value,
|
||||||
|
&transferable_message)) {
|
||||||
|
// SerializeV8Value sets an exception.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<v8::Local<v8::Object>> transferables;
|
||||||
|
if (transfer) {
|
||||||
|
if (!gin::ConvertFromV8(isolate, *transfer, &transferables)) {
|
||||||
|
thrower.ThrowTypeError("Invalid value for transfer");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<blink::MessagePortChannel> ports;
|
||||||
|
for (auto& transferable : transferables) {
|
||||||
|
base::Optional<blink::MessagePortChannel> port =
|
||||||
|
blink::WebMessagePortConverter::
|
||||||
|
DisentangleAndExtractMessagePortChannel(isolate, transferable);
|
||||||
|
if (!port.has_value()) {
|
||||||
|
thrower.ThrowTypeError("Invalid value for transfer");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ports.emplace_back(port.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
transferable_message.ports = std::move(ports);
|
||||||
|
electron_browser_ptr_->ReceivePostMessage(channel,
|
||||||
|
std::move(transferable_message));
|
||||||
|
}
|
||||||
|
|
||||||
void SendTo(v8::Isolate* isolate,
|
void SendTo(v8::Isolate* isolate,
|
||||||
bool internal,
|
bool internal,
|
||||||
bool send_to_all,
|
bool send_to_all,
|
||||||
|
@ -102,7 +143,7 @@ class IPCRenderer : public gin::Wrappable<IPCRenderer> {
|
||||||
const std::string& channel,
|
const std::string& channel,
|
||||||
v8::Local<v8::Value> arguments) {
|
v8::Local<v8::Value> arguments) {
|
||||||
blink::CloneableMessage message;
|
blink::CloneableMessage message;
|
||||||
if (!gin::ConvertFromV8(isolate, arguments, &message)) {
|
if (!electron::SerializeV8Value(isolate, arguments, &message)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
electron_browser_ptr_->MessageTo(internal, send_to_all, web_contents_id,
|
electron_browser_ptr_->MessageTo(internal, send_to_all, web_contents_id,
|
||||||
|
@ -113,25 +154,25 @@ class IPCRenderer : public gin::Wrappable<IPCRenderer> {
|
||||||
const std::string& channel,
|
const std::string& channel,
|
||||||
v8::Local<v8::Value> arguments) {
|
v8::Local<v8::Value> arguments) {
|
||||||
blink::CloneableMessage message;
|
blink::CloneableMessage message;
|
||||||
if (!gin::ConvertFromV8(isolate, arguments, &message)) {
|
if (!electron::SerializeV8Value(isolate, arguments, &message)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
electron_browser_ptr_->MessageHost(channel, std::move(message));
|
electron_browser_ptr_->MessageHost(channel, std::move(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
blink::CloneableMessage SendSync(v8::Isolate* isolate,
|
v8::Local<v8::Value> SendSync(v8::Isolate* isolate,
|
||||||
bool internal,
|
bool internal,
|
||||||
const std::string& channel,
|
const std::string& channel,
|
||||||
v8::Local<v8::Value> arguments) {
|
v8::Local<v8::Value> arguments) {
|
||||||
blink::CloneableMessage message;
|
blink::CloneableMessage message;
|
||||||
if (!gin::ConvertFromV8(isolate, arguments, &message)) {
|
if (!electron::SerializeV8Value(isolate, arguments, &message)) {
|
||||||
return blink::CloneableMessage();
|
return v8::Local<v8::Value>();
|
||||||
}
|
}
|
||||||
|
|
||||||
blink::CloneableMessage result;
|
blink::CloneableMessage result;
|
||||||
electron_browser_ptr_->MessageSync(internal, channel, std::move(message),
|
electron_browser_ptr_->MessageSync(internal, channel, std::move(message),
|
||||||
&result);
|
&result);
|
||||||
return result;
|
return electron::DeserializeV8Value(isolate, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
electron::mojom::ElectronBrowserPtr electron_browser_ptr_;
|
electron::mojom::ElectronBrowserPtr electron_browser_ptr_;
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include "base/environment.h"
|
#include "base/environment.h"
|
||||||
#include "base/macros.h"
|
#include "base/macros.h"
|
||||||
#include "base/threading/thread_restrictions.h"
|
#include "base/threading/thread_restrictions.h"
|
||||||
|
#include "gin/data_object_builder.h"
|
||||||
#include "mojo/public/cpp/system/platform_handle.h"
|
#include "mojo/public/cpp/system/platform_handle.h"
|
||||||
#include "shell/common/electron_constants.h"
|
#include "shell/common/electron_constants.h"
|
||||||
#include "shell/common/gin_converters/blink_converter.h"
|
#include "shell/common/gin_converters/blink_converter.h"
|
||||||
|
@ -18,10 +19,12 @@
|
||||||
#include "shell/common/heap_snapshot.h"
|
#include "shell/common/heap_snapshot.h"
|
||||||
#include "shell/common/node_includes.h"
|
#include "shell/common/node_includes.h"
|
||||||
#include "shell/common/options_switches.h"
|
#include "shell/common/options_switches.h"
|
||||||
|
#include "shell/common/v8_value_serializer.h"
|
||||||
#include "shell/renderer/electron_render_frame_observer.h"
|
#include "shell/renderer/electron_render_frame_observer.h"
|
||||||
#include "shell/renderer/renderer_client_base.h"
|
#include "shell/renderer/renderer_client_base.h"
|
||||||
#include "third_party/blink/public/web/blink.h"
|
#include "third_party/blink/public/web/blink.h"
|
||||||
#include "third_party/blink/public/web/web_local_frame.h"
|
#include "third_party/blink/public/web/web_local_frame.h"
|
||||||
|
#include "third_party/blink/public/web/web_message_port_converter.h"
|
||||||
|
|
||||||
namespace electron {
|
namespace electron {
|
||||||
|
|
||||||
|
@ -74,6 +77,7 @@ void InvokeIpcCallback(v8::Local<v8::Context> context,
|
||||||
void EmitIPCEvent(v8::Local<v8::Context> context,
|
void EmitIPCEvent(v8::Local<v8::Context> context,
|
||||||
bool internal,
|
bool internal,
|
||||||
const std::string& channel,
|
const std::string& channel,
|
||||||
|
std::vector<v8::Local<v8::Value>> ports,
|
||||||
v8::Local<v8::Value> args,
|
v8::Local<v8::Value> args,
|
||||||
int32_t sender_id) {
|
int32_t sender_id) {
|
||||||
auto* isolate = context->GetIsolate();
|
auto* isolate = context->GetIsolate();
|
||||||
|
@ -85,7 +89,8 @@ void EmitIPCEvent(v8::Local<v8::Context> context,
|
||||||
|
|
||||||
std::vector<v8::Local<v8::Value>> argv = {
|
std::vector<v8::Local<v8::Value>> argv = {
|
||||||
gin::ConvertToV8(isolate, internal), gin::ConvertToV8(isolate, channel),
|
gin::ConvertToV8(isolate, internal), gin::ConvertToV8(isolate, channel),
|
||||||
args, gin::ConvertToV8(isolate, sender_id)};
|
gin::ConvertToV8(isolate, ports), args,
|
||||||
|
gin::ConvertToV8(isolate, sender_id)};
|
||||||
|
|
||||||
InvokeIpcCallback(context, "onMessage", argv);
|
InvokeIpcCallback(context, "onMessage", argv);
|
||||||
}
|
}
|
||||||
|
@ -161,7 +166,7 @@ void ElectronApiServiceImpl::Message(bool internal,
|
||||||
|
|
||||||
v8::Local<v8::Value> args = gin::ConvertToV8(isolate, arguments);
|
v8::Local<v8::Value> args = gin::ConvertToV8(isolate, arguments);
|
||||||
|
|
||||||
EmitIPCEvent(context, internal, channel, args, sender_id);
|
EmitIPCEvent(context, internal, channel, {}, args, sender_id);
|
||||||
|
|
||||||
// Also send the message to all sub-frames.
|
// Also send the message to all sub-frames.
|
||||||
// TODO(MarshallOfSound): Completely move this logic to the main process
|
// TODO(MarshallOfSound): Completely move this logic to the main process
|
||||||
|
@ -171,11 +176,39 @@ void ElectronApiServiceImpl::Message(bool internal,
|
||||||
if (child->IsWebLocalFrame()) {
|
if (child->IsWebLocalFrame()) {
|
||||||
v8::Local<v8::Context> child_context =
|
v8::Local<v8::Context> child_context =
|
||||||
renderer_client_->GetContext(child->ToWebLocalFrame(), isolate);
|
renderer_client_->GetContext(child->ToWebLocalFrame(), isolate);
|
||||||
EmitIPCEvent(child_context, internal, channel, args, sender_id);
|
EmitIPCEvent(child_context, internal, channel, {}, args, sender_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ElectronApiServiceImpl::ReceivePostMessage(
|
||||||
|
const std::string& channel,
|
||||||
|
blink::TransferableMessage message) {
|
||||||
|
blink::WebLocalFrame* frame = render_frame()->GetWebFrame();
|
||||||
|
if (!frame)
|
||||||
|
return;
|
||||||
|
|
||||||
|
v8::Isolate* isolate = blink::MainThreadIsolate();
|
||||||
|
v8::HandleScope handle_scope(isolate);
|
||||||
|
|
||||||
|
v8::Local<v8::Context> context = renderer_client_->GetContext(frame, isolate);
|
||||||
|
v8::Context::Scope context_scope(context);
|
||||||
|
|
||||||
|
v8::Local<v8::Value> message_value = DeserializeV8Value(isolate, message);
|
||||||
|
|
||||||
|
std::vector<v8::Local<v8::Value>> ports;
|
||||||
|
for (auto& port : message.ports) {
|
||||||
|
ports.emplace_back(
|
||||||
|
blink::WebMessagePortConverter::EntangleAndInjectMessagePortChannel(
|
||||||
|
context, std::move(port)));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<v8::Local<v8::Value>> args = {message_value};
|
||||||
|
|
||||||
|
EmitIPCEvent(context, false, channel, ports, gin::ConvertToV8(isolate, args),
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
|
||||||
#if BUILDFLAG(ENABLE_REMOTE_MODULE)
|
#if BUILDFLAG(ENABLE_REMOTE_MODULE)
|
||||||
void ElectronApiServiceImpl::DereferenceRemoteJSCallback(
|
void ElectronApiServiceImpl::DereferenceRemoteJSCallback(
|
||||||
const std::string& context_id,
|
const std::string& context_id,
|
||||||
|
@ -198,7 +231,7 @@ void ElectronApiServiceImpl::DereferenceRemoteJSCallback(
|
||||||
args.AppendInteger(object_id);
|
args.AppendInteger(object_id);
|
||||||
|
|
||||||
v8::Local<v8::Value> v8_args = gin::ConvertToV8(isolate, args);
|
v8::Local<v8::Value> v8_args = gin::ConvertToV8(isolate, args);
|
||||||
EmitIPCEvent(context, true /* internal */, channel, v8_args,
|
EmitIPCEvent(context, true /* internal */, channel, {}, v8_args,
|
||||||
0 /* sender_id */);
|
0 /* sender_id */);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -33,6 +33,8 @@ class ElectronApiServiceImpl : public mojom::ElectronRenderer,
|
||||||
const std::string& channel,
|
const std::string& channel,
|
||||||
blink::CloneableMessage arguments,
|
blink::CloneableMessage arguments,
|
||||||
int32_t sender_id) override;
|
int32_t sender_id) override;
|
||||||
|
void ReceivePostMessage(const std::string& channel,
|
||||||
|
blink::TransferableMessage message) override;
|
||||||
#if BUILDFLAG(ENABLE_REMOTE_MODULE)
|
#if BUILDFLAG(ENABLE_REMOTE_MODULE)
|
||||||
void DereferenceRemoteJSCallback(const std::string& context_id,
|
void DereferenceRemoteJSCallback(const std::string& context_id,
|
||||||
int32_t object_id) override;
|
int32_t object_id) override;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
import { EventEmitter } from 'events'
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { BrowserWindow, ipcMain, IpcMainInvokeEvent } from 'electron'
|
import { BrowserWindow, ipcMain, IpcMainInvokeEvent, MessageChannelMain } from 'electron'
|
||||||
|
import { closeAllWindows } from './window-helpers'
|
||||||
import { emittedOnce } from './events-helpers'
|
import { emittedOnce } from './events-helpers'
|
||||||
|
|
||||||
const v8Util = process.electronBinding('v8_util')
|
const v8Util = process.electronBinding('v8_util')
|
||||||
|
@ -195,4 +197,298 @@ describe('ipc module', () => {
|
||||||
expect(received).to.deep.equal([...received].sort((a, b) => a - b))
|
expect(received).to.deep.equal([...received].sort((a, b) => a - b))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('MessagePort', () => {
|
||||||
|
afterEach(closeAllWindows)
|
||||||
|
|
||||||
|
it('can send a port to the main process', async () => {
|
||||||
|
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
|
||||||
|
w.loadURL('about:blank')
|
||||||
|
const p = emittedOnce(ipcMain, 'port')
|
||||||
|
await w.webContents.executeJavaScript(`(${function () {
|
||||||
|
const channel = new MessageChannel()
|
||||||
|
require('electron').ipcRenderer.postMessage('port', 'hi', [channel.port1])
|
||||||
|
}})()`)
|
||||||
|
const [ev, msg] = await p
|
||||||
|
expect(msg).to.equal('hi')
|
||||||
|
expect(ev.ports).to.have.length(1)
|
||||||
|
const [port] = ev.ports
|
||||||
|
expect(port).to.be.an.instanceOf(EventEmitter)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can communicate between main and renderer', async () => {
|
||||||
|
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
|
||||||
|
w.loadURL('about:blank')
|
||||||
|
const p = emittedOnce(ipcMain, 'port')
|
||||||
|
await w.webContents.executeJavaScript(`(${function () {
|
||||||
|
const channel = new MessageChannel();
|
||||||
|
(channel.port2 as any).onmessage = (ev: any) => {
|
||||||
|
channel.port2.postMessage(ev.data * 2)
|
||||||
|
}
|
||||||
|
require('electron').ipcRenderer.postMessage('port', '', [channel.port1])
|
||||||
|
}})()`)
|
||||||
|
const [ev] = await p
|
||||||
|
expect(ev.ports).to.have.length(1)
|
||||||
|
const [port] = ev.ports
|
||||||
|
port.start()
|
||||||
|
port.postMessage(42)
|
||||||
|
const [ev2] = await emittedOnce(port, 'message')
|
||||||
|
expect(ev2.data).to.equal(84)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can receive a port from a renderer over a MessagePort connection', async () => {
|
||||||
|
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
|
||||||
|
w.loadURL('about:blank')
|
||||||
|
function fn () {
|
||||||
|
const channel1 = new MessageChannel()
|
||||||
|
const channel2 = new MessageChannel()
|
||||||
|
channel1.port2.postMessage('', [channel2.port1])
|
||||||
|
channel2.port2.postMessage('matryoshka')
|
||||||
|
require('electron').ipcRenderer.postMessage('port', '', [channel1.port1])
|
||||||
|
}
|
||||||
|
w.webContents.executeJavaScript(`(${fn})()`)
|
||||||
|
const [{ ports: [port1] }] = await emittedOnce(ipcMain, 'port')
|
||||||
|
port1.start()
|
||||||
|
const [{ ports: [port2] }] = await emittedOnce(port1, 'message')
|
||||||
|
port2.start()
|
||||||
|
const [{ data }] = await emittedOnce(port2, 'message')
|
||||||
|
expect(data).to.equal('matryoshka')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can forward a port from one renderer to another renderer', async () => {
|
||||||
|
const w1 = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
|
||||||
|
const w2 = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
|
||||||
|
w1.loadURL('about:blank')
|
||||||
|
w2.loadURL('about:blank')
|
||||||
|
w1.webContents.executeJavaScript(`(${function () {
|
||||||
|
const channel = new MessageChannel();
|
||||||
|
(channel.port2 as any).onmessage = (ev: any) => {
|
||||||
|
require('electron').ipcRenderer.send('message received', ev.data)
|
||||||
|
}
|
||||||
|
require('electron').ipcRenderer.postMessage('port', '', [channel.port1])
|
||||||
|
}})()`)
|
||||||
|
const [{ ports: [port] }] = await emittedOnce(ipcMain, 'port')
|
||||||
|
await w2.webContents.executeJavaScript(`(${function () {
|
||||||
|
require('electron').ipcRenderer.on('port', ({ ports: [port] }: any) => {
|
||||||
|
port.postMessage('a message')
|
||||||
|
})
|
||||||
|
}})()`)
|
||||||
|
w2.webContents.postMessage('port', '', [port])
|
||||||
|
const [, data] = await emittedOnce(ipcMain, 'message received')
|
||||||
|
expect(data).to.equal('a message')
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('MessageChannelMain', () => {
|
||||||
|
it('can be created', () => {
|
||||||
|
const { port1, port2 } = new MessageChannelMain()
|
||||||
|
expect(port1).not.to.be.null()
|
||||||
|
expect(port2).not.to.be.null()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can send messages within the process', async () => {
|
||||||
|
const { port1, port2 } = new MessageChannelMain()
|
||||||
|
port2.postMessage('hello')
|
||||||
|
port1.start()
|
||||||
|
const [ev] = await emittedOnce(port1, 'message')
|
||||||
|
expect(ev.data).to.equal('hello')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can pass one end to a WebContents', async () => {
|
||||||
|
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
|
||||||
|
w.loadURL('about:blank')
|
||||||
|
await w.webContents.executeJavaScript(`(${function () {
|
||||||
|
const { ipcRenderer } = require('electron')
|
||||||
|
ipcRenderer.on('port', (ev) => {
|
||||||
|
const [port] = ev.ports
|
||||||
|
port.onmessage = () => {
|
||||||
|
ipcRenderer.send('done')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}})()`)
|
||||||
|
const { port1, port2 } = new MessageChannelMain()
|
||||||
|
port1.postMessage('hello')
|
||||||
|
w.webContents.postMessage('port', null, [port2])
|
||||||
|
await emittedOnce(ipcMain, 'done')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can be passed over another channel', async () => {
|
||||||
|
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
|
||||||
|
w.loadURL('about:blank')
|
||||||
|
await w.webContents.executeJavaScript(`(${function () {
|
||||||
|
const { ipcRenderer } = require('electron')
|
||||||
|
ipcRenderer.on('port', (e1) => {
|
||||||
|
e1.ports[0].onmessage = (e2) => {
|
||||||
|
e2.ports[0].onmessage = (e3) => {
|
||||||
|
ipcRenderer.send('done', e3.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}})()`)
|
||||||
|
const { port1, port2 } = new MessageChannelMain()
|
||||||
|
const { port1: port3, port2: port4 } = new MessageChannelMain()
|
||||||
|
port1.postMessage(null, [port4])
|
||||||
|
port3.postMessage('hello')
|
||||||
|
w.webContents.postMessage('port', null, [port2])
|
||||||
|
const [, message] = await emittedOnce(ipcMain, 'done')
|
||||||
|
expect(message).to.equal('hello')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can send messages to a closed port', () => {
|
||||||
|
const { port1, port2 } = new MessageChannelMain()
|
||||||
|
port2.start()
|
||||||
|
port2.on('message', () => { throw new Error('unexpected message received') })
|
||||||
|
port1.close()
|
||||||
|
port1.postMessage('hello')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can send messages to a port whose remote end is closed', () => {
|
||||||
|
const { port1, port2 } = new MessageChannelMain()
|
||||||
|
port2.start()
|
||||||
|
port2.on('message', () => { throw new Error('unexpected message received') })
|
||||||
|
port2.close()
|
||||||
|
port1.postMessage('hello')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws when passing null ports', () => {
|
||||||
|
const { port1 } = new MessageChannelMain()
|
||||||
|
expect(() => {
|
||||||
|
port1.postMessage(null, [null] as any)
|
||||||
|
}).to.throw(/conversion failure/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws when passing duplicate ports', () => {
|
||||||
|
const { port1 } = new MessageChannelMain()
|
||||||
|
const { port1: port3 } = new MessageChannelMain()
|
||||||
|
expect(() => {
|
||||||
|
port1.postMessage(null, [port3, port3])
|
||||||
|
}).to.throw(/duplicate/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws when passing ports that have already been neutered', () => {
|
||||||
|
const { port1 } = new MessageChannelMain()
|
||||||
|
const { port1: port3 } = new MessageChannelMain()
|
||||||
|
port1.postMessage(null, [port3])
|
||||||
|
expect(() => {
|
||||||
|
port1.postMessage(null, [port3])
|
||||||
|
}).to.throw(/already neutered/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws when passing itself', () => {
|
||||||
|
const { port1 } = new MessageChannelMain()
|
||||||
|
expect(() => {
|
||||||
|
port1.postMessage(null, [port1])
|
||||||
|
}).to.throw(/contains the source port/)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('GC behavior', () => {
|
||||||
|
it('is not collected while it could still receive messages', async () => {
|
||||||
|
let trigger: Function
|
||||||
|
const promise = new Promise(resolve => { trigger = resolve })
|
||||||
|
const port1 = (() => {
|
||||||
|
const { port1, port2 } = new MessageChannelMain()
|
||||||
|
|
||||||
|
port2.on('message', (e) => { trigger(e.data) })
|
||||||
|
port2.start()
|
||||||
|
return port1
|
||||||
|
})()
|
||||||
|
v8Util.requestGarbageCollectionForTesting()
|
||||||
|
port1.postMessage('hello')
|
||||||
|
expect(await promise).to.equal('hello')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('WebContents.postMessage', () => {
|
||||||
|
it('sends a message', async () => {
|
||||||
|
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
|
||||||
|
w.loadURL('about:blank')
|
||||||
|
await w.webContents.executeJavaScript(`(${function () {
|
||||||
|
const { ipcRenderer } = require('electron')
|
||||||
|
ipcRenderer.on('foo', (e, msg) => {
|
||||||
|
ipcRenderer.send('bar', msg)
|
||||||
|
})
|
||||||
|
}})()`)
|
||||||
|
w.webContents.postMessage('foo', { some: 'message' })
|
||||||
|
const [, msg] = await emittedOnce(ipcMain, 'bar')
|
||||||
|
expect(msg).to.deep.equal({ some: 'message' })
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('error handling', () => {
|
||||||
|
it('throws on missing channel', async () => {
|
||||||
|
const w = new BrowserWindow({ show: false })
|
||||||
|
await w.loadURL('about:blank')
|
||||||
|
expect(() => {
|
||||||
|
(w.webContents.postMessage as any)()
|
||||||
|
}).to.throw(/Insufficient number of arguments/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws on invalid channel', async () => {
|
||||||
|
const w = new BrowserWindow({ show: false })
|
||||||
|
await w.loadURL('about:blank')
|
||||||
|
expect(() => {
|
||||||
|
w.webContents.postMessage(null as any, '', [])
|
||||||
|
}).to.throw(/Error processing argument at index 0/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws on missing message', async () => {
|
||||||
|
const w = new BrowserWindow({ show: false })
|
||||||
|
await w.loadURL('about:blank')
|
||||||
|
expect(() => {
|
||||||
|
(w.webContents.postMessage as any)('channel')
|
||||||
|
}).to.throw(/Insufficient number of arguments/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws on non-serializable message', async () => {
|
||||||
|
const w = new BrowserWindow({ show: false })
|
||||||
|
await w.loadURL('about:blank')
|
||||||
|
expect(() => {
|
||||||
|
w.webContents.postMessage('channel', w)
|
||||||
|
}).to.throw(/An object could not be cloned/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws on invalid transferable list', async () => {
|
||||||
|
const w = new BrowserWindow({ show: false })
|
||||||
|
await w.loadURL('about:blank')
|
||||||
|
expect(() => {
|
||||||
|
w.webContents.postMessage('', '', null as any)
|
||||||
|
}).to.throw(/Invalid value for transfer/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws on transferring non-transferable', async () => {
|
||||||
|
const w = new BrowserWindow({ show: false })
|
||||||
|
await w.loadURL('about:blank')
|
||||||
|
expect(() => {
|
||||||
|
(w.webContents.postMessage as any)('channel', '', [123])
|
||||||
|
}).to.throw(/Invalid value for transfer/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws when passing null ports', async () => {
|
||||||
|
const w = new BrowserWindow({ show: false })
|
||||||
|
await w.loadURL('about:blank')
|
||||||
|
expect(() => {
|
||||||
|
w.webContents.postMessage('foo', null, [null] as any)
|
||||||
|
}).to.throw(/Invalid value for transfer/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws when passing duplicate ports', async () => {
|
||||||
|
const w = new BrowserWindow({ show: false })
|
||||||
|
await w.loadURL('about:blank')
|
||||||
|
const { port1 } = new MessageChannelMain()
|
||||||
|
expect(() => {
|
||||||
|
w.webContents.postMessage('foo', null, [port1, port1])
|
||||||
|
}).to.throw(/duplicate/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws when passing ports that have already been neutered', async () => {
|
||||||
|
const w = new BrowserWindow({ show: false })
|
||||||
|
await w.loadURL('about:blank')
|
||||||
|
const { port1 } = new MessageChannelMain()
|
||||||
|
w.webContents.postMessage('foo', null, [port1])
|
||||||
|
expect(() => {
|
||||||
|
w.webContents.postMessage('foo', null, [port1])
|
||||||
|
}).to.throw(/already neutered/)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
5
typings/internal-ambient.d.ts
vendored
5
typings/internal-ambient.d.ts
vendored
|
@ -17,12 +17,13 @@ declare namespace NodeJS {
|
||||||
isComponentBuild(): boolean;
|
isComponentBuild(): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IpcBinding {
|
interface IpcRendererBinding {
|
||||||
send(internal: boolean, channel: string, args: any[]): void;
|
send(internal: boolean, channel: string, args: any[]): void;
|
||||||
sendSync(internal: boolean, channel: string, args: any[]): any;
|
sendSync(internal: boolean, channel: string, args: any[]): any;
|
||||||
sendToHost(channel: string, args: any[]): void;
|
sendToHost(channel: string, args: any[]): void;
|
||||||
sendTo(internal: boolean, sendToAll: boolean, webContentsId: number, channel: string, args: any[]): void;
|
sendTo(internal: boolean, sendToAll: boolean, webContentsId: number, channel: string, args: any[]): void;
|
||||||
invoke<T>(internal: boolean, channel: string, args: any[]): Promise<{ error: string, result: T }>;
|
invoke<T>(internal: boolean, channel: string, args: any[]): Promise<{ error: string, result: T }>;
|
||||||
|
postMessage(channel: string, message: any, transferables: MessagePort[]): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface V8UtilBinding {
|
interface V8UtilBinding {
|
||||||
|
@ -42,7 +43,7 @@ declare namespace NodeJS {
|
||||||
_linkedBinding(name: string): any;
|
_linkedBinding(name: string): any;
|
||||||
electronBinding(name: string): any;
|
electronBinding(name: string): any;
|
||||||
electronBinding(name: 'features'): FeaturesBinding;
|
electronBinding(name: 'features'): FeaturesBinding;
|
||||||
electronBinding(name: 'ipc'): { ipc: IpcBinding };
|
electronBinding(name: 'ipc'): { ipc: IpcRendererBinding };
|
||||||
electronBinding(name: 'v8_util'): V8UtilBinding;
|
electronBinding(name: 'v8_util'): V8UtilBinding;
|
||||||
electronBinding(name: 'app'): { app: Electron.App, App: Function };
|
electronBinding(name: 'app'): { app: Electron.App, App: Function };
|
||||||
electronBinding(name: 'command_line'): Electron.CommandLine;
|
electronBinding(name: 'command_line'): Electron.CommandLine;
|
||||||
|
|
Loading…
Reference in a new issue