Skip to content

fix: 冷启动后台卡死导致代理失效与弹窗空白 (#276) - #327

Open
hughcube wants to merge 4 commits into
zero-peak:masterfrom
hughcube:bugfix/popup-hang-on-cold-start-276
Open

fix: 冷启动后台卡死导致代理失效与弹窗空白 (#276)#327
hughcube wants to merge 4 commits into
zero-peak:masterfrom
hughcube:bugfix/popup-hang-on-cold-start-276

Conversation

@hughcube

Copy link
Copy Markdown

问题 (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 从不执行 → 代理从未被设置(整体失效);
  • 后台也永远不回应 popup 的 getState 消息 → 弹窗一直空白("只剩图标")

两个症状同源,都卡在这次挂起的存储读取上。

修复

1. 后台根治 (omega-target/src/options.coffee)
给启动流程加一道超时保险:若 loadOptions + 应用初始情景在限定时间(默认 5s)内没完成,就用配置的「启动情景」或兜底情景强制把代理设上,并让 options.ready resolve。这样任何启动期存储读取挂起都不会再让代理和弹窗一起卡死。

2. 弹窗自愈 (omega-target-chromium-extension/src/coffee/omega_target_web.coffee)
callBackgroundchrome.runtime.lastError(service worker 尚未唤醒/就绪)时做有限次重试与退避,并让 state() 的 promise 正确传播拒绝,避免一次消息失败就让弹窗永久挂起。

测试

新增 omega-target/test/options_startup.coffee:用一个"读取永不返回"的 state 确定性模拟冷启动挂起,断言修复后仍能 applyProfileready 会 settle(无需浏览器/网络/凭据即可复现并验证)。

副作用

正常启动只需几十~几百毫秒,远不到超时阈值,超时分支不会触发,行为与改前一致。仅在"启动卡死"这一异常场景才介入:先用启动/兜底情景保证浏览器可用,待挂起的读取最终返回后原流程会再应用真正的情景(chrome.proxy.settings 幂等,最终一致)。

hughcube added 2 commits June 10, 2026 17:54
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.
@suziwen

suziwen commented Aug 17, 2026

Copy link
Copy Markdown
Member
  1. 我这边没法重现这种错误,最好能提供一个可重现步骤。
  2. 最好是能找出为什么 IndexedDB 会挂起的原因,是插件问题,还是底层 chrome 的问题,或者硬件问题等。
  3. 提交的代码需要保证自动编译能通过(比如 coffeescript lint 规范)。 de84142
  4. 使用了你的代码后,发现如果连继两次 reload 插件,状态就会被重置。
vokoscreenNG-2026-08-17_13-03-18.mp4

@suziwen suziwen closed this Aug 17, 2026
…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.
@hughcube

Copy link
Copy Markdown
Author

已找到真实复现,根因比 PR 里推测的更深:SW 冷启动启动失败,进入 INVALID 状态

复现(Chrome 稳定版 + 商店版 3.5.0,非本 PR 代码)

  1. 打开 options 页 / popup,console 稳定报:Possibly unhandled rejection: {"message":"Could not establish connection. Receiving end does not exist."},每次刷新都出现
  2. 手动 chrome.runtime.sendMessage 同样失败(约 1.7s 后报错,重复多次持续失败)
  3. chrome://extensions 详情页显示:Service Worker 状态:「无效」(INVALID)
  4. 手动 reload 扩展后,SW 恢复 ACTIVATED/RUNNING,消息立即正常(1ms 响应),代理恢复

为什么 "Receiving end does not exist" = SW 启动失败

对 PR 根因描述的修正

之前 PR 里推测根因是"IndexedDB 读取挂起导致 options.ready 永不 resolve"——那是没有实机证据的假设。真实复现显示根因是 SW 启动失败进入 INVALID。同一份代码 reload 后完全正常,说明不是代码 bug,而是冷启动环境性问题(本机 Chrome 注册了 134 个 service worker,冷启动竞争激烈,SW 启动超时概率大增)。启动失败那一瞬的具体错误(超时 vs 异常)还需要在 SW 启动时抓 DevTools 才能确认。

这也解释了原 PR 修复为什么没能让所有人都满意:后台 5s 超时兜底覆盖"SW 已启动但 ready 挂起",而真实场景是 SW 根本没启动,后台代码没机会执行。

新修复(已推送到分支 f820bcc)

  1. omega_target_web.coffee:检测 "Receiving end does not exist" 时改用长指数退避重试(5 次快速试探后接 2s/4s/8s 长退避,总窗口 ~15s),穿透 Chrome 对失效 SW 的启动抑制期;其他错误保持原短重试
  2. 修复 review 指出的 coffeelint 错误(options.coffee 行超长 82>80、测试文件 4 个空函数),本地 coffeelint 全绿、coffee 编译通过

对 review 意见的逐条回应

  1. "无法重现" → 现在有确定的重现路径:冷启动后打开 popup/options,查 chrome://extensions 详情页 SW 是否为「无效」;若是,所有消息都会报 Receiving end does not exist
  2. "找 IndexedDB 挂起根因" → 根因修正为 SW 启动失败 INVALID(IndexedDB 挂起只是此前无证据的推测);启动失败瞬间的精确错误建议在 SW 启动时挂 DevTools 抓一次
  3. "coffeescript lint 规范" → 已修复并本地验证全绿
  4. "连续 reload 两次状态被重置" → 该现象还需要排查是否与超时兜底逻辑交互有关(reload 期间挂起的 loadOptions 完成后可能覆盖兜底状态),会用同样方法复现验证

请求:能否重新打开 PR?#276 现在有了实机证据链(INVALID 状态 + 必须 reload 才能恢复),新修复也覆盖了 SW 无效场景,值得再跑一轮验证。也可以先按上面的复现路径在你本机确认一次。

@hughcube

Copy link
Copy Markdown
Author

@suziwen 您好,上面的评论里附上了 #276 的实机复现证据链(冷启动后 SW 进入「无效」状态 → 所有消息报 Receiving end does not exist → 必须手动 reload 才能恢复)和修正后的根因分析,并已把新的修复(检测 SW 无效时 15s 长退避重试 + lint 修复)推送到分支 f820bcc

能否麻烦重新打开 PR 再验证一轮?也可以先按评论里的复现路径在本机确认一次:冷启动后打开 popup,在 chrome://extensions 详情页看 SW 是否显示「无效」。

@suziwen suziwen reopened this Aug 20, 2026
@suziwen

suziwen commented Aug 20, 2026

Copy link
Copy Markdown
Member

插件比较多,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").
@hughcube

Copy link
Copy Markdown
Author

@suziwen 感谢重新打开 PR,也谢谢你的意见,按你说的把 background 的改动回退掉了(commit e35ee19,已推送)。

先回答你问的"过一会再点图标,popup 还会卡住吗":我们这边实测是会一直卡。当时反复点了几次 popup/options,每次都报 Receiving end does not exist,直到手动 reload 扩展才恢复(reload 后消息 1ms 就通了)。所以这个现象大概可以这样区分:如果只是启动慢,第二次点图标时 SW 已经就绪,就不会卡;如果是 SW 被标记成「无效」了(详情页能看到),Chrome 那段时间根本不会再启动它,点多少次都是白搭,只能 reload。

改动的部分:

  • options.coffee 已经恢复成和 master 一模一样,background 一点没动
  • 相关的测试文件也删了(它测的就是那段被回退的逻辑)
  • 保留的是 popup 侧的小改动:消息报 Receiving end does not exist 时,会多试几次(5 次快速 + 2s/4s/8s 递进,总共约 15 秒),其他错误还是原来的短重试。这个改动只影响 popup/options 页面,不碰 background

有一个边界得说实话:如果 SW 真到了「无效」状态,popup 这边重试再多也没用(Chrome 压根不启动它),15 秒重试耗尽之后会报错——现在弹窗会显示一个未处理的异常,观感不太好。我计划下一步改成重试完给个友好提示(比如"后台没响应,请到扩展页刷新一下"),而不是裸报错。

另外你之前提的"连续 reload 两次状态会被重置",回退掉那个超时兜底之后,这个副作用的来源应该也一起没了。方便的话可以拿这个分支再试一下,应该不会再出现。

@suziwen

suziwen commented Aug 20, 2026

Copy link
Copy Markdown
Member

先回答你问的"过一会再点图标,popup 还会卡住吗":我们这边实测是会一直卡。当时反复点了几次 popup/options,每次都报 Receiving end does not exist,直到手动 reload 扩展才恢复(reload 后消息 1ms 就通了)。所以这个现象大概可以这样区分:如果只是启动慢,第二次点图标时 SW 已经就绪,就不会卡;如果是 SW 被标记成「无效」了(详情页能看到),Chrome 那段时间根本不会再启动它,点多少次都是白搭,只能 reload。

这样的话,感觉还是有其他地方的原因引起这个问题。就算插件再多, service-worker 按队列一个个启动,再慢也会轮到 Zeroomega 这个插件,不可能 popup 页面会一直无效。

而且插件详情页里的 inactive 只是表示插件还没激活,因为 mv3 规范,为了节省内存,会把不活动的插件自动释放内存,并标记为 inactive,直到再次使用时(比如点击 popup ,右键菜单等),才会再次从 inactive 状态变成 active 激活状态。

视频里是我另一个插件从 inactive 状态到激活状态的变化。

vokoscreenNG-2026-08-20_14-31-31.mp4

你试一下用命令行启动浏览器,然后把数据目录放到硬盘的另一个位置上,安装完 Zeroomega 插件后,再重启这个新数据目录的浏览器,看 Zeroomega 插件还是会有冷启动问题吗?

比如我的 linux 机器是

/usr/bin/google-chrome-stable  --user-data-dir=/home/suziwen/.config/google-chrome-zh

window 机器应该是

"C:\Program Files\Google\Chrome\Application\chrome.exe" --user-data-dir="D:\ChromeData"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

在chrome刚打开时 很容易出现页面加载不出来的问题 这时点击任何地方都是无响应 导致无法使用

2 participants