Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions src/app/service/content/create_context.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { describe, it, expect } from "vitest";
import { shouldFnBind } from "./create_context";

describe.concurrent("shouldFnBind", () => {
it.concurrent("不处理非原生函数", () => {
const o: Record<string, any> = {};
o.targetArrowFn = () => {};
expect(shouldFnBind(o.targetArrowFn)).toBe(false);
o.targetArrowFn = new Proxy(o.targetArrowFn, {});
expect(shouldFnBind(o.targetArrowFn)).toBe(false);
o.targetFn1 = function () {};
expect(shouldFnBind(o.targetFn1)).toBe(false);
o.targetFn1 = new Proxy(o.targetFn1, {});
expect(shouldFnBind(o.targetFn1)).toBe(false);
o.targetFn2 = function targetFn2() {};
expect(shouldFnBind(o.targetFn2)).toBe(false);
o.targetFn2 = new Proxy(o.targetFn2, {});
expect(shouldFnBind(o.targetFn2)).toBe(false);
});
it.concurrent("处理Proxy Function #985", () => {
const o: Record<string, any> = {};
// 例1: valueOf
o.valueOf = global.valueOf;
expect(shouldFnBind(o.valueOf)).toBe(true);
o.valueOf = new Proxy(o.valueOf, {});
expect(shouldFnBind(o.valueOf)).toBe(true);
// 例2: setTimeoutForTest1: 验证一次拦截
// @ts-ignore
o.setTimeoutForTest1 = global.setTimeoutForTest1;
expect(shouldFnBind(o.setTimeoutForTest1)).toBe(true);
o.setTimeoutForTest1 = new Proxy(o.setTimeoutForTest1, {
apply: (target, thisArg, argArray) => {
console.log("proxy call", { target, thisArg, argArray });
},
});
expect(shouldFnBind(o.setTimeoutForTest1)).toBe(true);
// 例2: setTimeoutForTest2: 验证二次拦截
// @ts-ignore
o.setTimeoutForTest2 = global.setTimeoutForTest2;
expect(shouldFnBind(o.setTimeoutForTest2)).toBe(true);
o.setTimeoutForTest2 = new Proxy(o.setTimeoutForTest2, {
apply: (target, thisArg, argArray) => {
console.log("proxy call", { target, thisArg, argArray });
},
});
expect(shouldFnBind(o.setTimeoutForTest2)).toBe(true);
});
});
21 changes: 14 additions & 7 deletions src/app/service/content/create_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,22 +100,22 @@ export const createContext = (
const noEval = false;

// 取得原生函数代码表示
const getNativeCodeSeg = () => {
const getNativeCodeSegs = () => {
const k = "propertyIsEnumerable"; // 选用 Object.propertyIsEnumerable 取得原生函数代码表示
const codeSeg = `${Object[k]}`;
const idx1 = codeSeg.indexOf(k);
const idx2 = codeSeg.indexOf("()");
const idx3 = codeSeg.lastIndexOf("(");
if (idx1 > 0 && idx2 > 0 && idx3 === idx2) {
return codeSeg.substring(idx1 + k.length);
return [codeSeg.substring(0, idx1), codeSeg.substring(idx1 + k.length)];
}
return "";
return null;
};

const nativeCodeSeg = getNativeCodeSeg();
const ncs = getNativeCodeSegs();

// 判断是否应该将函数绑定到global (原生函数)
const shouldFnBind = (f: any) => {
export const shouldFnBind = (f: any) => {
if (typeof f !== "function") return false;
// 函数有 prototype 即为 Class
if ("prototype" in f) return false; // 避免getter, 使用 in operator (注意, nodeJS的测试环境有异)
Expand All @@ -125,8 +125,15 @@ const shouldFnBind = (f: any) => {
if (!name) return false;
const e = name.charCodeAt(0);
if (e >= 97 && e <= 122 && !name.includes(" ")) {
// 为避免浏览器插件封装了 原生函数,需要进行 toString 测试
if (nativeCodeSeg.length && `${f}`.endsWith(`${name}${nativeCodeSeg}`)) {
// 为避免浏览器插件封装了 原生函数,需要进行 toString 测试 (Proxy封装例外)
if (ncs?.[1]) {
const s = `${f}`;
// 广告拦截扩展进行Proxy封装后丢失名字 (Chrome:所有经Proxy封装都会变成无名原生函数)
if (s === `${ncs[0]}${name}${ncs[1]}` || s === `${ncs[0]}${ncs[1]}`) {
return true;
}
} else {
// 代码错误,全部 bind
return true;
}
}
Expand Down
43 changes: 34 additions & 9 deletions src/app/service/content/exec_script.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,8 @@ describe("沙盒环境测试", async () => {
// });

// 在模拟环境无法测试:在实际操作中和TM一致
// 在非拦截式沙盒裡删除 沙盒onload 后,会取得页面的真onload
// 在非拦截式沙盒裡删除 真onload 后,会变undefined
// 在非拦截式沙盒里删除 沙盒onload 后,会取得页面的真onload
// 在非拦截式沙盒里删除 真onload 后,会变undefined
// it.concurrent("删除 onload 后应该为 null", () => {
// const mockFn = vi.fn();
// _this["onload"] = function thisOnLoad() {
Expand Down Expand Up @@ -376,33 +376,58 @@ describe("沙盒环境测试", async () => {

it.concurrent("[兼容问题] Ensure Illegal invocation can be tested", () => {
expect(global.setTimeout.name).toEqual("setTimeout");
// -----
//@ts-ignore
expect(global.setTimeoutForTest.name).toEqual("setTimeoutForTest");
expect(_this.setTimeoutForTest.name).toEqual("bound setTimeoutForTest");
expect(global.setTimeoutForTest1.name).toEqual("setTimeoutForTest1");
expect(_this.setTimeoutForTest1.name).toEqual("bound setTimeoutForTest1");
//@ts-ignore
expect(() => global.setTimeout.call(global, () => {}, 1)).not.toThrow();
//@ts-ignore
expect(() => global.setTimeoutForTest.call(global, () => {}, 1)).not.toThrow();
expect(() => global.setTimeoutForTest1.call(global, () => {}, 1)).not.toThrow();
//@ts-ignore
expect(() => global.setTimeoutForTest.call({}, () => {}, 1)).toThrow();
expect(() => global.setTimeoutForTest1.call({}, () => {}, 1)).toThrow();
// -----
//@ts-ignore
expect(global.setTimeoutForTest2.name).toEqual("setTimeoutForTest2");
expect(_this.setTimeoutForTest2.name).toEqual("bound setTimeoutForTest2");
//@ts-ignore
expect(() => global.setTimeout.call(global, () => {}, 1)).not.toThrow();
//@ts-ignore
expect(() => global.setTimeoutForTest2.call(global, () => {}, 1)).not.toThrow();
//@ts-ignore
expect(() => global.setTimeoutForTest2.call({}, () => {}, 1)).toThrow();
});
// https://github.com/xcanwin/KeepChatGPT 环境隔离得不够干净导致的
it.concurrent("[兼容问题] Uncaught TypeError: Illegal invocation #189", () => {
// setTimeout 和 setTimeoutForTest 都測試吧
// setTimeout 和 setTimeoutForTest1 都测试吧
const promise1 = new Promise((resolve) => {
console.log(_this.setTimeout.prototype);
_this.setTimeoutForTest(resolve, 1);
_this.setTimeoutForTest1(resolve, 1);
});
const promise2 = new Promise((resolve) => {
console.log(_this.setTimeout.prototype);
_this.setTimeout(resolve, 1);
});
expect(Promise.all([promise1, promise2]).then(() => "ok")).resolves.toBe("ok");
const res = Promise.all([promise1, promise2]);
expect(res.then((res) => (!res[0] && !res[1] ? "ok" : "ng"))).resolves.toBe("ok");
});
// AC-baidu-重定向优化百度搜狗谷歌必应搜索_favicon_双列
it.concurrent("[兼容问题] TypeError: Object.freeze is not a function #116", () => {
expect(() => _this.Object.freeze({})).not.toThrow();
});
it.concurrent("Proxy Function #985", () => {
// setTimeout 和 setTimeoutForTest2 都测试吧
const promise1 = new Promise((resolve) => {
console.log(_this.setTimeout.prototype);
_this.setTimeoutForTest2(resolve, 1);
});
const promise2 = new Promise((resolve) => {
console.log(_this.setTimeout.prototype);
_this.setTimeout(resolve, 1);
});
const res = Promise.all([promise1, promise2]);
expect(res.then((res) => (res[0] === "proxy" && !res[1] ? "ok" : "ng"))).resolves.toBe("ok");
});

const tag = (<any>global)[Symbol.toStringTag]; // 实际环境:'[object Window]' 测试环境:'[object global]'

Expand Down
34 changes: 30 additions & 4 deletions tests/vitest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,17 +171,43 @@ if (!("onblur" in global)) {
}

Object.assign(global, {
setTimeoutForTest(...args: any) {
setTimeoutForTest1(...args: any) {
// 注意: function XXX (){} 会导致 Class prototype 出现
//@ts-ignore
if (typeof this === "object" && this && this !== global) throw new TypeError("Illegal invocation");
//@ts-ignore
return this.setTimeout(...args);
},
});
//@ts-ignore 强行修改 setTimeoutForTest toString 为 原生代码显示
global.setTimeoutForTest.toString = () =>
`${Object.propertyIsEnumerable}`.replace("propertyIsEnumerable", "setTimeoutForTest");
//@ts-ignore 强行修改 setTimeoutForTest1 toString 为 原生代码显示
global.setTimeoutForTest1.toString = () =>
`${Object.propertyIsEnumerable}`.replace("propertyIsEnumerable", "setTimeoutForTest1");

Object.assign(global, {
setTimeoutForTest2(...args: any) {
// 注意: function XXX (){} 会导致 Class prototype 出现
//@ts-ignore
if (typeof this === "object" && this && this !== global) throw new TypeError("Illegal invocation");
//@ts-ignore
return this.setTimeout(...args);
},
});
//@ts-ignore 强行修改 setTimeoutForTest2 toString 为 原生代码显示
global.setTimeoutForTest2.toString = () =>
`${Object.propertyIsEnumerable}`.replace("propertyIsEnumerable", "setTimeoutForTest2");

//@ts-ignore 模拟扩展拦截
global.setTimeoutForTest2 = new Proxy(global.setTimeoutForTest2, {
apply: (target, thisArg, argArray) => {
return target.call(
thisArg,
() => {
argArray[0]("proxy");
},
argArray[1]
);
},
});

vi.stubGlobal("sandboxTestValue", "sandboxTestValue");
vi.stubGlobal("sandboxTestValue2", "sandboxTestValue2");
Expand Down
Loading