postMessage 在 IE11 上仍存在问题?

29

似乎当消息在以下情况下被发送时,window.postMessage 在 IE 11 上仍然存在问题:

  • 在窗口和使用 window.open 打开的子弹出窗口/选项卡之间
  • 当消息从不同的域中发送 [或者某些情况下是相同的域,参见更新16/01]

IE 8/9/10也存在类似问题,但该功能在 IE 11 中从 IE 10 的“部分支持”标记为“支持”。

这里有一个在 chrome / ff 上工作但在 IE 上不起作用的代码示例:

打开者 (jsfiddle):

$(document).ready(function() {
    $('#log').append('listening...');
    window.addEventListener("message", function(e){
        $('#log').append("Received message: " + JSON.stringify(e.data));
    }, false);
    $('button').click(function() {
        window.open('http://jsbin.com/eQeSeros/1', 'popup','menubar=no, status=no, scrollbars=no, menubar=no, width=200, height=100');
    });
});

这个子窗口弹出(jsbin):(如果不是通过jsfiddle打开,则无法正常工作)

$(document).ready(function() {
   $('body').append('sending...');
   window.opener.postMessage("Hello?", "http://fiddle.jshell.net");
   $('body').append('sent...');
});

我从这篇帖子中读到,我们可以使用MessageChannel代替postMessage,但是看文档时,我没有找到如何在我的实际情况中使用它,因为你必须将端口传递给子窗口。

在我需要发送消息之前,有一个重定向链,所以即使我能发送一个端口,我仍然会失去最初/重定向之前发送的任何js对象。

有什么替代方案吗?

更新14/01: 我正在考虑在窗口/选项卡标题中传递我的数据,并经常从父级检查此标题...但这将是相当不道德的。

更新16/01: 真正糟糕的部分是,即使从同一域发送消息,但在通过另一个域重定向后也会出现问题。

以下是示例: http://jsfiddle.net/L4YzG/13/打开弹出窗口http://jsbin.com/eQeSeros/4/edit,然后重定向到http://jsfiddle.net/mxS8Q/2/(发送消息)

如果你直接将弹出窗口的URL更改为最终的URL,重定向到http://jsfiddle.net/mxS8Q/2/show,这在IE上可以工作,因为在打开和发布之间没有其他域。

我仍在研究如何通过窗口标题来实现“脏技巧”。当窗口处于另一个域时,我们无法接收到窗口的标题,但是如果它回到jsfiddle上,则标题可用(postMessage不会出现之前的问题)。这是一个示例:http://jsfiddle.net/L4YzG/14/ ... 这可能是一种替代方案,但我刚刚看到有关将数据传递给cookie的内容,只需要进行测试。

更新04/02:在标题中传递信息是不够的,在最终域相同的情况下效果很好,但在跨域情况下却不行。我想注入一个相同域的iframe来传递这些信息,但我也无法共享子窗口对象(postMessage需要一个可序列化对象)。

最后,我尝试在js中创建并接收cookie,并在注入的iframe和子窗口之间共享cookie,在Chrome和Firefox上运行良好,但在IE上仍然无法正确接收。添加P3P头之后,它就可以正常工作了,这似乎是真正的解决方法。Safari对这种技术似乎存在一些问题,因此我将这种技术作为备用方案。


1
你能分享一些关于你的cookie解决方案的细节吗? - William
4个回答

16

它坏了吗?嗯,有点。

我尝试了各种想法,但无法让您的jsFiddle中的代码正常工作。查看此MSDN博客文章,我们发现旧版本的IE中,postMessage仅在IFrames之间起作用,这一点甚至对IE 11尚未得到修复。

该文章链接到一个演示程序,其中有几个解决方法涉及调用window.opener上的脚本。然而,正如那篇博客所述(重点是我的):

不幸的是,这种解决方法通常行不通,因为同源策略规定,弹出窗口和window.opener页面必须来自相同的源,才能调用彼此的脚本函数。

所以看起来唯一的方法就像这个,其中子代以IFrame的形式托管在父代中。我基于您的代码创建了一个类似的演示here。它非常简单,但将消息发布到IFrame的contentWindow,后者再做出响应。

我看到使用MessageChannel的建议,但我也想知道是否值得调查使用Web Workers,尽管它们的使用当然取决于您的任务性质。还有这个问题的答案,其中使用了IFrame方法,但使用jQuery UI对话框来显示它-如果您更喜欢Bootstrap中的模态对话框,我想您也可以这样做。


作为参考:

HTML

<iframe id="iframe" src="http://jsbin.com/iLapokOS/7/"></iframe>
<div id="log"></div>
<button id="post-message-button">Post message to window</button>

父级脚本

var wnd;

$(document).ready(function() {
    $('#log').append('listening...');

    wnd = $('#iframe')[0].contentWindow;

    window.addEventListener('message', function(e){
      $('#log').append('<br/>Received message: ' + JSON.stringify(e.data));
    }, false);

    $('#post-message-button').click(function() {
        if(!wnd){
            return;
        }
        $('#log').append('<br/>sending...');
        wnd.postMessage('Hello?', 'http://jsbin.com');
    });
});

儿童HTML和JS

<!DOCTYPE html>
<html>
<head>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<meta charset=utf-8 />
<title>JS Bin</title>
</head>
<body>
  <script>
    $(document).ready(function() {

      window.addEventListener("message", function(e){
        $('body').append('<br/>Origin: ' + e.origin);        
        $('body').append("<br/>Received message: " + JSON.stringify(e.data));

        e.source.postMessage('Hello yourself', e.origin);
      }, false);
    });
  </script>


</body>
</html>

这是一个非常好的解决方法,但在许多情况下不可能实现,例如 X-Frame-Options 标头会在能够发送消息之前破坏重定向(不幸的是我的情况就是如此)。 - bumpmann
@bumpmann 你能控制链中每个网站吗?因为看起来你可以设置X-Frame-Options允许特定来源的页面进行托管。 - nick_w
@bumpmann 我感觉我正在尝试使用OAuth做类似的事情,你找到了什么吗? - Jakub Kuchar
postMessage在IE 11中仍然无法正常工作吗?我似乎无法让它起作用。有人可以帮忙吗? - John Roca

16

更新于01/16:最糟糕的部分是,即使消息是从同一域发送的,但在被另一个域重定向后,此错误也会发生。

有趣的是,这个“安全功能”可以被反向使用,完全绕过跨域限制。

在父窗口 example.com 中:

<script>
  window.open("http://example.com/dummy_redirect");
  window.addEventListener('message', function(ev) {console.log(ev.data)})
</script>

example.com 服务器上:

GET /dummy_redirect 302 http://jsfiddle.net/b6yfbunw/

一个弹出窗口将会打开到你的域名,重定向到jsfiddle,并且postMessage调用将会在IE中运行。之后,你甚至可以导航到任意域名,并继续对父窗口做postMessage调用。


谢谢!这解决了我在IE上的愚蠢问题!奇怪的是,在某些版本的IE 11中,它可以正常工作而不需要这个解决方法。可能是IT部门为某些用户设置的安全策略? - styfle

5

有一些关于iframe的解决方法,但我看到的唯一一个是向iframe发送消息。

这里是一个从iframe接收消息的例子:

父页面(http://first-domain.com/receive-message.html)

<html>
  <head>
    <script>
      window.addEventListener('message', console.log.bind(console, 'Got message:'));
    </script>
  </head>
  <body>
    <iframe src="http://second-domain.com/send-message.html"></iframe>
  </body>
</html>

子页面(http://second-domain.com/send-message.html)

<html>
  <head>
    <script>
      window.parent.postMessage('hi there', '*');
    </script>
  </head>
  <body></body>
</html>

1
但是由于跨域问题,window.parent被阻止访问,不是吗? - AwokeKnowing
Window.postMessage() 方法可以安全地实现跨源通信。该API的语法为 otherWindow.postMessage(message, targetOrigin, [transfer]);,其中 otherWindow 是“对另一个窗口的引用” :) - c24w

2

我发现,如果我打开另一个窗口,再打开我的窗口,然后关闭另一个窗口,那么我就可以正常使用我的窗口。

vvWindow0 = window.open("http://apsed4065:8047/virtualviewer/index.html");
vvWindow = window.open("http://apsed4065:8047/virtualviewer/index.html");
vvWindow0.close(); <!-- to close the window -->

vvWindow.postMessage(message, 'http://apsed4065:8047');


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接