在扩展更新后发送消息时,如何避免“扩展上下文无效”错误?

时间:2018-12-27 02:45:08

标签: google-chrome-extension

发布更新后,我正在尝试为Chrome扩展程序的用户提供流畅的体验。

我在应用程序更新时重新注入了内容脚本,即使用户在扩展程序更新后未刷新的页面上继续使用我的扩展程序,我的功能也起作用。刷新页面是理想的选择,但是我不想强迫用户使用它。

但是,在扩展程序更新之后,但在代码尝试从内容脚本/页面发送消息到后台时,页面刷新之前,我在内容脚本控制台(将内容脚本插入的页面)中收到以下错误页面:

  

未捕获的错误:扩展上下文无效。

是否有一种“重建”连接的方法?我尝试了长期使用的端口和常规消息传递-更新扩展具有相同的效果。

赞赏任何想法/方向。

这是我的简单消息传递代码(在内容脚本中),引发错误...但是可以与后台脚本进行通信:

chrome.runtime.sendMessage({ msg: "recordFeedback", obj: commentObj, source: source}, function(response){
  console.log('Sent to DB...response was: ', response, response.recordFeedbackResponse);
});

6 个答案:

答案 0 :(得分:4)

卸载扩展程序时,现有的内容脚本将失去与扩展程序其余部分的连接,即端口关闭,它们将无法使用runtime.sendMessage()-但是内容脚本本身仍然可以继续工作,因为它们已被注入到页面中。

重新加载扩展程序时是相同的:那些现有的内容脚本仍将存在,但是无法发送消息;尝试这样做会导致类似您遇到的错误的错误。此外,扩展程序通常在加载时将其内容脚本注入到现有标签中(在Chrome上,Firefox会自动执行),因此最终会在给定的标签中运行多个内容脚本副本:原来的(现在已断开连接),而当前的已断开连接。

如果出现以下情况,可能会出现问题:(1)您的原始内容脚本仍在尝试与扩展的其余部分进行通信,或者(2)您的原始内容脚本执行了诸如修改DOM之类的操作,因为最终可能会导致这些更改做不止一次。

这也许就是为什么即使重新注入内容脚本后仍然出现错误:错误来自仍然存在但已断开连接的原始内容脚本。

这是some helpful background information that I found when previously researching this problem的措辞。请注意,Firefox会自动卸载内容脚本(稍后会详细介绍)。

解决方案

如果您没有自动重新注入内容脚本,则可以按照上面try to get the existing script to reconnect on upgrade的评论wOxxOm

这里有一些进一步的信息和建议,如果您注入新的内容脚本并想disable the original, disconnected, one when a new one is injected,则特别有用。

无论您是想尝试使用现有内容脚本重新连接,还是要停止对原始内容脚本进行进一步的更改并注入新的脚本,都需要检测是否已发生卸载。您可以使用端口的disconnect处理程序来捕获扩展程序何时卸载。如果您的扩展名仅使用简单的一次性消息传递,而不使用端口,则该代码就像在您的内容脚本启动时运行以下代码一样简单(我从lydell的一些代码中首次看到了此技术)。 / p>

browser.runtime.connect().onDisconnect.addListener(function() {
    // clean up when content script gets disconnected
})

On Firefox, the port's disconnect is not fired,因为内容脚本在接收之前就已删除。这样做的优点是不需要检测(手动内容脚本注入也不需要,因为内容脚本也会自动注入),但这确实意味着没有机会重置内容脚本对DOM所做的任何更改正在运行。

答案 1 :(得分:1)

if(typeof chrome.app.isInstalled!=='undefined'){
   chrome.runtime.sendMessage()
}

答案 2 :(得分:1)

在后台脚本中尝试。现在已淘汰了许多旧方法,因此我重构了代码。对于我来说,我只安装单个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));
                }
            })
        }
    });    
}

答案 3 :(得分:1)

我花了几个小时阅读文档,SO,铬漏洞报告,最终才解决了所有与“扩展程序重新加载后重新连接到运行时”问题相关的错误。

此解决方案在更新/重新加载扩展程序后重新安装内容脚本,并确保旧的内容脚本不再与无效的运行时通信。

这是整个相关代码:

manifest.json

来自content_scripts.matches的所有URL也必须包含在permissions数组中。这是chrome.tabs.executeScript()工作所必需的。
实际上,您可以删除content_scripts对象(因为其唯一原因是自动注入内容脚本)并亲自处理background.js中的一切(请参见docs: "Inject Scripts")。但是我保留并使用它是出于更好的概述和“兼容性”的原因。

{
    "permissions": [
        "tabs",
        "http://example.org",
    ],
    "background": {
        "scripts": ["background.js"],
        "persistent": false
    },
    "content_scripts": [
        {
            "matches": ["http://example.org/*"],
            "js": ["contentScript.js"]
        }
    ]
}

请注意,content_scriptscontent_scripts[n].js都是数组。

background.js

const manifest = chrome.runtime.getManifest();

function installContentScript() {
  // iterate over all content_script definitions from manifest
  // and install all their js files to the corresponding hosts.
  let contentScripts = manifest.content_scripts;
  for (let i = 0; i < contentScripts.length; i++) {
    let contScript = contentScripts[i];
    chrome.tabs.query({ url: contScript.matches }, function(foundTabs) {
      for (let j = 0; j < foundTabs.length; j++) {
        let javaScripts = contScript.js;
        for (let k = 0; k < javaScripts.length; k++) {
          chrome.tabs.executeScript(foundTabs[j].id, {
            file: javaScripts[k]
          });          
        }
      }
    });
  }
}

chrome.runtime.onInstalled.addListener(installContentScript);

contentScript.js

var chromeRuntimePort = chrome.runtime.connect();
chromeRuntimePort.onDisconnect.addListener(() => {
  chromeRuntimePort = undefined;
});

// when using the port, always check if valid/connected
function postToPort(msg) {
  if (chromeRuntimePort) {
    chromeRuntimePort.postMessage(msg);
  }
}

// or
chromeRuntimePort?.postMessage('Hey, finally no errors');

答案 4 :(得分:1)

最佳答案在 Chrome 中对我不起作用。它不会在重新加载扩展程序时运行,而是在每个页面上运行,甚至在刷新时也是如此。

这是我决定实施的(在 Chrome 和 Firefox 中测试)。这会检查错误(英文字符串)是否是由在开发中重新加载扩展引起的。如果这是真的,那么它会禁止函数通过全局变量在扩展中执行导致错误的代码。它记录所有其他错误。

function isReloadExtErr(err) {
    if (err.toString().includes("Extension context invalidated")) {
        console.log("Extension has been reloaded, set global var");
        extensionActive = false;
        return true;
    }
    return false;
}

然后在刷新后会导致错误的函数内部(使用浏览器 API 的函数)

function someFunction() {
    try { 
        if (!extensionActive) return;
        /* code */ 
    } catch (err) {
        if (!isReloadExtErr(err)) console.error(err);
    }
}

答案 5 :(得分:-5)

关闭浏览器标签并重新开始。为我工作。