From 7f8956979a58d6b0a86c4d87bf89a40085aaa99c Mon Sep 17 00:00:00 2001 From: velzie Date: Fri, 30 Aug 2024 18:26:59 -0400 Subject: [PATCH] rewrite location proxy (regressions!) --- src/client/client.ts | 21 +++++++- src/client/dom/serviceworker.ts | 6 +-- src/client/location.ts | 94 ++++++++++++++++++++++----------- src/client/natives.ts | 14 ----- src/client/shared/wrap.ts | 92 +++++++++++++++++--------------- 5 files changed, 132 insertions(+), 95 deletions(-) diff --git a/src/client/client.ts b/src/client/client.ts index 09a50da..0f1ff29 100644 --- a/src/client/client.ts +++ b/src/client/client.ts @@ -1,10 +1,13 @@ -import { SCRAMJETCLIENT } from "../symbols"; +import { iswindow } from "."; +import { ScramjetFrame } from "../controller/frame"; +import { SCRAMJETCLIENT, SCRAMJETFRAME } from "../symbols"; import { createDocumentProxy } from "./document"; import { createGlobalProxy } from "./global"; import { getOwnPropertyDescriptorHandler } from "./helpers"; import { createLocationProxy } from "./location"; import { nativeGetOwnPropertyDescriptor } from "./natives"; import { CookieStore, config, decodeUrl } from "./shared"; +import { createWrapFn } from "./shared/wrap"; declare global { interface Window { @@ -51,6 +54,7 @@ export class ScramjetClient { descriptors: Record = {}; natives: Record = {}; + wrapfn: (i: any, ...args: any) => any; cookieStore = new CookieStore(); @@ -68,7 +72,7 @@ export class ScramjetClient { constructor(public global: typeof globalThis) { this.serviceWorker = this.global.navigator.serviceWorker; - if ("document" in global) { + if (iswindow) { this.documentProxy = createDocumentProxy(this, global); global.document[SCRAMJETCLIENT] = this; @@ -76,10 +80,23 @@ export class ScramjetClient { this.locationProxy = createLocationProxy(this, global); this.globalProxy = createGlobalProxy(this, global); + this.wrapfn = createWrapFn(this, global); global[SCRAMJETCLIENT] = this; } + get frame(): ScramjetFrame | null { + if (!iswindow) return null; + const frame = this.global.window.frameElement; + + if (!frame) return null; // we're top level + const sframe = frame[SCRAMJETFRAME]; + + if (!sframe) return null; // we're a subframe. TODO handle propagation but not now + + return sframe; + } + loadcookies(cookiestr: string) { this.cookieStore.load(cookiestr); } diff --git a/src/client/dom/serviceworker.ts b/src/client/dom/serviceworker.ts index eb9c4fa..5f5c4eb 100644 --- a/src/client/dom/serviceworker.ts +++ b/src/client/dom/serviceworker.ts @@ -8,11 +8,7 @@ export const order = 2; export const enabled = () => self.$scramjet.config.flags.serviceworkers; export function disabled(client: ScramjetClient, self: Self) { - client.Trap("navigator.serviceWorker", { - get() { - return undefined; - }, - }); + Reflect.deleteProperty(Navigator.prototype, "serviceWorker"); } export default function (client: ScramjetClient, self: Self) { diff --git a/src/client/location.ts b/src/client/location.ts index fb73aeb..b9a6817 100644 --- a/src/client/location.ts +++ b/src/client/location.ts @@ -1,44 +1,78 @@ // @ts-nocheck import { ScramjetClient } from "./client"; +import { nativeGetOwnPropertyDescriptor } from "./natives"; import { encodeUrl, decodeUrl } from "./shared"; export function createLocationProxy( client: ScramjetClient, self: typeof globalThis ) { - function createLocation() { - const loc = new URL(client.url.href); + // location cannot be Proxy()d + const fakeLocation = {}; + Object.setPrototypeOf(fakeLocation, self.Location.prototype); + fakeLocation.constructor = self.Location; - loc.assign = (url: string) => self.location.assign(encodeUrl(url)); - loc.reload = () => self.location.reload(); - loc.replace = (url: string) => self.location.replace(encodeUrl(url)); - loc.toString = () => loc.href; - - return loc; + const urlprops = [ + "hash", + "host", + "hostname", + "href", + "origin", + "pathname", + "port", + "search", + ]; + for (const prop of urlprops) { + const native = nativeGetOwnPropertyDescriptor(self.location, prop); + const desc = { + configurable: true, + enumerable: true, + get: new Proxy(native.get, { + apply() { + return client.url[prop]; + }, + }), + }; + if (native.set) { + desc.set = new Proxy(native.set, { + apply(target, thisArg, args) { + let url = new URL(client.url.href); + url[prop] = args[0]; + self.location.href = encodeUrl(url.href); + }, + }); + } + Object.defineProperty(fakeLocation, prop, desc); } - return new Proxy( - { - host: "", + // functions + fakeLocation.toString = new Proxy(self.location.toString, { + apply() { + return client.url.href; }, - { - get(target, prop) { - const loc = createLocation(); + }); + fakeLocation.valueOf = new Proxy(self.location.valueOf, { + apply() { + return client.url.href; + }, + }); + fakeLocation.assign = new Proxy(self.location.assign, { + apply(target, thisArg, args) { + args[0] = encodeUrl(args[0]); + Reflect.apply(target, thisArg, args); + }, + }); + fakeLocation.reload = new Proxy(self.location.reload, { + apply(target, thisArg, args) { + Reflect.apply(target, thisArg, args); + }, + }); + fakeLocation.replace = new Proxy(self.location.replace, { + apply(target, thisArg, args) { + args[0] = encodeUrl(args[0]); + Reflect.apply(target, thisArg, args); + }, + }); - return loc[prop]; - }, - - set(obj, prop, value) { - const loc = createLocation(); - - if (prop === "href") { - self.location.href = encodeUrl(value); - } else { - loc[prop] = value; - } - - return true; - }, - } - ); + return fakeLocation; } diff --git a/src/client/natives.ts b/src/client/natives.ts index 6f5eda6..39e75d5 100644 --- a/src/client/natives.ts +++ b/src/client/natives.ts @@ -1,17 +1,3 @@ export const nativeFunction = self.Function; export const nativeGetOwnPropertyDescriptor = self.Object.getOwnPropertyDescriptor; - -// descriptors -export const nativeDefaultViewGetter = nativeGetOwnPropertyDescriptor( - Document.prototype, - "defaultView" -)!.get!; -export const nativeContentDocumentGetter = nativeGetOwnPropertyDescriptor( - HTMLIFrameElement.prototype, - "contentDocument" -)!.get!; -export const nativeContentWindowGetter = nativeGetOwnPropertyDescriptor( - HTMLIFrameElement.prototype, - "contentWindow" -)!.get!; diff --git a/src/client/shared/wrap.ts b/src/client/shared/wrap.ts index 561e3c2..62abbe6 100644 --- a/src/client/shared/wrap.ts +++ b/src/client/shared/wrap.ts @@ -3,55 +3,59 @@ import { SCRAMJETCLIENT } from "../../symbols"; import { ScramjetClient } from "../client"; import { config } from "../shared"; +export function createWrapFn(client: ScramjetClient, self: typeof globalThis) { + return function (identifier: any, args: any) { + if (args && typeof args === "object" && args.length === 0) + for (const arg of args) { + // argdbg(arg); + } + if (iswindow && identifier instanceof self.Window) { + return client.globalProxy; + } else if (iswindow && identifier instanceof self.parent.self.Window) { + if (SCRAMJETCLIENT in self.parent.self) { + // ... then we're in a subframe, and the parent frame is also in a proxy context, so we should return its proxy + return self.parent.self[SCRAMJETCLIENT].globalProxy; + } else { + // ... then we should pretend we aren't nested and return the current window + return client.globalProxy; + } + } else if (iswindow && identifier instanceof self.top.self.Window) { + // instead of returning top, we need to return the uppermost parent that's inside a scramjet context + let current = self.self; + + for (;;) { + const test = current.parent.self; + if (test === current) break; // there is no parent, actual or emulated. + + // ... then `test` represents a window outside of the proxy context, and therefore `current` is the topmost window in the proxy context + if (!(SCRAMJETCLIENT in test)) break; + + // test is also insde a proxy, so we should continue up the chain + current = test; + } + + return current[SCRAMJETCLIENT].globalProxy.window; + } else if ( + (iswindow && identifier instanceof self.Location) || + (isworker && identifier instanceof self.WorkerLocation) + ) { + return client.locationProxy; + } else if (iswindow && identifier instanceof self.Document) { + return client.documentProxy; + } else if (isworker && identifier instanceof self.WorkerGlobalScope) { + return client.globalProxy; + } + + return identifier; + }; +} + export default function (client: ScramjetClient, self: typeof globalThis) { // the main magic of the proxy. all attempts to access any "banned objects" will be redirected here, and instead served a proxy object // this contrasts from how other proxies will leave the root object alone and instead attempt to catch every member access // this presents some issues (see element.ts), but makes us a good bit faster at runtime! Object.defineProperty(self, config.wrapfn, { - value: function (identifier: any, args: any) { - if (args && typeof args === "object" && args.length === 0) - for (const arg of args) { - argdbg(arg); - } - if (iswindow && identifier instanceof self.Window) { - return client.globalProxy; - } else if (iswindow && identifier instanceof self.parent.self.Window) { - if (SCRAMJETCLIENT in self.parent.self) { - // ... then we're in a subframe, and the parent frame is also in a proxy context, so we should return its proxy - return self.parent.self[SCRAMJETCLIENT].globalProxy; - } else { - // ... then we should pretend we aren't nested and return the current window - return client.globalProxy; - } - } else if (iswindow && identifier instanceof self.top.self.Window) { - // instead of returning top, we need to return the uppermost parent that's inside a scramjet context - let current = self.self; - - for (;;) { - const test = current.parent.self; - if (test === current) break; // there is no parent, actual or emulated. - - // ... then `test` represents a window outside of the proxy context, and therefore `current` is the topmost window in the proxy context - if (!(SCRAMJETCLIENT in test)) break; - - // test is also insde a proxy, so we should continue up the chain - current = test; - } - - return current[SCRAMJETCLIENT].globalProxy.window; - } else if ( - (iswindow && identifier instanceof self.Location) || - (isworker && identifier instanceof self.WorkerLocation) - ) { - return client.locationProxy; - } else if (iswindow && identifier instanceof self.Document) { - return client.documentProxy; - } else if (isworker && identifier instanceof self.WorkerGlobalScope) { - return client.globalProxy; - } - - return identifier; - }, + value: client.wrapfn, writable: false, configurable: false, });