扩展页面中嵌套 iframe 的编程注入

Iva*_*nRF 5 google-chrome google-chrome-extension

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


我正在转向optional_permissionsChrome 扩展,但我陷入了死胡同。

我想要的是:

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

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

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

我尝试过的:

  1. chrome.declarativeContent.RequestContentScript这里提到)与allFrames: true. 如果直接输入 URL,我只能看到脚本正在运行,当该 URL 设置为iframe.
  2. chrome.tabs.onUpdated:url用于undefined扩展页面。另外,iframe未检测到该 url。
  3. chrome.tabs.executeScript当我加载allFrames: true第一个iframe. 通过这样做,我得到一个异常Cannot access contents of the page. Extension manifest must request permission to access the respective host.,并且“各自的主机”是chrome-extension://,如果您想将其添加到权限中,它不是有效的主机。

我迷路了。我找不到一种方法来模拟与content_scripts>matches使用编程注入相同的行为。

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


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

问题仍然是如何在扩展页面上实现此功能。

Rob*_*b W 5

您无法在任何扩展页面(包括您自己的扩展页面)中运行内容脚本。

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

我已将这个答案中的所有代码片段放在一起(使用一些按钮来调用各个代码片段),并在https://robwu.nl/s/optical_permissions-script-subframe.zip
上 共享它。解压 zip 文件,在 chrome://extensions 加载扩展程序,然后单击扩展程序按钮打开测试页。

请求可选权限

由于目标是以编程方式运行具有可选权限的脚本,因此您需要请求权限。我的示例将使用 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!');
});
Run Code Online (Sandbox Code Playgroud)

在子帧中注入脚本

一旦有了选项卡 ID 和框架 ID,在特定框架中注入脚本就很容易了。由于 tabId 要求,此方法仅适用于选项卡中的框架,不适用于 browserAction/pageAction 弹出窗口或背景页面中的框架!

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

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

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

选项 1:使用 webNavigation 查找frameId

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

之后,用于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;
            }
        }
    });
});
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

框架助手.html

<script src="framehelper.js"></script>
Run Code Online (Sandbox Code Playgroud)

框架助手.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));
}
Run Code Online (Sandbox Code Playgroud)

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

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';
}
Run Code Online (Sandbox Code Playgroud)