✨ 为 GM_xmlhttpRequest / GM.xmlHttpRequest 支持上传进度事件 (GM/TM/VM)#1566
Draft
cyfung1031 wants to merge 11 commits into
Draft
✨ 为 GM_xmlhttpRequest / GM.xmlHttpRequest 支持上传进度事件 (GM/TM/VM)#1566cyfung1031 wants to merge 11 commits into
cyfung1031 wants to merge 11 commits into
Conversation
对齐 Tampermonkey:details.upload 可挂载 onloadstart/onprogress/onload/ onloadend/onerror/onabort/ontimeout,对应原生 XMLHttpRequestUpload 事件。 仅原生 XHR 路径支持(fetch 模式无法获取上传进度,维持既有限制)。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- 仅在脚本实际注册了 upload 回调时才为 xhr.upload 绑定监听器(GMSend.XHRDetails 新增 hasUpload 标记):为 xhr.upload 注册任何监听器都会令浏览器对跨域请求强制 触发 CORS 预检 (OPTIONS),未使用该功能的脚本不应承担这一开销/兼容性风险。 - 调用返回的 abort() 时,若 upload 阶段尚未完成,补发 details.upload.onabort / onloadend(对齐原生 XMLHttpRequest.abort() 语义);upload 阶段已完成后再次 abort 不会重复触发。放宽 abort() 的触发条件,使仅注册了 upload 回调(未注册 顶层 onabort)的脚本也能正常收到 abort 通知。 - 统一 upload 各回调 (onloadstart/onload/onloadend/onerror/onabort/ontimeout) 均携带 loaded/total/lengthComputable(对齐 XHR 规范:upload 的事件均为 ProgressEvent),类型定义同步放宽为 Listener<XHRProgress>。 - 更新/新增测试覆盖以上场景。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- uploadDone 现在于 load/error/abort/timeout 到达时即置位(对齐规范:upload
complete flag 在这些事件派发前已置位),而不仅仅是 loadend;避免在
upload.onload 回调内同步调用 abort() 时被误判为"尚未完成"而重复触发
upload.onabort。
- 新增 uploadLoadEndCalled 去重:upload.onloadend 保证恰好触发一次,无论是
由真实的 onuploadloadend 消息触发,还是由 abort() 兜底补发触发(两者可能
竞态:真实消息可能已在通道断开前送出但因断线而无法再被处理)。
- abort() 补发的 upload.onabort / onloadend 现在统一经由
makeUploadCallbackParam 携带 loaded:0/total:0/lengthComputable:false,
不再是缺少进度字段的裸响应对象,符合已声明的 XHRProgress 类型契约。
- hasAnyUploadHandler 改为只接受函数值:truthy 但非函数的回调(如
{ onprogress: true })不再被视为已注册,避免既误触发额外 CORS 预检,
又在事件到达时把非函数值当函数调用而抛出。
- 新增测试覆盖:upload.onload 内同步 abort、补发数据的进度字段、
abort 时的事件顺序 (upload.abort → upload.loadend → 主 abort → 主 loadend)、
以及非函数 upload 回调不启用 upload 监听。
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- 对齐规范:request error steps 在派发 upload 的 error/abort/timeout 前, 已将整个请求置为 DONE(原生此时再调用 abort() 不会产生新事件)。新增 suppressSyntheticAbort 标记,在 onuploaderror/onuploadabort/onuploadtimeout 到达时置位;置位后返回的 abort() 变为空操作(不再断开通道、不再合成 AbortError),让真实的主 onerror/ontimeout/onabort 消息能够正常到达并 驱动正确的回调,而不是被本地合成的 onabort 抢先覆盖。 - 新增 lastUploadEventData,记录 upload 成功完成(onuploadload)时的真实 loaded/total/lengthComputable;在 upload.onload 回调内同步调用 abort() 导致真实 onuploadloadend 消息因通道断开而丢失时,兜底补发的 onloadend 使用这份真实数据,而不是 abort() 合成响应对象里缺省的 undefined。 - 新增测试覆盖:upload.onerror / upload.ontimeout 内调用 abort() 不应覆盖 真实的主 onerror/ontimeout;upload.onload 内 abort() 导致 loadend 消息 丢失时,兜底补发的 onloadend 应携带真实的已传输字节数。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- 对齐 XHR 规范:GET/HEAD 请求体会被忽略;无请求体时 upload complete flag 在发送前已置位;fetch 传输不绑定 xhr.upload——以上情况均不会产生真实的 原生 upload 阶段。此前 uploadDone 仅依据"是否注册了 upload 回调"初始化, 与是否存在真实 upload 阶段无关,导致这些场景下调用 abort() 仍会错误地 合成 upload.onabort / onloadend。 - 新增 canHaveUploadLifecycle:在请求体编码完成、确认 method/body/transport 后计算(method 非 GET/HEAD、有实际请求体、且不会走 fetch 传输、且注册了 回调),用其替代 hasAnyUploadHandler(details.upload) 来初始化 uploadDone, 并据此决定发往后台的 hasUpload 标记——避免此前对 GET/无请求体的原生 XHR 请求也绑定 xhr.upload 监听器(本可避免的 CORS 预检开销)。 - doAbort 内的 upload 合成逻辑整体收敛到 canHaveUploadLifecycle 判断之下, 避免"upload 从未开始"与"upload 已成功完成"两种 uploadDone=true 的语义 被混淆(前者不应触发任何 upload.* 回调,后者才应携带真实数据补发 loadend)。 - 更新/新增测试:GET、HEAD、POST 无 data、fetch/redirect/anonymous/stream 等场景下 hasUpload 应为 false 且 abort() 不触发 upload 事件;POST 带 请求体的既有测试同步补上 method/data 以符合真实场景。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CI 下 workers:2 并行启动多个 Chrome 实例造成资源争抢,30s 偶发不足以等到扩展 service worker 就绪,导致 fixture 阶段超时误报(与 gm_xhr.ts 上传事件改动无关, 该测试不涉及任何 GM_xmlhttpRequest 调用)。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
之前每个 test 都要完整走两次 launchPersistentContext(含 chrome://extensions 导航 + developerPrivate 权限调用),CI 下多个 worker 并行时大量并发 Chrome 启动互相抢占 CPU,是 service worker 启动超时误报的根因。 参照 e2e/agent-fixtures.ts 已验证过的模式:新增 worker 级 gmApiProfileDir fixture,只在每个 worker 里做一次 Phase 1 权限配置,每个 test 改为拷贝该 profile 目录后只做一次轻量 Phase 2 启动。本地验证:整个 spec 文件 7 个测试 2 workers 下 16.8s 全部通过。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
12 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Checklist / 检查清单
背景
当前
GM_xmlhttpRequest/GM.xmlHttpRequest只能接收主请求下载阶段的事件,无法通过details.upload获取上传阶段的开始、进度、完成或异常信息。这会影响文件上传、表单上传等需要显示上传进度、处理超时或响应用户中止操作的用户脚本,也使 ScriptCat 与原生
XMLHttpRequestUpload以及常见用户脚本管理器的接口存在差异。本次改动
为
details.upload增加以下回调支持:onloadstartonprogressonloadonloadendonerroronabortontimeout后台原生
XMLHttpRequest路径监听xhr.upload,通过现有消息通道转发为onupload*事件。内容脚本侧将
onupload*消息分发到details.upload.on*,并统一提供:loadedtotallengthComputabledonetotalSize请求体编码完成后计算
hasUpload,只在请求确实可能产生上传生命周期时通知后台绑定上传监听器。对齐原生 XHR 的主动中止行为,处理上传回调与主请求回调的顺序、去重和同步竞态。
更新
GMSend.XHRDetails、GMTypes.XHRDetails,新增GMTypes.XHRUpload,并同步模板、英文及简体中文声明文件。新增后台、内容脚本、运行时及浏览器集成测试,覆盖正常上传、异常、超时、主动中止、事件顺序和竞态。
调整 GM API E2E fixture:每个 Playwright worker 只执行一次 user scripts 权限初始化,各测试从预配置 profile 创建独立副本,减少并行 CI 中重复启动 Chrome 导致的 Service Worker 超时,同时保持测试状态隔离。
实现考虑
1. 只在确实需要时绑定
xhr.upload为
XMLHttpRequestUpload注册监听器会改变跨域请求行为,并可能触发额外的 CORS 预检请求。因此不能无条件为所有 XHR 请求绑定上传监听器。
内容侧会在请求体完成编码后计算
canHaveUploadLifecycle,仅在同时满足以下条件时向后台发送hasUpload: true:fetch传输;GET或HEAD;details.upload中至少有一个回调值确实是函数。后台仅在原生 XHR 路径且
details.hasUpload === true时绑定七类xhr.upload事件。这样不会让未使用上传回调的既有脚本承担额外预检、性能开销和跨域兼容风险。
2. 区分“注册了回调”和“真实存在上传生命周期”
仅传入
details.upload并不代表浏览器一定会产生上传事件:GET/HEAD的请求体会被忽略;fetch传输路径没有XMLHttpRequestUpload;details.upload中 truthy 但非函数的值不是有效回调。因此后台监听开关、本地完成状态以及
abort()的上传事件补发都统一依赖canHaveUploadLifecycle。这可以避免在根本不存在上传阶段的请求中错误触发
upload.onabort或upload.onloadend。3. 对齐原生 XHR 的中止语义和事件顺序
当上传尚未完成时主动调用返回对象的
abort():upload.onabort;upload.onloadend;onabort;onloadend。实现使用
uploadDone和uploadLoadEndCalled维护上传完成状态并保证upload.onloadend只执行一次。同时处理以下竞态:
upload.onload回调中同步调用abort()时,上传已被视为完成,不应误触发upload.onabort;upload.onloadend丢失,因此需要本地兜底;upload.onloadend和本地兜底只能执行一次;upload.onloadend会保留最后一次真实上传进度;error、abort或timeout结束后,返回对象的abort()会成为空操作,让真实的主请求终止事件继续到达,避免被本地合成的AbortError覆盖。4. 上传事件统一使用进度响应结构
主请求
onprogress与全部上传事件共用同一套进度参数构造逻辑。上传事件会尽量保留现有 XHR 响应字段,并统一补充:
lengthComputableloadedtotaldonetotalSize其中
done与loaded一致,totalSize与total一致,用于兼容现有 ScriptCat 进度回调结构。5. 只接受函数类型的上传回调
details.upload中 truthy 但非函数的值不会:这样既避免无效配置改变请求行为,也避免运行时出现非函数调用异常。
6. 降低 E2E 并行执行的启动开销
GM API 浏览器测试需要先通过
chrome://extensions和developerPrivate启用 user scripts 权限,再重新启动持久化浏览器上下文。此前每个测试都会重复执行完整初始化;并行 worker 较多时,大量 Chrome 实例会同时争用 CPU,使扩展 Service Worker 启动时间超过测试超时。
现在权限初始化改为 worker 级 fixture:
这样可以减少重复启动成本,同时避免脚本、存储和扩展状态在不同测试之间泄漏。
已知限制
1. 强制走
fetch的配置不支持上传回调当前以下配置会让 ScriptCat 使用
fetch传输,因此不会产生details.upload事件:fetch: trueredirectanonymous: true或mozAnon: trueresponseType: "stream"其中
anonymous/mozAnon切换到fetch是现有架构通过credentials: "omit"实现不携带 Cookie 的方式。要让匿名请求继续使用原生 XHR,需要重新设计跨域 Cookie 处理机制,影响范围超出本 PR,因此本次只明确记录限制,不调整现有传输架构。
2. 兼容目标
本实现以原生
XMLHttpRequestUpload的事件模型和顺序为准,并提供与常见用户脚本管理器相近的details.upload接口。类型声明和说明不承诺与某个特定版本的 Tampermonkey、Greasemonkey 或 Violentmonkey 在所有边缘行为上完全一致。
3. 进度总量由浏览器决定
所有上传回调都会提供
loaded、total和lengthComputable,但浏览器不保证每次都能计算上传总大小。用户脚本仍应在
lengthComputable === false时使用不确定进度展示,而不能假设total始终有效。建议审查重点
hasUpload/canHaveUploadLifecycle是否完整覆盖所有传输选择条件;upload.onload内同步调用abort()时的事件顺序;abort()是否会保留真实的主请求错误类型;fetch配置下的限制说明是否足够清晰;参考
https://wiki.greasespot.net/GM.xmlHttpRequest
Tampermonkey/tampermonkey#1429
violentmonkey/violentmonkey#2302