Skip to content

Commit 84558e0

Browse files
committed
Merge branch 'main' into release/v1.3
2 parents 0970c90 + 265e122 commit 84558e0

8 files changed

Lines changed: 158 additions & 36 deletions

File tree

package.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "scriptcat",
3-
"version": "1.3.0-beta.2",
3+
"version": "1.3.0-beta.3",
44
"description": "脚本猫,一个可以执行用户脚本的浏览器扩展,万物皆可脚本化,让你的浏览器可以做更多的事情!",
55
"author": "CodFrm",
66
"license": "GPLv3",
@@ -39,7 +39,6 @@
3939
"eventemitter3": "^5.0.1",
4040
"i18next": "^23.16.4",
4141
"monaco-editor": "^0.52.2",
42-
"pako": "^2.1.0",
4342
"react": "^18.3.1",
4443
"react-dom": "^18.3.1",
4544
"react-dropzone": "^14.3.8",
@@ -64,7 +63,6 @@
6463
"@types/chrome": "^0.1.27",
6564
"@types/crypto-js": "^4.2.2",
6665
"@types/node": "^22.12.0",
67-
"@types/pako": "^2.0.3",
6866
"@types/react": "^18.3.1",
6967
"@types/react-dom": "^18.3.1",
7068
"@types/semver": "^7.7.1",

pnpm-lock.yaml

Lines changed: 0 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/service/content/global.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// 避免在全局页面环境中,内置处理函数被篡改或重写
2+
const unsupportedAPI = () => {
3+
throw "unsupportedAPI";
4+
};
5+
6+
export const Native = {
7+
structuredClone: typeof structuredClone === "function" ? structuredClone : unsupportedAPI,
8+
jsonStringify: JSON.stringify.bind(JSON),
9+
jsonParse: JSON.parse.bind(JSON),
10+
} as const;
11+
12+
export const customClone = (o: any) => {
13+
// 非对象类型直接返回(包含 Symbol、undefined、基本类型等)
14+
// 接受参数:阵列、物件、null
15+
if (typeof o !== "object") return o;
16+
17+
try {
18+
// 优先使用 structuredClone,支持大多数可克隆对象
19+
return Native.structuredClone(o);
20+
} catch {
21+
// 例如:被 Proxy 包装的对象(如 Vue 等框架处理过的 reactive 对象)
22+
// structuredClone 可能会失败,忽略错误继续尝试其他方式
23+
}
24+
25+
try {
26+
// 退而求其次,使用 JSON 序列化方式进行深拷贝
27+
// 仅适用于可被 JSON 表示的普通对象
28+
return Native.jsonParse(Native.jsonStringify(o));
29+
} catch {
30+
// 序列化失败,忽略错误
31+
}
32+
33+
// 其他无法克隆的非法对象,例如 window、document 等
34+
console.error("customClone failed");
35+
return undefined;
36+
};

src/app/service/content/gm_api/gm_api.test.ts

Lines changed: 103 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,12 @@ describe.concurrent("GM_value", () => {
440440
// 设置再删除
441441
GM_setValue("a", undefined);
442442
let ret2 = GM_getValue("a", 456);
443-
return {ret1, ret2};
443+
// 设置错误的对象
444+
GM_setValue("proxy-key", new Proxy({}, {}));
445+
let ret3 = GM_getValue("proxy-key");
446+
GM_setValue("window",window);
447+
let ret4 = GM_getValue("window");
448+
return {ret1, ret2, ret3, ret4};
444449
`;
445450
const mockSendMessage = vi.fn().mockResolvedValue({ code: 0 });
446451
const mockMessage = {
@@ -452,7 +457,7 @@ describe.concurrent("GM_value", () => {
452457
const ret = await exec.exec();
453458

454459
expect(mockSendMessage).toHaveBeenCalled();
455-
expect(mockSendMessage).toHaveBeenCalledTimes(2);
460+
expect(mockSendMessage).toHaveBeenCalledTimes(4);
456461

457462
// 第一次调用:设置值为 123
458463
expect(mockSendMessage).toHaveBeenNthCalledWith(
@@ -482,11 +487,45 @@ describe.concurrent("GM_value", () => {
482487
})
483488
);
484489

485-
expect(ret).toEqual({ ret1: 123, ret2: 456 });
490+
// 第三次调用:设置值为 Proxy 对象(应失败)
491+
expect(mockSendMessage).toHaveBeenNthCalledWith(
492+
3,
493+
expect.objectContaining({
494+
action: "content/runtime/gmApi",
495+
data: {
496+
api: "GM_setValue",
497+
params: [expect.any(String), "proxy-key", {}], // Proxy 会被转换为空对象
498+
runFlag: expect.any(String),
499+
uuid: undefined,
500+
},
501+
})
502+
);
503+
504+
// 第四次调用:设置值为 window 对象(应失败)
505+
expect(mockSendMessage).toHaveBeenNthCalledWith(
506+
4,
507+
expect.objectContaining({
508+
action: "content/runtime/gmApi",
509+
data: {
510+
api: "GM_setValue",
511+
params: [expect.any(String), "window"], // window 会被转换为空对象
512+
runFlag: expect.any(String),
513+
uuid: undefined,
514+
},
515+
})
516+
);
517+
518+
expect(ret).toEqual({
519+
ret1: 123,
520+
ret2: 456,
521+
ret3: {},
522+
ret4: undefined,
523+
});
486524
});
487525

488526
it.concurrent("value引用问题 #1141", async () => {
489527
const script = Object.assign({}, scriptRes) as ScriptLoadInfo;
528+
script.value = {};
490529
script.metadata.grant = ["GM_getValue", "GM_setValue", "GM_getValues"];
491530
script.code = `
492531
const value1 = {
@@ -646,7 +685,12 @@ return { value1, value2, value3, values1,values2, allValues1, allValues2, value4
646685
// 设置再删除
647686
GM_setValues({"a": undefined, "c": undefined});
648687
let ret2 = GM_getValues(["a","b","c"]);
649-
return {ret1, ret2};
688+
// 设置错误的对象
689+
GM_setValues({"proxy-key": new Proxy({}, {})});
690+
let ret3 = GM_getValues(["proxy-key"]);
691+
GM_setValues({"window": window});
692+
let ret4 = GM_getValues(["window"]);
693+
return {ret1, ret2, ret3, ret4};
650694
`;
651695
const mockSendMessage = vi.fn().mockResolvedValue({ code: 0 });
652696
const mockMessage = {
@@ -658,7 +702,7 @@ return { value1, value2, value3, values1,values2, allValues1, allValues2, value4
658702
const ret = await exec.exec();
659703

660704
expect(mockSendMessage).toHaveBeenCalled();
661-
expect(mockSendMessage).toHaveBeenCalledTimes(2);
705+
expect(mockSendMessage).toHaveBeenCalledTimes(4);
662706

663707
const keyValuePairs1 = [
664708
["a", encodeRValue(123)],
@@ -709,7 +753,60 @@ return { value1, value2, value3, values1,values2, allValues1, allValues2, value4
709753
})
710754
);
711755

712-
expect(ret).toEqual({ ret1: { a: 123, b: 456, c: "789" }, ret2: { b: 456 } });
756+
// 第三次调用:设置值为 Proxy 对象(应失败)
757+
expect(mockSendMessage).toHaveBeenNthCalledWith(
758+
3,
759+
expect.objectContaining({
760+
action: "content/runtime/gmApi",
761+
data: {
762+
api: "GM_setValues",
763+
params: [
764+
// event id
765+
expect.stringMatching(/^.+::\d+$/),
766+
// the object payload
767+
expect.objectContaining({
768+
k: expect.stringMatching(/^##[\d.]+##$/),
769+
m: expect.objectContaining({
770+
"proxy-key": {},
771+
}),
772+
}),
773+
],
774+
runFlag: expect.any(String),
775+
uuid: undefined,
776+
},
777+
})
778+
);
779+
780+
// 第四次调用:设置值为 window 对象(应失败)
781+
expect(mockSendMessage).toHaveBeenNthCalledWith(
782+
4,
783+
expect.objectContaining({
784+
action: "content/runtime/gmApi",
785+
data: {
786+
api: "GM_setValues",
787+
params: [
788+
// event id
789+
expect.stringMatching(/^.+::\d+$/),
790+
// the object payload
791+
expect.objectContaining({
792+
k: expect.stringMatching(/^##[\d.]+##$/),
793+
m: expect.objectContaining({
794+
window: expect.stringMatching(/^##[\d.]+##undefined$/),
795+
}),
796+
}),
797+
],
798+
runFlag: expect.any(String),
799+
uuid: undefined,
800+
},
801+
})
802+
);
803+
804+
expect(ret).toEqual({
805+
ret1: { a: 123, b: 456, c: "789" },
806+
ret2: { b: 456 },
807+
ret3: { "proxy-key": {} },
808+
ret4: { window: undefined },
809+
});
713810
});
714811

715812
it.concurrent("GM_deleteValue", async () => {

src/app/service/content/gm_api/gm_api.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { customClone, Native } from "../global";
12
import type { Message, MessageConnect } from "@Packages/message/types";
23
import type { CustomEventMessage } from "@Packages/message/custom_event_message";
34
import type {
@@ -239,7 +240,7 @@ export default class GMApi extends GM_Base {
239240
const ret = a.scriptRes.value[key];
240241
if (ret !== undefined) {
241242
if (ret && typeof ret === "object") {
242-
return structuredClone(ret);
243+
return customClone(ret)!;
243244
}
244245
return ret;
245246
}
@@ -278,10 +279,15 @@ export default class GMApi extends GM_Base {
278279
} else {
279280
// 对object的value进行一次转化
280281
if (value && typeof value === "object") {
281-
value = structuredClone(value);
282+
value = customClone(value);
282283
}
284+
// customClone 可能返回 undefined
283285
a.scriptRes.value[key] = value;
284-
a.sendMessage("GM_setValue", [id, key, value]);
286+
if (value === undefined) {
287+
a.sendMessage("GM_setValue", [id, key]);
288+
} else {
289+
a.sendMessage("GM_setValue", [id, key, value]);
290+
}
285291
}
286292
return id;
287293
}
@@ -306,8 +312,9 @@ export default class GMApi extends GM_Base {
306312
} else {
307313
// 对object的value进行一次转化
308314
if (value_ && typeof value_ === "object") {
309-
value_ = structuredClone(value_);
315+
value_ = customClone(value_);
310316
}
317+
// customClone 可能返回 undefined
311318
valueStore[key] = value_;
312319
}
313320
// 避免undefined 等空值流失,先进行映射处理
@@ -373,7 +380,7 @@ export default class GMApi extends GM_Base {
373380
if (!this.scriptRes) return {};
374381
if (!keysOrDefaults) {
375382
// Returns all values
376-
return structuredClone(this.scriptRes.value);
383+
return customClone(this.scriptRes.value)!;
377384
}
378385
const result: TGMKeyValue = {};
379386
if (Array.isArray(keysOrDefaults)) {
@@ -385,7 +392,7 @@ export default class GMApi extends GM_Base {
385392
// 对object的value进行一次转化
386393
let value = this.scriptRes.value[key];
387394
if (value && typeof value === "object") {
388-
value = structuredClone(value);
395+
value = customClone(value)!;
389396
}
390397
result[key] = value;
391398
}
@@ -485,7 +492,7 @@ export default class GMApi extends GM_Base {
485492
public GM_log(message: string, level: GMTypes.LoggerLevel = "info", ...labels: GMTypes.LoggerLabel[]): void {
486493
if (this.isInvalidContext()) return;
487494
if (typeof message !== "string") {
488-
message = JSON.stringify(message);
495+
message = Native.jsonStringify(message);
489496
}
490497
this.sendMessage("GM_log", [message, level, labels]);
491498
}
@@ -1346,7 +1353,7 @@ export default class GMApi extends GM_Base {
13461353
public GM_saveTab(tabData: object): void {
13471354
if (this.isInvalidContext()) return;
13481355
if (typeof tabData === "object") {
1349-
tabData = JSON.parse(JSON.stringify(tabData));
1356+
tabData = customClone(tabData);
13501357
}
13511358
this.sendMessage("GM_saveTab", [tabData]);
13521359
}

src/app/service/content/gm_api/gm_xhr.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Native } from "../global";
12
import type { CustomEventMessage } from "@Packages/message/custom_event_message";
23
import type GMApi from "./gm_api";
34
import { dataEncode } from "@App/pkg/utils/xhr/xhr_data";
@@ -344,7 +345,7 @@ export function GM_xmlhttpRequest(
344345
let o = undefined;
345346
if (text) {
346347
try {
347-
o = JSON.parse(text);
348+
o = Native.jsonParse(text);
348349
} catch {
349350
// ignored
350351
}

src/app/service/service_worker/script.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ import { getSimilarityScore, ScriptUpdateCheck } from "./script_update_check";
4848
import { LocalStorageDAO } from "@App/app/repo/localStorage";
4949
import { CompiledResourceDAO } from "@App/app/repo/resource";
5050
import { initRegularUpdateCheck } from "./regular_updatecheck";
51-
// import { gzip as pakoGzip } from "pako";
5251

5352
export type TCheckScriptUpdateOption = Partial<
5453
{ checkType: "user"; noUpdateCheck?: number } | ({ checkType: "system" } & Record<string, any>)

src/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"manifest_version": 3,
33
"name": "__MSG_scriptcat__",
4-
"version": "1.3.0.1300",
4+
"version": "1.3.0.1400",
55
"author": "CodFrm",
66
"description": "__MSG_scriptcat_description__",
77
"options_ui": {

0 commit comments

Comments
 (0)