window.postMessage

HTML5 跨域通信 API - window.postMessage
 

参考
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 跨域通信。

解决问题要从问题源头出发,我现在遇到的问题归根究底就是两个不同域名的页面如何进行通信?
浏览器的同源政策不允许跨域,然而 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 等等。

同样的,在 PageA 内添加一个事件监听绑定 message 事件,在 PageB 内通过 postMessage 方法发送信息给 PageA 一样可以进行跨域通信。

Tips
我们可以通过 event.origin 来过滤掉来自其他未知站点发送过来的 message 事件信息,防止跨站攻击!

大概就是以上的思考,然后继续写 Demo...

干货代码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() 可以对你的站点为所欲为。

转载于:https://my.oschina.net/u/2369810/blog/746642

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值