.postMessage()

クロスドメイン通信を安全に有効化します。 通常異なるページ間のスクリプトが互いにアクセスすることが許されるのは、 それが実行されるページが同じプロトコル(通常は両方ともHTTP)、同じポート番号(デフォルトであれば80)、 同じホスト(document.domainの値が両ページで同値となる)の場合のみです。 window.postMessageを正しく使用することで、この制限を安全に迂回することが出来ます。

window.postMessageが呼び出されると、 実行する必要のある待機中のスクリプトの実行が完了してから、 対象となるウインドウでMessageEventイベントの伝搬が発生します。 (例えば、window.postMessageがイベントハンドラから呼び出された場合、 その後に続く残りのイベントハンドラ、または事前に設定された待機中のタイムアウト処理、等) MessageEventにはメッセージ(message)型のオブジェクトが渡され、 このdataプロパティには、window.postMessageへ提供された第1引数の値が、 originプロパティには、window.postMessageが呼び出された際に、 それを呼び出した主ウインドウの生成元に相当するものが、 そしてsourceプロパティにはwindow.postMessageを呼び出したウインドウへの参照が格納されます。 (イベントのその他の標準プロパティは、それらの実行値と一緒に提供されます。)

文法

otherWindow.postMessage(message, targetOrigin, [transfer]);

otherWindowは別のウインドウへの参照です。 これは、例えばiframe要素のcontentWindowプロパティ、 window.open 1によって返されるオブジェクト、 またはwindow.frames内のインデックスや名前で取得できるような参照になります。

引数 説明
message 別ウインドウへ送信されるデータを指定します。 データは、structured clone algorithmを使用してシリアライズ化されます。 これは送信先のウインドウに、開発者自身がシリアライズをすること無く、 安全に多種多様なデータを渡せることを意味します。[1]
targetOrigin イベントが伝搬されるotherWindowの生成元とするURI、 またはリテラル文字列"*"(特に指定なし)を指定します。 イベントの伝搬がスケジュールされた際に、 otherWindowのドキュメントのスキーマ、ホスト名、ポートのいずれかが、 提供されるtargetOriginにマッチしない場合、イベントは伝搬されません。 これら3つの項目がマッチした場合にのみ、イベントが伝搬されます。 これにより、messageが送信される場所を制御するための仕組みが提供されます。 例えば、パスワードを送信するためにpostMessageが使用される場合、 この引数のURIの生成元がパスワードを含むmessageの受取先と同じになるようにすることで、 悪意のある第三者によるパスワードの傍受を防ぐことになります。 これを行うことは絶対に必要であり、重要なことです。 他のウインドウのdocumentが配置されるべき場所が分かっている場合は、 *は使用せず、常にtargetOriginに対して明示的な指定を行って下さい。 具体的な対象を指定しないということは、 悪意のあるサイトに対してデータを開示しているということになります。
transfer
(任意)
messageと一緒に送られるTransferableオブジェクトのリストを指定します。 これらのオブジェクトの所有権は送り先に与えられ、 送る側が利用することは出来なくなります。

伝搬するイベント

otherWindowは、下記のJavaScriptを実行することによって、 伝搬されるメッセージをリッスンすることが出来ます。

window.addEventListener("message", receiveMessage, false);

function receiveMessage(event)
{
  if (event.origin !== "http://example.org:8080")
    return;
  // ...
}

伝達される"message"のプロパティは、下記の通りです。
訳注) この例であれば、"message"イベントがトリガされることで、receiveMessage(event)関数が発火され、 この引数であるeventが下記のプロパティを持つということになります。

data
別のウインドウから渡されるオブジェクトです。
origin
postMessageが呼び出された際にメッセージを送信したウインドウの生成元(origin)です。 この文字列は、プロトコル(httpやhttps)と"://"、存在すればホスト名、 デフォルトのポートとは異なるポート番号が提供されていれば":"の後にそのポート番号が続き、 これらを連結したものになります。 典型的な生成元は、https://example.org(暗黙的にポート番号は443)、 http://example.net(暗黙的にポート番号は80)、 http://example.com:8080(明示的にポート番号に8080を提供)のようになります。 この生成元は、ウインドウの現在(または、今後そうなるであろう)の生成元を保証しないことに注意してください。 postMessageが呼び出された場所とは、異なる場所に誘導される可能性があります。
source
メッセージを送信したwindowオブジェクトの参照になります。 これを使用して、異なる生成元の2つのウインドウ間で双方向通信を行うことが可能になります。

セキュリティの懸念事項

もし別の場所からメッセージを受け取るつもりが無いのであれば、 messageイベントのためのイベントリスナーを追加しないでください。 これはセキュリティ上の問題を回避する、確実な方法です。

もし、messageを別の場所から受け取るつもりがあるのであれば、 originと出来る限りsourceプロパティを使用して、 常に送り先の検証と識別をするようにしてください。 どのようなウインドウ(例えば、http://evil.example.com - 悪意のあるサイト - を含む)も、 どうような別ウインドウであってもメッセージを送信することが可能であり、 未知の送信元が悪意のあるメッセージを送らないという保証をすることは出来ません。 送信元を検証・識別したとしても、受け取るメッセージの構文は常に検証すべきです。 そうしなければ、あなたが信頼しているサイトにセキュリティホールがあった場合、 そのサイトからのメッセージであれば信頼できるとして検証をしないと、 あなたのサイトにXSSのセキュリティホールが作られてしまう可能性があります。

postMessageを使用して他のウインドウにデータを送る際は、 常に*では無い正確な対象を指定してください。 悪意のあるサイトは、あなたが把握できない所でサイトの場所を変更することが可能であり、 そのためpostMessageを使用してデータを傍受することが出来てしまいます。

訳注)下記の例は、まず主ウインドウがポップアップウインドウを開き、 そのポップアップウインドウに対してpostMessageが送信され、 その送信によってイベントハンドラがトリガされることで、 今度はポップアップウインドウから主ウインドウに対してpostMessageが送信される、 といったものだと思われます。

  • 1つ目のコード: 主ウインドウ(http://example.com:8080)
  • 2つ目のコード: ポップアップウインドウ(http://example.org)
/*
 * <http://example.com:8080>上にあるwindow Aのスクリプト
 */
var popup = window.open(...ポップアップの詳細を指定...);

// ポップアップブロッカーにブロックされずに、
// ポップアップが完全に読み込まれた場合

// ウインドウの場所が変更されていなければ、
// 何も行いません。
popup.postMessage("ユーザーは'bob'で、パスワードは'secret'です。",
                  "https://secure.example.net");

// ウインドウの場所が変更されていなければ、
// ポップアップにメッセージを送り、
// キュー(順番待ち)に入れることに成功します。
popup.postMessage("こんにちは!", "http://example.org");

function receiveMessage(event)
{
  // メッセージの送り主を信頼できますか?
  // (例えば、開き元とは異なる可能性があります。)
  if (event.origin !== "http://example.org")
    return;

  // event.source はポップアップウインドウです。
  // event.data には、例えば、
  // "こんにちは!秘密の応答は"rheeeeet!です"
  // の文字列が格納されます。
}
window.addEventListener("message", receiveMessage, false);
/*
 * <http://example.org>上で実行されるポップアップのスクリプト
 */

// postMessageが呼び出された後に呼び出されます。
function receiveMessage(event)
{
  // このメッセージの送り元を信頼しますか?
  if (event.origin !== "http://example.com:8080")
    return;

  // event.sourceは、window.openerです。
  // event.dataは、"こんにちは!"です。

  // 受け取ったメッセージの生成元が検証済みの場合、
  // (どのようなケースでも検証を行うべきです)
  // メッセージに応答して返信する便利な方法は、
  // event.source上のpostMessageを呼び出し、
  // そのtargetOriginとして、event.originを提供することです。
  event.source.postMessage("こんにちは! 秘密の応答は" +
                           "rheeeeet!です。",
                           event.origin);
}

window.addEventListener("message", receiveMessage, false);

注意事項

あるウインドウが、別のウインドウのこのメソッドに、 任意のタイミングでウインドウのdocumentの場所に関係なくアクセスして、 メッセージを送信する可能性があります。 そのため、メッセージを受け取るあらゆるイベントリスナーは、 originと出来る限りsourceプロパティを使用して、 まず最初にメッセージの送信元の検証と識別を行わなければいけません。 これを怠ってはいけません。 origin(生成元)と出来る限りのsource(ウインドウ参照)プロパティの検証を行わないと、 クロスサイト・スクリプティングの攻撃を有効にしてしまいます。

非同期の伝搬スクリプト(タイムアウト、ユーザーが作成したイベント)と同様に、 postMessageの例外のスローによるイベントの送信をイベントハンドラがリッスンする場合、 postMessageの呼び出し元を検知することは出来ません。(翻訳に自信なし)

伝搬されるイベントのoriginプロパティの値は、 呼び出し元のウインドウ内のdocument.domainのその時の値に影響されません。

IDNのホスト名に限った話ですが、 生成元(origin)プロパティの値は、必ずUnicodeであったり、 必ずPunycodeである、 という事はありません。 そのため、もしIDNサイトからのメッセージを受けるのであれば、 このプロパティを使用する場合、IDNとPunycodeの両方の値の互換性を確認してください。 この値はゆくゆくは一貫してIDNになりますが、 現在はIDNとPunycodeの両方の形式を扱うようにするべきです。

javascript:data:を含む送信ウインドウのoriginプロパティの値は、 URLを読み込んだスクリプトの生成元のURLになります。

拡張機能でのwindow.postMessageの使用

window.postMessageは、 クロームコード(訳注: クローム(Chrome)とは、特定のアプリケーションや拡張機能のユーザインターフェイスを構成する要素全体を指します。)内で利用可能ですが(例えば、拡張機能や特権コード)、 伝搬されるイベントのsourceプロパティは、セキュリティ制限のために常にnullになります。 (その他の値は、期待通りの値を持ちます。) クロームに配置されたウインドウへ送信されるメッセージのtargetOrigin引数のURLは、 現在、"*"が送信されたメッセージの結果であるとして誤解されています。 悪意のあるサイトによって、対象ウインドウが何処かへ誘導されてしまう可能性があり、 この値は安全では無いことから、現在はpostMessageをクロームでのページとの通信で使用しないことを推奨しています。 クロームウインドウで通信を行う際は、異なるメソッド(ウインドウを開く際のクエリー文字列のような)を使用してください。 最後に、file:のページへのメッセージの送信には、 現在targetOrigin引数を"*"にする必要があります。 file://は、セキュリティ制限を適用することが出来ませんが、 この制限は将来修正されるかもしれません。

仕様

ブラウザ互換性

デスクトップ
機能 Chrome Firefox
(Gecko)
IE Opera Safari
基本 1.0 6.0 (6.0) [1] [4] 8.0 [2] [3] 9.5 4.0
transfer引数 ? 20.0 (20.0) × ? ?
モバイル
機能 Android Firefox
Mobile
IE
Mobile
Opera
Mobile
Safari
Mobile
基本
transfer引数 ? 6.0 (6.0) [1] [4] × ? ?

[1] Gecko 6.0(Firefox 6.0 / Thunderbird 6.0 / SeaMonkey 2.3)以前は、 message引数は文字列でなければいけません。 Gecko 6.0からは、 message引数はthe structured clone algorithmを使用して、 シリアライズ化されます。 これは、自身でシリアライズをすることはなく安全に送信先のウインドウに様々なデータオブジェクトを安全に渡すことができる事を意味します。

[2] IE8とIE9は、<frame>と<iframe>のみをサポートします。

[3] IE10には重要な制約があります。 詳細は、この記事(英語)を参照してください。

[4] Gecko 8.0では、ウインドウ間でのFileとFileListオブジェクトの送信をサポートしました。 これはセキュリティ上の理由から、受け取り元が送信元を含む場合に限られます。

関連項目

 Back to top

© 2017 Mozilla Contributors
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.

このページは、ページトップのURL先のMozilla Developer Network(以下、MDN)のコンテンツを翻訳した内容を基に構成されています。 構成について異なる点も含まれますので、下記の項目を確認し、必要に応じて元のコンテンツをご確認ください。 もし、誤訳などの間違いを見つけましたら、 @tomofまで教えていただければ幸いです。

  • 特定のブラウザに特化しすぎている情報やあまりにも古い情報、 または試験的に導入されているようなAPIや機能については、省略していることがあります。
  • 例やデモについて、実際にページ内で動作させる関係で一部ソースコードを変更している場合や、 その例で使用しているコンテンツの単語や文章などを日本人向けに変更しいてる場合があります。
  • MDNの更新頻度が高いため、元のコンテンツと比べ情報が古くなっている可能性があります。
  • "訳注:"などの断わりを入れた上で、日本人向けの情報の追記を行っている事があります。