Skip to content

🐛 修复 0.16 升级到 1.x 时 IndexedDB 数据不迁移导致脚本丢失 - #1804

Open
CodFrm wants to merge 2 commits into
mainfrom
fix/firefox-016-idb-migration
Open

CodFrm wants to merge 2 commits into
mainfrom
fix/firefox-016-idb-migration

Conversation

@CodFrm

@CodFrm CodFrm commented Oct 23, 2026

Copy link
Copy Markdown
Member

Checklist / 检查清单

  • Fixes mentioned issues / 修复已提及的问题
  • Code reviewed by human / 代码通过人工检查
  • Changes tested / 已完成测试

N/A — 「修复已提及的问题」:本问题是发 v1.5.0 时自测发现的,没有对应 issue。

背景

Firefox 正式版 AMO listing(scriptcat)目前最新版本仍是 MV2 的 0.16.15,约 1.27 万日活;1.x(MV3)此前只发到 beta listing。
发 v1.5.0 前在 Firefox Nightly 实测「AMO 签名的 0.16.15 永久安装 → 装脚本跑通 → 永久安装 1.5.0 覆盖升级」:
升级后脚本全部消失。IndexedDB ScriptCat 仍完整(v180,scripts / value / resource 都在),但 chrome.storage 里一个脚本都没有;
重启扩展、点「工具 → 数据迁移 → 重试迁移储存引擎」都救不回来。因此 v1.5.0 没有提交 AMO。

原因有两处:

  1. 自动迁移不触发。 1.x 把 migrateToChromeStorage 挂在 db.version(18).upgrade() 上,只有库版本升到 18 时才执行。
    但 0.16.7(2025-07-08,faae3b83「处理扩展降级问题 [BUG] Orion反安裝MV3版本後,安裝MV2無法正常運作 #503」)起 0.16 自身就声明了 db.version(18),
    之后的 0.16 用户库已经是 18,Dexie 不会再调 upgrade。用同版本 Dexie 4.0.10 验证:v17 库 → upgrade 触发;v18 库 → 不触发,再次打开(重启)也不触发。
  2. 手动按钮必然失败。 按钮在选项页直接调用 migrateToChromeStorage(),而选项页从不执行 migrate(),
    dao.ts 的 Dexie 实例没有任何表声明,db.table("scripts") 立即抛 InvalidTableError,又被 try/catch 吞掉,页面无任何反馈。
    现有 MigrationSection.test.tsx 把 @App/app/migrate 整个 mock 掉了,所以测试一直是绿的。

本次改动

  • 自动迁移改为按数据触发(migrateChromeStorage 第 2 项):chrome.storage 没有脚本、且 IndexedDB 有脚本时迁移;成功后记入 migrations,失败抛出、下次启动重试。
    db.version(18) 保留声明,去掉其 upgrade 回调,v17 / v18 两种旧库走同一条路径,不会重复迁移。
  • migrateToChromeStorage 改为可等待:await db.open() 取代 setTimeout(200),错误不再吞掉;迁移成功后照旧打开 v0.17 说明页并重载扩展。
  • 迁移时保留脚本原来的 origin。此前解构漏了 origin,save 里取到的是全局 origin(扩展自身的 chrome-extension:// / moz-extension://)。
  • 「重试迁移储存引擎」改为发 serviceWorker/system/migrateToChromeStorage 消息,由 SW 执行;失败时页面提示错误。
    处理函数注册在 service_worker.ts 入口(migrate() 声明表结构的地方),不放进 SystemService,避免服务层引入 Dexie。
  • 新增 devDependency fake-indexeddb,供迁移测试使用真实的 IndexedDB 语义;migrate.test.ts 会替换 Dexie 的 IndexedDB 来源、打开 Dexie 单例,因此放进 vitest 的 ISOLATED 列表。

实现考虑

  • 为什么不直接升到 db.version(19) 再迁移一次:Chrome / Edge 用户早在 0.16 → 1.0 时就迁移过,IndexedDB 里还留着当年的旧副本;
    按版本号再触发一次会用旧数据覆盖他们现在的脚本。按「chrome.storage 为空」判断,已迁移用户只会跑一次空检查。
  • 为什么不限定「1.x 首次启动」:手动装过 v1.5.0 覆盖 0.16 的用户 migrations 已是 [1],限定首次启动就救不回他们。

已知限制

  • 已迁移过、后来在 1.x 里删光了全部脚本的用户,升级后会从 IndexedDB 旧副本里「复活」当年的脚本(只发生一次)。判断上无法与「从没迁移过」区分,影响远小于不修。
  • 旧库的 resource 表不迁移(与当年 Chrome 0.16 → 1.0 一致),@require / @resource 在迁移后重新下载;资源地址已失效的脚本会缺资源。
  • Firefox MV3 的 userScripts 是可选权限,升级后不会自动授予。1.5.0 会打开开发者模式 / 授权引导页,Popup 有「请求权限」入口,本 PR 不改动。

建议审查重点

  • src/app/migrate.ts 的 diff 大部分是去掉 setTimeout 包裹后的整体缩进,建议用「Hide whitespace」(?w=1)查看,实际改动约 40 行。
  • migrateChromeStorage 第 2 项的触发条件:不能误伤已迁移的用户。
  • src/service_worker.ts 中 registerMigrationApi 在 Chrome(offscreen)/ Firefox(event page)两条分支都注册了。

验证

  • pnpm exec vitest run --no-coverage --project isolated src/app/migrate.test.ts:3/3 通过。修复前 v18、v17 两例失败(迁移后 0 个脚本),「已迁移用户不被覆盖」一例修复前后都通过。
  • pnpm exec vitest run --no-coverage --project ui src/pages/options/routes/Tools/sections/MigrationSection.test.tsx:2/2 通过。修复前该文件因页面引入 Dexie 在加载时崩溃。
  • CI=1 pnpm exec vitest run --no-coverage:375/378 文件通过。失败的 3 个是无关 UI 文件(SubscribeTable / Agent Skills / RuleForm),都是负载约 38 时的 850ms 超时,单独重跑 20/20 通过。
    (不带 CI=1 的本地单 worker 全量跑,未改动的 main 上同样有约 100 个文件因 chrome.permissions.reset is not a function 失败,是既有问题,不是本 PR 引入。)
  • pnpm lint:通过。
  • 真实 Firefox 升级路径(Firefox Nightly 155,AMO 签名的 0.16.15 永久安装后,再用本分支 pnpm run pack 产物永久安装覆盖,不预授予任何权限):
    • 修复前(v1.5.0):迁移后 0 个脚本,migrations=[1];addon.reload() 后仍为 0。
    • 修复后:migrations=[1,2],脚本与启用状态、GM 值 count=1 都迁移过来,打开了 v0.17 说明页;addon.reload() 后仍在。
      授予 userScripts 后脚本照常执行,GM 值在迁移值基础上继续累加,@require 重新下载成功。
    • 手动按钮:先清掉 chrome.storage 中的 script / scriptCode / value 键(脚本数 0),点「重试迁移储存引擎」后脚本恢复。
    • 这套驱动脚本是一次性验证(Selenium + geckodriver),没有放进仓库。

0.16.7 起 0.16 自身声明了 db.version(18)(防降级),这些用户的 IndexedDB 已是 18,
1.x 挂在 db.version(18).upgrade 上的 migrateToChromeStorage 永远不会触发,
升级后 chrome.storage 为空、脚本全部「消失」,重启扩展也不会重试。

- 迁移改由 migrateChromeStorage 第 2 项按数据触发:chrome.storage 无脚本且 IndexedDB 有脚本时迁移,
  v17 / v18 两种旧库走同一路径,且不会用旧副本覆盖已迁移用户的数据
- migrateToChromeStorage 改为可等待、失败抛出,迁移项只在成功后记录,失败下次启动重试
- 迁移脚本时保留原 origin(此前误取全局 origin,写成扩展自身的源)
选项页没有调用 migrate(),dao.ts 里的 Dexie 实例未声明任何表结构,
db.table("scripts") 直接抛 InvalidTableError,错误又被吞掉,按钮点了没有任何效果。

改为通过 SystemClient 消息交给 Service Worker 执行迁移,失败时在页面提示错误。
处理函数注册在 service_worker.ts 入口(migrate() 声明表结构的地方),
不放进 SystemService,避免服务层引入 Dexie。
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.

1 participant