Electron での MessagePort
MessagePort
は、異なるコンテキスト間でメッセージを受け渡すことができるウェブ機能です。 window.postMessage
に似ていますが、こちらはチャンネルが別々になります。 このドキュメントの目的は、Electron で拡張した Channel Messaging モデルの説明と、アプリ内での MessagePort の使用方法の例示です。
MessagePort がどのようなものでどのように動作するのかを、以下で簡単に説明します。
// MessagePort はペアで作成されます。 接続されたメッセージポートのペアを
// チャンネルといいます。
const channel = new MessageChannel()
// port1 と port2 の違いは、その使い方だけです。 port1 に
// 送信されたメッセージは port2 で受信され、逆も同様です。
const port1 = channel.port1
const port2 = channel.port2
// 受信側がリスナーを登録する前にそのチャンネルへメッセージを送信しても
// 大丈夫です。 リスナーが登録されるまでメッセージはキューに溜められます。
port2.postMessage({ answer: 42 })
// ここでチャネルの他方である port1 をメインプロセスに送信します。 MessagePort を
// 他のフレームや Web Worker などに送信することも可能です。
ipcRenderer.postMessage('port', null, [port1])
// メインプロセスでは、そのポートを受け取ります。
ipcMain.on('port', (event) => {
// メインプロセスで MessagePort を受信すると、それは
// MessagePortMain に変化します。
const port = event.ports[0]
// MessagePortMain はウェブ方式のイベント API ではなく
// Node.js 方式のイベント API を使用しています。 そのため .onmessage = ... ではなく .on('message', ...) とします。
port.on('message', (event) => {
// data は { answer: 42 }
const data = event.data
})
// MessagePortMain は .start() メソッドが呼ばれるまでメッセージをキューに溜めます。
port.start()
})
Channel Messaging API のドキュメントは、MessagePort の動作原理をより詳しく知るのによいでしょう。
メインプロセスでの MessagePort
レンダラーでの MessagePort
クラスは、ウェブ上とまったく同じように動作します。 メインプロセスはウェブページではありませんが、Blink と統合していないので MessagePort
や MessageChannel
のクラスがありません。 メインプロセスで MessagePort をハンドルしてやり取りするために、Electron は 2 つの新しいクラス MessagePortMain
と MessageChannelMain
を追加しています。 これらはレンダラーの類似クラスと同様に動作します。
MessagePort
オブジェクトは、レンダラープロセスかメインプロセスのいずれかで作成し、ipcRenderer.postMessage
や WebContents.postMessage
メソッドを使用して反対側へ送ります。 注意として、send
や invoke
のような通常の IPC メソッドは MessagePort
の転送に使用できず、postMessage
メソッドだけが MessagePort
を転送できます。
メインプロセス経由で MessagePort
を渡すと、他の方法では (同一オリジン制限などのため) 通信できないかもしれない 2 つのページを接続できます。
拡張: close
イベント
Electron は MessagePort
をより便利にするため、ウェブにない機能を追加しました。 それは、チャンネルの反対側が閉じられたときに発火する close
イベントです。 ポートはガベージコレクションによって暗黙的に閉じることもあります。
レンダラーでは、port.onclose
に代入するか port.addEventListener('close', ...)
を呼ぶことで close
イベントをリッスンできます。 メインプロセスでは、port.on('close', ...)
を呼ぶことで close
イベントをリッスンできます。
ユースケース例
2つのレンダラー間でMessageChannelを設定する
この例では、メ インプロセスは MessageChannel を設定し、それぞれのポートを異なるレンダラーに送信します。 これにより、レンダラーはメインプロセスを中間プロセスとして使用せずに相互にメッセージを送信することができます。
const { BrowserWindow, app, MessageChannelMain } = require('electron')
app.whenReady().then(async () => {
// ウインドウを作成します。
const mainWindow = new BrowserWindow({
show: false,
webPreferences: {
contextIsolation: false,
preload: 'preloadMain.js'
}
})
const secondaryWindow = new BrowserWindow({
show: false,
webPreferences: {
contextIsolation: false,
preload: 'preloadSecondary.js'
}
})
// チャンネルをセットアップします。
const { port1, port2 } = new MessageChannelMain()
// webContents の準備ができたら、 postMessage を用いてそれぞれの webContents にポートを送信します。
mainWindow.once('ready-to-show', () => {
mainWindow.webContents.postMessage('port', null, [port1])
})
secondaryWindow.once('ready-to-show', () => {
secondaryWindow.webContents.postMessage('port', null, [port2])
})
})
次に、プリロードスクリプトでIPCを通じてポートを受信し、リスナーを設定します。
const { ipcRenderer } = require('electron')
ipcRenderer.on('port', e => {
// ポートを受信できたら、グローバルに利用可能とします。
window.electronMessagePort = e.ports[0]
window.electronMessagePort.onmessage = messageEvent => {
// メッセージをハンドルします
}
})