iT邦幫忙

2023 iThome 鐵人賽

DAY 23
0
Modern Web

網頁的另一個大腦:從基礎到進階掌握 Web Worker 技術系列 第 23

瀏覽器中的消息傳遞 - postMessage

  • 分享至 

  • xImage
  •  

前幾天學習關於 Web worker 的知識時,我發現不同地方都會使用 postMessage 函式把訊息發送出去,但不同地方使用 postMessage 的方式都有點不同,所以今天這篇文章希望統整這些 postMessage 的使用方式與差異

postMessage 種類

目前 MDN 上列出的大致上有六種

window.postMessage

window.postMessage 通常使用在需要與 iframe 溝通的場景
當主頁面中有 iframe 存在,可以使用 iframe.contentWindow 取得 iframe 中的 window

const iframe = document.querySelector("iframe");
iframe.onload = () => {
  // 取得 iframe 裡的 window 屬性 
  const iframeWindow = iframe.contentWindow;
};

取得 iframeWindow 後,就可以直接利用這個屬性去操作 iframe 裡的一些東西,例如改變整個 iframe 網頁的背景顏色:

iframeWindow.document.querySelector("body").style.backgroundColor = "blue";

但有個問題是,以上的操作都限定在主頁面與 iframe 網頁是同源網站,如果主頁面跟 iframe 網頁不同源的話,瀏覽器會限制只能拿到某些特定的 window 的屬性 (ex. window.openerwindow.location ),被限制可以拿到的屬性請看 跨域腳本API訪問

所以當不同的 window, iframe 間不同源的狀況下,無法使用上面的方式拿到另一個頁面的資料,此時需要使用 window.postMessage

window.postMessage(message, targetOrigin, [transfer])
  • message: 傳送的訊息,會先經過 structuredClone 算法複製後再傳送
  • targetOrigin: 目標視窗的網域地址,可以使用 "*" 代表傳遞訊息到任意網域,但避免將洩漏敏感訊息到惡意網站,最好都提供固定的網域地址
  • transfer: 可選擇傳遞 Transferable objects 轉移物件所有權到另一個目標視窗

接著在另一邊的網頁使用 addEventListener('message') 接收 window.postMessage 傳來的訊息,為了避免從任意網站接收到惡意訊息,執行任何操作前都應該先檢查 訊息的發送網域(event.origin) 是否為可信任的

window.addEventListener('message', (event) => {
  // 確認訊息來源為可信任網域,執行接下來的操作
  if (event.origin === 'http://trust-website.org') {
    // 從 e.data 拿出訊息 
    console.log(event.data);
  }
})

以上述更改 iframe 網頁的背景顏色為例,改用 window.postMessage 的寫法如下:

// 主視窗 (http://main.example.com)
const iframe = document.querySelector("iframe");
iframe.onload = () => {
  // 取得 iframe 裡的 window 屬性 
  const iframeWindow = iframe.contentWindow;
  iframeWindow.postMessage({ backgroundColor: 'blue' }, );
};
// iframe 視窗 (http://iframe.example.com)
window.addEventListener('message', (e) => {
  if (event.origin === 'http://main.example.com') {
    const { backgroundColor } = e.data;
    document.querySelector("body").style.backgroundColor = backgroundColor;
  }
});

Worker.postMessage

Web worker 中的 postMessage 在之前的文章中已經多次出現,可以前往 第三天的文章 - Dedicated worker 基本用法,查看更多細節的介紹

ServiceWorker.postMessage

Service Worker 是瀏覽器進行網路請求時的中介層,在向 server 送出請求獲取 html, image, js 等資源的時候,可以中途攔截請求,直接回傳之前已經快取過的檔案,達到離線狀態也能正常瀏覽網頁的功能

ServiceWorker.postMessage 可以從主視窗向 service worker 發送訊息:

// 主視窗
// 註冊 service worker
navigator.serviceWorker.register("service-worker.js");

navigator.serviceWorker.ready.then((registration) => {
  // 發送訊息到 service-worker.js 
  registration.active.postMessage(
    "Test message sent immediately after creation",
  );
});

service-worker.js 檔案裡,獲得從主視窗傳來的訊息:

// service-worker.js
addEventListener("message", (event) => {
  console.log(`Message received: ${event.data}`);
});

Client.postMessage

在 Service worker 中,client 是指通過 Service worker 註冊過的頁面

Client.postMessage 基本上就是 ServiceWorker.postMessage 反向傳遞訊息的用法,Client.postMessage 負責從 service worker 檔案中,傳遞訊息到主視窗

以下範例傳遞訊息到所有 註冊過 service-worker.js 的頁面,首先呼叫 Clients.matchAll() 取得所有註冊過 Service worker 的頁面,接著再使用 Client.postMessage 傳遞訊息

// service-worker.js
self.clients.matchAll().then(function(clients) {
  clients.forEach(function(client) {
    client.postMessage('傳遞到主視窗的訊息');
  });
});

MessagePort: postMessage

請參考 第九天的文章 - Transferable objects - MessagePort (Channel messaging)

BroadcastChannel.postMessage

請參考 第十一天的文章 - BroadcastChannel

總結

不同的場景分別有不同的 postMessage 使用方式,共同點是每種 postMessage 都使用 structuredClone 複製數據後再傳遞訊息,而 是否可以傳遞 Transferable objects是否有同源限制 則有所差異

種類 structuredClone transfer 參數 限制只能同源使用
window.postMessage O O X
Worker.postMessage O O O
ServiceWorker.postMessage O O O
Client.postMessage O O O
MessagePort.postMessage O O X
BroadcastChannel.postMessage O X O

Reference

運用 postMessage 解決 iframe 與父層溝通的問題
postMessage:主頁、iframe 頁可互相傳值
postMessage可太有用了
如何与 Service Worker 通信


上一篇
在 Web worker 中處理音效 - AudioWorklet
下一篇
將 Web worker 內嵌於 HTML 中 - Embedded worker
系列文
網頁的另一個大腦:從基礎到進階掌握 Web Worker 技術30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言