mirror of
https://github.com/MercuryWorkshop/scramjet.git
synced 2025-05-13 22:40:01 -04:00
rewrite location proxy (regressions!)
This commit is contained in:
parent
2bc31d7e56
commit
7f8956979a
5 changed files with 132 additions and 95 deletions
|
@ -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 { createDocumentProxy } from "./document";
|
||||||
import { createGlobalProxy } from "./global";
|
import { createGlobalProxy } from "./global";
|
||||||
import { getOwnPropertyDescriptorHandler } from "./helpers";
|
import { getOwnPropertyDescriptorHandler } from "./helpers";
|
||||||
import { createLocationProxy } from "./location";
|
import { createLocationProxy } from "./location";
|
||||||
import { nativeGetOwnPropertyDescriptor } from "./natives";
|
import { nativeGetOwnPropertyDescriptor } from "./natives";
|
||||||
import { CookieStore, config, decodeUrl } from "./shared";
|
import { CookieStore, config, decodeUrl } from "./shared";
|
||||||
|
import { createWrapFn } from "./shared/wrap";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
|
@ -51,6 +54,7 @@ export class ScramjetClient {
|
||||||
|
|
||||||
descriptors: Record<string, PropertyDescriptor> = {};
|
descriptors: Record<string, PropertyDescriptor> = {};
|
||||||
natives: Record<string, any> = {};
|
natives: Record<string, any> = {};
|
||||||
|
wrapfn: (i: any, ...args: any) => any;
|
||||||
|
|
||||||
cookieStore = new CookieStore();
|
cookieStore = new CookieStore();
|
||||||
|
|
||||||
|
@ -68,7 +72,7 @@ export class ScramjetClient {
|
||||||
constructor(public global: typeof globalThis) {
|
constructor(public global: typeof globalThis) {
|
||||||
this.serviceWorker = this.global.navigator.serviceWorker;
|
this.serviceWorker = this.global.navigator.serviceWorker;
|
||||||
|
|
||||||
if ("document" in global) {
|
if (iswindow) {
|
||||||
this.documentProxy = createDocumentProxy(this, global);
|
this.documentProxy = createDocumentProxy(this, global);
|
||||||
|
|
||||||
global.document[SCRAMJETCLIENT] = this;
|
global.document[SCRAMJETCLIENT] = this;
|
||||||
|
@ -76,10 +80,23 @@ export class ScramjetClient {
|
||||||
|
|
||||||
this.locationProxy = createLocationProxy(this, global);
|
this.locationProxy = createLocationProxy(this, global);
|
||||||
this.globalProxy = createGlobalProxy(this, global);
|
this.globalProxy = createGlobalProxy(this, global);
|
||||||
|
this.wrapfn = createWrapFn(this, global);
|
||||||
|
|
||||||
global[SCRAMJETCLIENT] = this;
|
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) {
|
loadcookies(cookiestr: string) {
|
||||||
this.cookieStore.load(cookiestr);
|
this.cookieStore.load(cookiestr);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,7 @@ export const order = 2;
|
||||||
|
|
||||||
export const enabled = () => self.$scramjet.config.flags.serviceworkers;
|
export const enabled = () => self.$scramjet.config.flags.serviceworkers;
|
||||||
export function disabled(client: ScramjetClient, self: Self) {
|
export function disabled(client: ScramjetClient, self: Self) {
|
||||||
client.Trap("navigator.serviceWorker", {
|
Reflect.deleteProperty(Navigator.prototype, "serviceWorker");
|
||||||
get() {
|
|
||||||
return undefined;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function (client: ScramjetClient, self: Self) {
|
export default function (client: ScramjetClient, self: Self) {
|
||||||
|
|
|
@ -1,44 +1,78 @@
|
||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { ScramjetClient } from "./client";
|
import { ScramjetClient } from "./client";
|
||||||
|
import { nativeGetOwnPropertyDescriptor } from "./natives";
|
||||||
import { encodeUrl, decodeUrl } from "./shared";
|
import { encodeUrl, decodeUrl } from "./shared";
|
||||||
|
|
||||||
export function createLocationProxy(
|
export function createLocationProxy(
|
||||||
client: ScramjetClient,
|
client: ScramjetClient,
|
||||||
self: typeof globalThis
|
self: typeof globalThis
|
||||||
) {
|
) {
|
||||||
function createLocation() {
|
// location cannot be Proxy()d
|
||||||
const loc = new URL(client.url.href);
|
const fakeLocation = {};
|
||||||
|
Object.setPrototypeOf(fakeLocation, self.Location.prototype);
|
||||||
|
fakeLocation.constructor = self.Location;
|
||||||
|
|
||||||
loc.assign = (url: string) => self.location.assign(encodeUrl(url));
|
const urlprops = [
|
||||||
loc.reload = () => self.location.reload();
|
"hash",
|
||||||
loc.replace = (url: string) => self.location.replace(encodeUrl(url));
|
"host",
|
||||||
loc.toString = () => loc.href;
|
"hostname",
|
||||||
|
"href",
|
||||||
return loc;
|
"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(
|
// functions
|
||||||
{
|
fakeLocation.toString = new Proxy(self.location.toString, {
|
||||||
host: "",
|
apply() {
|
||||||
|
return client.url.href;
|
||||||
},
|
},
|
||||||
{
|
});
|
||||||
get(target, prop) {
|
fakeLocation.valueOf = new Proxy(self.location.valueOf, {
|
||||||
const loc = createLocation();
|
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];
|
return fakeLocation;
|
||||||
},
|
|
||||||
|
|
||||||
set(obj, prop, value) {
|
|
||||||
const loc = createLocation();
|
|
||||||
|
|
||||||
if (prop === "href") {
|
|
||||||
self.location.href = encodeUrl(value);
|
|
||||||
} else {
|
|
||||||
loc[prop] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,3 @@
|
||||||
export const nativeFunction = self.Function;
|
export const nativeFunction = self.Function;
|
||||||
export const nativeGetOwnPropertyDescriptor =
|
export const nativeGetOwnPropertyDescriptor =
|
||||||
self.Object.getOwnPropertyDescriptor;
|
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!;
|
|
||||||
|
|
|
@ -3,55 +3,59 @@ import { SCRAMJETCLIENT } from "../../symbols";
|
||||||
import { ScramjetClient } from "../client";
|
import { ScramjetClient } from "../client";
|
||||||
import { config } from "../shared";
|
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) {
|
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
|
// 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 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!
|
// this presents some issues (see element.ts), but makes us a good bit faster at runtime!
|
||||||
Object.defineProperty(self, config.wrapfn, {
|
Object.defineProperty(self, config.wrapfn, {
|
||||||
value: function (identifier: any, args: any) {
|
value: client.wrapfn,
|
||||||
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;
|
|
||||||
},
|
|
||||||
writable: false,
|
writable: false,
|
||||||
configurable: false,
|
configurable: false,
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue