Skip to content

🐛 修复 GM_download 传入空 url 时未触发 onerror 的兼容性问题 - #1619

Merged
CodFrm merged 2 commits into
scriptscat:mainfrom
cyfung1031:claude/scriptcat-gm-download-empty-url-d65147
Aug 11, 2026
Merged

🐛 修复 GM_download 传入空 url 时未触发 onerror 的兼容性问题#1619
CodFrm merged 2 commits into
scriptscat:mainfrom
cyfung1031:claude/scriptcat-gm-download-empty-url-d65147

Conversation

@cyfung1031

@cyfung1031 cyfung1031 commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

Checklist / 检查清单

  • Fixes ... / 已修复或实现 ...
  • Code reviewed by human / 代码通过人工检查
  • Changes tested / 已完成测试

背景

GM_download 传入空字符串 url 时,ScriptCat 与 Tampermonkey 行为不一致:TM 会同步报错或触发 onerror;ScriptCat 既不抛错也不触发 onerror,而是触发 onload

根因:src/app/service/content/gm_api/gm_xhr.tsnew URL(urlResolved, window.location.href) 对空字符串不会抛错,而是按 WHATWG URL 规范解析为当前页面自身的地址。_GM_downloadhandle() 在此之前没有对空 url 做拦截,导致空 url 被当作合法地址,静默对当前页面发起"下载"请求并触发 onload

复现脚本见 #1618(仅用于该 issue/本 PR 的说明,未加入自动化测试用例)。

本次改动

  • src/app/service/content/gm_api/gm_api.ts:在 _GM_downloadhandle() 内,await urlPromiseLike 得到 url 后立即校验;url 为空时直接触发 details.onerror?.({ error: "unknown" }) 并 reject retPromise,不再进入 browser/native 下载分支。该校验位于 downloadMode 分支之前,因此同时覆盖 nativebrowser 两种模式。
  • src/app/service/content/gm_api/gm_api.test.ts:新增 @grant GM_download 用例,分别验证 GM_download(回调形式)触发 onerror 且不触发 onload,以及 GM.download(Promise 形式)会 reject。

已知限制

  • example/tests/gm_download_test.js 已覆盖空 url 场景的手动/E2E 测试,本次未新增额外的手动测试脚本(按要求)。
  • Service worker 侧 src/app/service/service_worker/gm_api/gm_api.tsbrowser downloadMode 下的空 url 兜底分支(发送 data: nullonerror)仍保留原状,未做改动;该分支在本次修复后不再是空 url 场景下唯一的兜底路径,但作为纵深防御原样保留,属既有代码,不在本次改动范围内。

建议审查重点

  • _GM_download 中新增的空 url 判断是否会误伤合法但"假值"的 url(例如 urlconvObjectToURL 转换 Blob/File 后是否可能为空字符串)——convObjectToURL 产出的应始终是非空的 blob:/data URL,故不受影响。
  • error: "unknown" 是否是合适的错误码选择:GMTypes.DownloadError.error 的取值中没有专门的"url 为空/不合法"错误码,unknown 与本文件其余兜底错误路径(如后台连接失败、onMessage 收到 onerror)保持一致。

关联

Fixes #1618

验证

  • pnpm vitest run src/app/service/content/gm_api/gm_api.test.ts src/app/service/service_worker/gm_api/gm_api.test.ts — 54 passed
  • 回归验证:在应用修复前 git stash 掉实现改动后重跑新增用例,两条用例均按预期失败(onerror 未触发 / GM.download 超时未 reject),证明用例确实覆盖了该缺陷。
  • pnpm exec tsc --noEmit -p tsconfig.json — 无输出,通过
  • pnpm exec eslint src/app/service/content/gm_api/gm_api.ts src/app/service/content/gm_api/gm_api.test.ts — 无输出,通过

new URL("", location.href) 不会抛错而是解析为当前页面地址,导致空 url
被当作有效地址发起下载,与 TM 触发 onerror 的行为不一致。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Copy link
Copy Markdown
Collaborator Author

按 PickInvariant 的方式看了一遍这次改动(忽略 branch 落后 main / merge 相关问题),结论是 没有发现阻塞项

这里真正的缺口在 content 侧输入规范化与下游 URL 解析之间的 边界(B)url === "" 在进入 GM_xmlhttpRequest 后会被 new URL("", location.href) 解释成当前页面,因此上游“空 URL 应失败”的语义没有传递到下游。现在在 await urlPromiseLike 后、downloadMode 分支前统一拦截,并在回调形式触发 onerror、Promise 形式 reject,然后直接 return,能够同时覆盖 native/browser,且保留非空 URL、Blob/File 转换后的既有路径。

做了几个对照检查:

  • ""onerror / reject,且不进入下载分支;
  • 正常非空相对/绝对 URL → 继续走原逻辑;
  • Blob/File → convObjectToURL 得到非空 data URL,不会被误拦截;
  • 该问题不需要额外的全局/拓扑(χ)状态来区分。

所以对 #1618 当前定义的目标状态,新增的这个最小区分已经足够,我也没有把未在 issue 中定义的其它输入形态扩展成阻塞范围。

一个非阻塞的测试建议:现在 GM_download 的测试在 onerror 回调触发时立即 resolve,因此它能证明“触发了 onerror、此前没触发 onload”,但不能独立证明“onerror 后绝不会继续发起请求/下载”。如果后续方便,可以对实际传输入口(例如 connect/XHR 调用)加 spy,直接断言空 URL 时调用次数为 0;这样会更完整地锁住本 PR 想维护的“不发起下载”不变量。

整体 LGTM。

@CodFrm
CodFrm merged commit 125d58b into scriptscat:main Aug 11, 2026
10 checks passed
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.

GM_download 传入空 url 时行为与 Tampermonkey 不一致(触发 onload 而非 onerror)

2 participants