利用 postMessage 實(shí)現(xiàn)父子頁面通信

下面介紹一種 postMessage 方式實(shí)現(xiàn)父子頁面通信的方法,首先先了解一個應(yīng)用場景:

  1. 父頁面有一個列表,每一個列有一個"編輯"按鈕,點(diǎn)擊按鈕打開一個新的 tab 頁面為子頁面
  2. 子頁面是某一列內(nèi)容的編輯頁面,子頁面有一個"保存并關(guān)閉"的按鈕
  3. 點(diǎn)擊子頁面的"保存并關(guān)閉"的按鈕,子頁面關(guān)閉,向主頁面發(fā)起一次通信,告知主頁面需要刷新列表
  4. 主頁面接受子頁面的通信,根據(jù)父子頁面約定好的標(biāo)識,判斷是否需要刷新數(shù)據(jù)列表,是的話執(zhí)行刷新列表操作,over

這個需求在一些PC端的企業(yè)管理系統(tǒng)中經(jīng)常會遇到,也是一個比較好的用戶體驗(yàn),下面先看實(shí)現(xiàn)方法(框架使用的是 vue),demo后有原理實(shí)現(xiàn)呦~!

父頁面:

// "編輯"按鈕
methods: {
  handleEdit() {
    window.open('www.xxx.com', '_blank');
  }
},
mounted() {
  // 接受子頁面的請求信息
  window.addEventListener('message', (event) => {
    if (event.origin === window.location.origin) {
      // 根據(jù)接受到的信息執(zhí)行對應(yīng)的方法
      if (event.data === 'update') {
        // 這里調(diào)用刷新數(shù)據(jù)列表方法
      }
    }
  })
}

子頁面:

methods: {
  // 關(guān)閉子頁面并向父頁面發(fā)起通信
  handleClosePage() {
    if (window.opener) {
      window.opener.postMessage('update', window.opener.origin);
    }
    window.close();
  }
}

首頁第一個需要介紹的知識點(diǎn)是 HTML DOM 事件中的 "onmessage" 事件,這個事件的描述是"該事件通過或者從對象(WebSocket, Web Worker, Event Source 或者子 frame 或父窗口)接收到消息時觸發(fā)"。在這個應(yīng)用場景中"onmessage"的作用就是父窗口接收信息。

那如何向父頁面?zhèn)鬟f信息呢,就用到了子頁面中的 postMessage 方法,postMessage 可以安全地實(shí)現(xiàn)跨源通信,它接受三個參數(shù),第一個是 message,也就是需要發(fā)送的信息;第二個是 targetOrigin,通過窗口的 orgin 屬性來指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示無限制)或者一個URI。在發(fā)送消息的時候,如果目標(biāo)窗口的協(xié)議、主機(jī)地址或端口這三者的任意一項(xiàng)不匹配targetOrigin提供的值,那么消息就不會被發(fā)送;只有三者完全匹配,消息才會被發(fā)送。這一點(diǎn)在傳遞一些比如密碼之類的加密數(shù)據(jù)時尤為重要。強(qiáng)烈不建議用"*",這樣會有被惡意第三方截獲的風(fēng)險! 第三個參數(shù)是一串和 message 同時傳遞的 Transferable 對象. 這些對象的所有權(quán)將被轉(zhuǎn)移給消息的接收方,而發(fā)送一方將不再保有所有權(quán)。

這樣通過 "onmessage" 和 postMessage 就可以實(shí)現(xiàn)父子頁面的通信了,那為什么父子頁面中都加了 if 的 case 攔截呢?下面一個一個說:

先介紹子頁面的 window.opener 對象,我們知道在 <iframe> 中提供了一個用于父子頁面交互的對象,叫做 window.parent,我們可以通過 window.parent 對象來從框架中的頁面訪問父級頁面的 window。opener 和 parent 一樣,只不過用于 <a target="_blank"> 在新標(biāo)簽頁打開的頁面,我們可以直接使用 window.opener 來訪問來源頁面的 window 對象。

當(dāng)然如果父頁面被手動關(guān)閉了的話,window.opener 也就變成了 null,所以需要加一個 window.opener 是否存在的判斷,然后再調(diào)用父頁面的 postMessage 方法,targetOrigin則直接使用父頁面的 origin,這里我沒有寫死。這樣子頁面的邏輯就完整了。

注意,使用 target="_blank" + opener 的方式有可能是危險的,進(jìn)一步了解:危險的 target="_blank" 與 “opener”

再就是父頁面中 addEventListener('message') 的回調(diào)函數(shù)了,從回調(diào)函數(shù)中可以拿到 event 對象,event 中有三個屬性:① data,也就是接收到的信息(注意: 在 Gecko 6.0 (Firefox 6.0 / Thunderbird 6.0 / SeaMonkey 2.3)之前, 參數(shù) message 必須是一個字符串。 從 Gecko 6.0 (Firefox 6.0 / Thunderbird 6.0 / SeaMonkey 2.3)開始,參數(shù) message被使用結(jié)構(gòu)化克隆算法進(jìn)行序列化。這意味著您可以將各種各樣的數(shù)據(jù)對象安全地傳遞到目標(biāo)窗口,而不必自己序列化它們。);② origin,就是 postMessage 中的 targetOrigin 參數(shù),用來過 origin 過濾;③ source,對發(fā)送消息的窗口對象的引用;可以使用這個參數(shù)來使具有不同 origin 的兩個窗口之間建立雙向通信,因?yàn)檫@個應(yīng)用場景沒有這個需求,所以這塊就不展開說明了。

這樣我們就知道了 event.origin === window.location.origin 的作用,根據(jù) origin 這過濾其他 origin 傳遞過來的信息。這個判斷很重要,不加就很多可能會被 XSS 攻擊!

至此所有的知識點(diǎn)就介紹完了,更多細(xì)節(jié)請參考以下鏈接:

  1. window.postMessage
  2. window.opener
  3. HTML DOM 事件
  4. XSS攻擊
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容