前回の記事で「クロスオリジンのウィンドウ間でメッセージを送受信する Cross-document messaging」を紹介しましたが、これは親ウィンドウと子ウィンドウの 1 対 1 の通信チャネルでした。もし子ウィンドウが複数存在する場合、同じメッセージをまとめて送りたい場合もあるでしょう。今回はすべてのタブやウィンドウに同じメッセージを一斉に送信する BroadcastChannel について紹介します。
BroadcastChannel の概要
BroadcastChannel は、同じオリジンのウィンドウに対して、メッセージを一斉に送信することができます。ここで言うウィンドウとは、HTML の iframe
要素や object
要素で組み込まれたページや window.open()
メソッドでポップアップしたページを指します。
以前の記事で紹介した Cross-document messaging はクロスオリジンでメッセージを送信できましたが、BroadcastChannel は同じオリジンに限定されます。
一斉送信の方法はとてもシンプルです。まず各ウィンドウでブロードキャストチャンネルに参加します。参加の際にはチャンネル名を指定しなければいけません。ブロードキャストチャンネルに参加したら、あとはイベントリスナーをセットしてメッセージを待ち受けるだけです。
ブロードキャストチャンネルにメッセージを送信すると、自分以外のウィンドウすべてにそのメッセージが送信されます。ブロードキャストチャンネルが不要になったら、ウィンドウごとにチャンネルを閉じることができます。
以降、以上の手順を JavaScript コードを添えて解説します。
ブロードキャストチャンネルに参加する
ブロードキャストチャンネルへ参加するには次のように BroadcastChannel
オブジェクトを生成します。次のコードでは変数 ch
が BroadcastChannel
オブジェクトです。
const ch = new BroadcastChannel('greetings');
BroadcastChannel
コンストラクタにはチャンネル名を指定します。ここでは "greetings"
となっていますが、何でも構いません。しかし、メッセージを受信したい他のウィンドウも同じ名前を指定しなければいけません。
メッセージを送信する
ブロードキャストチャンネルへメッセージを送信するには、BroadcastChannel
オブジェクトの postMessage()
メソッドを使います。次の例では変数 ch
が BroadcastChannel
オブジェクトです。
ch.postMessage('Hello');
第一引数に指定するメッセージは文字列でもオブジェクトでも構いません。もしオブジェクトの場合は、そのクローンが相手に送られることになります。クローン処理には構造化複製アルゴリズムが使われます。Array
や Object
オブジェクトだけでなく Date
, Blob
, File
オブジェクトなども複製できます。
このメソッドが実行されると、該当のブロードキャストチャンネルに参加しているすべてのウィンドウにメッセージが送信されます。ただし、自分自身のウィンドウを除きます。
メッセージを受信する
ブロードキャストチャンネルにメッセージが送信されると、BroadcastChannel
オブジェクトで message
イベントが発生します。次のように、イベントリスナーをセットすることでメッセージを受け取ることができます。
ch.addEventListener('message', (event) => {
console.log(event.data);
}, false);
リスナーとなる関数には MessageEvent
オブジェクトが引き渡されます。上記のコードでは変数 event
が MessageEvent
オブジェクトです。 実際に送信されたメッセージは MessageEvent
オブジェクトの data
プロパティにセットされています。
チャンネルを閉じる
もしブロードキャストチャンネルが不要になったら、そのブロードキャストチャンネルを閉じることが強く推奨されます。
ch.close();
MessageEvent
オブジェクトの close()
メソッドを呼び出すことで、メッセージの送信先から除外されるだけでなく、MessageEvent
オブジェクトがガベージコレクションの対象になります。
もし close()
メソッドを呼び出さないと、とりわけ、多くのブロードキャストチャンネルの生成と破棄が繰り返される環境でメモリーリークを引き起こす可能性があります。なぜなら、MessageEvent
オブジェクトの変数が使われなくなったとしても、イベントリスナーがセットされたままだと、ガベージコレクションの対象にならないからです。
もちろん、意図的にイベントリスナーを解除しても良いのですが、MessageEvent
オブジェクトが存在している間はメッセージ送信先の対象になったままですので、不要になったら即座に close()
メソッドを呼び出すようにしましょう。
サンプル
次の例は、親ページの HTML に 3 つの iframe
要素がマークアップされています。JavaScript では、ページの読み込みが完了したら、”greetings” という名前のブロードキャストチャンネルに参加して “Hello” とメッセージを送信しています。メッセージの送信が完了したらチャンネルと閉じています。
<iframe src="child1.html"></iframe>
<iframe src="child2.html"></iframe>
<iframe src="child3.html"></iframe>
<script>
window.addEventListener('load', () => {
const ch = new BroadcastChannel('greetings');
ch.postMessage('Hello');
ch.close();
}, false);
</script>
iframe
要素に組み込まれるページでは、”greetings” という名前のブロードキャストチャンネルに参加して message
イベントのリスナーをセットしています。イベントが発生したら、メッセージを出力して、チャンネルを閉じています。
const ch = new BroadcastChannel('greetings');
ch.addEventListener('message', (event) => {
console.log(event.data);
ch.close();
}, false);
3 つの iframe
要素に組み込まれたページの JavaScript はすべて同じです。親ページが読み込まれると、コンソールには次のようなログが出力されます。
すべての iframe
要素のページにメッセージが届いていることが分かります。
まとめ
Cross-document messaging を使えば、親からすべての子ウィンドウに対して個別にメッセージを送信することで BroadcastChannel と同じようなことはできます。そのため、BroadcastChannel の必要性を感じにくいかもしれません。
しかし Cross-document messaging では、子から他のウィンドウにメッセージを同報することはできません。もしやろうとしたら、いったん親ウィンドウでメッセージを受けて、それを他の子ウィンドウに個別に送信するしかありません。
BroadcastChannel は同じオリジンのウィンドウにしか対応できませんが、メッセージを同報するシーンがあれば Cross-document messaging で何とかするのではなく BroadcastChannel を上手に使いたいものです。
今回は以上で終わりです。最後まで読んでくださりありがとうございました。それでは次回の記事までごきげんよう。