在Chrome扩展程序中,如何将父内容脚本中的跨源消息发送到特定子iframe中的内容脚本

时间:2016-06-29 15:08:36

标签: javascript iframe google-chrome-extension firefox-webextensions

我正在开发一个带有清单的Chrome扩展程序,目前可以访问所有主机。后台脚本将内容脚本注入所有帧。加载DOM后,首页/框架中的内容脚本开始遍历DOM树。当walker遇到iframe时,它需要发送与该iframe窗口相关联的特定内容脚本(可能是跨源)以开始它的工作并包含一些带有此消息的序列化数据。父窗口暂停执行并等待孩子完成它的步行并发回消息,它与序列化数据一起完成。然后父母继续工作。我尝试了两种解决这个问题的方法:

  1. frameElement.contentWindow.postMessage:这在大多数情况下有效,但并非总是如此。有时,与iframe窗口关联的内容脚本消息事件侦听器永远不会收到该消息。我无法确认原因,但我认为在听众呼叫event.stopImmediatePropagation()之前是听众。例如,在yahoo主页(https://www.yahoo.com)上,当向与iframe源https://s.yimg.com/rq/darla/2-9-9/html/r-sf.html关联的内容脚本发布消息时,永远不会收到消息。这是与广告相关的iframe。也许阻止消息是故意的。邮件发布时没有错误,我使用的是targetOrigin“*”。
  2. chrome.runtime.sendMessage:我可以向后台页面发送消息,但无法弄清楚如何告诉后台页面中继消息的帧。父窗口内容脚本不知道与在DOM漫游中遇到的子帧元素关联的chrome扩展frameId。所以它无法告诉后台页面如何指导消息。
  3. 对于第2点,我尝试了两种在stackoverflow上找到的技术:

    1. 使用此question中描述的概念:在父窗口中,确定iframe在window.frames数组中的位置,并使用此索引将消息发布到后台页面。后台页面将消息发布到消息数据中具有所需索引的所有帧。只有在window.parent.frames数组中找到它的窗口对象位置的iframe才能匹配从消息中收到的索引,并继续它的行走。这工作正常,但在异步消息传递过程中容易受到window.frames数组中的更改的影响(如果在发送消息后删除了iframe,则索引值可能不再与所需的帧匹配)。
    2. 而不是第1点的索引值,在父窗口中使用frameElement.name。使用相同的消息传递技术,将名称发送到子iframe以与其window.name值进行比较。我相信window.name在iframe元素创建时从frameElement.name获取它的值。但是,由于我不控制框架元素的创建,因此name属性通常是一个空字符串,不能依赖它来将iframe元素唯一地匹配到它们的窗口。
    3. 有没有办法让我可靠地向与DOM树中的iframe元素关联的内容脚本发送消息?

1 个答案:

答案 0 :(得分:6)

当您从内容脚本调用chrome.runtime.sendMessage时,chrome.runtime.onMessage侦听器(“发件人”)的第二个参数包含属性urlframeId。 您可以使用chrome.tabs.sendMessage使用给定frameId向特定框架发送消息(从扩展程序页面,例如背景页面)。

如果您想随时知道所有帧的列表(及其帧ID),请使用chrome.webNavigation.getAllFrames。如果这样做,那么您可以在选项卡中构建一个框架树,然后将此信息发送到所有框架以供进一步处理。

可靠postMessage / onMessage

  

frameElement.contentWindow.postMessage:这大部分时间都有效,但并非总是如此。有时,与iframe窗口关联的内容脚本消息事件侦听器永远不会收到该消息。我无法确认原因,但我认为在听众调用event.stopImmediatePropagation()

之前是听众

可以通过在"run_at":"document_start"运行脚本并立即注册message事件侦听器来解决此问题。然后,您的处理程序将始终首先被调用,页面无法通过event.stopImmediatePropagation()取消它。但是,不要盲目信任来自其他帧的信息并始终验证消息(例如,通过后台页面与其他帧通信)。

结合两种方法

第一种方法提供了一种在帧之间交换数据的安全方法,但没有提供将帧链接到特定DOM元素的通用方法。
第二种方法允许您定位特定的(i)框架元素,但任何网页都可以这样做,因此该方法本身并不可靠。 通过组合两者,您将获得一个链接到DOM元素的安全通信通道。

这是应用上述方法在帧A和B之间进行通信的基本示例:

  1. A:

    中的内容脚本
    1. 向背景页面发送消息(例如包含B帧索引的消息)。
  2. 背景页面:

    1. 收到来自A。
    2. 的消息
    3. 生成随机的随机数,例如 R crypto.getRandomValues)。
    4. 存储从 R frameId的映射(以及来自A的消息中包含的其他信息)。
    5. 使用此随机值调用响应回调。
  3. A:

    中的内容脚本
    1. 从后台页面接收 R
    2. 在第B帧调用postMessage并传递 R
  4. B中的内容脚本:

    1. 从A。
    2. 接收 R
    3. 向背景页面发送消息以检索frameId(以及可选的A中的其他信息)。
  5. 注意:对于坚如磐石的应用程序,您需要考虑在任何这些步骤中删除框架的事实。如果忽略此过程的异步性质,可能会使应用程序处于不一致状态。