【JavaScript】すべてのウィンドウにメッセージを同報する BroadcastChannel

前回の記事で「クロスオリジンのウィンドウ間でメッセージを送受信する Cross-document messaging」を紹介しましたが、これは親ウィンドウと子ウィンドウの 1 対 1 の通信チャネルでした。もし子ウィンドウが複数存在する場合、同じメッセージをまとめて送りたい場合もあるでしょう。今回はすべてのタブやウィンドウに同じメッセージを一斉に送信する BroadcastChannel について紹介します。

BroadcastChannel の概要

BroadcastChannel は、同じオリジンのウィンドウに対して、メッセージを一斉に送信することができます。ここで言うウィンドウとは、HTML の iframe 要素や object 要素で組み込まれたページや window.open() メソッドでポップアップしたページを指します。

以前の記事で紹介した Cross-document messaging はクロスオリジンでメッセージを送信できましたが、BroadcastChannel は同じオリジンに限定されます。

一斉送信の方法はとてもシンプルです。まず各ウィンドウでブロードキャストチャンネルに参加します。参加の際にはチャンネル名を指定しなければいけません。ブロードキャストチャンネルに参加したら、あとはイベントリスナーをセットしてメッセージを待ち受けるだけです。

ブロードキャストチャンネルにメッセージを送信すると、自分以外のウィンドウすべてにそのメッセージが送信されます。ブロードキャストチャンネルが不要になったら、ウィンドウごとにチャンネルを閉じることができます。

以降、以上の手順を JavaScript コードを添えて解説します。

ブロードキャストチャンネルに参加する

ブロードキャストチャンネルへ参加するには次のように BroadcastChannel オブジェクトを生成します。次のコードでは変数 chBroadcastChannel オブジェクトです。

const ch = new BroadcastChannel('greetings');

BroadcastChannel コンストラクタにはチャンネル名を指定します。ここでは "greetings" となっていますが、何でも構いません。しかし、メッセージを受信したい他のウィンドウも同じ名前を指定しなければいけません。

メッセージを送信する

ブロードキャストチャンネルへメッセージを送信するには、BroadcastChannel オブジェクトの postMessage() メソッドを使います。次の例では変数 chBroadcastChannel オブジェクトです。

ch.postMessage('Hello');

第一引数に指定するメッセージは文字列でもオブジェクトでも構いません。もしオブジェクトの場合は、そのクローンが相手に送られることになります。クローン処理には構造化複製アルゴリズムが使われます。Array や Object オブジェクトだけでなく DateBlobFile オブジェクトなども複製できます。

このメソッドが実行されると、該当のブロードキャストチャンネルに参加しているすべてのウィンドウにメッセージが送信されます。ただし、自分自身のウィンドウを除きます。

メッセージを受信する

ブロードキャストチャンネルにメッセージが送信されると、BroadcastChannel オブジェクトで message イベントが発生します。次のように、イベントリスナーをセットすることでメッセージを受け取ることができます。

ch.addEventListener('message', (event) => {
  console.log(event.data);
}, false);

リスナーとなる関数には MessageEvent オブジェクトが引き渡されます。上記のコードでは変数 eventMessageEvent オブジェクトです。 実際に送信されたメッセージは 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 を上手に使いたいものです。

今回は以上で終わりです。最後まで読んでくださりありがとうございました。それでは次回の記事までごきげんよう。

Share