fix: 冷启动后台卡死导致代理失效与弹窗空白 (#276) - #327
Conversation
On Manifest V3 the background service worker may still be asleep or initializing when the popup is opened right after a browser restart. The popup's first getState message then fails with chrome.runtime.lastError and no response is delivered. The omegaTarget.state() wrapper only resolved its deferred inside .then(), with no rejection handler, so a single missed response left the deferred unsettled forever: the popup stayed blank, showing only the toolbar icon, with no way to recover except reloading the extension. - Retry callBackground() a few times with a small backoff when sendMessage fails with chrome.runtime.lastError, to ride out the service-worker wake-up race. - Propagate rejections in omegaTarget.state() so a permanently failed call rejects the promise instead of hanging silently. Closes zero-peak#276
…o-peak#276) On a service-worker cold start, Options#init awaited a state (IndexedDB) read BEFORE applying a profile. When that read stalls (which happens on cold starts), options.ready never resolved: applyProfile never ran so the proxy was never configured, and the popup never got a getState reply so it stayed blank ("only an icon"), until the extension was reloaded by hand. Wrap startup in a timeout. If loadOptions plus applying the initial profile does not finish in time, fall back to applying the configured startup profile (or the fallback profile) so the proxy gets set and ready resolves. Add a deterministic regression test that simulates a stalling state read.
vokoscreenNG-2026-08-17_13-03-18.mp4 |
…ero-peak#276) On a cold start Chrome can fail to start the background service worker, mark it invalid and refuse to wake it again for a while; sendMessage then fails with "Receiving end does not exist" and the popup stays blank. The previous 5 quick retries (~1s) cannot ride that out, so detect that specific error and keep retrying with an exponential backoff (~15s total) before giving up. Also fix the coffeelint errors reported in the review: a line longer than 80 chars in options.coffee and empty functions in the startup test.
已找到真实复现,根因比 PR 里推测的更深:SW 冷启动启动失败,进入 INVALID 状态复现(Chrome 稳定版 + 商店版 3.5.0,非本 PR 代码)
为什么 "Receiving end does not exist" = SW 启动失败
对 PR 根因描述的修正之前 PR 里推测根因是"IndexedDB 读取挂起导致 这也解释了原 PR 修复为什么没能让所有人都满意:后台 5s 超时兜底覆盖"SW 已启动但 ready 挂起",而真实场景是 SW 根本没启动,后台代码没机会执行。 新修复(已推送到分支 f820bcc)
对 review 意见的逐条回应
请求:能否重新打开 PR?#276 现在有了实机证据链(INVALID 状态 + 必须 reload 才能恢复),新修复也覆盖了 SW 无效场景,值得再跑一轮验证。也可以先按上面的复现路径在你本机确认一次。 |
|
插件比较多,service-worker 冷启动是有可能会启动比较慢,那这样的话,好像只要在 popup 和 options 的页面里添加错误重试就可以了,没必要动 background 里的 options.coffee 代码。还有比较疑惑的是,如果点击图标,弹出 popup 页面,显示卡住的那个效果,过一会后,再点击图标, popup 页面还是会显示卡住的效果吗? 因为我这边可能插件还没达到那个数量,冷启动浏览器后, extensions 页面上 zeroomega 都是很快从 inactived 直接进入激活状态的,很难模拟出你说的这种情形。 |
…fee (zero-peak#276) Per review feedback, the background change is not needed: on a slow service-worker cold start the popup/options page retry is sufficient, and the fallback can interfere with options state (e.g. reloading the extension twice can reset settings while a stalled loadOptions is still pending). Keep only the popup-side retry with long backoff for the invalid-worker error ("Receiving end does not exist").
|
@suziwen 感谢重新打开 PR,也谢谢你的意见,按你说的把 background 的改动回退掉了(commit e35ee19,已推送)。 先回答你问的"过一会再点图标,popup 还会卡住吗":我们这边实测是会一直卡。当时反复点了几次 popup/options,每次都报 改动的部分:
有一个边界得说实话:如果 SW 真到了「无效」状态,popup 这边重试再多也没用(Chrome 压根不启动它),15 秒重试耗尽之后会报错——现在弹窗会显示一个未处理的异常,观感不太好。我计划下一步改成重试完给个友好提示(比如"后台没响应,请到扩展页刷新一下"),而不是裸报错。 另外你之前提的"连续 reload 两次状态会被重置",回退掉那个超时兜底之后,这个副作用的来源应该也一起没了。方便的话可以拿这个分支再试一下,应该不会再出现。 |
这样的话,感觉还是有其他地方的原因引起这个问题。就算插件再多, service-worker 按队列一个个启动,再慢也会轮到 Zeroomega 这个插件,不可能 popup 页面会一直无效。 而且插件详情页里的 inactive 只是表示插件还没激活,因为 mv3 规范,为了节省内存,会把不活动的插件自动释放内存,并标记为 inactive,直到再次使用时(比如点击 popup ,右键菜单等),才会再次从 inactive 状态变成 active 激活状态。 视频里是我另一个插件从 inactive 状态到激活状态的变化。 vokoscreenNG-2026-08-20_14-31-31.mp4你试一下用命令行启动浏览器,然后把数据目录放到硬盘的另一个位置上,安装完 Zeroomega 插件后,再重启这个新数据目录的浏览器,看 Zeroomega 插件还是会有冷启动问题吗? 比如我的 linux 机器是 window 机器应该是 |
问题 (Closes #276)
在 Chrome 刚打开(冷启动)时,扩展经常只剩一个工具栏图标:弹窗加载不出来、点击无响应,整个代理也一并失效,必须手动在
chrome://extensions里 reload 插件才能恢复。根因
后台 service worker 的
Options#init(omega-target/src/options.coffee)在应用情景模式(设置chrome.proxy.settings)之前,会先await一次state(IndexedDB)读取。在 MV3 service worker 冷启动时,这次 IndexedDB 读取有时会永久挂起,于是:options.ready永不 resolve →applyProfile从不执行 → 代理从未被设置(整体失效);getState消息 → 弹窗一直空白("只剩图标")。两个症状同源,都卡在这次挂起的存储读取上。
修复
1. 后台根治 (
omega-target/src/options.coffee)给启动流程加一道超时保险:若
loadOptions+ 应用初始情景在限定时间(默认 5s)内没完成,就用配置的「启动情景」或兜底情景强制把代理设上,并让options.readyresolve。这样任何启动期存储读取挂起都不会再让代理和弹窗一起卡死。2. 弹窗自愈 (
omega-target-chromium-extension/src/coffee/omega_target_web.coffee)callBackground在chrome.runtime.lastError(service worker 尚未唤醒/就绪)时做有限次重试与退避,并让state()的 promise 正确传播拒绝,避免一次消息失败就让弹窗永久挂起。测试
新增
omega-target/test/options_startup.coffee:用一个"读取永不返回"的state确定性模拟冷启动挂起,断言修复后仍能applyProfile且ready会 settle(无需浏览器/网络/凭据即可复现并验证)。副作用
正常启动只需几十~几百毫秒,远不到超时阈值,超时分支不会触发,行为与改前一致。仅在"启动卡死"这一异常场景才介入:先用启动/兜底情景保证浏览器可用,待挂起的读取最终返回后原流程会再应用真正的情景(
chrome.proxy.settings幂等,最终一致)。