Skip to content

Commit deea0bc

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

File tree

3 files changed

+103
-56
lines changed

3 files changed

+103
-56
lines changed

src/app/service/service_worker/runtime.ts

Lines changed: 59 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
getMetadataStr,
2222
getUserConfigStr,
2323
obtainBlackList,
24+
isFirefox,
2425
} from "@App/pkg/utils/utils";
2526
import { cacheInstance } from "@App/app/cache";
2627
import { UrlMatch } from "@App/pkg/utils/match";
@@ -53,7 +54,7 @@ const runtimeGlobal = {
5354
messageFlag: "PENDING",
5455
scriptLoadComplete: "PENDING",
5556
envLoadComplete: "PENDING",
56-
} as MessageFlags,
57+
} satisfies MessageFlags & Record<string, string>,
5758
};
5859

5960
export class RuntimeService {
@@ -305,8 +306,8 @@ export class RuntimeService {
305306

306307
let registered = false;
307308
try {
308-
const res = await chrome.userScripts.getScripts({ ids: ["scriptcat-content", "scriptcat-inject"] });
309-
registered = res.length === 2;
309+
const res = await chrome.userScripts.getScripts({ ids: ["scriptcat-inject"] });
310+
registered = res.length === 1;
310311
} finally {
311312
// 考虑 UserScripts API 不可使用等情况
312313
runtimeGlobal.registered = registered;
@@ -608,6 +609,7 @@ export class RuntimeService {
608609
runtimeGlobal.messageFlags = this.generateMessageFlags();
609610
await Promise.allSettled([
610611
chrome.userScripts.unregister(),
612+
chrome.scripting.unregisterContentScripts(),
611613
this.localStorageDAO.save({ key: "scriptInjectMessageFlags", value: runtimeGlobal.messageFlags }),
612614
]);
613615
}
@@ -781,32 +783,56 @@ export class RuntimeService {
781783
// do nothing
782784
}
783785
}
784-
const retScript: chrome.userScripts.RegisteredUserScript[] = [];
785-
const contentJs = await this.getContentJsCode();
786-
if (contentJs) {
787-
retScript.push({
788-
id: "scriptcat-content",
789-
js: [{ code: `(function (MessageFlags) {\n${contentJs}\n})(${JSON.stringify(messageFlags)})` }],
790-
matches: ["<all_urls>"],
791-
allFrames: true,
792-
runAt: "document_start",
793-
world: "USER_SCRIPT",
794-
excludeMatches,
795-
excludeGlobs,
796-
});
797-
}
798-
786+
let retContent: chrome.scripting.RegisteredContentScript[] = [];
787+
let retInject: chrome.userScripts.RegisteredUserScript[] = [];
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+
];
805+
}
806+
// Note: Chrome does not support file.js?query
807+
// 注意:Chrome 不支持 file.js?query
808+
if (isFirefox()) {
809+
retContent = [
810+
{
811+
id: "scriptcat-content",
812+
js: [`/src/content.js?FlagsStart&${`${new URLSearchParams(messageFlags)}`}&FlagsEnd`],
813+
matches: ["<all_urls>"],
814+
allFrames: true,
815+
runAt: "document_start",
816+
excludeMatches,
817+
} satisfies chrome.scripting.RegisteredContentScript,
818+
];
819+
} else {
820+
const contentJs = await this.getContentJsCode();
821+
if (contentJs) {
822+
retInject.push({
823+
id: "scriptcat-content",
824+
js: [{ code: `(function () {\n${contentJs}\n})(${JSON.stringify(messageFlags)})` }],
825+
matches: ["<all_urls>"],
826+
allFrames: true,
827+
runAt: "document_start",
828+
world: "USER_SCRIPT",
829+
excludeMatches,
830+
excludeGlobs,
831+
} satisfies chrome.userScripts.RegisteredUserScript);
832+
}
807833
}
808834

809-
return retScript;
835+
return { content: retContent, inject: retInject };
810836
}
811837

812838
// 如果是重复注册,需要先调用 unregisterUserscripts
@@ -818,8 +844,8 @@ export class RuntimeService {
818844
if (runtimeGlobal.registered) {
819845
// 异常情况
820846
// 检查scriptcat-content和scriptcat-inject是否存在
821-
const res = await chrome.userScripts.getScripts({ ids: ["scriptcat-content", "scriptcat-inject"] });
822-
if (res.length === 2) {
847+
const res = await chrome.userScripts.getScripts({ ids: ["scriptcat-inject"] });
848+
if (res.length === 1) {
823849
return;
824850
}
825851
// scriptcat-content/scriptcat-inject不存在的情况
@@ -843,9 +869,9 @@ export class RuntimeService {
843869
const particularScriptList = await this.getParticularScriptList(options);
844870
// getContentAndInjectScript依赖loadScriptMatchInfo
845871
// 需要等getParticularScriptList完成后再执行
846-
const generalScriptList = await this.getContentAndInjectScript(options);
872+
const { inject: injectScripList, content: contentScriptList } = await this.getContentAndInjectScript(options);
847873

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

850876
runtimeGlobal.registered = true;
851877
try {
@@ -870,6 +896,11 @@ export class RuntimeService {
870896
}
871897
}
872898
}
899+
try {
900+
await chrome.scripting.registerContentScripts(contentScriptList);
901+
} catch (e: any) {
902+
this.logger.error("register content.js error", Logger.E(e));
903+
}
873904
}
874905

875906
// 给指定tab发送消息
@@ -1200,27 +1231,6 @@ export class RuntimeService {
12001231
return await runScript(this.msgSender, res);
12011232
}
12021233

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-
12241234
scriptMatchEntry(
12251235
scriptRes: ScriptRunResource,
12261236
o: {

src/content.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,19 @@ 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+
// @ts-ignore
12+
const MessageFlags: MessageFlags | null = (typeof arguments === "object" && arguments?.[0]) || getUspMessageFlags();
1213

13-
if (typeof chrome?.runtime?.onMessage?.addListener !== "function") {
14-
// Firefox MV3 之类好像没有 chrome.runtime.onMessage.addListener ?
14+
if (!MessageFlags) {
15+
console.error("MessageFlags is unavailable.");
16+
} else if (typeof chrome?.runtime?.onMessage?.addListener !== "function") {
17+
// Firefox userScripts.RegisteredUserScript does not provide chrome.runtime.onMessage.addListener
18+
// Firefox scripting.RegisteredContentScript does provide chrome.runtime.onMessage.addListener
19+
// Firefox 的 userScripts.RegisteredUserScript 不提供 chrome.runtime.onMessage.addListener
20+
// Firefox 的 scripting.RegisteredContentScript 提供 chrome.runtime.onMessage.addListener
1521
console.error("chrome.runtime.onMessage.addListener is not a function");
1622
} else {
1723
// 建立与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)