[v1.3] userScripts / scripting API 调整,增强兼容性 ( 重做 #704 )#925
[v1.3] userScripts / scripting API 调整,增强兼容性 ( 重做 #704 )#925CodFrm merged 4 commits intoscriptscat:release/v1.3from
Conversation
96352e8 to
deea0bc
Compare
4f9723b to
01bb9fa
Compare
There was a problem hiding this comment.
Pull request overview
此 PR 重做了 #704 的修改,解决了 userScripts 和 scripting API 的兼容性问题。主要目标是适配 Firefox 和 Chrome 在脚本注入机制上的差异:Firefox 不支持动态代码注入,需要通过 URL 参数传递 MessageFlag;而 Chrome 不支持 URL 查询参数,继续使用 userScripts API 的代码注入方式。
- 引入
getUspMessageFlag()函数从错误堆栈中提取 URL 参数 - 重构脚本注册逻辑,区分 Firefox(scripting API)和 Chrome(userScripts API)两种注册方式
- 更新 ID 检查从 "scriptcat-content" 改为 "scriptcat-inject"
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 12 comments.
| File | Description |
|---|---|
src/pkg/utils/utils.ts |
新增 getUspMessageFlag() 函数用于从堆栈提取 URL 参数;更新 checkUserScriptsAvailable() 的 ID 检查 |
src/content.ts |
更新 MessageFlag 获取逻辑,支持从 arguments 或 URL 参数中获取;改进错误处理和注释说明 |
src/app/service/service_worker/runtime.ts |
重构 getContentAndInjectScript() 返回分离的 content 和 inject 脚本列表;Firefox 使用 scripting API 注册 content.js(URL参数),Chrome 使用 userScripts API(代码注入);更新注册和检查逻辑;移除 compileInjectUserScript() 方法 |
| // getContentAndInjectScript依赖loadScriptMatchInfo | ||
| // 需要等getParticularScriptList完成后再执行 | ||
| const generalScriptList = await this.getContentAndInjectScript(options); | ||
| const { inject: injectScripList, content: contentScriptList } = await this.getContentAndInjectScript(options); |
There was a problem hiding this comment.
变量名拼写错误(与第920行相同):injectScripList 应该是 injectScriptList(缺少字母 't')。
| } | ||
| const retScript: chrome.userScripts.RegisteredUserScript[] = []; | ||
| const contentJs = await this.getContentJsCode(); | ||
| if (contentJs) { | ||
| const codeBody = `(function (MessageFlag) {\n${contentJs}\n})('${messageFlag}')`; | ||
| const code = `${codeBody}${sourceMapTo("scriptcat-content.js")}\n`; | ||
| retScript.push({ | ||
| id: "scriptcat-content", | ||
| js: [{ code }], | ||
| matches: ["<all_urls>"], | ||
| allFrames: true, | ||
| runAt: "document_start", | ||
| world: "USER_SCRIPT", | ||
| excludeMatches, | ||
| excludeGlobs, | ||
| }); | ||
| } | ||
|
|
There was a problem hiding this comment.
返回类型的命名不够清晰:函数返回对象的键名 content 和 inject 没有明确表明它们分别对应不同的 API。
考虑到:
content仅在 Firefox 中使用,通过scripting.RegisteredContentScriptinject在两个浏览器中使用,通过userScripts.RegisteredUserScript
建议改进返回对象的结构或添加 JSDoc 注释,明确说明:
content: 用于 Firefox 的 scripting API 注册的脚本列表inject: 用于 userScripts API 注册的脚本列表(包括 inject.js 和 Chrome 下的 content.js)
这样可以提高代码的可读性和可维护性。
| @@ -869,8 +894,8 @@ export class RuntimeService { | |||
| if (runtimeGlobal.registered) { | |||
| // 异常情况 | |||
| // 检查scriptcat-content和scriptcat-inject是否存在 | |||
There was a problem hiding this comment.
注释与代码不一致:注释提到"检查scriptcat-content和scriptcat-inject是否存在",但代码第895行只检查了 "scriptcat-inject"。
由于新架构中:
- Firefox: scriptcat-content 通过 scripting API 注册(不在 userScripts 中)
- Chrome: scriptcat-content 通过 userScripts API 注册(在 userScripts 中)
建议更新注释以准确反映实际检查的内容,例如:"检查scriptcat-inject是否存在"
| // 检查scriptcat-content和scriptcat-inject是否存在 | |
| // 检查scriptcat-inject是否存在 | |
| // 说明:scriptcat-content 在 Firefox 通过 scripting API 注册(不在 userScripts 中), | |
| // 在 Chrome 通过 userScripts API 注册(在 userScripts 中),此处仅检查 scriptcat-inject 是否存在 |
| export const getUspMessageFlag = () => { | ||
| const s = new Error().stack; | ||
| if (s) { | ||
| const search1 = "content.js?usp_flag="; | ||
| const len1 = search1.length; | ||
| const idx1 = s.indexOf(search1); | ||
| if (idx1 > 0) { | ||
| const search2 = "&usp_end"; | ||
| const idx2 = s.indexOf(search2, idx1 + len1); | ||
| if (idx2 > 0) { | ||
| const param = s.substring(idx1 + len1, idx2); | ||
| try { | ||
| // 使用 URLSearchParams 避免字符编码问题 | ||
| const uspString = `usp_flag=${param}`; | ||
| const usp = new URLSearchParams(uspString); | ||
| if (usp.size === 1) return usp.get("usp_flag") || null; | ||
| } catch (e) { | ||
| console.error(e); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return null; | ||
| }; |
There was a problem hiding this comment.
新增的 getUspMessageFlag() 函数缺少测试覆盖。该函数通过解析错误堆栈提取 URL 参数,属于核心功能逻辑,应该有测试确保:
- 能够正确从堆栈中提取
usp_flag参数 - 正确处理 URLSearchParams 解码
- 对于无效或缺失的参数返回
null - 正确处理错误情况
建议在 src/pkg/utils/utils.test.ts 中添加相应的测试用例。
| retContent = [ | ||
| { | ||
| id: "scriptcat-content", | ||
| js: [`/src/content.js?${new URLSearchParams({ usp_flag: messageFlag })}&usp_end`], |
There was a problem hiding this comment.
URL 构建方式不够清晰:使用字符串拼接 ?${...}&usp_end 的方式可能让人混淆 &usp_end 的意图。
虽然 &usp_end 是一个用于标记参数结束位置的标识符(用于从堆栈字符串中提取),但这种直接拼接的方式不够直观。建议:
- 添加注释说明
&usp_end的作用 - 或者使用更清晰的方式构建,例如:
js: [`/src/content.js?${new URLSearchParams({ usp_flag: messageFlag, usp_end: '' })}`]这样可以让代码意图更明确,便于后续维护。
| js: [`/src/content.js?${new URLSearchParams({ usp_flag: messageFlag })}&usp_end`], | |
| // 使用 URLSearchParams 构建 usp_flag 和 usp_end 参数,避免字符串拼接歧义 | |
| js: [`/src/content.js?${new URLSearchParams({ usp_flag: messageFlag, usp_end: "" })}`], |
| if (res.length === 1) { | ||
| return; | ||
| } | ||
| // scriptcat-content/scriptcat-inject不存在的情况 |
There was a problem hiding this comment.
注释不准确:注释提到 "scriptcat-content/scriptcat-inject不存在的情况",但代码实际上只检查了 scriptcat-inject 的存在性(第895行)。
建议更新注释为:"scriptcat-inject不存在的情况",以准确反映实际逻辑。
| // scriptcat-content/scriptcat-inject不存在的情况 | |
| // scriptcat-inject不存在的情况 |
| const search1 = "content.js?usp_flag="; | ||
| const len1 = search1.length; | ||
| const idx1 = s.indexOf(search1); | ||
| if (idx1 > 0) { |
There was a problem hiding this comment.
条件检查 idx1 > 0 应该使用 idx1 >= 0。
String.indexOf() 在找到匹配时返回索引位置(可能为0),找不到时返回-1。当搜索字符串在位置0时,当前条件会错过该匹配。虽然在实际场景中 "content.js?usp_flag=" 不太可能出现在堆栈字符串的开头(位置0),但使用 >= 0 更符合 indexOf 的标准用法,提高代码的正确性和可读性。
| if (idx1 > 0) { | |
| if (idx1 >= 0) { |
| if (idx1 > 0) { | ||
| const search2 = "&usp_end"; | ||
| const idx2 = s.indexOf(search2, idx1 + len1); | ||
| if (idx2 > 0) { |
There was a problem hiding this comment.
条件检查 idx2 > 0 应该使用 idx2 > idx1 + len1。
当前条件只检查 idx2 > 0,这意味着如果 "&usp_end" 出现在 "content.js?usp_flag=" 之前(虽然不太可能),代码仍会尝试提取参数,导致 substring(idx1 + len1, idx2) 返回空字符串或负索引。更正确的检查应该是 idx2 > idx1 + len1,确保 "&usp_end" 在 "content.js?usp_flag=" 之后。
| if (idx2 > 0) { | |
| if (idx2 > idx1 + len1) { |
|
|
||
| /* global MessageFlag */ | ||
| // @ts-ignore | ||
| const MessageFlag: string | null = (typeof arguments === "object" && arguments?.[0]) || getUspMessageFlag(); |
There was a problem hiding this comment.
类型检查不够严谨:条件 typeof arguments === "object" 应该使用 typeof arguments === "object" && arguments !== null。
虽然 arguments 在实际运行中不太可能为 null,但 typeof null === "object" 在 JavaScript 中为 true,这是一个已知的语言特性。建议添加 null 检查以确保代码的健壮性和准确性。
| const MessageFlag: string | null = (typeof arguments === "object" && arguments?.[0]) || getUspMessageFlag(); | |
| const MessageFlag: string | null = (typeof arguments === "object" && arguments !== null && arguments?.[0]) || getUspMessageFlag(); |
概述 Descriptions
chrome.scripting.registerContentScripts#704之前
@early-start的代码修改,导致 #704 的修改被倒回去了现在重做 #704
历史原因,
scripting.RegisteredContentScript的设计不是用来做 自订脚本 用途,不支持动态代码。它只支持
url网址,,而不支持code代码这个PR做了在网址取 flags 的处理,因此不需要 code
但相反 chrome 不支持网址参数,因此还是用 userScripts API 做注入
已测试过能在 Firefox & Chrome 顺利执行
变更内容 Changes
截图 Screenshots