rewrite location proxy (regressions!)

This commit is contained in:
velzie 2024-08-30 18:26:59 -04:00
parent 2bc31d7e56
commit 7f8956979a
No known key found for this signature in database
GPG key ID: 048413F95F0DDE1F
5 changed files with 132 additions and 95 deletions

View file

@ -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<string, PropertyDescriptor> = {};
natives: Record<string, any> = {};
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);
}

View file

@ -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) {

View file

@ -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;
}

View file

@ -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!;

View file

@ -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,
});