Chrome扩展程序重新加载后,内容脚本中的chrome.runtime.sendMessage抛出异常。

9

我在Chrome扩展程序中从注入的内容脚本向我的后台脚本发送消息,如下所示:

chrome.runtime.sendMessage({action: "myResult"});

这很好运行,直到我重新加载我的扩展程序(通过转到“设置” ->“扩展程序” ->我的扩展程序的“重新加载(Ctrl + R)”)。

接着,当我的后台脚本启动时,它会重复调用chrome.tabs.executeScript以针对所有打开的标签自动重新注入我的内容脚本就像我在这个问题中展示的那样。)

但是在我这样做之后,如果我从我的内容脚本调用那个第一个sendMessage行,它会抛出以下异常:

错误:连接到扩展程序my_extension_id时出错

有任何想法为什么会发生这种情况吗?


在主题上,确保两件事情:1)您不是从内容脚本的先前实例获取此行,2)您在注册“onMessage”侦听器之前不会注入脚本。 - Xan
@Xan:是的。这不就是这个网站的目的吗?我显然已经付出了努力去研究,只有在遇到死路时才会提问。 - c00000fd
那么回到你的关于主题的评论。我如何确保#1? - c00000fd
1个回答

17
当扩展运行时重新加载时(这在以下任何情况下都会发生):
  • 您已调用 chrome.runtime.reload()
  • 您已单击 “chrome://extensions/” 上的 Reload extension。
  • 扩展已更新。
此时,内容脚本中的大多数扩展 API 方法将停止工作(包括导致问题的 chrome.runtime.sendMessage)。有两种方法可以解决这个问题。
选项1:回退到仅限内容脚本的功能
如果您的扩展程序没有背景页面也能完美运行,则这可能是一个可接受的解决方案。例如,如果您的内容脚本除了修改 DOM 和/或执行跨源请求之外没有其他功能。
我在我的某个扩展程序中使用以下代码片段来检测运行时是否仍然有效,然后再从我的内容脚本调用任何 Chrome 扩展 API。
// It turns out that getManifest() returns undefined when the runtime has been
// reload through chrome.runtime.reload() or after an update.
function isValidChromeRuntime() {
    // It turns out that chrome.runtime.getManifest() returns undefined when the
    // runtime has been reloaded.
    // Note: If this detection method ever fails, try to send a message using
    // chrome.runtime.sendMessage. It will throw an error upon failure.
    return chrome.runtime && !!chrome.runtime.getManifest();
}

// E.g.
if (isValidChromeRuntime()) {
    chrome.runtime.sendMessage( ... );
} else {
    // Fall back to contentscript-only behavior
}

选项2:在插入内容脚本时卸载先前的内容脚本

如果与后台页面建立连接对于您的内容脚本很重要,则必须实现适当的卸载程序,并设置一些事件以在通过chrome.tabs.executeScript重新插入内容脚本时卸载先前的内容脚本。

// Content script
function main() {
    // Set up content script
}

function destructor() {
    // Destruction is needed only once
    document.removeEventListener(destructionEvent, destructor);
    // Tear down content script: Unbind events, clear timers, restore DOM, etc.
}

var destructionEvent = 'destructmyextension_' + chrome.runtime.id;
// Unload previous content script if needed
document.dispatchEvent(new CustomEvent(destructionEvent));
document.addEventListener(destructionEvent, destructor);
main();

请注意,任何知道事件名称的页面都可以触发销毁您的内容脚本。这是不可避免的,因为在扩展运行时被销毁后,就没有合适的方式再与扩展进行安全通信了。


感谢您抽出时间解释这个问题!不过我有一个关于你的第二个选项的问题。重新加载后我开始重新注入内容脚本的原因是因为我无法再从背景脚本与它们进行通信。但如果是这样,我该如何让它们知道它们必须卸载呢?另一方面,既然内容脚本也没有任何方法知道扩展程序即将被卸载以执行您所解释的操作,那么背景脚本本身也没有任何方式知道这种情况是否会导致“进退两难”的局面? - c00000fd
2
@c00000fd 我正在使用自定义DOM事件(请参见代码片段末尾的main();之前的最后四行)来管理卸载。首先,我触发自定义事件以指示任何侦听器(可能是先前的内容脚本)清理自身。然后,我绑定一个新的事件侦听器来监听该事件。最后,通过调用main()函数来设置内容脚本。 - Rob W
+1表示发送DOM事件,我从来没有想过这点。 - Xan
@c00000fd DOM事件能够工作是因为内容脚本实际上只是继续执行,只是与父背景页面断开连接。 - Xan
@RobW 关于选项1的问题。在这种情况下,chrome.storage会失败吗?那么仅仅不使用后台页面是不够的。 - Xan
显示剩余19条评论

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