uhyohyo.net

JavaScript初級者から中級者になろう

十三章第四回 HTML5 Web Messaging 2

前回紹介したWeb Messagingには、まだ続きがあります。

それは、チャンネルメッセージングです。

これは何かというと、前回紹介したWeb Messagingにおいては、メッセージを送るpostMessageはwindowのメソッドであり、同じくメッセージを受けるのもwindowオブジェクトのmessageイベントです。

ここで、複数のウィンドウと同時に通信する場合を考えましょう。この場合、2つのウィンドウからくるメッセージが混在してしまい、判別する処理などが必要になり面倒です。

そこで、通信相手ごとに専用の回線を用意して、その中で通信することができます。これがチャンネルです。

MessageChannel

チャンネルを開設するには、MessageChannelというオブジェクトを用意します。このオブジェクトのインスタンスを作成すると、2つのMessagePortが作成され、port1port2という2つのプロパティに入ります。

var channel=new MessageChannel();
channel.port1;	//MessagePort
channel.port2;	//MessagePort

MessagePortとは、チャンネルを通してメッセージを送るための窓口のようなものです。チャンネルメッセージングにおいては、windowオブジェクトではなくMessagePortにおいてmessageイベントが発生します。

また、MessagePortは、postMessageメソッドも持っています。ここまで見て分かる通り、MessagePortは2つ組になっています。同じMessageChannelから作られたMessagePortがペアになっています。実質、MessageChannelには、このペアになったMessagePortを作る以外の機能はありません。

すなわち、あるMessagePortでpostMessageメソッドを利用すると、それで発せられたメッセージはもう1つのMessagePortへ届き、messageイベントが発生します。このようにしてチャンネルメッセージングが行われるのです。

MessagePort

それでは、MessagePortの使い方を見てみましょう。messageイベントが発生するので、その扱いかたは前回紹介した場合と同じです。

しかし、postMessageには少しだけ違いがあります。前回のwindow.postMessageの場合は、第一引数が送るデータで、第二引数がオリジンでした。しかし、MessagePortにおいては第二引数のオリジンはありません。なぜなら、MessagePortを受け取った時点(後述)で相手が誰かはわかっているので、改めてセキュリティチェックする必要はないからです。ですから、引数ひとつでメッセージを送るだけという単純な形で利用可能です。

また、MessagePortは、startcloseというメソッド(いずれも引数無し)を持ちます。実は、MessagePortを利用する際は、まずこのstartメソッドを呼び出しておかないとメッセージを受信してくれないのです。closeは反対に、受信を終了してそのポートを無効化するメソッドです。

以上を踏まえて、挙動を確かめるためのサンプルを用意しました。結果はconsole.logで表示されるのでコンソールを開きましょう。また、詳しくはソースを参照しましょう。

このサンプルで、port1のpostMessageで送ったメッセージはport2に届きます。逆も然りです。

MessagePortを渡す

さて、以上でMessagePortの挙動は理解できたと思います。しかし、このままでは使い物になりません。というのも、1つのページの中でいくら通信しても仕方ないのです。これを有効に利用するには、前回同様異なるウィンドウの間で通信しないと意味がありません。

つまり、MessagePortを作って他のウィンドウに渡せばいいのです。

この方法は、実は前回の、MessagePortを介さない通常のWindow Messagingです。つまり、まず通常のpostMessageでMessagePortを送ってやり、それを受け取った側は以後MessagePortを利用して個別のメッセージングをしようということです。

さて、postMessageでMessagePortを渡す方法ですが、じつは一筋縄ではいきません。普通に第一引数のメッセージで送ればいいように思いますが、前回「送ることが可能なもの」として紹介した中にMessagePortは含まれませんから、これでは送れません。

ここで、window.postMessageの第三引数が登場します。これは当然ながら省略可能であり、配列です。配列であるという点に注意しましょう。

この配列の中にMessagePortを入れてやれば、向こうに届きます。受け取る側は、第一引数ではありませんから、イベントオブジェクトのdataプロパティには入っていません。これは、イベントオブジェクトのportsプロパティとして配列のまま届きます。このようにしてMessagePortを受け渡すことができました。

なぜMessagePortは第一引数で普通に受け渡せないかというと、第一引数のデータは全てクローン(コピー)して送られるのに対し、MessagePortではクローンではなく現物をそのまま送らなければならない、という理屈からだそうです。第三引数は、現物を送るための機能ということになっていますが、今はMessagePort専用です。

それでは、以上を利用したサンプル2を用意しました。詳細はサンプル2のソースを参照して下さい。

PortCollection

次に紹介するのは、2012/11/02にHTML5に追加されたわりと新しい仕様です。実際にブラウザで使用可能になるのがいつかは分かりません。

それはPortCollectionというオブジェクトです。

これは、複数のMessagePortに対してまとめてメッセージを送ったりできるものです。しかし、普通に配列に入れておいてループするのでも十分だとは思いませんか。つまり、

var ports=[port1, port2, port3];	// port1〜3は適当なMessagePort

//全てのポートにメッセージを送る
ports.forEach(function(port){
  port.postMessage("foo");
});

これは、PortCollectionを用いて書くと次のようになります。

var ports=new PortCollection();
//作ってからaddメソッドで1つずつ追加する
ports.add(port1);
ports.add(port2);
ports.add(port3);

//iterateメソッドでポート全てに対してコールバックを呼んでもらう
ports.iterate(function(port){
  port.postMessage("foo");
});

使い方は、上のサンプルで紹介したメソッドの他に、removeメソッド(引数にポートを渡すと、ポート一覧の中からそのポートを除く)とcleanメソッド(ポート一覧を空にする)があります。

他にはありません。簡単ですね。

そして、ただの配列に対してPortCollectionを使う利点は、追加や、既知のポートを削除することはできても、PortCollectionの中にいったいいくつのMessagePortが入っているのかは良くわからないということです。

というのも、既にいらなくなったMessagePort(相手がいなくなって切断されたときなど)はもはや意味がありませんが、配列だと中身が勝手に変わっていいわけがありませんから、そのまま残ってしまいます。しかしPortCollectionならば、不要になったMessagePortを自動で削除してくれます。むしろ、この機能のためにPortCollectionという特別なオブジェクトが用意されたといっても過言ではありません。

ちなみに、このように不要になったものが(もはやあっても無くても影響がないものが)自動で(内部的なメモリから)消去されていく機能をガベージコレクションといいます。PortCollectionがないと、ただの配列の場合、MessagePortオブジェクトが配列の中に残ってしまいJavaScriptから参照可能なので、ガベージコレクションで消すことができないのです。

Window Messagingに関して説明することは以上です。機会があったら使ってみて下さい。