目的
HTML5 に Web Messaging API というのがある。サーバを介さずブラウザ間で通信ができるインターフェイスが提供されている。 調査がてら把握したことを整理。
概要と用途
Web Messaging API はブラウザの任意のドキュメント間での通信をサポートするインターフェイス。現在、postMessage
と MessageChannel
という API が用意されている。どちらも任意の window オブジェクト同士で、任意のタイミングでデータの受送信をさせることが可能。
よく使われる用途として、iframe
で別ドメインのコンテンツを読み込み、そのドメインと通信させる。 例えば、Facebook のいいねボタンは、 iframe で facebook.com のドメインを埋め込み通信させ、その中の cookie の値を取得してそのブラウザが保持するユーザ情報を表示しているみたい。また、Facebook や Google+ をはじめ OAuth に関わる SDK の JavaScript にもこの Web Messaging API がよく使われている印象。
本記事では Web Messaging をサポートするモジュール postMessage
と MessageChannel
をざっくり把握する。
postMessage
概要
- 異なるオリジンの間での通信を安全に(オリジンを確認することで)行うことを目的とした API。
- モダンブラウザ全てに対応している。(参考)
- 通信したい各々の window に "message" イベントを束縛させ、任意のタイミングでデータを送信できる。データを受信した側は、そのデータとオリジンの URL を取得できる。
仕様
送信
<データを送信する主体(window オブジェクト)>.postMessage(<データ(任意の型)>, <送信先のドメイン(String)>);
受信
<データを受信する主体(window オブジェクト)>.addEventListener('message', <ハンドラ関数(Function)>, false);
ハンドラ関数の引数 event
に含まれる値は以下。
プロパティ | 詳細 |
---|---|
event.data | 送信元から受信したデータ |
event.origin | 送信元のオリジンの URL |
event.source | 送信元への window オブジェクトの参照 |
サンプルコード
ドメイン A のdomainA.html
にドメイン B の domainB.html
を埋め込む。
ドメイン A の domainA.html
<iframe id="domainBIframe" src="<ドメイン B>/domainB.html">
<script src="domainA.js"></script>
// domeinB.html を埋め込む iframe DOM の ID
var domainBIframe = document.getElementById('domainBIframe');
/**
* この関数は任意のトリガでドメイン B にデータを送信する。
*/
var SendMsgToDomainB = function() {
var sendData = <送信するデータの文字列>;
// iframe の DOM の contentWindow プロパティに対して postMessage で送信する。
if (domainBIframe.contentWindow) {
// `postMessage ` を使ってデータを送信。
domainBIframe.contentWindow.postMessage(sendData, <ドメイン B>);
}
};
/**
* この関数は "message" イベント発火時に処理される。
*/
var receiveMessage = function(event) {
// 引数のオリジンの URL のチェック
if (event.origin !== <ドメイン B>) {
console.warn('INVALID ORIGIN');
return;
}
// 受け取ったデータの出力。
console.info(event.data);
};
// DomainA の window に "message" イベントリスナーを束縛。
window.addEventListener('message', receiveMessage, false);
ドメイン B の domainB.html
<script src="domainB.js"></script>
/**
* この関数は任意のトリガでドメイン A にデータを送信する。
*/
var SendMsgToDomainA = function() {
var sendData = <送信するデータの文字列>;
// postMessage を使ってデータを送信。この window はドメイン B の window を指す。この window は iframe で埋め込むことを想定し、window.parent は iframe 外のドメイン A の window。
window.parent.postMessage(sendData, <ドメイン A>);
};
/**
* この関数は "message" イベント発火時に処理される。
*/
var receiveMessage = function(event) {
// 引数のオリジンの URL のチェック
if (event.origin !== <ドメイン A>) {
console.warn('INVALID ORIGIN');
return;
}
// 受け取ったデータの出力。
console.info(event.data);
};
// ドメイン B の window に "message" イベントリスナーを束縛。
window.addEventListener('message', receiveMessage, false);
MessageChannel
概要
- データの送受信を
postMessage
のように window 単位で行うのではなく、任意の個数の生成が可能なMessageChannel
オブジェクトを使用して通信する。 - webkit 系ブラウザのみ対応。(参考)
-
MessageChannel
のインスタンスを生成すると、インスタンスのプロパティに port1、port2 というMessagePort
オブジェクトが生成される。この 2 つのオブジェクトを任意のオリジンに置き、各々に "message" イベントを束縛させることでペアリングする。ペアリング後、任意のタイミングでデータを受送信する。postMessage
と違い、ペアリングしたオリジン同士でのみ通信するので、データ受信時にオリジンの URL を受け取らない。
API
初期化
var channel = new MessageChannel()
channel インスタンスに含まれる値(メソッド)は以下。
プロパティ | 詳細 |
---|---|
channel.port1 | channel.port2 と通信させる対象の MessagePort オブジェクト |
channel.port2 | channel.port1 と通信させる対象の MessagePort オブジェクト |
送信
<channel.port1 または channel.port2>.postMessage(<データ(任意の型)>, <送信先のドメイン(String)>)
受信
<channel.port1 または channel.port2>.addEventListener('message', <ハンドラ関数(Function)>, false);
ハンドラ関数の引数 event
に含まれる値は以下。
プロパティ | 詳細 |
---|---|
event.data | 送信元から受信したデータ |
event.origin | 送信元のオリジンの URL |
event.source | 送信元への window オブジェクトの参照 |
サンプルコード
ドメイン A の domainA.html
にドメイン B の domainB.html
を埋め込む。
ドメイン A の domainA.html
<iframe id="domainBIframe" src="<ドメイン B>/domainB.html">
<script src="domainA.js"></script>
// domeinB.html を埋め込む iframe DOM の ID
var domainBIframe = document.getElementById('domainBIframe');
var port = null;
/**
* この関数は任意のトリガでドメイン B にデータを送信する。
*/
var SendMsgToInner = function() {
var sendData = <送信するデータの文字列>;
// port オブジェクトに対して postMessage で送信する。
if (port) {
// postMessage を使ってデータを送信。
port.postMessage(sendData);
}
};
/**
* この関数は "message" イベント発火時に処理される。
*/
var receiveMessage = function(event) {
console.info(event);
};
/**
* ペアリング完了後の通信のイベントハンドラ
*/
var paringToDomainA = function(event) {
port = event.ports[0];
console.info(event);
port.addEventListener('message', receiveMessage, false);
};
// ペアリングするために初回のみ、ドメイン B と通信する。
window.addEventListener('message', paringToDomainA, false);
ドメイン B の domainB.html
<script src="domainB.js"></script>
var parentOrigin = '<ドメイン A>';
// MessageChannel のインスタンスを生成する。
var channel = new MessageChannel;
// インスタンスの port1 をドメイン B、port2 をドメイン A のメッセージ受送信をする主体オブジェクトに割り当てる。
var port = channel.port1;
window.parent.postMessage('接続開始', [channel.port2], parentOrigin);
/**
* この関数は任意のトリガでドメイン A にデータを送信する。
*/
var SendMsgToDomainA = function () {
var sendData = <送信するデータの文字列>;
port.postMessage(sendData);
};
/**
* この関数は "message" イベント発火時に処理される。
*/
var receiveMessage = function(event) {
console.info(event.data);
};
// ドメイン B の window に "message" イベントリスナーを束縛。
port.addEventListener('message', receiveMessage, false);
まとめ(感想)
postMessage
と MessageChannel
どちらも、任意のオブジェクトに "message" イベントを張っておき、任意のオブジェクトに対してデータを受送信している。
ペアリングが成立したら通信時にデータ送信元のオリジンをチェックしなくて済むので MessageChannel
の方が実装は楽そうだが、こちらは webkit 系しか対応していない為、 postMessage
を使った実例が多い印象。
MessageChennel
は、任意のオブジェクト同士でペアリングさせて通信できるので、 iframe
を使ったクロスドメイン間の通信に限らない用途としても使えそう。
References
https://developer.mozilla.org/docs/DOM/window.postMessage
http://msdn.microsoft.com/en-us/library/windows/apps/hh441303.aspx