跳转至

[ISSUE-0070] 共享窗口孤儿累积

用户截图(v0.10.96 之前累积,到 v0.10.100 用户报):任务栏出现几十个"新标签页 - Cent Browser"窗口缩略图。

病灶

src/utils/scrape-window.ts:ensureWindowstorage.local:scrapeWindowId 持久化 windowId 复用:

async function ensureWindow(): Promise<number> {
  const existing = await getWindowId();
  if (existing != null) return existing;
  const win = await browser.windows.create({ url: 'about:blank', ... });
  await storage.setItem(SHARED_KEY, win.id);  // ← async,可能未完成
  return win.id;
}

SW kill 时机不对: 1. SW 启动 → ensureWindow → windows.create ✓ 创建窗口 A 2. storage.setItem(SHARED_KEY, A) ← async,还在 microtask 队列 3. SW 被 Chrome kill(30s 闲置 / 内存压力 / 浏览器 idle) 4. SW 重启 → getWindowId 返回 null(storage 没记到) 5. windows.create → 又创建窗口 B 6. 反复 N 次 → 几十个孤儿窗口

Cent Browser + RDC 远程桌面不稳定,windows.get(savedId) 验证假阴性概率高 → 即使 storage 写成功也会"找不到" → 重新创建。

修(v0.10.101)

1. storage 漏写 fallback — windows.getAll 找回

async function ensureWindow(): Promise<number> {
  const existing = await getWindowId();
  if (existing != null) return existing;

  // 新增:扫所有窗口找尺寸匹配的,避免重复 create
  const all = await browser.windows.getAll({ populate: false });
  const recovered = (all || []).find(looksLikeSharedWindow);
  if (recovered?.id != null) {
    await storage.setItem(SHARED_KEY, recovered.id);
    return recovered.id;
  }

  // 真的没有才新建
  const win = await browser.windows.create({ ... });
  ...
}

2. cleanupOrphanScrapeWindows — SW 启动时清扫

export async function cleanupOrphanScrapeWindows(): Promise<number> {
  const currentId = await getWindowId();
  const all = await browser.windows.getAll({ populate: true });
  for (const w of all) {
    if (!looksLikeSharedWindow(w)) continue;
    if (w.id === currentId) continue;
    const httpTabs = (w.tabs || []).filter(t => /^https?:/.test(t.url || ''));
    if (httpTabs.length > 0) continue; // 有真抓取 tab,留给 watchdog
    // 全是 about:blank 孤儿,关掉
    await browser.tabs.remove(allTabIds);
    closed++;
  }
}

background/index.ts SW 启动后调用一次。

3. system-log 追踪

general/shared-window-created / recovered / orphan-windows-cleaned 事件便于排障。

元教训

  1. async 写 storage 的瞬间 SW 不能 kill:但这无法控制。必须有 fallback(windows.getAll 找回 / 启动清扫)
  2. Cent Browser / RDC 等非标 Chromium 环境 windows.get 不可靠 — 写代码要假设 API 返回不稳定
  3. 任务栏缩略图是用户最直观的"东西不对"信号 — 用户不会看 console,会看窗口数

v0.10.103 后续修

cleanupOrphanScrapeWindows 第一版(v0.10.101)阈值太宽(±80px)+ 误关 chrome://newtab — agent code review 找到,ISSUE-0072 修。

相关

  • Tab生命周期与看门狗 — 类似的 SW kill 资源孤儿问题
  • [[0072-cleanuporphan-kills-user-newtabs|0072-cleanupOrphan误杀用户新标签页]] — 本修的后续优化
  • MV3持久化陷阱清单(更新规则.md 第七章)— SW kill 时机的 16 条铁律