Chrome扩展程序内容脚本在升级或安装后重新注入

66
安装或升级我正在开发的 Chrome 扩展后,内容脚本(在清单文件中指定)不会被重新注入,因此需要刷新页面才能使扩展程序正常工作。是否有一种方法可以强制重新注入这些脚本?
我认为我可以通过从清单文件中删除它们并在后台页处理要注入的页面来以编程方式再次注入它们,但这不是一个好的解决办法。
我不想自动刷新用户的标签页,因为这可能会丢失一些数据。Safari 在安装或升级扩展时会自动刷新所有页面。

看起来Safari 7(至少)不再在安装扩展时自动刷新页面。 - baseten
7个回答

64

有一种方法可以允许内容脚本重的扩展在升级后继续运行,并在安装后立即起作用。

安装/升级

安装方法是简单地遍历所有窗口中的所有选项卡,并将一些脚本以编程方式注入具有匹配URL的选项卡中。

ManifestV3

manifest.json:

"background": {"service_worker": "background.js"},
"permissions": ["scripting"],
"host_permissions": ["<all_urls>"],

这些 host_permissions 应该与内容脚本的 matches 属性相同。

background.js:

chrome.runtime.onInstalled.addListener(async () => {
  for (const cs of chrome.runtime.getManifest().content_scripts) {
    for (const tab of await chrome.tabs.query({url: cs.matches})) {
      chrome.scripting.executeScript({
        target: {tabId: tab.id},
        files: cs.js,
      });
    }
  }
});

这是一个简化的示例,不处理框架。您可以使用getAllFrames API并自行匹配URL,参见匹配模式文档。

ManifestV2

显然,您必须在manifest.json中声明的background pageevent page脚本中执行此操作:
"background": {
    "scripts": ["background.js"]
},

background.js:

// Add a `manifest` property to the `chrome` object.
chrome.manifest = chrome.runtime.getManifest();

var injectIntoTab = function (tab) {
    // You could iterate through the content scripts here
    var scripts = chrome.manifest.content_scripts[0].js;
    var i = 0, s = scripts.length;
    for( ; i < s; i++ ) {
        chrome.tabs.executeScript(tab.id, {
            file: scripts[i]
        });
    }
}

// Get all windows
chrome.windows.getAll({
    populate: true
}, function (windows) {
    var i = 0, w = windows.length, currentWindow;
    for( ; i < w; i++ ) {
        currentWindow = windows[i];
        var j = 0, t = currentWindow.tabs.length, currentTab;
        for( ; j < t; j++ ) {
            currentTab = currentWindow.tabs[j];
            // Skip chrome:// and https:// pages
            if( ! currentTab.url.match(/(chrome|https):\/\//gi) ) {
                injectIntoTab(currentTab);
            }
        }
    }
});

历史趣闻

在古老的Chrome 26及更早版本中,内容脚本可以恢复与后台脚本之间的连接。这个问题已经在2013年http://crbug.com/168263得到解决。您可以在此答案的早期修订版中看到此技巧的示例。


3
еңЁChrome 46дёӯпјҢжӮЁйңҖиҰҒдҪҝз”Ёchrome.runtime.getManifest()иҖҢдёҚжҳҜchrome.manifestгҖӮ - Lee
2
要执行chrome.tabs.executeScript,您必须在您的manifest.json中添加权限"tabs","http:///","https:///"。 - cnmuc
你好,能告诉我你在哪里执行getAll和InjectIntoTab吗?因为在manifest.json文件中我有'background.js'和content_scripts.js文件,但我无法弄清首次安装时执行的是什么。谢谢,Mirko - mirkobrankovic
但是connectToExtension和reconnectToExtension正在无限循环中运行。可能出了什么问题?onDisconnect只有在扩展升级/重新加载时才应该监听,不应该一直监听。 - Przemo
这个内容脚本是否适用于多个文件?文档中写道:“注意:目前仅支持单个文件。” 另外,浏览器更新后重新注入它是否可以?还是应该检查runtime.onInstalled的“reason”参数并将其限制为更新/安装? - icl7126
显示剩余7条评论

31
唯一的方法是通过编程注入来强制注入内容脚本而不刷新页面。
您可以使用chrome标签API获取所有选项卡并向它们注入代码。例如,您可以在本地存储中存储清单版本,并每次检查清单版本是否过时(在后台页中),如果是,则可以获取所有活动选项卡并以编程方式注入代码,或者任何其他确保扩展程序已更新的解决方案。
使用以下命令获取所有选项卡:
chrome.tabs.query
并将您的代码注入所有页面
chrome.tabs.executeScript(tabId, {file: "content_script.js"});

谢谢,但我不想使用这种方法 - 编程注入需要更多的代码并增加了复杂性。 - Tom Ashworth
4
我很确定那是唯一的方法。我实现了类似的东西,同时使用了清单注入和程序注入,这样在后台脚本首次加载时打开的所有选项卡都会被注入,并且所有新的选项卡都由正常的清单注入处理。但你需要注意的是,任何你在DOM或页面上留下的内容(如重新加载或升级)都应该有一个撤消所有操作的函数,并且还需要让新上下文与旧上下文通信。 - Adam M-W
谢谢@AdamM-W - 我一直在思考类似的事情。理想情况下,会有一个事件可以触发“退出”脚本,撤销所有DOM更改。 - Tom Ashworth

9
在你的后台脚本中尝试这个方法。许多旧的方法现在已经过时,所以我已经重构了代码。为了我的使用,我只安装了单个content_script文件。如果需要,您可以遍历chrome.runtime.getManifest().content_scripts数组以获取所有.js文件。
chrome.runtime.onInstalled.addListener(installScript);

function installScript(details){
    // console.log('Installing content script in all tabs.');
    let params = {
        currentWindow: true
    };
    chrome.tabs.query(params, function gotTabs(tabs){
        let contentjsFile = chrome.runtime.getManifest().content_scripts[0].js[0];
        for (let index = 0; index < tabs.length; index++) {
            chrome.tabs.executeScript(tabs[index].id, {
                file: contentjsFile
            },
            result => {
                const lastErr = chrome.runtime.lastError;
                if (lastErr) {
                    console.error('tab: ' + tabs[index].id + ' lastError: ' + JSON.stringify(lastErr));
                }
            })
        }
    });    
}

2

1
由于https://bugs.chromium.org/p/chromium/issues/detail?id=168263,您的内容脚本和后台脚本之间的连接已经断开。正如其他人所提到的,解决此问题的一种方法是重新注入内容脚本。这个StackOverflow答案详细介绍了一个大致的概述。
主要的棘手部分是在注入新的内容脚本之前需要“销毁”当前的内容脚本。销毁可能非常棘手,因此减少必须销毁的状态的方法之一是创建一个小型可重新注入的脚本,通过DOM与您的主内容脚本通信。

0

我也曾尝试做类似的事情,但在我这种情况下,要求本身是错误的。

我原本以为我想在安装应用程序后立即向内容脚本传递消息,但实际上我想要的是在页面加载完成后传递消息。

在安装过程中,没有理由访问内容脚本。

为了实现这一点,我做了以下几步:

  1. 在 manifest.json 的权限字段中添加 webNavigation
  2. 在后台脚本中监听 webNavigation.complete 事件:
chrome.webNavigation.onCompleted.addListener();
  1. 从回调函数向内容脚本传递消息:
chrome.webNavigation.onCompleted.addListener(function () {
    chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
      // Send a message to the content script in the active tab to create the modal
      chrome.tabs.sendMessage(tabs[0].id, { message: "mymessage" });
    });
  },
  { url: [{ schemes: ["http", "https"] }] })

在内容脚本中添加一个监听器

-14

你不能在升级的CSS或JS末尾添加?ver=2.10吗?

"content_scripts": [ {
      "css": [ "css/cs.css?ver=2.10" ],
      "js": [ "js/contentScript.js?ver=2.10" ],
      "matches": [ "http://*/*", "https://*/*" ],
      "run_at": "document_end"
   } ],

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接