Skip to content

Commit 1f26c04

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

File tree

3 files changed

+78
-50
lines changed

3 files changed

+78
-50
lines changed

src/app/service/service_worker/runtime.ts

Lines changed: 35 additions & 43 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 {
@@ -305,8 +305,8 @@ export class RuntimeService {
305305

306306
let registered = false;
307307
try {
308-
const res = await chrome.userScripts.getScripts({ ids: ["scriptcat-content", "scriptcat-inject"] });
309-
registered = res.length === 2;
308+
const res = await chrome.userScripts.getScripts({ ids: ["scriptcat-inject"] });
309+
registered = res.length === 1;
310310
} finally {
311311
// 考虑 UserScripts API 不可使用等情况
312312
runtimeGlobal.registered = registered;
@@ -608,6 +608,7 @@ export class RuntimeService {
608608
runtimeGlobal.messageFlags = this.generateMessageFlags();
609609
await Promise.allSettled([
610610
chrome.userScripts.unregister(),
611+
chrome.scripting.unregisterContentScripts(),
611612
this.localStorageDAO.save({ key: "scriptInjectMessageFlags", value: runtimeGlobal.messageFlags }),
612613
]);
613614
}
@@ -781,32 +782,39 @@ export class RuntimeService {
781782
// do nothing
782783
}
783784
}
784-
const retScript: chrome.userScripts.RegisteredUserScript[] = [];
785-
const contentJs = await this.getContentJsCode();
786-
if (contentJs) {
787-
retScript.push({
785+
let retContent: chrome.scripting.RegisteredContentScript[] = [];
786+
let retInject: chrome.userScripts.RegisteredUserScript[] = [];
787+
retContent = [
788+
{
788789
id: "scriptcat-content",
789-
js: [{ code: `(function (MessageFlags) {\n${contentJs}\n})(${JSON.stringify(messageFlags)})` }],
790+
js: [`/src/content.js?FlagsStart&${`${new URLSearchParams(messageFlags)}`}&FlagsEnd`],
790791
matches: ["<all_urls>"],
791792
allFrames: true,
792793
runAt: "document_start",
793-
world: "USER_SCRIPT",
794794
excludeMatches,
795-
excludeGlobs,
796-
});
797-
}
795+
} satisfies chrome.scripting.RegisteredContentScript,
796+
];
798797

799798
// inject.js
800799
const injectJs = await this.getInjectJsCode();
801800
if (injectJs) {
802-
const apiScripts = this.compileInjectUserScript(injectJs, messageFlags, {
803-
excludeMatches,
804-
excludeGlobs,
805-
});
806-
retScript.push(...apiScripts);
801+
// 构建inject.js的脚本注册信息
802+
const code = `(function (MessageFlags) {\n${injectJs}\n})(${JSON.stringify(messageFlags)})`;
803+
retInject = [
804+
{
805+
id: "scriptcat-inject",
806+
js: [{ code }],
807+
matches: ["<all_urls>"],
808+
allFrames: true,
809+
world: "MAIN",
810+
runAt: "document_start",
811+
excludeMatches: excludeMatches,
812+
excludeGlobs: excludeGlobs,
813+
} satisfies chrome.userScripts.RegisteredUserScript,
814+
];
807815
}
808816

809-
return retScript;
817+
return { content: retContent, inject: retInject };
810818
}
811819

812820
// 如果是重复注册,需要先调用 unregisterUserscripts
@@ -818,8 +826,8 @@ export class RuntimeService {
818826
if (runtimeGlobal.registered) {
819827
// 异常情况
820828
// 检查scriptcat-content和scriptcat-inject是否存在
821-
const res = await chrome.userScripts.getScripts({ ids: ["scriptcat-content", "scriptcat-inject"] });
822-
if (res.length === 2) {
829+
const res = await chrome.userScripts.getScripts({ ids: ["scriptcat-inject"] });
830+
if (res.length === 1) {
823831
return;
824832
}
825833
// scriptcat-content/scriptcat-inject不存在的情况
@@ -843,9 +851,9 @@ export class RuntimeService {
843851
const particularScriptList = await this.getParticularScriptList(options);
844852
// getContentAndInjectScript依赖loadScriptMatchInfo
845853
// 需要等getParticularScriptList完成后再执行
846-
const generalScriptList = await this.getContentAndInjectScript(options);
854+
const { inject: injectScripList, content: contentScriptList } = await this.getContentAndInjectScript(options);
847855

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

850858
runtimeGlobal.registered = true;
851859
try {
@@ -870,6 +878,11 @@ export class RuntimeService {
870878
}
871879
}
872880
}
881+
try {
882+
await chrome.scripting.registerContentScripts(contentScriptList);
883+
} catch (e: any) {
884+
this.logger.error("register content.js error", Logger.E(e));
885+
}
873886
}
874887

875888
// 给指定tab发送消息
@@ -1200,27 +1213,6 @@ export class RuntimeService {
12001213
return await runScript(this.msgSender, res);
12011214
}
12021215

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-
12241216
scriptMatchEntry(
12251217
scriptRes: ScriptRunResource,
12261218
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)