【问题标题】:Programmatic injection on nested iframes in extension page扩展页面中嵌套 iframe 的编程注入
【发布时间】:2017-06-08 01:09:43
【问题描述】:

总结:我需要找到一种方法来实现与在清单上使用content_scripts > matches"all_frames": true 相同的行为。为什么?因为这是我发现在扩展页面中注入 iframe 的内容而不会出现跨域错误的唯一方法。


我要在 Chrome 扩展程序上转到 optional_permissions,但我走上了死胡同。

我想要什么:

将此行为移至optional_permissions,以便将来能够添加更多主机。使用当前代码,通过在 content_scripts > matches 添加一个新主机,Chrome 会禁用扩展。

为了移动,我在清单中删除了content_scripts,并添加了"optional_permissions": ["*://*/"],。然后,我成功实现了一个对话框,向chrome.permissions.request 的用户询问新权限。

正如我之前所说,问题是如何在扩展页面中注入iframe的内容。

我尝试过的:

  1. chrome.declarativeContent.RequestContentScript(提到here)和allFrames: true。如果我直接输入 URL,我只能看到脚本正在运行,如果在 iframe 中设置该 URL,则不会发生任何事情。
  2. chrome.tabs.onUpdatedurlundefined 用于扩展页面。此外,未检测到 iframe 网址。
  3. 加载第一个iframe 后,立即使用allFrames: true 调用chrome.tabs.executeScript。通过这样做,我得到了一个异常 Cannot access contents of the page. Extension manifest must request permission to access the respective host. 并且“相应的主机”是 chrome-extension://,如果你想将它添加到权限中,它不是一个有效的主机。

我迷路了。我找不到一种方法来模拟与content_scripts > matchesprogrammatic injection 相同的行为。

注意:使用webNavigation API 不是一个选项,因为该扩展程序已上线并且拥有数千名用户。因此,我不能将frameId 属性用于executeScript。因此,我对executeScript 的唯一选择是注入所有帧,但chrome-extension 主机问题不允许我继续。


更新:我能够完成我想要的,但只能在 HTTP 主机上完成。我使用了chrome.tabs.executeScript(选项3)。

问题仍然在于如何在扩展页面上进行这项工作。

【问题讨论】:

  • 您无法将内容脚本注入到您的扩展程序中的页面中,这是您遇到的错误。您已经拥有或可以拥有在后台上下文中以提升的权限在页面中运行的 JavaScript,您不需要也不能使用内容脚本。此外,tabs.executeScript() 中的 allFrames:truemanifest.json content_scripts 条目中的 "all_frames": true 的含义截然不同。
  • 可以修改默认CSP,将youtube添加到script-src和frame-src。
  • @wOxxOm 我刚刚尝试过,但遇到了同样的chrome-extension 主机问题。如果我在 http 域中注入扩展页面内容,我可以绕过该错误。有没有办法使用declarativeContent API 在动态iframes 中注入代码?我在示例中写了 youtube,但我有几个域权限。
  • 感谢您提供附加代码。为您已经尝试过但不起作用的事情提供代码也会很有帮助。
  • 顺便说一句:虽然 IIFE 通常是很好的做法,但在代码所在的上下文/范围完全由您控制的扩展中,它们并不是必需的。除了您编写的代码之外,您的背景、内容脚本或扩展弹出窗口中没有任何内容。使用它们没有任何问题。显然,除非它们阻止您做您想做的事情(例如,在后台上下文中提供对范围之间数据的访问)。

标签: google-chrome google-chrome-extension


【解决方案1】:

您不能在任何扩展页面中运行内容脚本,包括您自己的页面。

如果您想在扩展页面的子框架中运行代码,则必须使用frameId。有两种方法可以做到这一点,有和没有webNavigation

我已经把这个答案中的所有代码 sn-ps 放在一起(带有一些按钮来调用单个代码 sn-ps)并在https://robwu.nl/s/optional_permissions-script-subframe.zip共享它
要试用,请下载并解压 zip 文件,在 chrome://extensions 加载扩展程序,然后单击扩展程序按钮打开测试页面。

请求可选权限

由于目标是使用optional permissions 以编程方式运行脚本,因此您需要请求权限。我的示例将使用 example.com。 如果您也想使用 webNavigation API,请将其权限也包含在权限请求中。

chrome.permissions.request({
    // permissions: ['webNavigation'], // uncomment if you want this.
    origins: ['*://*.example.com/*'],
}, function(granted) {
    alert('Permission was ' + (granted ? '' : 'not ') + 'granted!');
});

在子帧中注入脚本

一旦您有了标签 ID 和 frameId,在特定框架中注入脚本就很容易了。由于 tabId 的要求,这个方法只适用于 tabs 中的框架,而不适用于 browserAction/pageAction 弹窗或背景页面中的框架!

为了证明代码执行成功,我下面的示例将在已知 tabId 和 frameId 后调用下一个 injectInFrame 函数。

function injectInFrame(tabId, frameId) {
    chrome.tabs.executeScript(tabId, {
        frameId,
        code: 'document.body.textContent = "The document content replaced with content at " + new Date().toLocaleString();',
    });
}

如果您不仅要在特定帧中运行代码,而且要在该帧的所有子帧中运行代码,只需将 allFrames: true 添加到 chrome.tabs.executeScript 调用即可。

选项 1:使用 webNavigation 查找 frameId

使用 chrome.tabs.getCurrent 查找运行脚本的选项卡的 ID(如果您想从另一个脚本(例如后台脚本)了解当前的 tabId,则使用 chrome.tabs.query{active:true,currentWindow:true}

之后,使用chrome.webNavigation.getAllFrames 查询选项卡中的所有帧。识别框架的主要方法是通过页面的 URL,因此如果框架页面重定向到其他地方,或者有多个具有相同 URL 的框架,您就会遇到问题。这是一个例子:

// Assuming that you already have a frame in your document,
// i.e. <iframe src="https://example.com"></iframe>
chrome.tabs.getCurrent(function(tab) {
    chrome.webNavigation.getAllFrames({
        tabId: tab.id,
    }, function(frames) {
        for (var frame of frames) {
            if (frame.url === 'https://example.com/') {
                injectInFrame(tab.id, frame.frameId);
                break;
            }
        }
    });
});

选项2:使用框架中的帮助页面查找frameId

webNavigation 的选项看起来很简单,但有两个主要缺点:

  • 它需要 webNavigation 权限(导致“阅读您的浏览历史”权限警告)
  • 如果有多个框架具有相同的 URL,则框架的识别可能会失败。

另一种方法是首先打开一个发送扩展消息的扩展页面,然后在chrome.runtime.onMessage 侦听器的第二个参数中提供的元数据中找到frameId(和选项卡ID)。此代码比其他选项更复杂,但更可靠且不需要任何额外权限。

framehelper.html

<script src="framehelper.js"></script>

framehelper.js

var parentOrigin = location.ancestorOrigins[location.ancestorOrigins.length - 1];
if (parentOrigin === location.origin) {
    // Only send a message if the frame was opened by ourselves.
    chrome.runtime.sendMessage(location.hash.slice(1));
}

要在您的扩展页面中运行的代码:

chrome.runtime.onMessage.addListener(frameMessageListener);
var randomMessage = 'Random message: ' + Math.random();

var f = document.createElement('iframe');
f.src = chrome.runtime.getURL('framehelper.html') + '#' + randomMessage;
document.body.appendChild(f);

function frameMessageListener(msg, sender) {
    if (msg !== randomMessage) return;
    var tabId = sender.tab.id;
    var frameId = sender.frameId;

    chrome.runtime.onMessage.removeListener(frameMessageListener);
    // Note: This will cause the script to be run on the first load.
    // If the frame redirects elsewhere, then the injection can seemingly fail.
    f.addEventListener('load', function onload() {
        f.removeEventListener('load', onload);
        injectInFrame(tabId, frameId);
    });
    f.src = 'https://example.com';
}

【讨论】:

  • 太棒了!我知道你是我唯一的希望 :) 感谢您抽出宝贵时间! I uploaded a working example here on GitHub.
  • 在清单中你没有在"background" 上使用"persistent": false,它会改变什么来使用它吗?
  • @IvanRF 不管怎样都无所谓。在演示示例中,我只使用背景页面为打开测试页面的按钮注册一个点击处理程序。您甚至可以删除背景页面,演示仍然可以工作。
  • 哦,你是对的!我错过了 sender 参数上的 frameId 属性。这就是诀窍! :)
  • 您知道"run_at": "document_end" 是否与iframe.onload 完全相同吗?我的意思是,代码是同时注入的吗?
猜你喜欢
  • 2013-10-12
  • 1970-01-01
  • 2018-05-03
  • 1970-01-01
  • 2016-10-09
  • 1970-01-01
  • 2020-04-28
  • 2015-10-29
  • 2012-04-23
相关资源
最近更新 更多