postMessage 源 IFrame

Posted

技术标签:

【中文标题】postMessage 源 IFrame【英文标题】:postMessage Source IFrame 【发布时间】:2013-02-26 02:32:09 【问题描述】:

我正在开发一个具有跨域 iframe 的网站,这些 iframe 使用 postMessage 调整到正确的高度。我遇到的唯一问题是确定哪个 iframe 具有哪个高度。我目前的设置方式是,当一个 iframe 将其高度发送给父级时,所有 iframe 的高度都会更改。

家长:

var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod];
var messageEvent = eventMethod == "attachEvent" ? "onmessage" : "message";

eventer(messageEvent, function(e) 
    $('iframe').height(e.data);
, false);

内嵌框架:

var updateHeight = function() 
    if(window.parent) 
        window.parent.postMessage($('.widget').outerHeight(), '*');
    
;

有什么方法可以识别是哪个 iframe 发送了message 事件?

【问题讨论】:

【参考方案1】:

是的,您可以识别执行postMessage 的 IFRAME。有不同的情况:

源 IFRAME 与接收消息的窗口具有 同源 URL(例如 http://example.com/):IFRAME 使用标识

myIFRAME.contentWindow == event.source

源 IFRAME 与父 html 页面有一个同源但相对 URL(例如 /myApp/myPage.html):使用

标识 IFRAME

myIFRAME.contentWindow == event.source.parent

源 IFRAME 有一个 跨域 URL(例如http://example.com/)与接收消息的页面(例如http://example.org/)不同:上述方法不起作用(比较是总是false 并且访问event.source 的属性会导致Access Deniederrors),并且必须根据其原始域来识别 IFRAME;

myIFRAME.src.indexOf(event.origin)==0

为了管理这三种不同的情况,我使用以下方法:

var sourceFrame = null; // this is the IFRAME which send the postMessage
var myFrames = document.getElementsByTagName("IFRAME");
var eventSource = event.source; // event is the event raised by the postMessage
var eventOrigin = event.origin; // origin domain, e.g. http://example.com

// detect the source for IFRAMEs with same-origin URL
for (var i=0; i<myFrames.length; i++) 
    var f = myFrames[i];
    if (f.contentWindow==eventSource || // for absolute URLs
        f.contentWindow==eventSource.parent)  // for relative URLs
        sourceFrame = f;
        break;
    


// detect the source for IFRAMEs with cross-origin URL (because accessing/comparing event.source properties is not allowed for cross-origin URL)
if (sourceFrame==null) 
    for (var i=0; i<myFrames.length; i++) 
        if (myFrames[i].src.indexOf(eventOrigin)==0) 
            sourceFrame = myFrames[i];
            break;
        
    

对于跨域 URL,请注意,如果 event.origin 是多个 IFRAME 共用的域,我们无法区分真实来源。

有些人使用=== 而不是==,但我在这种情况下没有发现任何区别,所以我使用的是最短比较器。

此实现已经过测试并在以下条件下工作:

MSIE 9 火狐17

作为替代方案(Griffin 建议),您可以使用具有唯一标识符(例如时间戳)的 IFRAME src,并且 IFRAME 的 Web 应用程序将在发布的消息中发回此唯一标识符。虽然 IFRAME 识别会更简单,但这种方法需要修改 IFRAME 的 Web 应用程序(这并不总是可能的)。这也可能导致安全问题(例如,IFRAME 的 Web 应用程序试图猜测其他 IFRAME 应用程序的唯一标识符)。

【讨论】:

您最好使用时间戳查询字符串确保每个 iframe 都有一个唯一的 href,然后在跨文档消息中包含源 href。 @Griffin:你是对的,但也有一些缺点。我编辑了答案以添加您的替代方案并支持您的评论。 不允许访问 eventSource.parent。与 eventSource 相比应该没问题。 @JulienKronegg 你确定myIFRAME.contentWindow == event.source 不适用于第二种情况吗? ev.source.parent 看起来像是来自两个嵌入级别的消息。 我在大约 3 年前为我以前的一位客户的一个复杂的应用程序框架编写了这段代码(所以我不再有代码了)。我遇到了相对 URL 的问题,所以我必须找到 ev.source.parent 解决方法。据我记得,postMessage 对于相对 URL 的行为不同,并提供更深层次的 HTMLElement 作为源,因此.parent【参考方案2】:

我从这里找到了解决方案:How to share a data between a window and a frame in javascript

家长:

var frames = document.getElementsByTagName('iframe');
for (var i = 0; i < frames.length; i++) 
    if (frames[i].contentWindow === event.source) 
        $(frames[i]).height(event.data); //the height sent from iframe
        break;
    

【讨论】:

event.source 的比较并非在所有情况下都有效。为了使 IFRAME 识别更加可靠,我必须设置一个后备条件,例如 if (frames[i].src.indexOf(event.origin)==0) @JulienKronegg 这实际上不太健壮,因为它会匹配任何具有同一来源的 src 的 iframe(除非您另外这样做)。哪些情况?我在 FF 中遇到了匹配问题。 @RandyHall:是的,另外,为了管理跨域问题。为了更好地理解,我将我的评论提升为答案,请参阅***.com/a/20404180/698168 @JulienKronegg 您在哪里遇到过这个问题?我无法在 IE10、Chrome33 或 ios7 中重现它 @JörnBerkefeld:跨域 URL 出现问题,请参阅 my answer【参考方案3】:

我有一个想法来解决这个问题。当您创建 iframe 时,为 iframe 提供名称/ID。 .

并且,在 iframe 内的脚本中将消息作为对象发送,看起来像

window.parent.postMessage("height" : $('.widget').outerHeight(), "frmname" : window.name, '*');

在父监听器中,

eventer(messageEvent, function(e) `enter code here`
    $(e.data.frmname).height(e.data.height);
, false);

【讨论】:

【参考方案4】:

以下对我来说是跨域的:

window.addEventListener('message', function (event) 
  if (event.data.size) 
    Array.prototype.forEach.call(document.getElementsByTagName('iframe'), function (element) 
      if (element.contentWindow === event.source) 
        element.style.height = `$event.data.size.heightpx`;
      
    );
  
, false);

在 Chromium 64 和 Firefox 59 中测试。

【讨论】:

【参考方案5】:

如果源 iframe 嵌套在多个父 iframe 中,则您需要递归每个 iframe 的 window.frames 属性并将其与 messageEvent#source 属性进行比较。

例如,如果消息是由这个Dom的iframe#level3生成的。

<iframe Id=level1>
   <iframe Id=level2>
       <iframe Id=level3 />
   </iframe>
</iframe>

您应该能够在当前窗口中找到祖先 iframe 的索引。

FindMe = event.source
FrameIndex = find(window)
frames[FrameIndex].frameElement ==     getElByTagName(iframe)[FrameIndex] 

function find(target)
    for (i=0; I< target.frames.length; i ++)
       if(target.frames[i] == FindMe ||   find(target.frames[i]))
           return i
    return false 

重要的是要注意

Window.frames 属性不受跨域策略的限制

无论源 iframe 的嵌套有多深,此技术都将起作用

window.frames 是窗口对象的集合,而不是 iframe 元素。

对 window.frames 集合成员的属性的访问由 Same Origin 限制(即您可能无法访问 window.frames[i] 的 frameElement 或 location 属性

【讨论】:

【参考方案6】:

事件还应该有一个属性“source”,可以与 iframe 的“contentWindow”属性进行比较。

【讨论】:

比较 event.source 不适用于跨域 URL,请参阅 ***.com/a/20404180/698168

以上是关于postMessage 源 IFrame的主要内容,如果未能解决你的问题,请参考以下文章

使用window.postMessage()方法跨域通信

使用window.postMessage()方法跨域通信

使用window.postMessage()方法跨域通信

使用window.postMessage()方法跨域通信

h5h5数据跨域交换postMessage用法

Youtube API 错误 - 无法在“DOMWindow”上执行“postMessage”: