Edition for Web Developers — Last Updated 26 June 2025
Support in all current engines.
ウェブブラウザーは、セキュリティおよびプライバシー上の理由から、異なるドメインの文書が相互に影響を与えないようにしている。つまり、クロスサイトスクリプティングは許可されてない。
これは重要なセキュリティ機能であるが、たとえ敵対的でないページであっても、異なるドメインにあるそれらのページの通信を妨げる。このセクションでは、クロスサイトスクリプティング攻撃を可能にしないように設計された、ソースドメインに関係なく文書が相互に通信を可能にするメッセージングシステムを紹介する。
postMessage()
APIは、トラッキングベクターとして使用できる。
たとえば、文書Aに文書Bを含むiframe
要素が含まれ、文書Aのスクリプトが文書Bの Window
オブジェクトでpostMessage()
を呼び出す場合、文書AのWindow
から発信されたものとしてマークされたメッセージイベントがそのオブジェクトで発生する。文書Aのスクリプトは次のようになる;
var o = document. getElementsByTagName( 'iframe' )[ 0 ];
o. contentWindow. postMessage( 'Hello world' , 'https://b.example.org/' );
着信イベントのイベントハンドラーを登録するために、スクリプトはaddEventListener
(または類似のメカニズム)を使用する。たとえば、文書Bのスクリプトは次のようになる:
window. addEventListener( 'message' , receiver, false );
function receiver( e) {
if ( e. origin == 'https://example.com' ) {
if ( e. data == 'Hello world' ) {
e. source. postMessage( 'Hello' , e. origin);
} else {
alert( e. data);
}
}
}
このスクリプトは、ドメインが予想されるドメインであることを最初に確認し、次にメッセージを調べる。メッセージは、ユーザーに表示されるまたは、最初にメッセージを送信した文書にメッセージを送り返すことによって応答される。
このAPIを使用するには、サイトを悪用する悪意のある組織からユーザーを保護するために、特別な注意が必要である。
著者は、origin
属性をチェックし、メッセージを受信すると予想されるドメインからのみメッセージが受け入れられるようにすべきである。さもなければ、著者のメッセージ処理コードのバグが悪意のあるサイトによって悪用される可能性がある。
さらに、origin
属性をチェックした後でも、著者は問題のデータが期待されたフォーマットであることもチェックすべきである。さもなければ、イベントのソースがクロスサイトスクリプティングの欠陥を使用して攻撃された場合、 postMessage()
メソッドを使用して送信された情報の処理がさらにチェックされないことで、攻撃が受信側に伝播する可能性がある。
著者は、機密情報を含むメッセージのtargetOrigin 引数にワイルドカードキーワード(*)を使用すべきではない。さもなければ、メッセージが意図した受信者にのみ配信されることを保証できなくなる。
あらゆる生成元からのメッセージを受け入れる著者は、サービス拒否攻撃のリスクを考慮することが奨励される。攻撃者は大量のメッセージを送信する可能性がある。受信ページがコストのかかる計算を実行したり、そのようなメッセージごとにネットワークトラフィックが送信されたりすると、攻撃者のメッセージはサービス拒否攻撃に増幅される可能性がある。著者は、そのような攻撃を非現実的にするために、レート制限(1分あたり特定の数のメッセージのみを受け入れる)を採用することが奨励される。
window.postMessage(message [, options ])
指定されたウィンドウにメッセージをポストする。メッセージは、ネストされたオブジェクト、配列などの構造化されたオブジェクトにすることができ、JavaScriptの値(文字列、数値、Date
オブジェクトなど)を含めることができ、 File
Blob
、FileList
、ArrayBuffer
オブジェクトなどの特定のデータオブジェクトを含めることができる。
optionsのtransfer
メンバーにリストされているオブジェクトは、複製されるだけでなく、転送される。つまり、送信側では使用できなくなる。
ターゲットの生成元は、optionsのtargetOrigin
メンバーを使用して指定できる。指定しない場合、デフォルトの"/
"になる。このデフォルトでは、メッセージは生成元が同じターゲットだけに制限される。
ターゲットウィンドウの生成元が指定されたターゲットの生成元とマッチしない場合、情報の漏洩を避けるためにメッセージは破棄される。生成元に関係なく宛先にメッセージを送信するには、ターゲットの生成元を"*
"に設定する。
transfer配列に重複するオブジェクトが含まれている場合、またはmessageをクローンできなかった場合、"DataCloneError
" DOMException
を投げる。
window.postMessage(message, targetOrigin [, transfer ])
これはpostMessage()
の代替バージョンで、ターゲットの生成元がパラメータとして指定される。window.postMessage(message, target, transfer)
の呼び出しは、window.postMessage(message, {targetOrigin, transfer})
と同じである。
新しいDocument
にナビゲートされたばかりのブラウジングコンテキストのWindow
にメッセージを投稿するとき、メッセージが意図した受信者を受信しない可能性がある。ターゲットブラウジングコンテキストのスクリプトには、メッセージのリスナーを設定する時間が必要である。したがって、たとえば、メッセージが新しく作成された子iframe
のWindow
に送信される状況では、著者は、子Document
にメッセージを受信する準備ができていることを知らせるメッセージを親に投稿させ、親がこのメッセージを待ってからメッセージの投稿を開始するようにアドバイスされる。
Support in all current engines.
Channel_Messaging_API/Using_channel_messaging
Support in all current engines.
独立したコード部分(たとえば、異なるブラウジングコンテキストで実行される )が直接通信できるようにするために、著者はチャンネルメッセージングを使用することができる。
このメカニズムの通信チャンネルは、両端にポートを持つ双方向パイプとして実装される。一方のポートで送信されたメッセージはもう一方のポートに配信され、その逆も同様である。メッセージはDOMイベントとして配信され、実行タスクを中断したりブロックしたりすることはない。
接続(2つの"もつれた"ポート)を作成するには、MessageChannel()
コンストラクターを呼び出す:
var channel = new MessageChannel();
ポートの1つはローカルポートとして保持され、もう1つのポートはリモートコードに送信される。たとえば、postMessage()
を使用する:
otherWindow. postMessage( 'hello' , 'https://example.com' , [ channel. port2]);
メッセージを送信するには、ポートのpostMessage()
メソッドを使用する:
channel. port1. postMessage( 'hello' );
メッセージを受信するには、message
イベントをリッスンする:
channel. port1. onmessage = handleMessage;
function handleMessage( event) {
// message is in event.data
// ...
}
ポートで送信されるデータは構造化データである可能性がある。たとえば、ここでは文字列の配列がMessagePort
で渡される:
port1. postMessage([ 'hello' , 'world' ]);
この例では、2つのJavaScriptライブラリがMessagePort
を使用して相互に接続されている。これにより、APIを変更することなく、ライブラリを後で別のフレームまたはWorker
オブジェクトにホストできるようになる。
< script src = "contacts.js" ></ script > <!-- exposes a contacts object -->
< script src = "compose-mail.js" ></ script > <!-- exposes a composer object -->
< script >
var channel = new MessageChannel();
composer. addContactsProvider( channel. port1);
contacts. registerConsumer( channel. port2);
</ script >
"addContactsProvider()"関数の実装は次のようになる:
function addContactsProvider( port) {
port. onmessage = function ( event) {
switch ( event. data. messageType) {
case 'search-result' : handleSearchResult( event. data. results); break ;
case 'search-done' : handleSearchDone(); break ;
case 'search-error' : handleSearchError( event. data. message); break ;
// ...
}
};
};
あるいは、次のように実装することもできる:
function addContactsProvider( port) {
port. addEventListener( 'message' , function ( event) {
if ( event. data. messageType == 'search-result' )
handleSearchResult( event. data. results);
});
port. addEventListener( 'message' , function ( event) {
if ( event. data. messageType == 'search-done' )
handleSearchDone();
});
port. addEventListener( 'message' , function ( event) {
if ( event. data. messageType == 'search-error' )
handleSearchError( event. data. message);
});
// ...
port. start();
};
重要な違いは、addEventListener()
を使用する場合、start()
メソッドも呼び出さなければならないことである。onmessage
を使用する場合、start()
メソッドが暗黙的に呼び出される。
start()
メソッドは、明示的に呼び出された場合でも、(onmessage
を設定することで)暗黙的に呼び出された場合でも、メッセージのフローを開始する。メッセージポートにポストされたメッセージは、スクリプトがハンドラーをセットアップする前に破棄されないように、最初は一時停止される。
ポートは、システム内の他の行為者に対して、限定された機能(オブジェクト機能モデルの意味で)を公開する手段と捉えることができる。これは、ポートが特定の生成元内での便利なモデルとしてのみ使用される弱い機能システムと、別の生成元の消費者がプロバイダーに変更を加えたり情報を取得したりするための唯一の手段として、ある生成元のプロバイダーによって提供される強い機能モデルのいずれかである。
たとえば、ソーシャルウェブサイトが、あるiframe
にユーザーのメール連絡先プロバイダー(別の生成元のアドレス帳サイト)を埋め込み、別のiframe
にゲーム(別の生成元のゲーム)を埋め込んでいる状況を考えてみる。外側のソーシャルサイトと2番目のiframe
内のゲームは、1つ目のiframe
内のいかなる機能にもアクセスできない。つまり、これら2つのサイトでできることは次のとおりである:
iframe
を新しいURL(同じURLであるがフラグメントが異なるフラグメントなど)にナビゲートし、iframe
内のWindow
にhashchange
イベントを受信する。
window.postMessage()
APIを使用して、iframe
内のWindow
にmessage
イベントを送信する。
連絡先プロバイダーはこれらのメソッド、特に3番目のメソッドを使用することで、他の生成元がユーザーのアドレス帳を操作できるAPIを提供できる。たとえば、"add-contact Guillaume Tell <tell@pomme.example.net>
"というメッセージに応答し、指定された人物およびメールアドレスをユーザーのアドレス帳に追加することができる。
ウェブ上のどのサイトもユーザーの連絡先を操作できないようにするために、連絡先プロバイダーは、ソーシャルサイトなどの特定の信頼できるサイトのみにこの操作を許可する場合がある。
さて、ゲームがユーザーのアドレス帳に連絡先を追加したいと考えており、ソーシャルサイトがゲームに代わってその操作を許可し、連絡先プロバイダーがソーシャルサイトと築いていた信頼を「共有」することを望んだとする。これにはいくつかの方法があるが、最も単純な方法は、ゲームサイトと連絡先サイトとの間のメッセージをプロキシすることである。しかし、この解決策にはいくつかの難点がある。ソーシャルサイトは、ゲームサイトが権限を乱用しないことを完全に信頼するか、ソーシャルサイトが各リクエストを検証して、許可したくないリクエスト(複数の連絡先の追加、連絡先の閲覧、削除など)ではないことを確認する必要がある。また、複数のゲームが同時に連絡先プロバイダーとやり取りする可能性がある場合、複雑さが増す。
しかし、メッセージチャンネルおよびMessagePort
オブジェクトを使用すれば、これらの問題はすべて解消する。ゲームがソーシャルサイトに連絡先を追加したいと伝えると、ソーシャルサイトは連絡先プロバイダーに、連絡先を1つ追加するのではなく、1つの連絡先を追加する機能を要求できる。連絡先プロバイダーはMessagePort
オブジェクトのペアを作成し、そのうちの1つをソーシャルサイトに送り返し、ソーシャルサイトはそれをゲームに転送する。こうしてゲームと連絡先プロバイダーは直接接続され、連絡先プロバイダーは"連絡先の追加"リクエスト1件のみを承認すればよく、それ以外のリクエストは受け付けない。つまり、ゲームには1つの連絡先を追加する機能が付与されたことになる。
前のセクションからの例に続いて、特に連絡先プロバイダーについて考えてみる。初期の実装では、サービスのiframe
で単にXMLHttpRequest
オブジェクトを使用していたかもしれないが、サービスの進化により、代わりに単一のWebSocket
接続で共有ワーカーを使用したい場合がある。
最初の設計でMessagePort
オブジェクトを使用して機能を付与した場合、または単に複数の同時独立セッションを許可した場合でも、サービス実装は、APIをまったく変更することなく、 XMLHttpRequest
の各iframe
モデルから共有WebSocket
モデルに切り替えることができる。サービスプロバイダー側のポートはすべて、APIのユーザーに少しも影響を与えることなく、共有ワーカーに転送できる。
Support in all current engines.
channel = new MessageChannel()
2つの新しいMessagePort
オブジェクトをもつ新しいMessageChannel
オブジェクトを返す。
channel.port1
1つ目のMessagePort
オブジェクトを返す。
channel.port2
2つ目のMessagePort
オブジェクトを返す。
MessagePort
、Worker
、およびDedicatedWorkerGlobalScope
に存在するプロパティ下記は、MessagePort
、Worker
およびDedicatedWorkerGlobalScope
オブジェクトによってイベントハンドラーIDL属性としてサポートされるイベントハンドラー(および対応するイベントハンドラーイベントタイプ)である:
イベントハンドラー | イベントハンドラーイベント型 |
---|---|
onmessage Support in all current engines. Firefox41+Safari5+Chrome2+ Opera10.6+Edge79+ Edge (Legacy)12+Internet Explorer10+ Firefox Android?Safari iOS?Chrome Android?WebView Android37+Samsung Internet?Opera Android11.5+ DedicatedWorkerGlobalScope/message_event Support in all current engines. Firefox3.5+Safari4+Chrome4+ Opera10.6+Edge79+ Edge (Legacy)12+Internet Explorer10+ Firefox Android?Safari iOS5+Chrome Android?WebView Android37+Samsung Internet?Opera Android11.5+ | message |
onmessageerror MessagePort/messageerror_event Support in all current engines. Firefox57+Safari16.4+Chrome60+ Opera?Edge79+ Edge (Legacy)18Internet ExplorerNo Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android47+ DedicatedWorkerGlobalScope/messageerror_event Support in all current engines. Firefox57+Safari16.4+Chrome60+ Opera?Edge79+ Edge (Legacy)18Internet ExplorerNo Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android47+ | messageerror |
Support in all current engines.
各チャネルには2つのメッセージポートがある。一方のポートから送信されたデータはもう一方のポートで受信され、その逆も同様である。
port.postMessage(message [, transfer])
port.postMessage(message [, { transfer }])
チャンネルを通じてメッセージをポストする。Objects listed in transferにリストされたオブジェクトは、単に複製されるのではなく転送される。つまり、送信側では使用できなくなる。
transferが重複するオブジェクトもしくはportを含む場合、またはmessageを複製できなかった場合、"DataCloneError
" DOMException
を投げる。
port.start()
ポートで受信したメッセージの発送を開始する。
port.close()
ポートを切断し、アクティブでなくなる。
著者は、リソースを再収集できるように、MessagePort
オブジェクトを明示的に閉じて、それらを分離することを強く勧める。多くのMessagePort
オブジェクトを作成し、それらを閉じずに破棄すると、ガベージコレクションが必ずしも迅速に実行されるとは限らないため、一時的にメモリーの使用率が高くなる可能性がある。特に、ガベージコレクションにプロセス間の調整が含まれるMessagePort
の場合はそうなる。
Support in all current engines.
Support in all current engines.
同じユーザーエージェント内の同じユーザーによって開かれたが、異なる関連のないブラウジングコンテキストの単一の生成元上のページは、時には互いに通知を送信する必要がある。例えば、「ユーザーはここにログインしました、あなたの認証情報をもう一度確認してください」。
共有状態のロックの管理、サーバーと複数のローカルクライアント間のリソースの同期の管理、リモートホストとのWebSocket
接続の共有など、複雑なケースでは、共有ワーカーが最も適切なソリューションである。
しかし、共有ワーカーが過度のオーバーヘッドとなるような単純な場合には、著者はこのセクションで説明する単純なチャンネルベースのブロードキャストメカニズムを使用することができる。
broadcastChannel = new BroadcastChannel(name)
指定されたチャンネル名のメッセージを送受信できる新しいBroadcastChannel
オブジェクトを返す。
broadcastChannel.name
(コンストラクターに渡された)チャンネル名を返す。
broadcastChannel.postMessage(message)
指定されたメッセージを、このチャンネルに設定されている他のBroadcastChannel
オブジェクトに送信する。メッセージは、ネストされたオブジェクトや配列などの構造化オブジェクトにすることができる。
broadcastChannel.close()
BroadcastChannel
オブジェクトを閉じ、ガベージコレクションに対して開く。
著者は、不要になったときにBroadcastChannel
オブジェクトを明示的に閉じることを強く勧める。これにより、オブジェクトをガベージコレクションすることができる。多くのBroadcastChannel
オブジェクトを作成し、それらをイベントリスナーに残したまま、それらを閉じずに破棄すると、オブジェクトはイベントリスナーがある限り(またはそれらのページまたはワーカーが閉じられるまで)存続し続けるため、明らかなメモリリークが発生する可能性がある。
ユーザーが同じサイトの別のタブからログアウトした場合でも、ユーザーがいつログアウトしたかをページが知りたいとする:
var authChannel = new BroadcastChannel( 'auth' );
authChannel. onmessage = function ( event) {
if ( event. data == 'logout' )
showLogout();
}
function logoutRequested() {
// called when the user asks us to log them out
doLogout();
showLogout();
authChannel. postMessage( 'logout' );
}
function doLogout() {
// actually log the user out (e.g. clearing cookies)
// ...
}
function showLogout() {
// update the UI to indicate we're logged out
// ...
}