Window: postMessage() 方法

Baseline 已廣泛支援

此特性已相當成熟,可在許多裝置和瀏覽器版本上使用。自 ⁨2015 年 7 月⁩以來,各瀏覽器均已提供此特性。

window.postMessage() 方法可以在 Window 物件之間安全地實現跨源通訊;例如,在頁面與其生成的彈出視窗之間,或者在頁面與其嵌入的 iframe 之間。

通常,不同頁面上的指令碼只有在它們來自的頁面共享相同(也稱為“同源策略”)時才能相互訪問。window.postMessage() 提供了一種受控機制,可以安全地規避此限制(如果使用得當)。

此外,訪問指令碼必須事先已獲得被訪問文件的視窗物件。這可以透過諸如彈出視窗的 window.open() 或 iframe 的 iframe.contentWindow 等方法來實現。

概括地說,一個視窗可以獲取對另一個視窗的引用(例如,透過 targetWindow = window.opener),然後使用 targetWindow.postMessage() 在其上分派一個 MessageEvent。接收視窗隨後可以根據需要處理此事件。傳遞給 window.postMessage() 的引數(即“訊息”)透過事件物件暴露給接收視窗

語法

js
postMessage(message)
postMessage(message, targetOrigin)
postMessage(message, targetOrigin, transfer)

postMessage(message, options)

引數

message

要分派給另一個視窗的資料。資料使用結構化克隆演算法進行序列化。這意味著你可以安全地將各種資料物件傳遞到目標視窗,而無需自行序列化它們。

targetOrigin 可選

指定接收視窗必須具有的才能接收事件。為了分派事件,源必須完全匹配(包括方案、主機名和埠)。如果省略,則預設為 "/",即呼叫該方法的源。此機制提供了對訊息傳送位置的控制;例如,如果使用 postMessage() 傳輸密碼,則此引數必須是一個 URI,其源與包含密碼的訊息的預期接收者相同,以防止惡意第三方截獲密碼,這一點絕對至關重要。也可以提供 *,這意味著訊息可以分派給具有任何源的偵聽器。

注意:如果你知道另一個視窗的文件應該位於何處,請始終提供特定的 targetOrigin,而不是 *。未能提供特定目標可能會將資料洩露給惡意站點。

由於 data: URL 具有不透明源,因此要向具有 data: URL 的上下文傳送訊息,你必須指定 "*"

transfer 可選

一個可選的可傳輸物件陣列,用於轉移所有權。這些物件的所有權將轉移到目標方,並且它們在傳送方不再可用。這些可傳輸物件應該附加到訊息中;否則它們將被移動,但在接收端實際上無法訪問。

options 可選

一個包含以下屬性的可選物件

transfer 可選

transfer 引數具有相同的含義。

targetOrigin 可選

targetOrigin 引數具有相同的含義。

返回值

無(undefined)。

分派的事件

一個 window 可以透過執行以下 JavaScript 來偵聽分派的訊息

js
window.addEventListener("message", (event) => {
  if (event.origin !== "http://example.org:8080") return;

  // …
});

分派訊息的屬性為

data

從另一個視窗傳遞的物件。

origin

在呼叫 postMessage 時傳送訊息的視窗的。此字串是協議和“://”、如果存在的主機名以及“:”後跟埠號(如果存在埠並且與給定協議的預設埠不同)的串聯。典型源的示例包括 https://example.org(表示埠 443)、http://example.net(表示埠 80)和 http://example.com:8080。請注意,此源保證是該視窗的當前或未來源,該視窗可能自呼叫 postMessage 後已導航到不同的位置。

source

對傳送訊息的 window 物件的引用;你可以使用它在兩個不同源的視窗之間建立雙向通訊。

安全問題

如果你不期望收到來自其他站點的訊息,請勿message 事件新增任何事件偵聽器。這是一種完全避免安全問題的萬無一失的方法。

如果你確實期望收到來自其他站點的訊息,請始終使用 origin 和可能的 source 屬性驗證傳送者的身份。任何視窗(例如 http://evil.example.com)都可以向當前文件的 iframe 層次結構中從頂部到每個 iframe 下方的任何其他視窗傳送訊息。但是,在驗證身份後,你仍然應該始終驗證收到的訊息的語法。否則,你信任的傳送可信訊息的站點中的安全漏洞可能會在你的站點中開啟跨站指令碼漏洞。

當你使用 postMessage 將資料分派到其他視窗時,請始終指定精確的目標源,而不是 *。惡意站點可以在你不知情的情況下更改視窗的位置,因此它可以截獲使用 postMessage 傳送的資料。

安全共享記憶體訊息傳遞

如果將 postMessage()SharedArrayBuffer 物件一起使用時丟擲異常,你可能需要確保你的站點已正確進行跨站點隔離。共享記憶體受兩個 HTTP 標頭保護

http
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

要檢查跨源隔離是否成功,你可以針對視窗和 worker 上下文中可用的 Window.crossOriginIsolated 屬性進行測試

js
const myWorker = new Worker("worker.js");

if (crossOriginIsolated) {
  const buffer = new SharedArrayBuffer(16);
  myWorker.postMessage(buffer);
} else {
  const buffer = new ArrayBuffer(16);
  myWorker.postMessage(buffer);
}

示例

js
/*
 * In window A's scripts, with A being on http://example.com:8080:
 */

const popup = window.open(/* popup details */);

// When the popup has fully loaded, if not blocked by a popup blocker:

// This does nothing, assuming the window hasn't changed its location.
popup.postMessage(
  "The user is 'bob' and the password is 'secret'",
  "https://secure.example.net",
);

// This will successfully queue a message to be dispatched to the popup, assuming
// the window hasn't changed its location.
popup.postMessage("hello there!", "http://example.com");

window.addEventListener("message", (event) => {
  // Do we trust the sender of this message? (might be
  // different from what we originally opened, for example).
  if (event.origin !== "http://example.com") return;

  // event.source is popup
  // event.data is "hi there yourself! the secret response is: rheeeeet!"
});
js
/*
 * In the popup's scripts, running on http://example.com:
 */

// Called sometime after postMessage is called
window.addEventListener("message", (event) => {
  // Do we trust the sender of this message?
  if (event.origin !== "http://example.com:8080") return;

  // event.source is window.opener
  // event.data is "hello there!"

  // Assuming you've verified the origin of the received message (which
  // you must do in any case), a convenient idiom for replying to a
  // message is to call postMessage on event.source and provide
  // event.origin as the targetOrigin.
  event.source.postMessage(
    "hi there yourself! the secret response is: rheeeeet!",
    event.origin,
  );
});

注意

視窗中文件中的任何指令碼都可以透過在該視窗物件上呼叫 .postMessage() 來請求將訊息分派到其已獲得視窗物件的另一個視窗中的文件。因此,用於接收訊息的任何事件偵聽器必須首先使用 origin 和可能的 source 屬性檢查訊息傳送者的身份。這怎麼強調都不為過:未能檢查 origin 和可能的 source 屬性會導致跨站指令碼攻擊。

與任何非同步分派指令碼(超時、使用者生成的事件)一樣,postMessage 的呼叫者無法檢測偵聽 postMessage 傳送的事件的事件處理程式何時丟擲異常。

呼叫 postMessage() 後,MessageEvent僅在所有待處理的執行上下文完成後才分派。例如,如果在事件處理程式中呼叫 postMessage(),則該事件處理程式以及該相同事件的任何剩餘處理程式將執行完成,然後才分派 MessageEvent

分派事件的 origin 屬性的值不受呼叫視窗中 document.domain 當前值的影響。

對於僅 IDN 主機名,origin 屬性的值不始終是 Unicode 或 Punycode;為了獲得最大的相容性,如果你期望來自 IDN 站點的訊息,請在使用此屬性時檢查 IDN 和 Punycode 值。此值最終將始終是 IDN,但目前你應同時處理 IDN 和 Punycode 形式。

當傳送視窗包含 javascript:data: URL 時,origin 屬性的值是載入該 URL 的指令碼的源。

在擴充套件中使用 window.postMessage 非標準

window.postMessage 可用於在 chrome 程式碼(例如,在擴充套件和特權程式碼中)中執行的 JavaScript,但作為安全限制,分派事件的 source 屬性始終為 null。(其他屬性具有其預期值。)

內容或 Web 上下文指令碼無法指定 targetOrigin 以直接與擴充套件程式(無論是後臺指令碼還是內容指令碼)通訊。Web 或內容指令碼可以使用 window.postMessagetargetOrigin"*" 進行廣播,但這是不鼓勵的,因為擴充套件程式無法確定此類訊息的來源,並且其他偵聽器(包括你無法控制的偵聽器)可以竊聽。

內容指令碼應使用 runtime.sendMessage 與後臺指令碼通訊。Web 上下文指令碼可以使用自定義事件與內容指令碼通訊(如果需要,使用隨機生成的事件名稱,以防止來賓頁面竊聽)。

最後,向 file: URL 處的頁面分派訊息目前要求 targetOrigin 引數為 "*"file:// 不能用作安全限制;此限制將來可能會修改。

規範

規範
HTML
# dom-window-postmessage-options-dev

瀏覽器相容性

另見