Skip to content

Conversation

@cyfung1031
Copy link
Collaborator

@cyfung1031 cyfung1031 commented Dec 9, 2025

概述 Descriptions

已测试在Chrome版和Firefox版都能够正常执行
后台脚本没有测试。
(VSCodeConnect以外的功能,Firefox MV3 能完全执行。可以正常在Firefox跑)

不会被浏览器截取。inject 要发一个 id key, content 要配合这个 key 发另一个沟通key (同步处理)
沟通key = uuidv5(inject/scripting发出的requestId, content的messageFlagId)
双方要用同一个沟通key才能对话

scripting在 scripting 分离环境直接透过 chrome.storage.onChange 取得 service worker 的broadcast 指示,再转发到 content & inject
这个机制减省了很多不必要的处理
(不用 tabs.query, 不用 tabs.sendMessage, 不用先传到 content 再传到 inject)
(不然的话,我浏览器有100个以上的tab, 每一次 valueUpdate 都要发 100次 sendMessage, 对 serviceWorker 本身处理影响很大)

scripting.js 无法像 inject 跟 content 把messageFlag封装到代码
而是一个通用脚本直接查 chrome.storage 的 messageFlag 再跟 content 请求对话
因为它是用来被动接收 service worker 的东西,所以不需要一个同步的messageFlag
不会影响 inject 和 content 的同步执行

同时,也解决了 Firefox MV3 中,runtime 没有 onMessage 的问题
(根据官方文档,这个看起来不像 Bug. userscriptAPI 的 runtime 就是只能发不能接收)

这个改动里, early-start 处理不变。
因为改的部份是 extServer 的接收处理。

非broadcast类的通讯,例如 emitEventToTab 那些有留著。
在 scripting 中接收然后转发。

变更内容 Changes

inject.js 和 content.js 也改动了,不再是一层套一层的 class 设计
代码不会重用。
直接写代码就好。方便理解和维护

为了方便使用 uuidv5, messageFlag 改成 uuidv4 格式

截图 Screenshots

@cyfung1031 cyfung1031 requested a review from CodFrm December 9, 2025 03:32
@cyfung1031 cyfung1031 force-pushed the develop/messaging-performance-boost2 branch from a08c0b8 to 2396bb5 Compare December 9, 2025 03:34
@cyfung1031 cyfung1031 changed the title 以类似broadcast机制重构通讯机制 [v1.3?] 以类似broadcast机制重构通讯机制 Dec 9, 2025
@CodFrm
Copy link
Member

CodFrm commented Dec 9, 2025

后台脚本没有测试

我测试了一下,没有发现问题

scripting在 scripting 分离环境直接透过 chrome.storage.onChange 取得 service worker 的broadcast 指示,再转发到 content & inject
这个机制减省了很多不必要的处理
(不用 tabs.query, 不用 tabs.sendMessage, 不用先传到 content 再传到 inject)
(不然的话,我浏览器有100个以上的tab, 每一次 valueUpdate 都要发 100次 sendMessage, 对 serviceWorker 本身处理影响很大)

这个消息投递的次数并没有消失,只是进行了转移,从我们的业务代码转移到了浏览器的内部机制,现在是真真正正的要投递触发100次 chrome.storage.local.onChanged.addListener 事件消息,同时有一次存储消耗,之前的逻辑反而可以根据实际的运行情况只推指定的tab,当然这是一个不错的思路,只是我觉得性能消耗要根据实际情况来看

不用先传到 content 再传到 inject

变成了先传到 scripting 再传到 inject,复杂度并没有消失,如果不是考虑firefox的话,我觉得不需要这一层

scripting.js 无法像 inject 跟 content 把messageFlag封装到代码
而是一个通用脚本直接查 chrome.storage 的 messageFlag 再跟 content 请求对话
因为它是用来被动接收 service worker 的东西,所以不需要一个同步的messageFlag
不会影响 inject 和 content 的同步执行

需要 查 chrome.storage 的 messageFlag 再跟 content 请求对话,early-start 的脚本,在极端情况下,可能丢失message

@CodFrm CodFrm added the P1 🔥 重要但是不紧急的内容 label Dec 9, 2025
@cyfung1031
Copy link
Collaborator Author

cyfung1031 commented Dec 9, 2025

这个消息投递的次数并没有消失,只是进行了转移,从我们的业务代码转移到了浏览器的内部机制

因为每一个 tab 有自己的 scripting
不是 serviceWorker 一个程序发出100个
而是每个 scripting 去处理
这是主要的分别吧

serviceWorker 是单线程。所有 scripts 的后台都是它在处理
尽量把工作能分散都分散处理吧

同时有一次存储消耗

这个还好吧。虽然更进一步是可以直接在 value 储存那边做。现在先这样。

之前的逻辑反而可以根据实际的运行情况只推指定的tab

valueUpdate 没有指定 tab
有指定tab的 emitEvent 还是保留原有的做法

当然这是一个不错的思路,只是我觉得性能消耗要根据实际情况来看

你拿 虚拟机 跑几百个脚本试试吧。应该能测试得出分别。

需要 查 chrome.storage 的 messageFlag 再跟 content 请求对话,early-start 的脚本,在极端情况下,可能丢失message

531ac10 这里有处理。
反正本身 valueChange 就不是同步
现在这个,不会丢失message,得到 messageFlag 后会补回事件
(messageFlag 改变的话,对话就会中断。需要重新载入。)


理想的话,应该是改成 UserScripts API Dynamic

UserScripts API Dynamic:
Uses the browser's UserScripts API to inject both the wrapper code and the userscript code.
The userscript is executed instantly -> document-start is supported.

这样的话后台API全部都经由 scripting 跟 service_worker 对话
不用多出一个 content.js 和 inject.js

但很可惜我不太懂如何改 rspack.config.ts 的设定,让它生成 wrapper 用的js, 然后跟脚本代码一同放在 userScripts API register 里

@CodFrm
Copy link
Member

CodFrm commented Dec 9, 2025

理想的话,应该是改成 UserScripts API Dynamic
这样的话后台API全部都经由 scripting 跟 service_worker 对话
不用多出一个 content.js 和 inject.js

不是很理解这个想法,为什么不用多出 content.js 和 inject.js,难道把 GM、沙盒之类的逻辑也放到 userscript 中?

另外注入的脚本可能是在页面环境中的(inject),也还是要由 content/scripting 转发消息给 service_worker

@cyfung1031
Copy link
Collaborator Author

cyfung1031 commented Dec 9, 2025

理想的话,应该是改成 UserScripts API Dynamic
这样的话后台API全部都经由 scripting 跟 service_worker 对话
不用多出一个 content.js 和 inject.js

不是很理解这个想法,为什么不用多出 content.js 和 inject.js,难道把 GM、沙盒之类的逻辑也放到 userscript 中?

另外注入的脚本可能是在页面环境中的(inject),也还是要由 content/scripting 转发消息给 service_worker

难道把 GM、沙盒之类的逻辑也放到 userscript 中?

对。
userscript 独自能跑
要用 GM API 时,scripting 没准备好的话,可以延迟发消息, scripting 准备好後再交由 scripting 发给 service_worker
(用 dispatchEvent + preventDefault 可以知道 scripting 是否有监听。没有的话 scripting 监听后再发出)
(虽然实际上 scripting 执行应总是优先于 userScripts )

这样就不用等 content.js 和 inject.js 的载入
直接自行做沙盒环境跑
( userscript API 所有都要使用 document-start 来建立沙盒。如果是 document-end 那些就等 body载入好跑 window['flag']

另外注入的脚本可能是在页面环境中的(inject),也还是要由 content/scripting 转发消息给 service_worker

对。转发消息用 dispatchEvent 根据 messageFlag 给 scripting
然后 scripting 再转发消息给 service_worker


脚本环境是 userScript API 的 main / content_script (没 chrome.storage)
转发消息的环境 是 scripting API 的 isolated ( 能跑 extension API. 包括 chrome.storage.local 存取)

@CodFrm
Copy link
Member

CodFrm commented Dec 9, 2025

那这样可能需要打包出一个 模板,service worker读取这个模板,然后将脚本代码放进去,说实话,不是很必要,这样有多少个脚本就要执行多少次初始化代码

TM 的 UserScripts API Dynamic 模式也是有 inject 和 content的

@CodFrm
Copy link
Member

CodFrm commented Dec 9, 2025

我进行了一次测试,storage的总耗时更长,可能因为有IO操作,实际的资源消耗在io操作上,但是接收端接受消息的时间更短


计时有误,storage的耗时都比 chrome.runtime.sendMessage 短,tab越多,差距越大,cpu用的performance.now,忽略掉这个,感觉也不好评估

image

@cyfung1031
Copy link
Collaborator Author

cyfung1031 commented Dec 9, 2025

我进行了一次测试,storage的总耗时更长,可能因为有IO操作,实际的资源消耗在io操作上,但是接收端接受消息的时间更短

计时有误,storage的耗时都比 chrome.runtime.sendMessage 短,tab越多,差距越大,cpu用的performance.now,忽略掉这个,感觉也不好评估

image

没代码 不太清楚实际测试方式

不过建议是,background 那边发一个消息,包含 timestamp
然后在接收那边计一下时间差,最后把时间差加起来做一个指标吧 ( $\sum_{i} (T_{r,i} - T_s)$ )
(尽量贴近后台 background 单线程 跟 多个前台执行环境的情况)
(tabs 那个如果有包含 tabs.query 就更好)

@CodFrm
Copy link
Member

CodFrm commented Dec 9, 2025

代码:归档.zip 里面的时间是消息到达时间

storage的耗时会短一些,但是cpu消耗这个不好评估

@CodFrm
Copy link
Member

CodFrm commented Dec 9, 2025

chrome.tab.query 耗时 1ms 左右,这个差距并不是很大 2x 的样子,但是实际情况会更复杂一些,valueUpdate并不算是广播,会根据实际情况去推送具体的tab,广播所有的tab都会去检查valueUpdate也是一个额外的消耗

我还是比较想保留原来的逻辑,没啥说服力,自己都说服不太了😅,chrome.storage 也可以接受吧

@cyfung1031
Copy link
Collaborator Author

cyfung1031 commented Dec 9, 2025

sc1067-test-code-v2.zip
sc1067-test-code-v3.zip

你拿这个跑跑看结果~
CPU时间那个,不用理会返回结果。 ScriptCat 也只是发出不理返回
预设跑100次。不然不够准
每次跑完后,后台部份要停一下 (20ms),不然一直堆,content_script 那边的处理追不上

看 SW 执行时间 和 latency 就好

例如我这边,23个 tabs ( 其他是未载入的)
结果是这样的
Screenshot 2025-12-10 at 1 45 18

@CodFrm
Copy link
Member

CodFrm commented Dec 10, 2025

image

不过我发现这种方式,页面中会有两个content,实际资源占用情况,我觉得还是不太好说

@cyfung1031
Copy link
Collaborator Author

不过我发现这种方式,页面中会有两个content,实际资源占用情况,我觉得还是不太好说

所以我才說, 把wrapper的部份直接塞到代碼腳本就好
反正沙盒環境也是獨立,互不相干,也是要獨立生成
而且沙盒環境跟inject/content本來就要一致

對話的部份是沒辨法,一定要有scripting /content script

但動態代碼都是userscripts api那邊搞
對話部份用靜態的就可以。直接scripting做


不過這些更進一步的改動可以之後處理
不用一步登天

@CodFrm
Copy link
Member

CodFrm commented Dec 10, 2025

所以我才說, 把wrapper的部份直接塞到代碼腳本就好 反正沙盒環境也是獨立,互不相干,也是要獨立生成 而且沙盒環境跟inject/content本來就要一致

對話的部份是沒辨法,一定要有scripting /content script

但動態代碼都是userscripts api那邊搞 對話部份用靜態的就可以。直接scripting做

不過這些更進一步的改動可以之後處理 不用一步登天

wrapper赛到脚本代码里面,那么每个脚本都要去处理沙盒和一些共用的东西,这样消耗的更多,始终还是要 inject 和 content 的,而且就算是塞进去,那也是另外一种概念上的 inject 和 content,你的脚本始终是要在这两个环境中运行的,不要考虑这个了

不考虑firefox的情况下,我感觉这个scripting还是意义不大,还是不太想引入scripting加大复杂度,光看性能测试,差距不算很大,而且还额外加入了一个页面的消耗,实际情况也不是会去给每个tab发的

或者只在firefox的环境下使用这种模式,chrome使用老的模式

@CodFrm
Copy link
Member

CodFrm commented Dec 15, 2025

我看到了你在mozilla的提问了,本来想等他们回复再决定加不加scripting的,但是没信了?

还是不太想加入scripting,有额外的消耗,且加大了代码和维护的复杂度,至于考虑Firefox的兼容问题,我觉得再等一段时间看看吧,Firefox的mv3缺少好多东西

@cyfung1031
Copy link
Collaborator Author

我看到了你在mozilla的提问了,本来想等他们回复再决定加不加scripting的,但是没信了?

还是不太想加入scripting,有额外的消耗,且加大了代码和维护的复杂度,至于考虑Firefox的兼容问题,我觉得再等一段时间看看吧,Firefox的mv3缺少好多东西

还好吧。用 scripting 解决了, Firefox MV3 版就大致完成了
如果要用 chrome.storage.onChanged.addListener, 还是要 scripting

这个PR的scripting 是在异步协助跟service_worker的沟通
不会加大了代码和维护的复杂度 (你喜欢的还可以搞个什么 scriptingServer scriptingClient )
有额外的消耗是没错。但为了不用在 service_worker 用 tabs.sendMessage 每次valueUpdate 来来回回跑,我觉得是值得
chrome.storage.onChanged.addListener 的好处是,即使资讯量大&频密,浏览器自行分配资源处理,这样就不会影响前台的操作
相反 tabs.sendMessage 的优先度高,在多分页多脚本的操作下, service_worker 不断发消息的话,开支更大,影响前台(UI)的操作。

userScriptAPI 的 content 环境肯定是有限制
那么GM的API呀,跟后台相关的部份,就让 scripting 处理吧。
scripting 弹性大,直接能操作 storage, 日后也许不需要什么都发到 service_worker, scripting 处理也可以

@CodFrm
Copy link
Member

CodFrm commented Dec 17, 2025

我看到了你在mozilla的提问了,本来想等他们回复再决定加不加scripting的,但是没信了?
还是不太想加入scripting,有额外的消耗,且加大了代码和维护的复杂度,至于考虑Firefox的兼容问题,我觉得再等一段时间看看吧,Firefox的mv3缺少好多东西

还好吧。用 scripting 解决了, Firefox MV3 版就大致完成了 如果要用 chrome.storage.onChanged.addListener, 还是要 scripting

这个PR的scripting 是在异步协助跟service_worker的沟通 不会加大了代码和维护的复杂度 (你喜欢的还可以搞个什么 scriptingServer scriptingClient ) 有额外的消耗是没错。但为了不用在 service_worker 用 tabs.sendMessage 每次valueUpdate 来来回回跑,我觉得是值得 chrome.storage.onChanged.addListener 的好处是,即使资讯量大&频密,浏览器自行分配资源处理,这样就不会影响前台的操作 相反 tabs.sendMessage 的优先度高,在多分页多脚本的操作下, service_worker 不断发消息的话,开支更大,影响前台(UI)的操作。

userScriptAPI 的 content 环境肯定是有限制 那么GM的API呀,跟后台相关的部份,就让 scripting 处理吧。 scripting 弹性大,直接能操作 storage, 日后也许不需要什么都发到 service_worker, scripting 处理也可以

勉强可以接受,但是我一直想等firefox的回复,如果支持了在 userScripts 中使用 onMessage,我会毫不犹豫的砍掉 scripting 来保证架构简单

@cyfung1031
Copy link
Collaborator Author

勉强可以接受,但是我一直想等firefox的回复,如果支持了在 userScripts 中使用 onMessage,我会毫不犹豫的砍掉 scripting 来保证架构简单

https://discourse.mozilla.org/t/is-firefox-mv3-intentionally-not-supporting-browser-runtime-onmessage-in-mv3-userscripts-api-why/146671/6

To answer the original question, yes, it looks like Firefox is intentionally not supporting browser.runtime.onMesssage. As @rob mentioned in this comment, Firefox collaboratively designed the User Scripts API with other browser vendors in the WebExtensions Community Group (WECG), and the design they created did not expose the runtime.onMesssage event to user scripts. When I spoke with a Firefox add-one engineer about this a bit ago, they confirmed that that’s the design Firefox implemented and intends to continue following.

Personally, I’m aligned with Firefox’s approach and I think Chrome’s implementation is a bug. I intend to final an issue on their repo to lay out my concerns. As I see it, the problem is that Chrome’s current implementation prevents an extension’s content scripts and background script from exchanging messages without exposing them to users scripts that have messaging enabled. This presents a material problem for extensions because user scripts are a less privileged context than content scripts and therefore shouldn’t have access to privileged communications between extension contexts. If we (the broader Web Extension community) want to allow extension contexts to send one-off messages to user scripts, I think extensions APIs should be expanded to explicitly provide that capability.

因此要支持 Firefox MV3, 需要 scripting 做转发。

@cyfung1031
Copy link
Collaborator Author

https://discourse.mozilla.org/t/is-firefox-mv3-intentionally-not-supporting-browser-runtime-onmessage-in-mv3-userscripts-api-why/146671/7

User scripts can’t receive messages directly from the background script. Instead, you’d need to set up a content script that receives messages from the background and passes them on to user scripts. If I were implementing this, I’d probably exchange messages between the content script and user script via a CustomEvent with a unique event name for each user script or for each user script world.

@cyfung1031
Copy link
Collaborator Author

先整理一下代碼

@cyfung1031 cyfung1031 marked this pull request as draft December 30, 2025 05:26
@cyfung1031 cyfung1031 force-pushed the develop/messaging-performance-boost2 branch from 878ac1a to 2173bdd Compare December 31, 2025 22:57
@cyfung1031
Copy link
Collaborator Author

可以重新 review 了

2026-01-01

  • 对齐了 inject.ts 跟 content.ts 的代码
  • 修正了现时 BETA 1.3 @inject-into content 无法正常执行问题
  • 修正了 CAT_fetchDocument 无法在 @inject-into content 正常执行问题
  • 改用另外的方法生成 MessageFlag - 同步,独立,不可追踪,总是不同
  • 修正了 pkg/utils/uuid 引用

@cyfung1031 cyfung1031 marked this pull request as ready for review January 1, 2026 00:19
@cyfung1031 cyfung1031 changed the title [v1.3?] 以类似broadcast机制重构通讯机制 [v1.3] 重构通讯机制 - storage.local broadcast广播、符合 FF MV3 的 scripting 设计、采用不可追踪不断变动的同步 MessageFlag Jan 1, 2026
@cyfung1031 cyfung1031 changed the title [v1.3] 重构通讯机制 - storage.local broadcast广播、符合 FF MV3 的 scripting 设计、采用不可追踪不断变动的同步 MessageFlag [v1.3] 重构通讯机制 - storage.local 广播、符合 FF MV3 的 scripting 设计、采用不可追踪不断变动的同步 MessageFlag Jan 1, 2026
Copy link
Contributor

Copilot AI left a 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 实施了一次重大的通讯机制重构,主要目的是支持 Firefox MV3 并优化性能。

Changes:

  • 引入新的 scripting.ts 作为通信中间层,使用 chrome.storage.local 广播机制替代 tabs.sendMessage
  • 采用动态 MessageFlag(基于 performance.timeOrigin 和编译时随机密钥生成),提高安全性和唯一性
  • 简化 inject.ts 和 content.ts,移除复杂的类层次结构,改用函数式设计

Reviewed changes

Copilot reviewed 32 out of 33 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
src/scripting.ts 新增 scripting 层,作为 service_worker 和 inject/content 之间的桥梁
src/inject.ts 完全重写,移除 InjectRuntime 类,改用函数式设计并直接处理握手
src/content.ts 完全重写,移除 ContentRuntime 类,简化为直接消息处理
src/message-delivery.ts 新增 MessageDelivery 类,用于缓冲和分发消息
packages/message/custom_event_message.ts 重构为使用 PageMessaging 对象,支持动态 token 绑定
packages/message/common.ts 新增公共工具函数,提取 pageDispatchEvent 等避免页面篡改
src/app/service/service_worker/value.ts 改用 chrome.storage.local 广播 valueUpdate,移除 tabs.query 循环
src/app/service/service_worker/runtime.ts 调整脚本注册逻辑,移除 Firefox 特殊处理,统一使用 scripting.js
rspack.config.ts 添加 SC_RANDOM_KEY 编译时常量,用于 MessageFlag 生成
其他文件 更新 uuid 导入路径,调整测试用例以适配新架构

function createScriptcatExpose(pageMsg: Message) {
const scriptExpose: App.ExternalScriptCat = {
isInstalled(name: string, namespace: string, callback: (res: App.IsInstalledResponse | undefined) => unknown) {
sendMessage<App.IsInstalledResponse>(pageMsg, "scripting/script/isInstalled", { name, namespace }).then(callback);
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

sendMessage 返回的 Promise 如果被 reject(例如通信失败),callback 将不会被调用,这可能导致调用方一直等待回调。建议添加 .catch() 处理,在发生错误时也调用 callback,传递 undefined 或错误信息。

Suggested change
sendMessage<App.IsInstalledResponse>(pageMsg, "scripting/script/isInstalled", { name, namespace }).then(callback);
sendMessage<App.IsInstalledResponse>(pageMsg, "scripting/script/isInstalled", { name, namespace })
.then(callback)
.catch(() => callback(undefined));

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

应该不用吧。这个改动跟PR没直接关系。有需要可另外处理

Comment on lines +81 to +83
chrome.storage.local.set({
valueUpdateDelivery: {
rId: `${Date.now()}.${Math.random()}`,
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

变量 rId 使用 Date.now()Math.random() 组合生成唯一标识,但这种方式在高并发场景下可能产生重复值(虽然概率极低)。建议改用 uuidv4() 以确保唯一性,与项目其他地方的ID生成方式保持一致。例如:rId: uuidv4()

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Date.now() 和 Math.random() 的组合就够了
uuidv4() 速度比较慢,不需要这么搞

Comment on lines +133 to +134
const node = parentNode || document.head || document.body || document.querySelector("*");
node.appendChild(el);
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

GM_addElement 中,如果 document.querySelector("*") 返回 null(例如在空文档中),则 node.appendChild(el) 将抛出异常。虽然这种情况极为罕见,但建议增加空值检查或至少添加 try-catch 处理,以避免整个 scripting 环境崩溃。

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ScriptCat 作者假設總是有document.documentElement 嘛

Comment on lines 194 to 230
// readyFlag 位运算:inject=1,content=2,凑齐 3 表示都 ready. ready 后设为 4 避免再触发
let readyFlag = 0;

const finalizeWhenReady = () => {
if (readyFlag === 3) {
readyFlag = 4;

// 统一设置 token
scriptingMessaging.et = injectFlagEvt;
scriptExecutorMsgIT.et = `${injectFlagEvt}_${ScriptEnvTag.inject}`;
scriptExecutorMsgCT.et = `${injectFlagEvt}_${ScriptEnvTag.content}`;

// 绑定 receiver(允许 inject/content 发消息给 scripting)
scriptExecutorMsgTxIT.bindReceiver();
scriptExecutorMsgTxCT.bindReceiver();

// 建立 server:inject/content -> scripting 通道
const server = new Server("scripting", [scriptExecutorMsgTxIT, scriptExecutorMsgTxCT]);
prepareServer(server, senderToExt, scriptExecutorMsgTxIT, scriptExecutorMsgTxCT);

// 建立向页面投递消息的 delivery 通道
setupDeliveryChannel();
}
};

// 接收 inject/content 的 ready 回执
pageAddEventListener(`${injectFlagEvt}`, (ev) => {
if (!(ev instanceof CustomEvent)) return;

const key = `emitterKeyFor${injectFlagEvt}`;
let value = ev.detail?.[key];
if (!value) return;

if (value !== ScriptEnvType.content) value = ScriptEnvType.inject; // 使 value 必定为 1 或 2
readyFlag |= value;
finalizeWhenReady();
});
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

握手过程中,如果只有 inject 或只有 content 一方响应,readyFlag 将永远无法达到 3,导致 finalizeWhenReady 永远不会执行,scripting 通道无法建立。建议添加超时机制,在一定时间后即使未收到全部响应也继续初始化,或者根据页面类型(是否为 userScripts 环境)判断是否需要等待 content 响应。

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

如果只有一方响应,整个ScriptCat设计就跑不动啦
不需要加超时强制触发 readyFlag 3
如果跑不动,自然会有Bug Report

Comment on lines 80 to 111
// const { storageName } = sendData;
chrome.storage.local.set({
valueUpdateDelivery: {
rId: `${Date.now()}.${Math.random()}`,
sendData,
},
});
// chrome.tabs.query({}, (tabs) => {
// const lastError = chrome.runtime.lastError;
// if (lastError) {
// console.error("chrome.runtime.lastError in chrome.tabs.query:", lastError);
// // 没有 tabs 资讯,无法发推送到 tabs
// return;
// }
// // 推送到所有加载了本脚本的tab中
// for (const tab of tabs) {
// const tabId = tab.id!;
// this.popup!.getScriptMenu(tabId).then((scriptMenu) => {
// if (scriptMenu.find((item) => item.storageName === storageName)) {
// this.runtime!.sendMessageToTab(
// {
// tabId,
// },
// "valueUpdate",
// sendData
// );
// }
// });
// }
// });
// 推送到offscreen中
this.runtime!.sendMessageToTab(
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

使用 chrome.storage.local 广播 valueUpdate 时,每次更新都会触发所有 tabs 的 onChanged 监听器,即使某些 tabs 不包含该脚本。这可能导致性能问题,尤其是在有大量 tabs 时。虽然注释提到"注意资讯量不要过大",但建议考虑添加额外的过滤机制(例如基于 storageName 的白名单),或者在注释中明确说明这种设计权衡的原因。

Suggested change
// const { storageName } = sendData;
chrome.storage.local.set({
valueUpdateDelivery: {
rId: `${Date.now()}.${Math.random()}`,
sendData,
},
});
// chrome.tabs.query({}, (tabs) => {
// const lastError = chrome.runtime.lastError;
// if (lastError) {
// console.error("chrome.runtime.lastError in chrome.tabs.query:", lastError);
// // 没有 tabs 资讯,无法发推送到 tabs
// return;
// }
// // 推送到所有加载了本脚本的tab中
// for (const tab of tabs) {
// const tabId = tab.id!;
// this.popup!.getScriptMenu(tabId).then((scriptMenu) => {
// if (scriptMenu.find((item) => item.storageName === storageName)) {
// this.runtime!.sendMessageToTab(
// {
// tabId,
// },
// "valueUpdate",
// sendData
// );
// }
// });
// }
// });
// 推送到offscreen中
this.runtime!.sendMessageToTab(
// 为避免使用 chrome.storage.local 广播导致所有 tabs 的 storage.onChanged 被触发,
// 这里通过 chrome.tabs.query + runtime.sendMessageToTab 仅向实际加载了对应脚本的 tab 推送。
const { storageName } = sendData;
chrome.tabs.query({}, (tabs) => {
const lastError = chrome.runtime.lastError;
if (lastError) {
// 没有 tabs 资讯或查询失败,无法发推送到 tabs
this.logger.error("chrome.tabs.query error in pushValueToTab", lastError);
return;
}
// 推送到所有加载了本脚本的 tab 中
for (const tab of tabs) {
const tabId = tab.id;
if (tabId == null) {
continue;
}
this.popup
?.getScriptMenu(tabId)
.then((scriptMenu) => {
if (!scriptMenu) {
return;
}
if (scriptMenu.find((item) => item.storageName === storageName)) {
this.runtime?.sendMessageToTab(
{
tabId,
},
"valueUpdate",
sendData
);
}
})
.catch((err) => {
this.logger.error("getScriptMenu error in pushValueToTab", err);
});
}
});
// 推送到 offscreen 中
this.runtime?.sendMessageToTab(

Copilot uses AI. Check for mistakes.
Comment on lines +258 to +271
chrome.storage.local.onChanged.addListener((changes) => {
if (changes["localStorage:scriptInjectMessageFlag"]?.newValue) {
messageDeliveryToPage.dispatch({
tag: "localStorage:scriptInjectMessageFlag",
value: changes["localStorage:scriptInjectMessageFlag"]?.newValue,
});
}
if (changes["valueUpdateDelivery"]?.newValue) {
messageDeliveryToPage.dispatch({
tag: "valueUpdateDelivery",
value: changes["valueUpdateDelivery"]?.newValue,
});
}
});
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

chrome.storage.local.onChanged 监听器缺少错误处理。如果 messageDeliveryToPage.dispatch 抛出异常,将导致后续的 storage 变更无法被处理。建议添加 try-catch 包裹 dispatch 调用,或者在 MessageDelivery.dispatch 内部处理异常。

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

这个不需要考虑处理异常

chrome.userScripts?.unregister(),
chrome.scripting.unregisterContentScripts(),
this.localStorageDAO.save({ key: "scriptInjectMessageFlag", value: runtimeGlobal.messageFlag }),
chrome.storage.session.set({ unregisterUserscriptsFlag: `${Date.now()}.${Math.random()}` }),
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

同样的问题:使用 Date.now()Math.random() 组合生成 flag。建议改用 uuidv4() 以确保唯一性和一致性。

Suggested change
chrome.storage.session.set({ unregisterUserscriptsFlag: `${Date.now()}.${Math.random()}` }),
chrome.storage.session.set({ unregisterUserscriptsFlag: uuidv4() }),

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Date.now() 和 Math.random() 的组合就够了
uuidv4() 速度比较慢,不需要这么搞

@CodFrm
Copy link
Member

CodFrm commented Jan 19, 2026

想重构一下,每次来都没review完整

@cyfung1031
Copy link
Collaborator Author

想重构一下,每次来都没review完整

? 什麼意思?
你指copilot的review?

@CodFrm
Copy link
Member

CodFrm commented Jan 20, 2026

想重构一下,每次来都没review完整

? 什麼意思? 你指copilot的review?

太难阅读了,每次都花费很多时间,看到后面越来越没有耐心 😧

@cyfung1031
Copy link
Collaborator Author

想重构一下,每次来都没review完整

? 什麼意思? 你指copilot的review?

太难阅读了,每次都花费很多时间,看到后面越来越没有耐心 😧

新年放假才看吧
这个是重构,没什么能拆细。没办法

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.

3 participants