Skip to content

Commit 96352e8

Browse files
committed
userScripts / scripting API 调整,增强兼容性 ( #704 )
1 parent 2769a24 commit 96352e8

File tree

3 files changed

+80
-62
lines changed

3 files changed

+80
-62
lines changed

src/app/service/service_worker/runtime.ts

Lines changed: 37 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ const runtimeGlobal = {
5353
messageFlag: "PENDING",
5454
scriptLoadComplete: "PENDING",
5555
envLoadComplete: "PENDING",
56-
} as MessageFlags,
56+
} satisfies MessageFlags & Record<string, string>,
5757
};
5858

5959
export class RuntimeService {
@@ -211,18 +211,6 @@ export class RuntimeService {
211211
return this.injectJsCodePromise;
212212
}
213213

214-
async getContentJsCode() {
215-
if (!this.contentJsCodePromise) {
216-
this.contentJsCodePromise = fetch("/src/content.js")
217-
.then((res) => res.text())
218-
.catch((e) => {
219-
console.error("Unable to fetch /src/content.js", e);
220-
return undefined;
221-
});
222-
}
223-
return this.contentJsCodePromise;
224-
}
225-
226214
createMatchInfoEntry(
227215
scriptRes: ScriptRunResource,
228216
o: { scriptUrlPatterns: URLRuleEntry[]; originalUrlPatterns: URLRuleEntry[] | null }
@@ -305,8 +293,8 @@ export class RuntimeService {
305293

306294
let registered = false;
307295
try {
308-
const res = await chrome.userScripts.getScripts({ ids: ["scriptcat-content", "scriptcat-inject"] });
309-
registered = res.length === 2;
296+
const res = await chrome.userScripts.getScripts({ ids: ["scriptcat-inject"] });
297+
registered = res.length === 1;
310298
} finally {
311299
// 考虑 UserScripts API 不可使用等情况
312300
runtimeGlobal.registered = registered;
@@ -608,6 +596,7 @@ export class RuntimeService {
608596
runtimeGlobal.messageFlags = this.generateMessageFlags();
609597
await Promise.allSettled([
610598
chrome.userScripts.unregister(),
599+
chrome.scripting.unregisterContentScripts(),
611600
this.localStorageDAO.save({ key: "scriptInjectMessageFlags", value: runtimeGlobal.messageFlags }),
612601
]);
613602
}
@@ -781,32 +770,41 @@ export class RuntimeService {
781770
// do nothing
782771
}
783772
}
784-
const retScript: chrome.userScripts.RegisteredUserScript[] = [];
785-
const contentJs = await this.getContentJsCode();
786-
if (contentJs) {
787-
retScript.push({
773+
let retContent: chrome.scripting.RegisteredContentScript[] = [];
774+
let retInject: chrome.userScripts.RegisteredUserScript[] = [];
775+
// Note: Chrome does not support file.js?query
776+
// 注意:Chrome 不支持 file.js?query
777+
retContent = [
778+
{
788779
id: "scriptcat-content",
789-
js: [{ code: `(function (MessageFlags) {\n${contentJs}\n})(${JSON.stringify(messageFlags)})` }],
780+
js: [`/src/content.js#FlagsStart&${`${new URLSearchParams(messageFlags)}`}&FlagsEnd`],
790781
matches: ["<all_urls>"],
791782
allFrames: true,
792783
runAt: "document_start",
793-
world: "USER_SCRIPT",
794784
excludeMatches,
795-
excludeGlobs,
796-
});
797-
}
785+
} satisfies chrome.scripting.RegisteredContentScript,
786+
];
798787

799788
// inject.js
800789
const injectJs = await this.getInjectJsCode();
801790
if (injectJs) {
802-
const apiScripts = this.compileInjectUserScript(injectJs, messageFlags, {
803-
excludeMatches,
804-
excludeGlobs,
805-
});
806-
retScript.push(...apiScripts);
791+
// 构建inject.js的脚本注册信息
792+
const code = `(function (MessageFlags) {\n${injectJs}\n})(${JSON.stringify(messageFlags)})`;
793+
retInject = [
794+
{
795+
id: "scriptcat-inject",
796+
js: [{ code }],
797+
matches: ["<all_urls>"],
798+
allFrames: true,
799+
world: "MAIN",
800+
runAt: "document_start",
801+
excludeMatches: excludeMatches,
802+
excludeGlobs: excludeGlobs,
803+
} satisfies chrome.userScripts.RegisteredUserScript,
804+
];
807805
}
808806

809-
return retScript;
807+
return { content: retContent, inject: retInject };
810808
}
811809

812810
// 如果是重复注册,需要先调用 unregisterUserscripts
@@ -818,8 +816,8 @@ export class RuntimeService {
818816
if (runtimeGlobal.registered) {
819817
// 异常情况
820818
// 检查scriptcat-content和scriptcat-inject是否存在
821-
const res = await chrome.userScripts.getScripts({ ids: ["scriptcat-content", "scriptcat-inject"] });
822-
if (res.length === 2) {
819+
const res = await chrome.userScripts.getScripts({ ids: ["scriptcat-inject"] });
820+
if (res.length === 1) {
823821
return;
824822
}
825823
// scriptcat-content/scriptcat-inject不存在的情况
@@ -843,9 +841,9 @@ export class RuntimeService {
843841
const particularScriptList = await this.getParticularScriptList(options);
844842
// getContentAndInjectScript依赖loadScriptMatchInfo
845843
// 需要等getParticularScriptList完成后再执行
846-
const generalScriptList = await this.getContentAndInjectScript(options);
844+
const { inject: injectScripList, content: contentScriptList } = await this.getContentAndInjectScript(options);
847845

848-
const list: chrome.userScripts.RegisteredUserScript[] = [...particularScriptList, ...generalScriptList];
846+
const list: chrome.userScripts.RegisteredUserScript[] = [...particularScriptList, ...injectScripList];
849847

850848
runtimeGlobal.registered = true;
851849
try {
@@ -870,6 +868,11 @@ export class RuntimeService {
870868
}
871869
}
872870
}
871+
try {
872+
await chrome.scripting.registerContentScripts(contentScriptList);
873+
} catch (e: any) {
874+
this.logger.error("register content.js error", Logger.E(e));
875+
}
873876
}
874877

875878
// 给指定tab发送消息
@@ -1200,27 +1203,6 @@ export class RuntimeService {
12001203
return await runScript(this.msgSender, res);
12011204
}
12021205

1203-
compileInjectUserScript(
1204-
injectJs: string,
1205-
messageFlags: MessageFlags,
1206-
{ excludeMatches, excludeGlobs }: { excludeMatches: string[] | undefined; excludeGlobs: string[] | undefined }
1207-
) {
1208-
// 构建inject.js的脚本注册信息
1209-
const code = `(function (MessageFlags) {\n${injectJs}\n})(${JSON.stringify(messageFlags)})`;
1210-
const script: chrome.userScripts.RegisteredUserScript = {
1211-
id: "scriptcat-inject",
1212-
js: [{ code }],
1213-
matches: ["<all_urls>"],
1214-
allFrames: true,
1215-
world: "MAIN",
1216-
runAt: "document_start",
1217-
excludeMatches: excludeMatches,
1218-
excludeGlobs: excludeGlobs,
1219-
};
1220-
1221-
return [script] as chrome.userScripts.RegisteredUserScript[];
1222-
}
1223-
12241206
scriptMatchEntry(
12251207
scriptRes: ScriptRunResource,
12261208
o: {

src/content.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,18 @@ import { CustomEventMessage } from "@Packages/message/custom_event_message";
55
import { Server } from "@Packages/message/server";
66
import ContentRuntime from "./app/service/content/content";
77
import { ScriptExecutor } from "./app/service/content/script_executor";
8-
import { randomMessageFlag } from "./pkg/utils/utils";
8+
import { randomMessageFlag, getUspMessageFlags } from "./pkg/utils/utils";
99
import type { Message } from "@Packages/message/types";
1010

11-
/* global MessageFlags */
11+
const MessageFlags = getUspMessageFlags();
1212

13-
if (typeof chrome?.runtime?.onMessage?.addListener !== "function") {
14-
// Firefox MV3 之类好像没有 chrome.runtime.onMessage.addListener ?
13+
if (!MessageFlags) {
14+
console.error("MessageFlags is available.");
15+
} else if (typeof chrome?.runtime?.onMessage?.addListener !== "function") {
16+
// Firefox userScripts.RegisteredUserScript does not provide chrome.runtime.onMessage.addListener
17+
// Firefox scripting.RegisteredContentScript does provide chrome.runtime.onMessage.addListener
18+
// Firefox 的 userScripts.RegisteredUserScript 不提供 chrome.runtime.onMessage.addListener
19+
// Firefox 的 scripting.RegisteredContentScript 提供 chrome.runtime.onMessage.addListener
1520
console.error("chrome.runtime.onMessage.addListener is not a function");
1621
} else {
1722
// 建立与service_worker页面的连接

src/pkg/utils/utils.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,37 @@ export function randomMessageFlag(): string {
1111
return `-${Date.now().toString(36)}.${randNum(8e11, 2e12).toString(36)}`;
1212
}
1313

14+
export const getUspMessageFlags = () => {
15+
const s = new Error().stack;
16+
if (s) {
17+
const search1 = "content.js#FlagsStart&";
18+
const len1 = search1.length;
19+
const idx1 = s.indexOf(search1);
20+
if (idx1 > 0) {
21+
const search2 = "&FlagsEnd";
22+
const idx2 = s.indexOf(search2, idx1 + len1);
23+
if (idx2 > 0) {
24+
const param = s.substring(idx1 + len1, idx2);
25+
try {
26+
const ret: Record<string, string> = {};
27+
const usp = new URLSearchParams(param);
28+
// ⚠️ In Firefox content scripts, Xray wrappers strip the iterator from URLSearchParams.entries()
29+
// → Do not use for...of or [...usp.entries()] here
30+
// ⚠️ 在 Firefox 内容脚本中,Xray 包装器会移除 URLSearchParams.entries() 的迭代器
31+
// → 此处不要使用 for...of 或 [...usp.entries()]
32+
usp.forEach((value, key) => {
33+
ret[key] = value;
34+
});
35+
return ret as unknown as MessageFlags;
36+
} catch (e) {
37+
console.error(e);
38+
}
39+
}
40+
}
41+
}
42+
return null;
43+
};
44+
1445
export function isFirefox() {
1546
//@ts-ignore
1647
return typeof mozInnerScreenX !== "undefined";
@@ -178,7 +209,7 @@ export async function checkUserScriptsAvailable() {
178209
// Method call which throws if API permission or toggle is not enabled.
179210
chrome.userScripts;
180211
const ret: chrome.userScripts.RegisteredUserScript[] | any = await chrome.userScripts.getScripts({
181-
ids: ["scriptcat-content", "undefined-id-3"],
212+
ids: ["scriptcat-inject", "undefined-id-3"],
182213
});
183214
// 返回结果不是阵列的话表示API不可使用
184215
if (ret === undefined || ret === null || typeof ret[Symbol.iterator] !== "function") {
@@ -187,10 +218,10 @@ export async function checkUserScriptsAvailable() {
187218

188219
if (ret[0]) {
189220
// API内部处理实际给予扩展权限才会有返回Script
190-
// 含有 "scriptcat-content" 或 "undefined-id-3"
221+
// 含有 "scriptcat-inject" 或 "undefined-id-3"
191222
return true;
192223
} else {
193-
// 没有 "scriptcat-content" 和 "undefined-id-3"
224+
// 没有 "scriptcat-inject" 和 "undefined-id-3"
194225
// 进行 "undefined-id-3" 的注册反注册测试
195226
// Chrome MV3 的一部分浏览器(如 Vivaldi )没正确处理 MV3 UserScripts API 权限问题 (API内部处理没有给予扩展权限)
196227
// 此时会无法注册 (1. register 报错)

0 commit comments

Comments
 (0)