char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somport web extension to typescript44fe2c6

main
16.7 KiB596 linesraw
1import {
2  ArrayPrototypePush as push,
3  FinalizationRegistry,
4  getter,
5  ObjectCreate,
6  ObjectFreeze as freeze,
7  ReflectApply as apply,
8  ReflectConstruct as construct,
9  Set,
10  SetPrototypeAdd as retain,
11  SetPrototypeDelete as release,
12  SymbolIterator,
13  TypeError,
14  uncurryThis,
15  WeakMap,
16  WeakMapPrototypeGet as get,
17  WeakMapPrototypeSet as set,
18} from "ext:core/primordials";
19import { enqueueTask } from "ext:core/tasks";
20import { promiseThen as then } from "ext:core/promises";
21import {
22  op_port_adopt,
23  op_port_close,
24  op_port_move,
25  op_port_pair,
26  op_port_peer,
27  op_port_send,
28  op_port_take,
29  op_port_wait,
30  op_port_wait_ref,
31} from "ext:core/ops";
32import { array, dictionary, required, string, usv } from "./idl.js";
33import {
34  addListener,
35  dataCloneError,
36  dispatch,
37  Event,
38  EventTarget,
39  hasListener,
40  isDispatching,
41  observeListeners,
42  preventDefault,
43  removeListener,
44} from "./events.js";
45import {
46  classify,
47  decodeWire,
48  registerTransferable,
49  registerUncloneable,
50  registerWire,
51  transferCallbacks,
52} from "./serialization.js";
53
54const WeakRef = globalThis.WeakRef,
55  deref = uncurryThis(WeakRef.prototype.deref);
56interface Endpoint {
57  id: number;
58  epoch: number;
59  port: WeakRef<MessagePort> | null;
60  live: boolean;
61  started: boolean;
62  closed: boolean;
63  scheduled: boolean;
64  referenced: boolean;
65  onclose?: () => void;
66}
67interface PortState {
68  endpoint: Endpoint | null;
69  detached: boolean;
70  onmessage: ((event: Event) => unknown) | null;
71  onmessageerror: ((event: Event) => unknown) | null;
72  messageListener: ((event: Event) => void) | null;
73  errorListener: ((event: Event) => void) | null;
74}
75interface MessageEventState {
76  data: unknown;
77  origin: string;
78  lastEventId: string;
79  source: MessagePort | null;
80  ports: readonly MessagePort[];
81}
82interface ChannelState {
83  port1: MessagePort;
84  port2: MessagePort;
85}
86const activePorts = new Set<MessagePort>();
87const initEvent = uncurryThis(Event.prototype.initEvent);
88const registerFinalizer = uncurryThis(FinalizationRegistry.prototype.register);
89const unregisterFinalizer = uncurryThis(
90  FinalizationRegistry.prototype.unregister,
91);
92const portFinalizer = new FinalizationRegistry<number>((id) =>
93  op_port_close(id)
94);
95const ports = new WeakMap<object, PortState>(),
96  messageEvents = new WeakMap<object, MessageEventState>(),
97  channels = new WeakMap<object, ChannelState>(),
98  secret = ObjectCreate(null);
99registerTransferable(ports, preparePort, validatePort, commitPort);
100registerWire({
101  name: "MessagePort",
102  states: ports,
103  encode: (current: unknown) => (current as PortState).endpoint?.id ?? null,
104  decode: (id: unknown) => adoptPort(id as number | null),
105  prepare: (_current: unknown, source: unknown) => {
106    validatePort(null, source);
107    return null;
108  },
109  validate: validatePort,
110  commit: (_current: unknown, source: unknown) => {
111    const origin = state(ports, source);
112    if (origin.endpoint) op_port_move(origin.endpoint.id);
113    unregisterFinalizer(portFinalizer, source as MessagePort);
114    origin.endpoint = null;
115    origin.detached = true;
116    retainPort(source as MessagePort);
117  },
118});
119registerUncloneable(messageEvents);
120registerUncloneable(channels);
121
122function state<K extends object, V>(map: WeakMap<K, V>, value: unknown): V {
123  const result = get(map, value);
124  if (result === undefined) throw new TypeError("Illegal invocation");
125  return result;
126}
127
128function port(value: unknown): value is MessagePort {
129  return get(ports, value) !== undefined;
130}
131
132function freezePorts(values: readonly MessagePort[]): readonly MessagePort[] {
133  const result: MessagePort[] = [];
134  for (let i = 0; i < values.length; i++) push(result, values[i]);
135  return freeze(result);
136}
137
138function portList(
139  value: unknown,
140  requirePorts: true,
141  method?: unknown,
142  methodRead?: boolean,
143): MessagePort[];
144function portList(
145  value: unknown,
146  requirePorts?: false,
147  method?: unknown,
148  methodRead?: boolean,
149): object[];
150function portList(
151  value: unknown,
152  requirePorts = false,
153  method: unknown = undefined,
154  methodRead = false,
155) {
156  if (value === undefined) return array<object>();
157  if (value === null) {
158    throw new TypeError("Expected an iterable of MessagePorts");
159  }
160  if (!methodRead) {
161    method = (value as Record<PropertyKey, unknown>)[SymbolIterator];
162  }
163  if (typeof method !== "function") {
164    throw new TypeError("Expected an iterable of MessagePorts");
165  }
166  const iterator = apply(
167    method as (...args: never[]) => unknown,
168    value,
169    [],
170  ) as Record<PropertyKey, unknown>;
171  if (
172    iterator === null ||
173    (typeof iterator !== "object" && typeof iterator !== "function")
174  ) {
175    throw new TypeError("Invalid transfer iterator");
176  }
177  const next = iterator.next;
178  if (typeof next !== "function") {
179    throw new TypeError("Invalid transfer iterator next method");
180  }
181  const result = array<object>();
182  while (true) {
183    const step = apply(
184      next as (...args: never[]) => unknown,
185      iterator,
186      [],
187    ) as Record<PropertyKey, unknown>;
188    if (
189      step === null || (typeof step !== "object" && typeof step !== "function")
190    ) {
191      throw new TypeError("Invalid transfer iterator result");
192    }
193    if (step.done) return result;
194    const item = step.value;
195    if (
196      item === null || (typeof item !== "object" && typeof item !== "function")
197    ) {
198      throw new TypeError("Sequence entries must be objects");
199    }
200    if (requirePorts && !port(item)) {
201      throw new TypeError("Expected a MessagePort");
202    }
203    push(result, item);
204  }
205}
206
207function transferList(options: unknown): object[] {
208  if (options === undefined) return array();
209  if (
210    options !== null &&
211    (typeof options === "object" || typeof options === "function")
212  ) {
213    const method = (options as Record<PropertyKey, unknown>)[SymbolIterator];
214    if (typeof method === "function") {
215      return portList(options, false, method, true);
216    }
217  }
218  const value = dictionary(options).transfer;
219  return portList(value);
220}
221
222function preparePort(_state: unknown, source: unknown): MessagePort {
223  if (state(ports, source).detached) {
224    throw dataCloneError("MessagePort has already been transferred");
225  }
226  return new MessagePort(secret);
227}
228
229function validatePort(_state: unknown, source: unknown): void {
230  if (state(ports, source).detached) {
231    throw dataCloneError("MessagePort has already been transferred");
232  }
233}
234
235function commitPort(
236  _state: unknown,
237  source: unknown,
238  destination: unknown,
239): void {
240  const origin = state(ports, source), target = state(ports, destination);
241  target.endpoint = origin.endpoint;
242  target.detached = false;
243  if (origin.endpoint) {
244    op_port_move(origin.endpoint.id);
245    origin.endpoint.epoch = op_port_adopt(origin.endpoint.id);
246    origin.endpoint.port = new WeakRef(destination as MessagePort);
247    origin.endpoint.started = false;
248    origin.endpoint.scheduled = false;
249    registerFinalizer(
250      portFinalizer,
251      destination as MessagePort,
252      origin.endpoint.id,
253      destination as MessagePort,
254    );
255  }
256  unregisterFinalizer(portFinalizer, source as MessagePort);
257  origin.endpoint = null;
258  origin.detached = true;
259  retainPort(source as MessagePort);
260  retainPort(destination as MessagePort);
261}
262
263function retainPort(value: object): void {
264  const endpoint = state(ports, value).endpoint;
265  if (
266    endpoint && endpoint.live && endpoint.started && !endpoint.closed &&
267    (endpoint.referenced || hasListener(value, "message") ||
268      hasListener(value, "messageerror"))
269  ) {
270    retain(activePorts, value as MessagePort);
271  } else release(activePorts, value as MessagePort);
272}
273
274function schedule(endpoint: Endpoint | null): void {
275  if (!endpoint || endpoint.closed || !endpoint.started || endpoint.scheduled) {
276    return;
277  }
278  endpoint.scheduled = true;
279  const epoch = endpoint.epoch;
280  const wait = endpoint.referenced ? op_port_wait_ref : op_port_wait;
281  then(wait([endpoint.id, epoch]), (ready) => {
282    if (endpoint.epoch !== epoch) return;
283    endpoint.scheduled = false;
284    if (!ready) {
285      endpoint.live = false;
286      const target = endpoint.port && deref(endpoint.port);
287      if (target) retainPort(target);
288      endpoint.onclose?.();
289      return;
290    }
291    if (endpoint.closed || !endpoint.started) return;
292    enqueueTask(() => {
293      if (endpoint.epoch !== epoch || endpoint.closed || !endpoint.started) {
294        return;
295      }
296      const target = endpoint.port && deref(endpoint.port);
297      if (!target) {
298        endpoint.closed = true;
299        op_port_close(endpoint.id);
300        return;
301      }
302      let packet;
303      try {
304        packet = op_port_take(endpoint.id, epoch, decodeWire, dataCloneError);
305      } catch {
306        if (target) {
307          dispatch(
308            target,
309            new MessageEvent("messageerror", ObjectCreate(null)),
310          );
311        }
312        return;
313      }
314      if (packet !== null && target) {
315        dispatchMessage(target, packet.data, packet.ports);
316      }
317    }, () => {
318      schedule(endpoint);
319      return true;
320    });
321  });
322}
323
324function adoptPort(id: number | null): MessagePort {
325  const result = new MessagePort(secret);
326  if (id !== null) {
327    registerFinalizer(portFinalizer, result, id, result);
328    state(ports, result).endpoint = {
329      id,
330      epoch: op_port_adopt(id),
331      port: new WeakRef(result),
332      live: true,
333      started: false,
334      closed: false,
335      scheduled: false,
336      referenced: false,
337    };
338  }
339  return result;
340}
341
342function startPort(value: MessagePort, referenced = false): void {
343  const endpoint = state(ports, value).endpoint;
344  if (endpoint && !endpoint.closed) {
345    endpoint.referenced = referenced;
346    endpoint.started = true;
347    retainPort(value);
348    schedule(endpoint);
349  }
350}
351
352function dispatchMessage(
353  target: object,
354  data: unknown,
355  eventPorts: readonly MessagePort[] = [],
356): void {
357  const event = new MessageEvent("message", ObjectCreate(null));
358  set(messageEvents, event, {
359    data,
360    origin: "",
361    lastEventId: "",
362    source: null,
363    ports: freezePorts(eventPorts),
364  });
365  dispatch(target, event);
366}
367
368class MessageEvent extends Event {
369  // @ts-expect-error Reflect.construct preserves new.target without a guest-replaceable super().
370  constructor(type: unknown, init: unknown = {}) {
371    required(arguments.length, 1);
372    type = string(type);
373    const options = dictionary(init);
374    const self = construct(Event, [type, options], new.target);
375    const rawData = options.data,
376      rawOrigin = options.origin,
377      rawId = options.lastEventId,
378      rawSource = options.source;
379    const data = rawData === undefined ? null : rawData;
380    const origin = rawOrigin === undefined ? "" : usv(rawOrigin);
381    const lastEventId = rawId === undefined ? "" : string(rawId);
382    const source = rawSource === undefined ? null : rawSource;
383    if (source !== null && !port(source)) {
384      throw new TypeError("Expected a MessagePort or null");
385    }
386    const eventPorts = portList(options.ports, true);
387    set(messageEvents, self, {
388      data,
389      origin,
390      lastEventId,
391      source,
392      ports: freezePorts(eventPorts),
393    });
394    return self as MessageEvent;
395  }
396  get data() {
397    return state(messageEvents, this).data;
398  }
399  get origin() {
400    return state(messageEvents, this).origin;
401  }
402  get lastEventId() {
403    return state(messageEvents, this).lastEventId;
404  }
405  get source() {
406    return state(messageEvents, this).source;
407  }
408  get ports() {
409    return state(messageEvents, this).ports;
410  }
411  initMessageEvent(
412    type: unknown,
413    bubbles: unknown = false,
414    cancelable: unknown = false,
415    data: unknown = null,
416    origin: unknown = "",
417    lastEventId: unknown = "",
418    source: unknown = null,
419    eventPorts: unknown = [],
420  ) {
421    required(arguments.length, 1);
422    type = string(type);
423    bubbles = !!bubbles;
424    cancelable = !!cancelable;
425    origin = string(origin);
426    lastEventId = string(lastEventId);
427    if (source !== null && !port(source)) {
428      throw new TypeError("Expected a MessagePort or null");
429    }
430    eventPorts = portList(eventPorts, true);
431    if (isDispatching(this)) return;
432    initEvent(this, type, bubbles, cancelable);
433    const current = state(messageEvents, this);
434    current.data = data;
435    current.origin = origin as string;
436    current.lastEventId = lastEventId as string;
437    current.source = source;
438    current.ports = freezePorts(eventPorts as MessagePort[]);
439  }
440}
441
442class MessagePort extends EventTarget {
443  // @ts-expect-error Reflect.construct preserves new.target without a guest-replaceable super().
444  constructor(key: unknown = undefined) {
445    if (key !== secret) throw new TypeError("Illegal constructor");
446    const self = construct(EventTarget, [], new.target) as MessagePort;
447    set(ports, self, {
448      endpoint: null,
449      detached: false,
450      onmessage: null,
451      onmessageerror: null,
452      messageListener: null,
453      errorListener: null,
454    });
455    observeListeners(self, retainPort);
456    return self as MessagePort;
457  }
458  get onmessage() {
459    return state(ports, this).onmessage;
460  }
461  set onmessage(value) {
462    const current = state(ports, this);
463    value = typeof value === "function" ? value : null;
464    current.onmessage = value;
465    if (!value) {
466      if (current.messageListener) {
467        removeListener(this, "message", current.messageListener);
468      }
469      current.messageListener = null;
470    } else {
471      if (!current.messageListener) {
472        current.messageListener = (event: Event) => {
473          if (apply(current.onmessage!, this, [event]) === false) {
474            preventDefault(event);
475          }
476        };
477        addListener(this, "message", current.messageListener);
478      }
479      const endpoint = current.endpoint;
480      if (endpoint && !endpoint.closed) {
481        endpoint.started = true;
482        retainPort(this);
483        schedule(endpoint);
484      }
485    }
486  }
487
488  get onmessageerror() {
489    return state(ports, this).onmessageerror;
490  }
491  set onmessageerror(value) {
492    const current = state(ports, this);
493    value = typeof value === "function" ? value : null;
494    current.onmessageerror = value;
495    if (!value) {
496      if (current.errorListener) {
497        removeListener(this, "messageerror", current.errorListener);
498      }
499      current.errorListener = null;
500    } else if (!current.errorListener) {
501      current.errorListener = (event) => {
502        if (apply(current.onmessageerror!, this, [event]) === false) {
503          preventDefault(event);
504        }
505      };
506      addListener(this, "messageerror", current.errorListener);
507    }
508  }
509
510  postMessage(data: unknown, options: unknown = undefined): void {
511    required(arguments.length, 1);
512    const transfer = transferList(options);
513    const current = state(ports, this), endpoint = current.endpoint;
514    const peer = endpoint ? op_port_peer(endpoint.id) : null;
515    let doomed = false;
516    const messagePorts = array<MessagePort>();
517    for (let i = 0; i < transfer.length; i++) {
518      if (transfer[i] === this) {
519        throw dataCloneError("A MessagePort cannot be transferred to itself");
520      }
521      if (port(transfer[i])) {
522        if (state(ports, transfer[i]).endpoint?.id === peer) doomed = true;
523        push(messagePorts, transfer[i] as MessagePort);
524      }
525    }
526    const callbacks = transferCallbacks(transfer, dataCloneError, true);
527    try {
528      callbacks.prepare();
529      op_port_send(
530        doomed ? null : endpoint?.id ?? null,
531        { data, ports: messagePorts },
532        transfer,
533        classify,
534        callbacks.encodeHost,
535        dataCloneError,
536        callbacks.validate,
537        callbacks.commit,
538      );
539    } finally {
540      callbacks.clear();
541    }
542  }
543  start() {
544    startPort(this);
545  }
546  close() {
547    const current = state(ports, this), endpoint = current.endpoint;
548    if (!endpoint) return;
549    endpoint.closed = true;
550    endpoint.port = null;
551    op_port_close(endpoint.id);
552    unregisterFinalizer(portFinalizer, this);
553    current.endpoint = null;
554    retainPort(this);
555  }
556}
557
558class MessageChannel {
559  constructor() {
560    const ids = op_port_pair();
561    const port1 = adoptPort(ids[0]), port2 = adoptPort(ids[1]);
562    set(channels, this, { port1, port2 });
563  }
564  get port1() {
565    return state(channels, this).port1;
566  }
567  get port2() {
568    return state(channels, this).port2;
569  }
570}
571
572const messageData = getter(MessageEvent.prototype, "data");
573const messagePorts = getter(MessageEvent.prototype, "ports");
574
575function setPortCloseHandler(value: MessagePort, callback: () => void): void {
576  const endpoint = state(ports, value).endpoint;
577  if (endpoint) endpoint.onclose = callback;
578}
579function referencePort(value: MessagePort): Promise<boolean> {
580  const endpoint = state(ports, value).endpoint;
581  return op_port_wait_ref([endpoint!.id, endpoint!.epoch]);
582}
583export {
584  adoptPort,
585  dispatchMessage,
586  MessageChannel,
587  messageData,
588  MessageEvent,
589  MessagePort,
590  messagePorts,
591  port as isMessagePort,
592  referencePort,
593  setPortCloseHandler,
594  startPort,
595  transferList,
596};