window.postMessage

HTML5 跨域通訊 API - window.postMessage
 html

參考
MDN - Window.postMessage()跨域

Syntax
otherWindow.postMessage(message, targetOrigin, [transfer])瀏覽器

  • message 你要發送的信息(字符串和對象均可以)
  • targetOrigin 你要發送信息的目標域名
  • transfer 可選參數,具體啥意思還沒作深刻了解,也暫時都還沒用到過。

MDN 介紹的 window.postMessage() 是針對在一個頁面使用 window.open() 動態打開新的頁面而進行的跨域通訊,很是詳細,Demo 也很實用,可是對我而言貌似還欠缺什麼東西。框架

注意,在實現跨域通訊是,必須首先先得到其餘域的window窗體對象函數

Tips
在 var targetPage = window.open('http://target.com') 打開新頁面以後須要等到 http://target.com 頁面加載完成以後才能進行 postMessage 跨域通訊,可是在跨域的狀況下咱們是沒法對 targetPage 進行 onload 事件監聽的,因此這裏只能作 延遲 setTimeout 或者 定時 setInterval 處理。 一樣的,在頁面內嵌入 iframe 頁面的狀況下,咱們也須要等到頁面內的 iframe 加載完成以後進行 postMessage 跨域通訊。post

解決問題要從問題源頭出發,我如今遇到的問題歸根究底就是兩個不一樣域名的頁面如何進行通訊?
瀏覽器的同源政策不容許跨域,然而 HTML5 API window.postMessage() 就是用來實現跨域通訊的。
那麼通訊的原理是怎樣的了?
若是有兩個頁面 PageA 和 PageB,PageA 頁面內嵌入 iframe PageB,那麼理論上是應該能夠實現雙向通訊的。
其實很是簡單,就是 PageA 經過 window.postMessage() 發送一個信息給 PageB,PageB 在 window 上添加一個事件監聽綁定 message 事件能夠接收到來自任何不一樣域名經過 postMessage 方法發送過來的信息,當 PageB 接收到 PageA 發送過來的信息時執行監聽事件就 OK,在監聽事件的 event 參數中包含了全部 message 事件接收到的相關數據。包括髮送信息的內容 event.data,發送信息的域名 event.origin 等等。spa

一樣的,在 PageA 內添加一個事件監聽綁定 message 事件,在 PageB 內經過 postMessage 方法發送信息給 PageA 同樣能夠進行跨域通訊。code

Tips
咱們能夠經過 event.origin 來過濾掉來自其餘未知站點發送過來的 message 事件信息,防止跨站攻擊!orm

大概就是以上的思考,而後繼續寫 Demo...htm

乾貨代碼PageA

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Page A</title>
</head>
<body>
  <h1>This is Page A</h1>
  <button id="openNewWindowBtn" type="button">Open New Window</button>
  <button id="postMessageBtn" type="button">Post Message</button>
  <p id="message"></p>
  <iframe id="receiverIframe" src="http://192.168.198.157:3000/pageB.html" frameborder="1" width="800" height="500"></iframe>
  <script>
    window.onload = function() {
      var receiver = document.getElementById('receiverIframe').contentWindow;
      var postBtn = document.getElementById('postMessageBtn');
      var openBtn = document.getElementById('openNewWindowBtn');
      var messageEle = document.getElementById('message');

      function sendMessage() {
        receiver.postMessage('Hello Page B.. This is page A.. You are my iframe', 'http://192.168.198.157:3000');
      }

      function openNewWindow() {
        var pageB = window.open('http://192.168.198.157:3000/pageB.html');

        setTimeout(function() {
          pageB.postMessage('Hello Page B.. This is Page A.. (form PageA window.open())', 'http://192.168.198.157:3000');
        }, 500)
      }

      function receiveMessage(event) {
        console.log(event);

        if (event.origin !== 'http://192.168.198.157:3000') return;

        messageEle.innerHTML = "Message Received: " + event.data;
      }

      postBtn.addEventListener('click', sendMessage, false);

      openBtn.addEventListener('click', openNewWindow, false);

      window.addEventListener('message', receiveMessage, false);
    }
  </script>
</body>
</html>

PageB 

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Page B</title>
</head>
<body>
  <h1>This is Page B</h1>
  <button id="postMessageBtn" type="button">Post Message</button>
  <p id="message"></p>
  <script>
    window.onload = function() {
      var postBtn = document.getElementById('postMessageBtn')
      var messageEle = document.getElementById('message');

      function receiveMessage(event) {
        console.log(event);

        if (event.origin !== 'http://192.168.198.157:8000') return;

        messageEle.innerHTML = "Message Received: " + event.data;

        // 接收 PageA 的任何消息都自動回覆並加上時間戳
        event.source.postMessage('Hello Page A.. This is page B.. (from PageB autoreply) timestamp = ' + new Date().getTime(), event.origin);
      }

      function sendMessage() {
        // 這裏須要特別注意!!!
        // 直接打開 PageB (當前頁面) 是沒法向 PageA 發送跨域信息的!!!
        // 只有當 PageB (當前頁面) 處於 PageA 頁面內的 iframe 中的時候才能發送跨域信息 
        // 並且此處不能使用 window.postMessage()
        // 由於 PageB (當前頁面) 是 PageA 頁面內嵌入的 iframe
        // 此時 PageB 的 window 指向的是 PageA 內 iframe 框架內的 window
        // 而當前狀況須要指向父級 window (即 top 或者 parent) 才能進行 postMessage
        top.postMessage('Hello Page A.. This is page B..', 'http://192.168.198.157:8000');
      }

      postBtn.addEventListener('click', sendMessage, false);

      window.addEventListener('message', receiveMessage, false);
    }
  </script>
</body>
</html>


踩過的坑

PageB 須要特別注意的地方!!!
直接在瀏覽器中打開 PageB 頁面是沒法向 PageA 頁面發送跨域信息的!!!

PageB 頁面的 receiveMessage 方法自動回覆了全部來自 PageA 頁面的 postMessage 信息而且加上了時間戳。

爲何 PageB 頁面內的 sendMessage 方法使用的是 top.postMessage() 發送跨域信息???

答案就在下面的結論中
Tips
若是不是使用 window.open() 打開的頁面或者 iframe 嵌入的頁面,就跟當前頁面扯不上任何關係,是沒法使用 window.postMessage() 進行跨域通訊的!!!

描述的貌似不是很清楚,舉個栗子:

若是你打開瀏覽器,輸入一個頁面地址 PageA,而後打開一個新的標籤頁,又輸入一個頁面地址 PageB,那麼這兩個頁面是不管如何都不能使用 window.postMessage() 來進行跨域通訊的,他們並無任何血緣關係...

一樣,打開瀏覽器,輸入一個頁面地址 PageA,而後經過 PageA 動態打開 PageB (固然,不是經過 PageA 內的 a 標籤連接打開),或者 PageA 內嵌入了 iframe PageB,那麼這個時候就厲害了,它兩有血緣關係啦!PageB 這個時候是否是就至關因而 PageA 是崽崽?是由於有了 PageA,因此纔有了 PageB 的出現。而後理所固然的,PageA 擁有了控制 PageB 的某些權限,其中就包括 window.postMessage()。

得出結論

window.postMessage() 中的 window 究竟是什麼呢?

A:始終是你要通訊的目標頁面的 window

PageA 頁面內嵌入 iframe PageB 頁面
PageA 頁面向 PageB 頁面發送跨域信息,window 爲 PageB 頁面的 window,即 iframe.contentWindow。

PageB 頁面向 PageA 頁面發送跨域信息,window 爲 PageA 頁面的 window,即 top 或者 parent。

PageA 頁面內代碼使用 window.open() 打開 PageB 頁面
PageA 頁面向 PageB 頁面發送跨域信息,window 爲 var pageB = window.open('http://192.168.197.157:3000/pageB.html') 中的變量 pageB。

PageB 頁面沒法主動給 PageA 頁面發送跨域信息,必須先接收到 PageA 頁面發送過來的 message 而後再經過 event.source 發送給 PageA,沒錯... 此時的 window 就是 event.source,即 PageA 的 window

最後最後

請不管如何在監聽 message 事件的函數內對 event.origin 進行過濾,否則來自未知站點的 window.postMessage() 能夠對你的站點隨心所欲。

相關文章
相關標籤/搜索