Skip to content

🐛 修复云同步推送时脚本列表与编辑页长时间空白 - #1765

Open
CodFrm wants to merge 5 commits into
mainfrom
fix/sync-queue-blocking
Open

CodFrm wants to merge 5 commits into
mainfrom
fix/sync-queue-blocking

Conversation

@CodFrm

@CodFrm CodFrm commented Sep 22, 2026

Copy link
Copy Markdown
Member

Checklist / 检查清单

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

背景

开启云同步后,在脚本设置里删除或重置网站匹配,再 F5 刷新编辑页,页面会长时间空白:脚本列表显示 0,编辑区为空,也没有报错。过一段时间后才恢复。关闭云同步后恢复正常。

根因是 getAllScripts 整体放在 CLOUD_SYNC_QUEUE_KEY 串行队列里执行(#1690 引入)。这条队列里同时还有每次脚本变更触发的推送和整轮 syncOnce,而这些任务都要等网络 I/O。修改 match 会触发推送;网盘慢或重试时,刷新后的列表请求排在推送后面,页面只能干等。

排查时还发现两个会放大问题的点:

  • 脚本 @match edge://settings/* 这类浏览器不支持的 scheme,会让 chrome.userScripts.register 的整批注册失败(批量注册是原子的),然后退回逐个注册。excludeMatches(包括黑名单拼入的部分)同样会被拒绝,所以黑名单里只要有一条 edge://,所有脚本都会注册失败。
  • 编辑页和脚本设置在数据没到时直接 return null,失败后也不重试。所以不管是在等待还是出错,看到的都只是空白。

本次改动

分三个提交,可以分别审查:

  1. 🐛 获取脚本列表不再排在云同步队列后面
    • getAllScripts 直接读取、排序后返回(返回浅拷贝,不改 DAO 缓存对象)。
    • 只有 sort 不连续时,才把归一化写入入队;入队后在队列里重新读取再写,保留 🐛 修复多设备同步脚本排序错乱 #1690 对排序写入的串行保护。
    • 同步修正 docs/cloud-sync.md 队列一节:过时的常量名,以及补充「页面依赖的读取不得进入这条队列」。
  2. 🐛 注册脚本时丢弃浏览器不支持的 match scheme
    • getApiMatchesAndGlobs 在输出处过滤 matches,只保留 * / http / https / file / ftp。过滤放在最后:如果提前过滤,前面的逻辑会把空列表当成「没有 match」而退回 *://*/*。过滤后为空时,走 runtime 已有的「不注册」分支,对应日志从 error 降为 warn。
    • 脚本和黑名单的 excludeMatches 同样过滤;blackMatch 仍保留原规则参与黑名单判断。
    • 更换 CompiledResourceNamespace,让已缓存的旧 matches 在下次启动时重建。
    • 设置页「添加匹配」会把无法注册的规则标为「无效」,不写入。排除规则允许 glob/正则,不支持的 scheme 在注册时已被丢弃,所以不做校验。
  3. 💄 编辑页与脚本设置加载时不再留白
    • 脚本列表返回前,编辑区显示 LoadingState
    • 脚本设置请求中显示 LoadingState;失败时用 StateScreen 展示错误详情和重试按钮。未保存脚本(data === null)维持原有行为。

新增文案 bulk_status_invalidsettings_load_failedretry,10 个 locale 都已补齐。日语「无效」用「不正」,因为术语规范中「無効」固定表示「禁用」。

已知限制

  • 同步文件系统请求仍然没有单次超时(OneDrive 的 fetch 没有 signallimiter.ts 对读操作的重试退避最长约 6 分钟)。请求挂住时,推送和 syncOnce 仍可能长时间占用队列。现在这只会推迟 sortScript / pinToTop 的完成时间,不再阻塞页面读取。超时时长和 syncOnce 的锁粒度需要单独设计,另开 PR。
  • 脚本设置加载失败时,编辑页原有的 toast 仍会弹出,和页面内的错误态同时出现。
  • getAllScripts 如果直接 reject,useScriptDataManagement 不会退出加载态(原有行为,未改)。

建议审查重点

  • script.tsgetAllScripts / normalizeScriptSort:读取不再与同步互斥,确认没有调用方依赖「返回时归一化已经落盘」。已有测试改成等队列清空后再断言落盘,就是这个契约变化。
  • url_matcher.ts 中过滤的位置:过滤后为空时,必须走「不注册」分支,不能退回全站匹配。
  • 更换 CompiledResourceNamespace 的代价:升级后首次启动会清理旧注册并全量重建一次。

验证

本地 base 61cc2481(origin/main),head 8016524a

  • 复现(修复前,基于 fix/mobile-script-drag-swipe 的构建产物):用每个请求延迟 8 秒的本地 WebDAV 模拟慢网盘,开启同步后改 match,再刷新编辑页。推送进行中 getAllScripts 耗时 125.9s,findInfo 只要 22ms,截图与用户描述一致(列表 0、编辑区空白)。改 match 用的是和 UI 相同的 resetMatch 消息,不是点击按钮。
  • 修复后,同样的场景(本分支 pnpm run build):推送进行中 getAllScripts 耗时 26ms,编辑页正常显示 32 个脚本和代码。已通过 WebDAV 服务日志确认推送请求与这次读取在时间上重叠。
  • 修复后,清空注册再冷启动 SW(含 @match edge://settings/* 脚本):没有 batch registration errorInvalid scheme,其余脚本和 scriptcat-inject 都已注册,edge:// 脚本未注册。
  • 实测 Chrome 对 excludeMatches: ["edge://settings/*"]about:blankurn:*ws:// 都会抛 Invalid scheme
  • 每项都先写失败测试并确认失败,再修复:script.test.tsurl_matcher.test.tsutils.test.tsruntime.test.tsSettingsPane.test.tsxScriptEditor/index.test.tsx
  • pnpm run typecheckpnpm run check:i18n、改动文件的 eslint 和 prettier 都通过。
  • pnpm test:第一次全量有 3 个失败,都在未改动的 eslint-rules/harness.test.mjssrc/pages/install/useInstallData.test.ts;这两个文件单独跑能通过(88/88),第二次全量 373 个文件、4848 个用例全部通过。推测是并行负载导致的偶发失败,未进一步定位。
  • git diff origin/main...HEAD 只涉及上述源码、测试、docs/cloud-sync.md 和 10 个 editor.json

Screenshots / 截图

未附截图。修复前后的截图是在本地验证会话里拍的。新增的加载态和错误态复用了现有的 LoadingState / StateScreen 组件和设计令牌,但暗色主题下没有单独截图确认。

CodFrm and others added 5 commits September 22, 2026 14:57
getAllScripts 整体放在 CLOUD_SYNC_QUEUE_KEY 里执行,网盘推送慢或重试时
脚本列表与编辑页会长时间空白。只把 sort 归一化写入留在队列里(入队后重新读取),
读取直接返回。
chrome.userScripts 批量注册是原子的,任一脚本的 matches/excludeMatches
含 edge://、chrome:// 等 scheme 都会让整批失败并退回逐个注册。
- getApiMatchesAndGlobs 输出时过滤 matches;过滤后为空则不注册,不会退回 *://*/*
- 脚本与黑名单的 excludeMatches 同样过滤,黑名单判断仍保留原规则
- 更换 CompiledResourceNamespace,让旧缓存里的 matches 重建
- 设置页添加匹配时标出无法注册的规则且不写入
- 脚本列表返回前编辑区显示加载状态
- 脚本设置请求中显示加载状态,失败时展示错误详情与重试(原先一律 return null)
@cyfung1031

Copy link
Copy Markdown
Collaborator

已完成这次 PR #1765 的 rework audit,并按两个独立问题补上修正。PR 标题和正文未改动。

  • df62118c:合并重复的脚本排序归一化任务。脚本列表读取期间最多保留一个归一化任务,任务失败后仍可由后续读取重新触发。
  • a6c2d8d3:脚本列表加载失败时展示错误并支持重试。编辑页和脚本列表都会结束 loading 状态、显示错误详情和重试按钮;编辑页在列表失败时不会继续打开空标签页。

验证结果:

  • 四个新增回归测试通过:脚本服务 89/89、列表 Hook 5/5、编辑页 6/6、脚本列表 8/8。
  • pnpm run lint 通过,包含 Prettier、typecheck、i18n、issue-template 和 ESLint。
  • 主机环境执行 pnpm run test:ci 通过:373/373 个测试文件、4852/4852 个用例。
  • GitHub Checks 当前全部成功,包括 Lint、两组单元测试、四组 E2E、聚合测试、Codecov 和 License Compliance。

本次没有重新执行慢 WebDAV/浏览器扩展运行时探针;附件中关于原 PR head 的运行时证据仍按审计材料记录,不能作为本次新增代码的独立运行时证据。云文件系统请求超时和 syncOnce 锁粒度问题按审计结论保留为后续工作,没有扩大本 PR 范围。

最终发布 head:a6c2d8d3e52dd42dfa43a66ad953c2ad545b2d96

return {
matches: apiMatches, // primary
// 放在最后过滤:过滤后为空时调用方会不注册该脚本;若提前过滤,上面的逻辑会把空列表当成「无 match」退回 *://*/*
matches: apiMatches.filter(isApiSupportedMatchPattern), // primary

@cyfung1031 cyfung1031 Sep 22, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CodFrm

我觉得不需要因为 agent 单一测试问题而修改 url_matcher.

不过你实机测试过没问题也可以保留修改。

@cyfung1031 cyfung1031 added the P1 🔥 重要但是不紧急的内容 label Sep 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P1 🔥 重要但是不紧急的内容

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants