scramjet/src/client/shared/event.ts

165 lines
4 KiB
TypeScript

import { iswindow } from "..";
import { SCRAMJETCLIENT } from "../../symbols";
import { ScramjetClient } from "../client";
import { getOwnPropertyDescriptorHandler } from "../helpers";
import { nativeGetOwnPropertyDescriptor } from "../natives";
import { unproxy } from "./unproxy";
const realOnEvent = Symbol.for("scramjet original onevent function");
export default function (client: ScramjetClient, self: Self) {
const handlers = {
message: {
_init() {
if (typeof this.data === "object" && "$scramjet$type" in this.data) {
// this is a ctl message
return false;
}
return true;
},
ports() {
// don't know why i have to do this?
return this.ports;
},
source() {
let scram: ScramjetClient = this.source[SCRAMJETCLIENT];
if (scram) return scram.globalProxy;
return this.source;
},
origin() {
if (typeof this.data === "object" && "$scramjet$origin" in this.data)
return this.data.$scramjet$origin;
// then it must be from a worker, which we aren't currently rewriting
return client.url.origin;
},
data() {
if (typeof this.data === "object" && "$scramjet$data" in this.data)
return this.data.$scramjet$data;
return this.data;
},
},
};
function wraplistener(listener: (...args: any) => any) {
return new Proxy(listener, {
apply(target, thisArg, argArray) {
const realEvent: Event = argArray[0];
// we only need to handle events dispatched from the browser
if (realEvent.isTrusted) {
const type = realEvent.type;
if (type in handlers) {
const handler = handlers[type];
if (handler._init) {
if (handler._init.call(realEvent) === false) return;
}
argArray[0] = new Proxy(realEvent, {
get(_target, prop, reciever) {
if (prop in handler) {
return handler[prop].call(_target);
}
return Reflect.get(target, prop, reciever);
},
getOwnPropertyDescriptor: getOwnPropertyDescriptorHandler,
});
}
}
return Reflect.apply(target, thisArg, argArray);
},
getOwnPropertyDescriptor: getOwnPropertyDescriptorHandler,
});
}
client.Proxy("EventTarget.prototype.addEventListener", {
apply(ctx) {
unproxy(ctx, client);
if (typeof ctx.args[1] !== "function") return;
const origlistener = ctx.args[1];
const proxylistener = wraplistener(origlistener);
ctx.args[1] = proxylistener;
let arr = client.eventcallbacks.get(ctx.this);
arr ||= [] as any;
arr.push({
event: ctx.args[0] as string,
originalCallback: origlistener,
proxiedCallback: proxylistener,
});
client.eventcallbacks.set(ctx.this, arr);
},
});
client.Proxy("EventTarget.prototype.removeEventListener", {
apply(ctx) {
unproxy(ctx, client);
if (typeof ctx.args[1] !== "function") return;
const arr = client.eventcallbacks.get(ctx.this);
if (!arr) return;
const i = arr.findIndex(
(e) => e.event === ctx.args[0] && e.originalCallback === ctx.args[1]
);
if (i === -1) return;
const r = arr.splice(i, 1);
client.eventcallbacks.set(ctx.this, arr);
ctx.args[1] = r[0].proxiedCallback;
},
});
client.Proxy("EventTarget.prototype.dispatchEvent", {
apply(ctx) {
unproxy(ctx, client);
},
});
if (!iswindow) return;
const targets = [self.window, self.HTMLElement.prototype];
for (const target of targets) {
const keys = Reflect.ownKeys(target);
for (const key of keys) {
if (
typeof key === "string" &&
key.startsWith("on") &&
handlers[key.slice(2)]
) {
const descriptor = nativeGetOwnPropertyDescriptor(target, key);
if (!descriptor.get || !descriptor.set || !descriptor.configurable)
continue;
// these are the `onmessage`, `onclick`, etc. properties
client.RawTrap(target, key, {
get(ctx) {
if (this[realOnEvent]) return this[realOnEvent];
return ctx.get();
},
set(ctx, value: any) {
this[realOnEvent] = value;
if (typeof value !== "function") return ctx.set(value);
ctx.set(wraplistener(value));
},
});
}
}
}
}