チャネルメッセージングを実装するには?
解説
ドキュメント間でデータのやり取りを行うには、windowオブジェクト間のデータ送受信である、クロスドキュメントメッセージングを利用することができます。(クロスドキュメントメッセージングについてはクロスドキュメントメッセージングを実装するには?を参照ください)
しかし、クロスドキュメントメッセージングは手軽に利用できる反面、通信がwindow単位となるため、独立した複数の種類のメッセージが並行してやり取りされるといった場合には、メッセージ受信イベントのハンドラで、煩雑な振り分け処理を行う必要があります。
チャネルメッセージングでは、こうした状況に対応するため、window単位ではなく、任意の数だけ生成可能なMessageChannelオブジェクトを利用して通信を行います。
MessageChannelオブジェクトは、個別の通信経路を抽象化したもので、それ自体にはport1とport2という属性しか存在しません。このport1属性とport2属性にそれぞれ格納されたMessagePortオブジェクトが、実際のメッセージの送受信を担当します。
MessageChannelオブジェクトを介して、port1とport2のMessagePortオブジェクトは、互いに相手とメッセージを送受信する関係になっています。MessageChannelオブジェクトを通信ケーブルとした場合の、両端の端末のような関係に当たります。
MessagePortオブジェクトには以下のようなメソッドとイベントハンドラが存在し、port1もしくはport2の一方のMessagePortオブジェクトが「postMessage(message)」メソッドを呼び出してメッセージを送信すると、他方のMessagePortオブジェクトで、そのメッセージを受信するmessageイベントが発生します。
以下は同一ドキュメント内でのチャネルメッセージングの例です。
// 通信チャンネル生成処理
var channel = new MessagePort();
// port1での通信開始。通信を行うためには必須。
channel.port1.start();
// port1のメッセージ受信ハンドラ定義
channel.port1.onmessage = function(event){
console.log(event.data);
}
// port2での通信開始。通信を行うためには必須。
channel.port2.start();
// port2のメッセージ受信ハンドラ定義
channel.port2.onmessage = function(event){
console.log(event.data);
}
// port1からport2へのメッセージ送信
// この後、port2のメッセージ受信ハンドラが呼ばれる
channel.port1.postMessage("送信データ");
異なるドキュメントの間でのチャネルメッセージング
チャネルメッセージングを、異なるドキュメントの間で行うためには、通信を行うMessageChannelオブジェクトのport1またはport2のどちらかのMessagePortオブジェクトを、通信先のドキュメントに送り届ける必要があります。ドキュメントをまたいで存在するport1とport2の間で通信することで、ドキュメント間の通信を行います。
これには、クロスドキュメントメッセージングの仕組みを使用します。クロスドキュメントメッセージングの送信メソッド、「window.postMessage(message, targetOrigin)」には、省略可能な追加の引数として、MessagePortオブジェクトの配列を渡すことができ、これによって、別のドキュメントにMessagePortを渡すことができます。
ただし、仕様の改訂を反映して、この追加の引数の位置が、ブラウザによって異なります。ChromeとSafariでは、この追加の引数は、messageとtargetOriginの間に割り込む形になります。
var channel = new MessageChannel();
channel.port1.start();
channel.port2.start();
window.postMessage("送信メッセージ",[channel.port2],"http://example.com");
これに対して、Operaでは、最後に引数が追加されます。(FireFoxとIEはチャネルメッセージングをサポートしていません)
window.postMessage("送信メッセージ","http://example.com",[channel.port2]);
MessagePortオブジェクトは配列に複数格納して渡すことができますが、同一のインスタンスを重複して格納した場合、もしくは同一のMessageChannelオブジェクトに属するMessagePortオブジェクトを両方とも格納した場合はエラーになります。
クロスドキュメントメッセージングで相手先に渡されるMessagePortオブジェクトは、そのものではなく複製になりますが、メッセージの送受信は同じように行われます。
送信したMessagePortオブジェクトの配列は、相手先の受信イベントの引数であるMessageEventオブジェクトのports属性を参照することで取得できます。
// 受信側のコード
window.onmessagge(event){
// この例では送信元のchannel.port2が取得される
var port = event.ports[0];
// 受け取ったMessagePortを利用して通信を行う
port.onmessage = function(event) {
// 受け取ったデータを返信する
port.postMessage(event.data);
}
}
Web Workersのワーカーとの通信もチャネルメッセージングのAPIを利用して実装されています。
関連項目