9

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

  1. frameElement.contentWindow.postMessage: 这在大多数情况下都有效,但并非总是如此。有时,与 iframe 窗口关联的内容脚本消息事件侦听器永远不会收到该消息。我无法确认原因,但我认为它是在我的听众呼叫之前附加的听众event.stopImmediatePropagation()。例如,在雅虎主页 ( 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 walk 中遇到的子框架元素关联的 chrome 扩展 frameId。所以它不能告诉后台页面如何引导消息。

对于第 2 点,我尝试了在 stackoverflow 上找到的两种技术:

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

有没有办法让我可靠地将消息发送到与遍历 DOM 树时发现的 iframe 元素相关联的内容脚本?

4

1 回答 1

11

当您chrome.runtime.sendMessage从内容脚本调用时,chrome.runtime.onMessage侦听器的第二个参数(“sender”)包括属性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)frame 元素,但任何网页都可以做到这一点,因此该方法本身并不可靠。通过将两者结合起来,您可以获得一个链接到 DOM 元素的安全通信通道。

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

  1. A中的内容脚本:

    1. 向后台页面发送消息(例如,包含帧 B 索引的消息)。
  2. 背景页面:

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

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

    1. 从 A接收R。
    2. 向后台页面发送消息以检索 frameId(以及可选的来自 A 的其他信息)。

注意:对于坚如磐石的应用程序,您需要考虑框架在任何这些步骤中被移除的事实。如果您忽略此过程的异步特性,您可能会使您的应用程序处于不一致的状态。

于 2016-07-03T07:01:42.840 回答