Browse Source

docs: new main -> renderers messageChannel example (#35134)

* docs: new main -> renderers messageChannel example

* consistent use of your

* fix a typo

* linting

* markdown linting

* Update docs/tutorial/message-ports.md

Co-authored-by: Erick Zhao <[email protected]>

* update code example headings, reference contextIsolation example

* remove nodeIntegration: false from browserWindows

* rename "messagePort" to "electronMessagePort" for compatibility

Co-authored-by: Kilian Valkhof <[email protected]>
Co-authored-by: Erick Zhao <[email protected]>
trop[bot] 2 years ago
parent
commit
f1c7562d9c
1 changed files with 81 additions and 22 deletions
  1. 81 22
      docs/tutorial/message-ports.md

+ 81 - 22
docs/tutorial/message-ports.md

@@ -8,8 +8,7 @@ your app.
 
 Here is a very brief example of what a MessagePort is and how it works:
 
-```js
-// renderer.js ///////////////////////////////////////////////////////////////
+```js title='renderer.js (Renderer Process)'
 // MessagePorts are created in pairs. A connected pair of message ports is
 // called a channel.
 const channel = new MessageChannel()
@@ -28,8 +27,7 @@ port2.postMessage({ answer: 42 })
 ipcRenderer.postMessage('port', null, [port1])
 ```
 
-```js
-// main.js ///////////////////////////////////////////////////////////////////
+```js title='main.js (Main Process)'
 // In the main process, we receive the port.
 ipcMain.on('port', (event) => {
   // When we receive a MessagePort in the main process, it becomes a
@@ -84,14 +82,84 @@ process, you can listen for the `close` event by calling `port.on('close',
 
 ## Example use cases
 
+### Setting up a MessageChannel between two renderers
+
+In this example, the main process sets up a MessageChannel, then sends each port
+to a different renderer. This allows renderers to send messages to each other
+without needing to use the main process as an in-between.
+
+```js title='main.js (Main Process)'
+const { BrowserWindow, app, MessageChannelMain } = require('electron')
+
+app.whenReady().then(async () => {
+  // create the windows.
+  const mainWindow = new BrowserWindow({
+    show: false,
+    webPreferences: {
+      contextIsolation: false,
+      preload: 'preloadMain.js'
+    }
+  })
+
+  const secondaryWindow = BrowserWindow({
+    show: false,
+    webPreferences: {
+      contextIsolation: false,
+      preload: 'preloadSecondary.js'
+    }
+  })
+
+  // set up the channel.
+  const { port1, port2 } = new MessageChannelMain()
+
+  // once the webContents are ready, send a port to each webContents with postMessage.
+  mainWindow.once('ready-to-show', () => {
+    mainWindow.webContents.postMessage('port', null, [port1])
+  })
+
+  secondaryWindow.once('ready-to-show', () => {
+    secondaryWindow.webContents.postMessage('port', null, [port2])
+  })
+})
+```
+
+Then, in your preload scripts you receive the port through IPC and set up the
+listeners.
+
+```js title='preloadMain.js and preloadSecondary.js (Preload scripts)'
+const { ipcRenderer } = require('electron')
+
+ipcRenderer.on('port', e => {
+  // port received, make it globally available.
+  window.electronMessagePort = e.ports[0]
+
+  window.electronMessagePort.onmessage = messageEvent => {
+    // handle message
+  }
+})
+```
+
+In this example messagePort is bound to the `window` object directly. It is better
+to use `contextIsolation` and set up specific contextBridge calls for each of your
+expected messages, but for the simplicity of this example we don't. You can find an
+example of context isolation further down this page at [Communicating directly between the main process and the main world of a context-isolated page](#communicating-directly-between-the-main-process-and-the-main-world-of-a-context-isolated-page)
+
+That means window.messagePort is globally available and you can call
+`postMessage` on it from anywhere in your app to send a message to the other
+renderer.
+
+```js title='renderer.js (Renderer Process)'
+// elsewhere in your code to send a message to the other renderers message handler
+window.electronMessagePort.postmessage('ping')
+```
+
 ### Worker process
 
 In this example, your app has a worker process implemented as a hidden window.
 You want the app page to be able to communicate directly with the worker
 process, without the performance overhead of relaying via the main process.
 
-```js
-// main.js ///////////////////////////////////////////////////////////////////
+```js title='main.js (Main Process)'
 const { BrowserWindow, app, ipcMain, MessageChannelMain } = require('electron')
 
 app.whenReady().then(async () => {
@@ -129,8 +197,7 @@ app.whenReady().then(async () => {
 })
 ```
 
-```html
-<!-- worker.html ------------------------------------------------------------>
+```html  title='worker.html'
 <script>
 const { ipcRenderer } = require('electron')
 
@@ -153,8 +220,7 @@ ipcRenderer.on('new-client', (event) => {
 </script>
 ```
 
-```html
-<!-- app.html --------------------------------------------------------------->
+```html  title='app.html'
 <script>
 const { ipcRenderer } = require('electron')
 
@@ -182,9 +248,7 @@ Electron's built-in IPC methods only support two modes: fire-and-forget
 can implement a "response stream", where a single request responds with a
 stream of data.
 
-```js
-// renderer.js ///////////////////////////////////////////////////////////////
-
+```js title='renderer.js (Renderer Process)'
 const makeStreamingRequest = (element, callback) => {
   // MessageChannels are lightweight--it's cheap to create a new one for each
   // request.
@@ -213,9 +277,7 @@ makeStreamingRequest(42, (data) => {
 // We will see "got response data: 42" 10 times.
 ```
 
-```js
-// main.js ///////////////////////////////////////////////////////////////////
-
+```js  title='main.js (Main Process)'
 ipcMain.on('give-me-a-stream', (event, msg) => {
   // The renderer has sent us a MessagePort that it wants us to send our
   // response over.
@@ -242,8 +304,7 @@ the renderer are delivered to the isolated world, rather than to the main
 world. Sometimes you want to deliver messages to the main world directly,
 without having to step through the isolated world.
 
-```js
-// main.js ///////////////////////////////////////////////////////////////////
+```js title='main.js (Main Process)'
 const { BrowserWindow, app, MessageChannelMain } = require('electron')
 const path = require('path')
 
@@ -278,8 +339,7 @@ app.whenReady().then(async () => {
 })
 ```
 
-```js
-// preload.js ////////////////////////////////////////////////////////////////
+```js title='preload.js (Preload Script)'
 const { ipcRenderer } = require('electron')
 
 // We need to wait until the main world is ready to receive the message before
@@ -297,8 +357,7 @@ ipcRenderer.on('main-world-port', async (event) => {
 })
 ```
 
-```html
-<!-- index.html ------------------------------------------------------------->
+```html title='index.html'
 <script>
 window.onmessage = (event) => {
   // event.source === window means the message is coming from the preload