-
Notifications
You must be signed in to change notification settings - Fork 309
[v1.3] 異步 getValue/getValues/listValues 相关修改 #950
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[v1.3] 異步 getValue/getValues/listValues 相关修改 #950
Conversation
CodFrm
commented
Nov 15, 2025
怎么都到这个分支去了 develop/raw-message
cyfung1031
commented
Nov 15, 2025
怎么都到这个分支去了 develop/raw-message
因为两边的 commit 互相影响
但处理的内容不一样,写在同一PR又太多又乱
b6d77de to
31a4165
Compare
31a4165 to
dc9f387
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
这个 PR 实现了异步 getValue/getValues/listValues 相关的重要改进,主要解决了在多标签页并发场景下值读取的一致性问题。通过引入 waitForFreshValueState 机制和批处理架构,确保在读取值之前能够获得最新的状态。
主要变更:
- 新增
waitForFreshValueState方法,确保在读取前获取最新的 value 状态 - 重构
setValues方法为批处理架构,使用任务队列和setValuesByStorageName进行批量处理 - 修改
GM.getValue/GM.listValues/GM.getValues以在读取前等待最新状态
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/app/service/service_worker/value.ts | 核心变更:新增 waitForFreshValueState 和 setValuesByStorageName,重构 value 更新逻辑为批处理模式,引入 ValueUpdateTaskInfo 任务队列 |
| src/app/service/service_worker/value.test.ts | 更新测试以反映新的批处理行为,调整期望值以匹配新的数据结构,添加 flush() 调用处理异步逻辑 |
| src/app/service/service_worker/runtime.ts | 更新 valueUpdate 订阅以使用新的 TScriptValueUpdate 类型,添加脚本状态重新验证逻辑 |
| src/app/service/service_worker/permission_verify.ts | 更新泛型约束从 T 到 T extends Array<any> 以匹配 API 参数类型 |
| src/app/service/service_worker/gm_api/gm_api.ts | 新增 internalApiWaitForFreshValueState API 方法,修正权限链接配置 |
| src/app/service/sandbox/runtime.ts | 更新 valueUpdate 方法以处理新的 ValueUpdateSendData 数据结构 |
| src/app/service/queue.ts | 修改 TScriptValueUpdate 类型定义,从包含 Script 对象改为包含 uuid、status 和 isEarlyStart 字段 |
| src/app/service/content/types.ts | 在 ValueUpdateDataEncoded 中添加 updatetime 字段,新增 ValueUpdateSendData 类型 |
| src/app/service/content/script_executor.ts | 更新 valueUpdate 方法签名以适配新的数据结构 |
| src/app/service/content/inject.ts | 更新类型引用从 ValueUpdateDataEncoded 到 ValueUpdateSendData |
| src/app/service/content/gm_api/gm_api.ts | 实现 waitForFreshValueState 静态方法,更新 GM.getValue/GM.listValues/GM.getValues 以调用该方法,重构 valueUpdate 处理多个更新事件,引入 extValueStoreCopy 和 readFreshes 机制 |
| src/app/service/content/gm_api/gm_api.test.ts | 添加 valueDaoUpdatetimeFix 辅助函数,更新测试以处理新的 waitForFreshValueState 行为,修正正则表达式以匹配计数器格式 |
| src/app/service/content/exec_script.ts | 更新 valueUpdate 方法签名以传递 storageName、uuid 和数据列表 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
为什么不直接包裹,而且整个流程都是同步的,用stackAsyncTask的意义是什么
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
如果是想 完全处理完毕后再 emitToListener,我觉得不如直接把这个逻辑放在最后
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
emitToListener 跟 valueChangePromiseMap 使用同一条 task 队列
valueUpdate 开始时,放了 stackAsyncTask("valueUpdateEventListenerEmit", () => hold.promise);
这让 emitToListener 先不开始处理
valueUpdate 完结后, hold.resolve(); 执行, emitToListener里的才开始处理
< 注意如果有其他 valueUpdate 的 valueChangeListener 未跑好,這輪的 valueChangeListener 也不會立即開始 >
用其他方法也可以
例如做一个 tasklist.
emitToListener 时把 this, key, oldValue, value, remote, sender.tabId 储存成一个 entry 放在 tasklist
最后 forloop 执行
这样写比较长,(削除) 但效果一样 (削除ここまで)
(這個只能考慮同一輪的valueChangeListener執行)
可是这个不保证 valueUpdate 二次 三次执行的顺序问题
而且 valueChangeListener 的執行會拖慢 valueUpdate 的完結
(考慮 userscript作者會利用 valueChangeListener 加一堆長時間執行的東西)
如果 valueUpdate 不等 valueChangeListener 的執行,第二次的第2個valueChangeListener 可能會較第一次的第3個 valueChangeListener 先執行
统一用 valueUpdateEventListenerEmit 的话,任何 valueUpdate 的 valueChangeListener 都要乖乖排队
这确保次序必为一致,不会插队。而且 valueUpdate 的執行不會受到 valueChangeListener 的執行而被拖慢
因為有 stackAsyncTask("valueUpdateEventListenerEmit", () => hold.promise);,所以第一個 valueChangeListener 的操作不會被立即執行,而是等 promise resolve 後的下一個 microTask
當要考慮上面所講的所有考慮,用現在這個做法是最簡單,否則寫長3倍 4倍的代碼還一堆 bug
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
< 注意如果有其他 valueUpdate 的 valueChangeListener 未跑好,這輪的 valueChangeListener 也不會立即開始 >
但是这一整个步骤都是同步的,只能跑完这一整个步骤后,才会进入下一轮,JS的核心逻辑是单线程的,这里也不会有多个线程去同时跑,不存在并发的问题
我理解你的想法是同时会触发 valueUpdate ,产生并发问题,导致数据不一致?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#1125 改掉了写法。你說要簡單點嘛
valueChangeListener 不应同步跑。会卡住 valueUpdate. 影响接收新数据
valueChangeListener要放在下一个 microTask.
这样就是次序问题
我没再更新这个 950 了
全部都用 Promise.resolve().then(....) 就没事
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
valueChangeListener 不应同步跑。会卡住 valueUpdate. 影响接收新数据
也有道理,不过关于这个,应该没多少影响,如果 valueChangeListener 逻辑很复杂,一直占用线程,那么下一个microTask也动不了
另外,你使用了 stackAsyncTask 也会卡住 valueUpdate 的处理呀;在emitToListener里的valueChangeListener,因为js单线程的特性,也还是会卡住 valueUpdate,stackAsyncTask并不太适用这种场景
Promise.resolve().then(....) 的处理,意义也不大
我没再更新这个 950 了
那我就不继续看950了,这里只做讨论
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
接收到的數據直接在sc的控制内做處理。Listener是外部的,裡面的代碼長度不可控制。 單一頁面是單線程,但不同頁面是不同線程。 用microtask拆出來,valueupdate的時間就只有valueupdate 不會因listener而變長。
如果 valuelistener裡包含了 其他value操作,例如 GM_setValue , 就會互相影操。
而且在valueupdate執行期間,直接處理listener, listener裡用GM_getValue會取不到改變後的值,因為valueupdate處理完後才會設置 scriptRes.value
所以1125 用了最簡單方法,用 Promise.resolve().then 排隊
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
用promise拆task是很正常啦
瀏覽器才可以正常運作起來
CodFrm
commented
Jan 2, 2026
唉,真的需要这么复杂的处理吗?有点看不下去
waitForFreshValueState 既然都发消息给 SW 了,为什么不直接返回要get的值
cyfung1031
commented
Jan 2, 2026
waitForFreshValueState 既然都发消息给 SW 了,为什么不直接返回要get的值
waitForFreshValueState 是用来等待最新值经valueUpdate 反映
"GM.getValue" "GM.listValues" "GM.getValues" 还是用本身的 GM_getValue, GM_listValues, GM_getValues 取本地的来做
如果另外处理的话,日后维护可能会令 "GM.getValue" 和 GM_getValue 的结果不一致
(SW处理跟本地处理)
waitForFreshValueState 不返回值让设计灵活简单一点
waitForFreshValueState 是用来等待最新值经valueUpdate 反映 "GM.getValue" "GM.listValues" "GM.getValues" 还是用本身的 GM_getValue, GM_listValues, GM_getValues 取本地的来做
如果另外处理的话,日后维护可能会令 "GM.getValue" 和 GM_getValue 的结果不一致 (SW处理跟本地处理)
只可能是值更新了,但是消息还没发出去到达content进行处理,才会不一致,而且这个不一致是合理的,因为GM.getValue/GM_getValue就是应该要取到最新的值,只是GM_getValue因为是同步的,可能取的并不是最新
waitForFreshValueState 不返回值让设计灵活简单一点
太弯弯绕绕了
cyfung1031
commented
Jan 2, 2026
waitForFreshValueState 是用来等待最新值经valueUpdate 反映 "GM.getValue" "GM.listValues" "GM.getValues" 还是用本身的 GM_getValue, GM_listValues, GM_getValues 取本地的来做
如果另外处理的话,日后维护可能会令 "GM.getValue" 和 GM_getValue 的结果不一致 (SW处理跟本地处理)只可能是值更新了,但是消息还没发出去到达content进行处理,才会不一致,而且这个不一致是合理的,因为GM.getValue/GM_getValue就是应该要取到最新的值,只是GM_getValue因为是同步的,可能取的并不是最新
waitForFreshValueState 不返回值让设计灵活简单一点
太弯弯绕绕了
waitForFreshValueState 是等SW给最新的值
如果直接取SW的值,会导致那个值跟 GM_addValueChangeListener 最后取得的值有机会不一致
现在只是等SW给东西,确认了是页面当前的取得的「最新」后,就用 GM_getValue 拿数值
(实际上在 waitForFreshValueState resolve后,其他tab 也可能在呼叫SW改数值,只是还没广播到页面)
值也会跟页面收到 GM_addValueChangeListener的资讯一致 ( valueA -> valueB -> valueC <- GM_getValue 也是 valueC)
waitForFreshValueState 的设计是,发一个 internalApiWaitForFreshValueState 给SW,在SW那边插一个 task 到 SW valueUpdate 队列
假设 waitForFreshValueState 的发生时间是 t=4.1s,
SW插进去肯定是包含 t=4.1s 之前所有 valueUpdate
因此 internalApiWaitForFreshValueState 返回时,就可以确定要等到那些 valueUpdate 接收好,本地的数据才会是 >= t=4.1s
此时 SW的数据可能已经是又变动了 (t=4.6s, 4.8s)
但 waitForFreshValueState resolve 后已经保证是 t=4.1s 以前的变动通通 apply 了,所以可以取 GM_getValue 值
这是相对于 setValue 要等 SW 设置后才 Promise结束
getValue 是等 頁面 读取后才 Promise开始
waitForFreshValueState 不返回值让设计灵活简单一点
太弯弯绕绕了
可以移除 waitForFreshValueState
改成一个通用的方式跟SW取最新值
getFreshValues(keys: []) -> listValues
getFreshValues(keys: ["key1"]) -> getValue("key1")
getFreshValues(keys: ["key1", "key2"]) -> getValues(["key1", "key2"])
(PR其他修改不变)
(没通过 页面valueUpdate接收处理, 不保证 GM.getValue 的东西跟 valueChangeListener 的触发和页面数据会否一致 。 不清楚隐藏了什么样的 bug. 由于跟 valueChange 处理不一致,可能影响到现时脚本)
cyfung1031
commented
Jan 2, 2026
waitForFreshValueState 不返回值让设计灵活简单一点
太弯弯绕绕了
可以移除 waitForFreshValueState 改成一个通用的方式跟SW取最新值 getFreshValues(keys: []) -> listValues getFreshValues(keys: ["key1"]) -> getValue("key1") getFreshValues(keys: ["key1", "key2"]) -> getValues(["key1", "key2"])
(PR其他修改不变) (没通过 页面valueUpdate接收处理, 不保证 GM.getValue 的东西跟 valueChangeListener 的触发和页面数据会否一致 。 不清楚隐藏了什么样的 bug. 由于跟 valueChange 处理不一致,可能影响到现时脚本)
我觉得加 waitForFreshValueState 只是一个辅助。不必改变 GM.getValue 的本质。
即使改动了,事实上 .getValue 和 .setValue 还是避免不了多个执行是有冲突的问题
如果为了一个少少修改,影响了本来的运作,就是本末倒置了
这个修改不像 GM.setValue 那么大影响(之前 GM.setValue 会因为页面关掉而没有实际呼叫 SW 改值,影响大)
也找不到脚本去说明这个PR的重要性
所以可以长期放置,不合并也可以
cyfung1031
commented
Jan 2, 2026
我把争议大的部份先拿走,移至 #1125
Uh oh!
There was an error while loading. Please reload this page.
概述 Descriptions
依存: #949
取值前确保读取的是最新 values
否则向service_worker发消息,取得最新的 valueUpdated
变更内容 Changes
截图 Screenshots
测试代码(一):
修改后:
GM.listValues()能在冲突中取得最新,而且不会因本地缓存与valueUpdate冲突而造成次序不一(
useAsync改为false的话就能看 GM_xxxx 的结果 )测试代码(二):
(有
GM_lock做时间控制)修改后GM.getValue的列表新增没问题