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
5.1 KiB185 linesraw
1import {
2  FinalizationRegistry,
3  ObjectCreate,
4  ReflectApply as apply,
5  ReflectConstruct as construct,
6  Set,
7  SetPrototypeAdd as retain,
8  SetPrototypeDelete as release,
9  uncurryThis,
10  WeakMap,
11  WeakMapPrototypeSet as set,
12} from "ext:core/primordials";
13import {
14  op_broadcast_close,
15  op_broadcast_open,
16  op_broadcast_send,
17} from "ext:core/ops";
18import { array, required, state, string } from "./idl.js";
19import {
20  addListener,
21  createException,
22  dataCloneError,
23  dispatch,
24  type Event,
25  EventTarget,
26  hasListener,
27  observeListeners,
28  preventDefault,
29  removeListener,
30} from "./events.js";
31import {
32  adoptPort,
33  dispatchMessage,
34  messageData,
35  MessageEvent,
36  MessagePort,
37  startPort,
38} from "./messaging.js";
39import {
40  classify,
41  registerUncloneable,
42  transferCallbacks,
43} from "./serialization.js";
44
45interface BroadcastState {
46  __proto__?: null;
47  name: string;
48  id: number;
49  port: MessagePort;
50  closed: boolean;
51  onmessage?: ((event: Event) => unknown) | null;
52  onmessageerror?: ((event: Event) => unknown) | null;
53  onmessageListener?: ((event: Event) => void) | null;
54  onmessageerrorListener?: ((event: Event) => void) | null;
55}
56const channels = new WeakMap<object, BroadcastState>();
57registerUncloneable(channels);
58const closePort = MessagePort.prototype.close;
59const WeakRef = globalThis.WeakRef,
60  deref = uncurryThis(WeakRef.prototype.deref);
61const register = uncurryThis(FinalizationRegistry.prototype.register),
62  unregister = uncurryThis(FinalizationRegistry.prototype.unregister);
63const activeChannels = new Set<object>();
64const finalizer = new FinalizationRegistry<{ id: number; port: MessagePort }>(
65  ({ id, port }) => {
66    apply(closePort, port, []);
67    op_broadcast_close(id);
68  },
69);
70function retainChannel(target: object): void {
71  if (
72    !state(channels, target).closed &&
73    (hasListener(target, "message") || hasListener(target, "messageerror"))
74  ) {
75    retain(activeChannels, target);
76  } else release(activeChannels, target);
77}
78
79function handler(
80  target: BroadcastChannel,
81  current: BroadcastState,
82  type: "message" | "messageerror",
83  value: unknown,
84): void {
85  const name = "on" + type as "onmessage" | "onmessageerror",
86    key = name + "Listener" as "onmessageListener" | "onmessageerrorListener";
87  value = typeof value === "function" ? value : null;
88  current[name] = value as ((event: Event) => unknown) | null;
89  if (!value && current[key]) {
90    removeListener(target, type, current[key]);
91    current[key] = null;
92  } else if (value && !current[key]) {
93    current[key] = (event) => {
94      if (current[name] && apply(current[name]!, target, [event]) === false) {
95        preventDefault(event);
96      }
97    };
98    addListener(target, type, current[key]);
99  }
100}
101
102class BroadcastChannel extends EventTarget {
103  // @ts-expect-error Reflect.construct preserves new.target without a guest-replaceable super().
104  constructor(name: unknown) {
105    required(arguments.length, 1);
106    name = string(name);
107    const self = construct(EventTarget, [], new.target) as BroadcastChannel;
108    const id = op_broadcast_open(name as string), port = adoptPort(id);
109    const current: BroadcastState = {
110      __proto__: null,
111      name: name as string,
112      id,
113      port,
114      closed: false,
115    };
116    set(channels, self, current);
117    observeListeners(self, retainChannel);
118    const weak = new WeakRef(self);
119    addListener(port, "message", (event: Event) => {
120      const target = deref(weak);
121      if (target && !state(channels, target).closed) {
122        dispatchMessage(target, messageData(event));
123      }
124    });
125    addListener(port, "messageerror", () => {
126      const target = deref(weak);
127      if (target && !state(channels, target).closed) {
128        dispatch(target, new MessageEvent("messageerror", ObjectCreate(null)));
129      }
130    });
131    register(finalizer, self, { id, port }, self);
132    startPort(port);
133    return self as BroadcastChannel;
134  }
135  get name() {
136    return state(channels, this).name;
137  }
138  postMessage(message: unknown): void {
139    const current = state(channels, this);
140    required(arguments.length, 1);
141    if (current.closed) {
142      throw createException({
143        name: "InvalidStateError",
144        message: "BroadcastChannel is closed",
145      });
146    }
147    const callbacks = transferCallbacks(array(), dataCloneError, true);
148    try {
149      op_broadcast_send(
150        current.id,
151        { __proto__: null, data: message, ports: [] },
152        classify,
153        callbacks.encodeHost,
154        dataCloneError,
155        callbacks.validate,
156        callbacks.commit,
157      );
158    } finally {
159      callbacks.clear();
160    }
161  }
162  close() {
163    const current = state(channels, this);
164    if (current.closed) return;
165    current.closed = true;
166    retainChannel(this);
167    unregister(finalizer, this);
168    apply(closePort, current.port, []);
169    op_broadcast_close(current.id);
170  }
171  get onmessage() {
172    return state(channels, this).onmessage ?? null;
173  }
174  set onmessage(value) {
175    handler(this, state(channels, this), "message", value);
176  }
177  get onmessageerror() {
178    return state(channels, this).onmessageerror ?? null;
179  }
180  set onmessageerror(value) {
181    handler(this, state(channels, this), "messageerror", value);
182  }
183}
184
185export { BroadcastChannel };