【问题标题】:How to avoid "Extension context invalidated" errors when messaging AFTER an Extension update?在扩展更新后发送消息时如何避免“扩展上下文无效”错误?
【发布时间】:2018-12-27 02:45:07
【问题描述】:

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

我在应用更新时重新注入了我的内容脚本,即使用户继续在扩展更新后未刷新的页面上使用我的扩展,我的功能仍然有效。刷新页面是理想的,但我不想强迫我的用户这样做。

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

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

有没有办法“重建”连接?我已经尝试了一个长期存在的端口和定期消息传递 - 更新扩展具有相同的效果。

任何想法/方向表示赞赏。

这是我的简单消息代码(在内容脚本中),它会引发错误...但能够与后台脚本通信:

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

【问题讨论】:

  • 只需reinsert the content scripts。唯一的问题是当你的内容脚本有 DOM 监听器时,你需要通过another DOM message 告诉它断开它们。
  • 感谢您查看...和链接。我在那篇文章中尝试了“var reconnectToExtension = function () {”方法并得到了同样的结果。我找不到任何有关“未捕获错误:扩展上下文无效”错误的文档。基本上我的内容脚本,即使重新注入,也可以与后台页面对话,但会抛出该错误......这令人担忧。
  • 使用我的方法,您不需要过多的 reconnectToExtension hack。
  • 不明白。您的方法“重新插入内容脚本”有 reconnectToExtension hack,或者您有其他方法?

标签: google-chrome-extension


【解决方案1】:

卸载扩展程序时,现有内容脚本会失去与扩展程序其余部分的连接 - 即端口关闭,他们将无法使用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 的一些代码中看到了这种技术)。

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

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

【讨论】:

  • 我遇到了Chrome Extension message passing: Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist 原来你必须先在后台脚本中打开一个监听器。把它放在你的后台脚本中可以解决它:chrome.runtime.onConnect.addListener(port => { console.log('connected ', port); if (port.name === 'hi') { port.onMessage.addListener(this.processMessage); } });
  • ...所以显然你不能在五分钟后编辑评论。我想改变的是:chrome.runtime.onConnect.addListener(port => {}); 在后台脚本中就足够了。答案来自这里:stackoverflow.com/questions/54181734/…
  • 是否可以捕获异常?
  • onDisconnect 事件(类型:PortDisconnectEvent)如果没有“更新”(在扩展第一次初始化时)也会触发
【解决方案2】:
if(typeof chrome.app.isInstalled!=='undefined'){
   chrome.runtime.sendMessage()
}

【讨论】:

  • 我一直在寻找解决此问题的方法,而您的回答实际上对我有用,但老实说,我真的不知道为什么。主要是因为条件总是返回 false (这是很多人显然遇到的一个问题,但也不知道这会如何影响我的情况)。请问有什么发展的机会吗?
  • 这适用于 Chrome,但不适用于我的 Firefox。
  • chrome.app 已弃用,不应使用,有关详细信息,请参阅我的答案。
【解决方案3】:

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

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

这是整个相关代码:

ma​​nifest.json

content_scripts.matches 中的所有 URL 也必须包含在 permissions 数组中。这是chrome.tabs.executeScript() 工作所必需的。
实际上,您可以删除 content_scripts 对象(因为它的唯一原因是自动注入内容脚本)并自己处理 background.js 中的 everything(参见 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】:

    我建议访问chrome.runtime.id 来判断扩展上下文是否无效。

    // use null-safe operator since chrome.runtime
    // is lazy inited and might return undefined
    //
    if (chrome.runtime?.id) {
       
    }
    

    【讨论】:

    • 这个解决方案简单而完美,应该是默认接受的答案!不敢相信它还没有很多赞成票。
    【解决方案5】:

    在你的后台脚本中试试这个。很多老方法现在已经被弃用了,所以我重构了代码。对于我的使用,我只安装单个 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));
                    }
                })
            }
        });    
    }
    

    【讨论】:

      【解决方案6】:

      最佳答案在 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);
          }
      }
      

      【讨论】:

        【解决方案7】:

        关闭浏览器选项卡并重新开始。为我工作。

        【讨论】:

        • 这是我的解决方案,我只是在本地测试一个解压的扩展
        猜你喜欢
        • 1970-01-01
        • 2014-11-08
        • 2020-12-10
        • 2017-01-28
        • 2017-06-29
        • 2020-06-24
        • 1970-01-01
        • 1970-01-01
        • 2017-05-09
        相关资源
        最近更新 更多