Edition for Web Developers — Last Updated 12 May 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つの連絡先を追加する機能が付与されたことになる。
Continuing the example from the previous section, consider the contacts provider in particular. While an initial implementation might have simply used XMLHttpRequest
objects in the service's iframe
, an evolution of the service might instead want to use a shared worker with a single WebSocket
connection.
If the initial design used MessagePort
objects to grant capabilities, or even just to allow multiple simultaneous independent sessions, the service implementation can switch from the XMLHttpRequest
s-in-each-iframe
model to the shared-WebSocket
model without changing the API at all: the ports on the service provider side can all be forwarded to the shared worker without it affecting the users of the API in the slightest.
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()
ポートを切断し、アクティブでなくなる。
Authors are strongly encouraged to explicitly close MessagePort
objects to disentangle them, so that their resources can be recollected. Creating many MessagePort
objects and discarding them without closing them can lead to high transient memory usage since garbage collection is not necessarily performed promptly, especially for MessagePort
s where garbage collection can involve cross-process coordination.
Support in all current engines.
Support in all current engines.
Pages on a single origin opened by the same user in the same user agent but in different unrelated browsing contexts sometimes need to send notifications to each other, for example "hey, the user logged in over here, check your credentials again".
For elaborate cases, e.g. to manage locking of shared state, to manage synchronization of resources between a server and multiple local clients, to share a WebSocket
connection with a remote host, and so forth, shared workers are the most appropriate solution.
For simple cases, though, where a shared worker would be an unreasonable overhead, authors can use the simple channel-based broadcast mechanism described in this section.
broadcastChannel = new BroadcastChannel(name)
Returns a new BroadcastChannel
object via which messages for the given channel name can be sent and received.
broadcastChannel.name
Returns the channel name (as passed to the constructor).
broadcastChannel.postMessage(message)
Sends the given message to other BroadcastChannel
objects set up for this channel. Messages can be structured objects, e.g. nested objects and arrays.
broadcastChannel.close()
Closes the BroadcastChannel
object, opening it up to garbage collection.
Authors are strongly encouraged to explicitly close BroadcastChannel
objects when they are no longer needed, so that they can be garbage collected. Creating many BroadcastChannel
objects and discarding them while leaving them with an event listener and without closing them can lead to an apparent memory leak, since the objects will continue to live for as long as they have an event listener (or until their page or worker is closed).
Suppose a page wants to know when the user logs out, even when the user does so from another tab at the same site:
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
// ...
}