前言
自我的谷歌插件从升级到V3起,一直遇到一些偶现的一些小bug,定位了好久,是因为background(V3叫做service worker)加载不出导致的。
我在baidu搜了一圈,发现V2有个persistent属性,如果设置为true会始终后台运行,相当于一个独立的服务器页面。
false则会让background 变成了一种短暂加载进内存的脚本,脚本可以多次被线程加载执行,执行完毕后就释放,可以降低谷歌浏览器的内存耗费。
事实上,这么点内存耗费并不是那么重要,所以大多数情况下无脑设置persistent:true。
在V3中,chrome extensions大刀阔斧地砍掉了background,直接改为了persistent始终为false的service worker,也就是说,新的background会不断的卸载重装卸载重装。 官网迁移指南中提到,我们要因此修改几个点:
- manifest 调整
- 不能嵌套监听,因为当第一个执行完成后,线程就被关闭了,第二次还没来的及执行,就被从内存中销毁了。那这个问题怎么处理呢?官方给出的答案是chrome.storage.local.set/get 解决
- 同时因为失去了页面,也没有了window对象,无法使用setTimeout 和 setInterval 了,必须用 alarm 来处理
- 同样,也没有了XMLHttpRequest对象,但是提供了简洁版的 fetch(好坑啊)
- 同样,没有了document,无法进行DOM 结构解析,需要自行寻找解析库来用,如果非得用 Dom 结构或者 browser environment,则需要新打开一个 tab 或者 window,与 background 进行通信来解决
- 视频和音频捕捉也需要新开 windows 和 tabs 来处理
- canvas 画布需要用service worker中提供的new OffscreenCanvas(width, height)
导致的问题
这个思路本身是节省性能的一个优化,但是它导致了background随机失效的bug。
具体表现就是background随机失效。
这个问题非常难定位,一开始我也以为是自己的设置问题,终于,我去google搜了一下,事实上这个问题已经有无数人遇到了,并有相关的讨论《Persistent Service Worker in Chrome Extension》
以下是该问题的解决方案的搬运(机翻)
这是由 ManifestV3 中的这些问题引起的:
1.crbug.com/1024211,工作人员不会因 webRequest 事件而醒来。
下面列出了解决方法。
2.crbug.com/1271154,更新后工人随机损坏。
大部分在 Chrome 101 中修复。
3.根据 Service Worker (SW) 规范,它不能持久化,浏览器必须在一段时间后强制终止所有 SW 连接,例如网络请求或端口,在 Chrome 中为 5 分钟。Chromium 团队目前认为这种行为是有意的并且有利于扩展,因为团队从未调查过现实世界中这种 SW 行为不好的情况,以防扩展必须观察频繁事件:此类事件是响应用户操作而生成的,因此会有几分钟的自然停顿,在此期间 SW 会终止。然后它再次开始一个新事件,这需要至少 50 毫秒来创建进程加上加载和编译代码的时间,平均约 50 毫秒,即它比调用简单 JS 事件所需的约 1 毫秒重约 100 倍听众。对于活跃的在线用户,它可能每天重启数百次,从而耗尽 CPU/磁盘/电池,并经常引入扩展程序反应的频繁可感知滞后。
-chrome.tabs.onUpdated/onActivated,
-chrome.webNavigation 如果未限定为稀有 url,
-chrome.webRequest 如果未限定为稀有的 url 或类型,
-chrome.runtime.onMessage/onConnect 用于所有选项卡中来自内容脚本的消息。
解决方法
对于 webRequest 未唤醒
另外订阅一个 API,如chrome.webNavigation其他答案所示。
这适用于观察不频繁事件的扩展,例如,您urls为webRequest/webNavigation 指定过滤器,只为一个很少访问的站点。这样的扩展可以重新设计以避免需要持久的后台脚本,因此它每天只启动几次,这将有利于内存占用,同时不会对 CPU 造成太大压力。chrome.storage.session您可以通过(temporary, 1MB max)chrome.storage.local或 甚至IndexedDB在每个侦听器中保存/加载变量/状态,这对于大/复杂数据来说要快得多。
但是,如果您必须观察频繁事件(在此答案的开头列出),则必须使用以下解决方法人为地延长后台脚本的生命周期。
存在可连接选项卡时的“持久”服务工作者
如果您不将端口与在所有选项卡中运行的内容脚本一起使用(如下面的另一个解决方法所示),这里是从任何选项卡的内容脚本或从扩展的另一个页面(如弹出页面)打开运行时端口的示例和在 5 分钟之前重新连接。
缺点:
需要打开网页选项卡或打开扩展选项卡/弹出窗口。
内容脚本的广泛主机权限(如<all_urls>或*: ///),可将大多数扩展程序放入网上商店的缓慢审查队列中。
警告!如果您使用 sendMessage,还要实现 sendMessage(如下)的解决方法。
警告!如果您已经将 chrome.runtime.connect (下)的解决方法与在所有选项卡中运行的内容脚本一起使用,则不需要这个。
如果您还使用 sendMessage
即使您不需要响应,也请始终在您的 chrome.runtime.onMessage 侦听器中调用 sendResponse()。这是 MV3 中的一个错误。此外,请确保您在不到 5 分钟的时间内完成,否则立即调用 sendResponse 并在工作完成后通过 chrome.tabs.sendMessage(到选项卡)或 chrome.runtime.sendMessage(到弹出窗口)发回一条新消息完毕。
如果您已经使用端口,例如 chrome.runtime.connect
在5分钟之前重新连接每个端口。
“永远”,通过专用选项卡,在选项卡打开时
打开一个带有扩展页面的新标签页,例如chrome.tabs.create({url: 'bg.html'}).
它将具有与 ManifestV2 的持久背景页面相同的功能,但 a) 它是可见的并且 b) 无法通过chrome.extension.getBackgroundPage(可以替换为chrome.extension.getViews)访问。 缺点:
消耗更多内存,
浪费标签条中的空间,
分散用户的注意力,
当多个扩展打开这样的标签时,缺点会滚雪球并成为真正的 PITA。
您可以通过将 info/logs/charts/dashboard 添加到页面并添加beforeunload侦听器以防止选项卡意外关闭来使用户更容易忍受。
ManifestV3 的未来
让我们希望 Chromium 将提供一个 API 来控制这种行为,而无需求助于这种肮脏的黑客和可悲的变通方法。同时在crbug.com/1152255中描述您的用例(如果尚未在此处描述),以帮助 Chromium 团队意识到一个既定事实,即许多扩展可能需要任意时间的持久后台脚本,并且至少有一个这样的脚本大多数扩展用户可能会安装扩展。
当然,还有人有别的奇葩的思路,比如说安装两个chrome-extensions,然后相互唤醒
我找到了一个不同的解决方案来保持扩展活着。它通过使用辅助扩展打开与我们的主扩展的连接端口来改进 wOxxOm 的答案。然后,两个扩展都尝试在任何断开连接的情况下相互通信,从而使两者保持活动状态。
需要这样做的原因是,根据我公司的另一个团队的说法,wOxxOm 的回答结果是不可靠的。据报道,他们的软件最终会以不确定的方式失败。
再说一次,我的解决方案适用于我的公司,因为我们正在部署企业安全软件,我们将强制安装扩展。在其他用例中,让用户安装 2 个扩展可能仍然是不可取的。
但是,这才是最佳的解决方案
非常感谢你!!我花了两天时间认为这是我的代码中的一个错误,我刚刚切换到 MV2,现在它可以工作了!!
– 凯文·奥古斯托 2021 年 3 月 14 日 18:03
回退版本至V2,完美解决问题。
参考文档
developer.chrome.com/docs/extens…
stackoverflow.com/questions/6…
bugs.chromium.org/p/chromium/…
bugs.chromium.org/p/chromium/…