diff --git a/example/tests/sandbox_test.js b/example/tests/sandbox_test.js index da41b5be5..03ae01295 100644 --- a/example/tests/sandbox_test.js +++ b/example/tests/sandbox_test.js @@ -577,6 +577,15 @@ ); }); + await test("TM半沙盒:把祖先类别继承直接写在半沙盒上 (Issue #1462 PR #1463)", async () => { + const trueWindow = unsafeWindow; + const sandboxWindow = window; + assertSame(false, Object.hasOwn(trueWindow, "addEventListener"), "unsafeWindow 继承 Object.hasOwn"); + assertSame(true, Reflect.has(trueWindow, "addEventListener"), "unsafeWindow 继承 Reflect.has"); + assertSame(true, Object.hasOwn(sandboxWindow, "addEventListener"), "window 属性 Object.hasOwn"); + assertSame(true, Reflect.has(sandboxWindow, "addEventListener"), "window 属性 Reflect.has"); + }); + section("GM API 注入与命名空间"); await test("GM_info、GM.info 与 unsafeWindow 正确暴露", () => { diff --git a/src/app/service/content/create_context.test.ts b/src/app/service/content/create_context.test.ts index 8903b0e97..ca3deabdb 100644 --- a/src/app/service/content/create_context.test.ts +++ b/src/app/service/content/create_context.test.ts @@ -232,4 +232,9 @@ describe.concurrent("createProxyContext", () => { sandbox.onload = null; expect(removeEventListener).toHaveBeenCalledWith("load", eventObject); }); + + it.concurrent("TM半沙盒:把祖先类别继承直接写在半沙盒上 ( #1462 #1463 )", () => { + const sandbox = createProxyContext(createTestContext([])); + expect(Object.hasOwn(sandbox, "addEventListener")).toBe(true); + }); }); diff --git a/src/app/service/content/create_context.ts b/src/app/service/content/create_context.ts index 818d522f4..996f5a404 100644 --- a/src/app/service/content/create_context.ts +++ b/src/app/service/content/create_context.ts @@ -173,6 +173,9 @@ const overridedDescs: Record = Object.create(null); // 记录原生 onxxxxx 的 PropertyDescriptor const eventDescs: Record = Object.create(null); +// 在 USE_PSEUDO_WINDOW 情况下,由于没有 类的prototype, 父类的成员要手动传下去 +const protoBaseDescs: Record = Object.create(null); + // 包含物件本身及所有父类(不包含Object)的PropertyDescriptor // 主要是找出哪些 function值, setter/getter 需要替换 global window getAllPropertyDescriptors(global, ([key, desc]) => { @@ -194,6 +197,18 @@ getAllPropertyDescriptors(global, ([key, desc]) => { value: boundValue, }; descsCache.add(key); // 必须:子类属性覆盖父类属性 + } else if (!(key in initOwnDescs) && !Object.hasOwn(global, key)) { + if (!protoBaseDescs[key]) { + if (typeof value === "function") { + const boundValue = value.bind(global); + protoBaseDescs[key] = { + ...desc, + value: boundValue, + }; + } else { + protoBaseDescs[key] = { ...desc }; + } + } } } else { if (desc.configurable && desc.get && desc.set && desc.enumerable && key.startsWith("on")) { @@ -248,6 +263,7 @@ Object.defineProperty(PseudoWindowPrototype, "__proto__", { const sharedInitCopy = USE_PSEUDO_WINDOW ? Object.create(null, { + ...protoBaseDescs, // 较快的 @unwrap 注入时有机会改变 EventTarget.prototype ...Object.getOwnPropertyDescriptors(PseudoWindowPrototype), ...initOwnDescs, ...overridedDescs,