Skip to content

Commit 4ace499

Browse files
authored
feat: MessagePorts in the main process (#24323)
Co-authored-by: Milan Burda <miburda@microsoft.com>
1 parent 71e3296 commit 4ace499

34 files changed

Lines changed: 1319 additions & 114 deletions

docs/api/ipc-renderer.md

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ Removes all listeners, or those of the specified `channel`.
5757

5858
Send an asynchronous message to the main process via `channel`, along with
5959
arguments. Arguments will be serialized with the [Structured Clone
60-
Algorithm][SCA], just like [`postMessage`][], so prototype chains will not be
60+
Algorithm][SCA], just like [`window.postMessage`][], so prototype chains will not be
6161
included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will
6262
throw an exception.
6363

@@ -68,6 +68,10 @@ throw an exception.
6868
The main process handles it by listening for `channel` with the
6969
[`ipcMain`](ipc-main.md) module.
7070

71+
If you need to transfer a [`MessagePort`][] to the main process, use [`ipcRenderer.postMessage`](#ipcrendererpostmessagechannel-message-transfer).
72+
73+
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).
74+
7175
### `ipcRenderer.invoke(channel, ...args)`
7276

7377
* `channel` String
@@ -77,7 +81,7 @@ Returns `Promise<any>` - Resolves with the response from the main process.
7781

7882
Send a message to the main process via `channel` and expect a result
7983
asynchronously. Arguments will be serialized with the [Structured Clone
80-
Algorithm][SCA], just like [`postMessage`][], so prototype chains will not be
84+
Algorithm][SCA], just like [`window.postMessage`][], so prototype chains will not be
8185
included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will
8286
throw an exception.
8387

@@ -102,6 +106,10 @@ ipcMain.handle('some-name', async (event, someArgument) => {
102106
})
103107
```
104108

109+
If you need to transfer a [`MessagePort`][] to the main process, use [`ipcRenderer.postMessage`](#ipcrendererpostmessagechannel-message-transfer).
110+
111+
If you do not need a respons to the message, consider using [`ipcRenderer.send`](#ipcrenderersendchannel-args).
112+
105113
### `ipcRenderer.sendSync(channel, ...args)`
106114

107115
* `channel` String
@@ -111,7 +119,7 @@ Returns `any` - The value sent back by the [`ipcMain`](ipc-main.md) handler.
111119

112120
Send a message to the main process via `channel` and expect a result
113121
synchronously. Arguments will be serialized with the [Structured Clone
114-
Algorithm][SCA], just like [`postMessage`][], so prototype chains will not be
122+
Algorithm][SCA], just like [`window.postMessage`][], so prototype chains will not be
115123
included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will
116124
throw an exception.
117125

@@ -127,6 +135,35 @@ and replies by setting `event.returnValue`.
127135
> last resort. It's much better to use the asynchronous version,
128136
> [`invoke()`](ipc-renderer.md#ipcrendererinvokechannel-args).
129137
138+
### `ipcRenderer.postMessage(channel, message, [transfer])`
139+
140+
* `channel` String
141+
* `message` any
142+
* `transfer` MessagePort[] (optional)
143+
144+
Send a message to the main process, optionally transferring ownership of zero
145+
or more [`MessagePort`][] objects.
146+
147+
The transferred `MessagePort` objects will be available in the main process as
148+
[`MessagePortMain`](message-port-main.md) objects by accessing the `ports`
149+
property of the emitted event.
150+
151+
For example:
152+
```js
153+
// Renderer process
154+
const { port1, port2 } = new MessageChannel()
155+
ipcRenderer.postMessage('port', { message: 'hello' }, [port1])
156+
157+
// Main process
158+
ipcMain.on('port', (e, msg) => {
159+
const [port] = e.ports
160+
// ...
161+
})
162+
```
163+
164+
For more information on using `MessagePort` and `MessageChannel`, see the [MDN
165+
documentation](https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel).
166+
130167
### `ipcRenderer.sendTo(webContentsId, channel, ...args)`
131168

132169
* `webContentsId` Number
@@ -150,4 +187,5 @@ in the [`ipc-renderer-event`](structures/ipc-renderer-event.md) structure docs.
150187

151188
[event-emitter]: https://nodejs.org/api/events.html#events_class_eventemitter
152189
[SCA]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
153-
[`postMessage`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
190+
[`window.postMessage`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
191+
[`MessagePort`]: https://developer.mozilla.org/en-US/docs/Web/API/MessagePort

docs/api/message-channel-main.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# MessageChannelMain
2+
3+
`MessageChannelMain` is the main-process-side equivalent of the DOM
4+
[`MessageChannel`][] object. Its singular function is to create a pair of
5+
connected [`MessagePortMain`](message-port-main.md) objects.
6+
7+
See the [Channel Messaging API][] documentation for more information on using
8+
channel messaging.
9+
10+
## Class: MessageChannelMain
11+
12+
Example:
13+
```js
14+
const { port1, port2 } = new MessageChannelMain()
15+
w.webContents.postMessage('port', null, [port2])
16+
port1.postMessage({ some: 'message' })
17+
```
18+
19+
### Instance Properties
20+
21+
#### `channel.port1`
22+
23+
A [`MessagePortMain`](message-port-main.md) property.
24+
25+
#### `channel.port2`
26+
27+
A [`MessagePortMain`](message-port-main.md) property.
28+
29+
[`MessageChannel`]: https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel
30+
[Channel Messaging API]: https://developer.mozilla.org/en-US/docs/Web/API/Channel_Messaging_API

docs/api/message-port-main.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# MessagePortMain
2+
3+
`MessagePortMain` is the main-process-side equivalent of the DOM
4+
[`MessagePort`][] object. It behaves similarly to the DOM version, with the
5+
exception that it uses the Node.js `EventEmitter` event system, instead of the
6+
DOM `EventTarget` system. This means you should use `port.on('message', ...)`
7+
to listen for events, instead of `port.onmessage = ...` or
8+
`port.addEventListener('message', ...)`
9+
10+
See the [Channel Messaging API][] documentation for more information on using
11+
channel messaging.
12+
13+
`MessagePortMain` is an [EventEmitter][event-emitter].
14+
15+
## Class: MessagePortMain
16+
17+
### Instance Methods
18+
19+
#### `port.postMessage(message, [transfer])`
20+
21+
* `message` any
22+
* `transfer` MessagePortMain[] (optional)
23+
24+
Sends a message from the port, and optionally, transfers ownership of objects
25+
to other browsing contexts.
26+
27+
#### `port.start()`
28+
29+
Starts the sending of messages queued on the port. Messages will be queued
30+
until this method is called.
31+
32+
#### `port.close()`
33+
34+
Disconnects the port, so it is no longer active.
35+
36+
### Instance Events
37+
38+
#### Event: 'message'
39+
40+
Returns:
41+
42+
* `messageEvent` Object
43+
* `data` any
44+
* `ports` MessagePortMain[]
45+
46+
Emitted when a MessagePortMain object receives a message.
47+
48+
[`MessagePort`]: https://developer.mozilla.org/en-US/docs/Web/API/MessagePort
49+
[Channel Messaging API]: https://developer.mozilla.org/en-US/docs/Web/API/Channel_Messaging_API

docs/api/structures/ipc-main-event.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* `frameId` Integer - The ID of the renderer frame that sent this message
44
* `returnValue` any - Set this to the value to be returned in a synchronous message
55
* `sender` WebContents - Returns the `webContents` that sent the message
6+
* `ports` MessagePortMain[] - A list of MessagePorts that were transferred with this message
67
* `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.
78
* `channel` String
89
* `...args` any[]

docs/api/structures/ipc-renderer-event.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22

33
* `sender` IpcRenderer - The `IpcRenderer` instance that emitted the event originally
44
* `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`.
5+
* `ports` MessagePort[] - A list of MessagePorts that were transferred with this message
56

67
[ipc-renderer-sendto]: #ipcrenderersendtowindowid-channel--arg1-arg2-

docs/api/web-contents.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1603,6 +1603,32 @@ ipcMain.on('ping', (event) => {
16031603
})
16041604
```
16051605

1606+
#### `contents.postMessage(channel, message, [transfer])`
1607+
1608+
* `channel` String
1609+
* `message` any
1610+
* `transfer` MessagePortMain[] (optional)
1611+
1612+
Send a message to the renderer process, optionally transferring ownership of
1613+
zero or more [`MessagePortMain`][] objects.
1614+
1615+
The transferred `MessagePortMain` objects will be available in the renderer
1616+
process by accessing the `ports` property of the emitted event. When they
1617+
arrive in the renderer, they will be native DOM `MessagePort` objects.
1618+
1619+
For example:
1620+
```js
1621+
// Main process
1622+
const { port1, port2 } = new MessageChannelMain()
1623+
webContents.postMessage('port', { message: 'hello' }, [port1])
1624+
1625+
// Renderer process
1626+
ipcRenderer.on('port', (e, msg) => {
1627+
const [port] = e.ports
1628+
// ...
1629+
})
1630+
```
1631+
16061632
#### `contents.enableDeviceEmulation(parameters)`
16071633

16081634
* `parameters` Object

filenames.auto.gni

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ auto_filenames = {
3232
"docs/api/locales.md",
3333
"docs/api/menu-item.md",
3434
"docs/api/menu.md",
35+
"docs/api/message-channel-main.md",
36+
"docs/api/message-port-main.md",
3537
"docs/api/modernization",
3638
"docs/api/native-image.md",
3739
"docs/api/native-theme.md",
@@ -223,6 +225,7 @@ auto_filenames = {
223225
"lib/browser/api/menu-item.js",
224226
"lib/browser/api/menu-utils.js",
225227
"lib/browser/api/menu.js",
228+
"lib/browser/api/message-channel.ts",
226229
"lib/browser/api/module-list.ts",
227230
"lib/browser/api/native-theme.ts",
228231
"lib/browser/api/net-log.js",
@@ -258,6 +261,7 @@ auto_filenames = {
258261
"lib/browser/ipc-main-impl.ts",
259262
"lib/browser/ipc-main-internal-utils.ts",
260263
"lib/browser/ipc-main-internal.ts",
264+
"lib/browser/message-port-main.ts",
261265
"lib/browser/navigation-controller.js",
262266
"lib/browser/remote/objects-registry.ts",
263267
"lib/browser/remote/server.ts",

filenames.gni

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ filenames = {
125125
"shell/browser/api/gpu_info_enumerator.h",
126126
"shell/browser/api/gpuinfo_manager.cc",
127127
"shell/browser/api/gpuinfo_manager.h",
128+
"shell/browser/api/message_port.cc",
129+
"shell/browser/api/message_port.h",
128130
"shell/browser/api/process_metric.cc",
129131
"shell/browser/api/process_metric.h",
130132
"shell/browser/api/save_page_handler.cc",
@@ -510,6 +512,7 @@ filenames = {
510512
"shell/common/gin_helper/event_emitter_caller.h",
511513
"shell/common/gin_helper/function_template.cc",
512514
"shell/common/gin_helper/function_template.h",
515+
"shell/common/gin_helper/function_template_extensions.h",
513516
"shell/common/gin_helper/locker.cc",
514517
"shell/common/gin_helper/locker.h",
515518
"shell/common/gin_helper/microtasks_scope.cc",
@@ -561,6 +564,8 @@ filenames = {
561564
"shell/common/skia_util.h",
562565
"shell/common/v8_value_converter.cc",
563566
"shell/common/v8_value_converter.h",
567+
"shell/common/v8_value_serializer.cc",
568+
"shell/common/v8_value_serializer.h",
564569
"shell/common/world_ids.h",
565570
"shell/renderer/api/context_bridge/object_cache.cc",
566571
"shell/renderer/api/context_bridge/object_cache.h",

lib/browser/api/message-channel.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { MessagePortMain } from '@electron/internal/browser/message-port-main';
2+
const { createPair } = process.electronBinding('message_port');
3+
4+
export default class MessageChannelMain {
5+
port1: MessagePortMain;
6+
port2: MessagePortMain;
7+
constructor () {
8+
const { port1, port2 } = createPair();
9+
this.port1 = new MessagePortMain(port1);
10+
this.port2 = new MessagePortMain(port2);
11+
}
12+
}

lib/browser/api/module-list.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export const browserModuleList: ElectronInternal.ModuleEntry[] = [
1414
{ name: 'inAppPurchase', loader: () => require('./in-app-purchase') },
1515
{ name: 'Menu', loader: () => require('./menu') },
1616
{ name: 'MenuItem', loader: () => require('./menu-item') },
17+
{ name: 'MessageChannelMain', loader: () => require('./message-channel') },
1718
{ name: 'nativeTheme', loader: () => require('./native-theme') },
1819
{ name: 'net', loader: () => require('./net') },
1920
{ name: 'netLog', loader: () => require('./net-log') },

0 commit comments

Comments
 (0)