MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include <stdlib.h>
2#include <string.h>
3#include <utarray.h>
4#include <uv.h>
5
6#include "ant.h"
7#include "errors.h"
8#include "internal.h"
9#include "runtime.h"
10#include "descriptors.h"
11
12#include "gc/modules.h"
13#include "modules/symbol.h"
14#include "modules/timer.h"
15#include "modules/abort.h"
16#include "modules/domexception.h"
17#include "silver/engine.h"
18
19typedef struct {
20 ant_value_t callback;
21 bool once;
22} abort_listener_t;
23
24typedef struct {
25 bool aborted;
26 bool fired;
27 ant_value_t reason;
28 UT_array *listeners;
29 UT_array *followers;
30} abort_signal_data_t;
31
32typedef struct abort_timeout_entry {
33 uv_timer_t handle;
34 ant_t *js;
35 ant_value_t signal;
36 int closed;
37 struct abort_timeout_entry *next;
38} abort_timeout_entry_t;
39
40static const UT_icd abort_listener_icd = { sizeof(abort_listener_t), NULL, NULL, NULL };
41static const UT_icd abort_value_icd = { sizeof(ant_value_t), NULL, NULL, NULL };
42
43static abort_timeout_entry_t *timeout_entries = NULL;
44static ant_value_t g_signal_proto = 0;
45static bool g_initialized = false;
46
47static inline unsigned int abort_array_len(UT_array *arr) {
48 return arr ? utarray_len(arr) : 0;
49}
50
51static abort_signal_data_t *get_signal_data(ant_value_t obj) {
52 ant_value_t slot = js_get_slot(obj, SLOT_DATA);
53 if (vtype(slot) != T_NUM) return NULL;
54 return (abort_signal_data_t *)(uintptr_t)js_getnum(slot);
55}
56
57static abort_signal_data_t *get_signal_data_if_signal_object(ant_value_t obj) {
58 if (!g_initialized || !is_object_type(obj)) return NULL;
59 if (!js_check_brand(obj, BRAND_ABORT_SIGNAL)) return NULL;
60 return get_signal_data(obj);
61}
62
63static ant_value_t make_abort_error(ant_t *js) {
64 return make_dom_exception(js, "signal is aborted without reason", "AbortError");
65}
66
67static ant_value_t make_timeout_error(ant_t *js) {
68 return make_dom_exception(js, "signal timed out", "TimeoutError");
69}
70
71static void signal_mark_aborted(ant_t *js, ant_value_t signal_obj, ant_value_t reason) {
72 abort_signal_data_t *data = get_signal_data(signal_obj);
73 if (!data || data->aborted) return;
74
75 data->aborted = true;
76 data->fired = true;
77 data->reason = reason;
78
79 js_set(js, signal_obj, "aborted", js_true);
80 js_set(js, signal_obj, "reason", reason);
81}
82
83void signal_do_abort(ant_t *js, ant_value_t signal_obj, ant_value_t reason) {
84 abort_signal_data_t *data = get_signal_data(signal_obj);
85 if (!data || data->aborted) return;
86
87 UT_array *queue = NULL;
88 UT_array *to_fire = NULL;
89
90 utarray_new(queue, &abort_value_icd);
91 utarray_new(to_fire, &abort_value_icd);
92
93 if (!queue || !to_fire) {
94 if (queue) utarray_free(queue);
95 if (to_fire) utarray_free(to_fire);
96 signal_mark_aborted(js, signal_obj, reason);
97 return;
98 }
99
100 utarray_push_back(queue, &signal_obj);
101
102 for (unsigned int qi = 0; qi < utarray_len(queue); qi++) {
103 ant_value_t *cur = (ant_value_t *)utarray_eltptr(queue, qi);
104 abort_signal_data_t *d = get_signal_data(*cur);
105 if (!d || d->aborted) continue;
106
107 d->aborted = true;
108 d->fired = true;
109 d->reason = reason;
110
111 js_set(js, *cur, "aborted", js_true);
112 js_set(js, *cur, "reason", reason);
113 utarray_push_back(to_fire, cur);
114
115 unsigned int nf = abort_array_len(d->followers);
116 for (unsigned int i = 0; i < nf; i++) {
117 ant_value_t *sig = (ant_value_t *)utarray_eltptr(d->followers, i);
118 utarray_push_back(queue, sig);
119 }}
120
121 utarray_free(queue);
122 for (unsigned int qi = 0; qi < utarray_len(to_fire); qi++) {
123 ant_value_t *cur = (ant_value_t *)utarray_eltptr(to_fire, qi);
124 abort_signal_data_t *d = get_signal_data(*cur);
125 if (!d) continue;
126
127 ant_value_t event_obj = js_mkobj(js);
128 js_set(js, event_obj, "type", js_mkstr(js, "abort", 5));
129 js_set(js, event_obj, "target", *cur);
130 ant_value_t call_args[1] = { event_obj };
131
132 ant_value_t onabort = js_get(js, *cur, "onabort");
133 if (is_callable(onabort)) {
134 sv_vm_call(js->vm, js, onabort, *cur, call_args, 1, NULL, false);
135 process_microtasks(js);
136 }
137
138 for (unsigned int i = 0;;) {
139 unsigned int n = abort_array_len(d->listeners);
140 if (i >= n) break;
141 abort_listener_t *entry = (abort_listener_t *)utarray_eltptr(d->listeners, i);
142
143 if (!entry) break;
144 ant_value_t cb = entry->callback;
145 bool once = entry->once;
146
147 if (once) { utarray_erase(d->listeners, i, 1); n--; } else i++;
148 if (!is_callable(cb)) continue;
149
150 sv_vm_call(js->vm, js, cb, *cur, call_args, 1, NULL, false);
151 process_microtasks(js);
152 }}
153
154 utarray_free(to_fire);
155}
156
157void abort_signal_remove_listener(ant_t *js, ant_value_t signal, ant_value_t callback) {
158 abort_signal_data_t *data = get_signal_data_if_signal_object(signal);
159 if (!data) return;
160
161 unsigned int n = abort_array_len(data->listeners);
162 for (unsigned int i = 0; i < n; i++) {
163 abort_listener_t *entry = (abort_listener_t *)utarray_eltptr(data->listeners, i);
164 if (entry->callback != callback) continue;
165 utarray_erase(data->listeners, i, 1);
166 return;
167 }
168}
169
170static ant_value_t make_new_signal(ant_t *js) {
171 abort_signal_data_t *data = ant_calloc(sizeof(abort_signal_data_t));
172 if (!data) return js_mkerr(js, "AbortSignal: out of memory");
173
174 data->aborted = false;
175 data->fired = false;
176 data->reason = js_mkundef();
177
178 utarray_new(data->listeners, &abort_listener_icd);
179 utarray_new(data->followers, &abort_value_icd);
180
181 if (!data->listeners || !data->followers) {
182 if (data->listeners) utarray_free(data->listeners);
183 if (data->followers) utarray_free(data->followers);
184 free(data);
185 return js_mkerr(js, "AbortSignal: out of memory");
186 }
187
188 ant_value_t obj = js_mkobj(js);
189 js_set_slot(obj, SLOT_DATA, ANT_PTR(data));
190 js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_ABORT_SIGNAL));
191 if (g_initialized) js_set_slot_wb(js, obj, SLOT_PROTO, g_signal_proto);
192
193 js_set(js, obj, "aborted", js_false);
194 js_set(js, obj, "reason", js_mkundef());
195 js_set(js, obj, "onabort", js_mkundef());
196 js_set_sym(js, obj, get_toStringTag_sym(), js_mkstr(js, "AbortSignal", 11));
197
198 return obj;
199}
200
201// signal.addEventListener(type, listener, options?)
202static ant_value_t abort_signal_add_event_listener(ant_t *js, ant_value_t *args, int nargs) {
203 if (nargs < 2) return js_mkundef();
204
205 const char *type = js_getstr(js, args[0], NULL);
206 if (!type || strcmp(type, "abort") != 0) return js_mkundef();
207 if (!is_callable(args[1])) return js_mkundef();
208
209 abort_signal_data_t *data = get_signal_data(js_getthis(js));
210 if (!data) return js_mkundef();
211 if (data->aborted) return js_mkundef();
212
213 bool once = false;
214 if (nargs >= 3 && vtype(args[2]) == T_OBJ) {
215 ant_value_t once_val = js_get(js, args[2], "once");
216 if (vtype(once_val) != T_UNDEF) once = js_truthy(js, once_val);
217 } else if (nargs >= 3 && vtype(args[2]) == T_BOOL) once = js_truthy(js, args[2]);
218
219 unsigned int n = abort_array_len(data->listeners);
220 for (unsigned int i = 0; i < n; i++) {
221 abort_listener_t *e = (abort_listener_t *)utarray_eltptr(data->listeners, i);
222 if (e->callback == args[1] && e->once == once) return js_mkundef();
223 }
224
225 abort_listener_t entry = { args[1], once };
226 if (data->listeners) utarray_push_back(data->listeners, &entry);
227
228 return js_mkundef();
229}
230
231// signal.removeEventListener(type, listener)
232static ant_value_t abort_signal_remove_event_listener(ant_t *js, ant_value_t *args, int nargs) {
233 if (nargs < 2) return js_mkundef();
234
235 const char *type = js_getstr(js, args[0], NULL);
236 if (!type || strcmp(type, "abort") != 0) return js_mkundef();
237
238 abort_signal_data_t *data = get_signal_data(js_getthis(js));
239 if (!data) return js_mkundef();
240
241 unsigned int n = abort_array_len(data->listeners);
242 for (unsigned int i = 0; i < n; i++) {
243 abort_listener_t *e = (abort_listener_t *)utarray_eltptr(data->listeners, i);
244 if (e->callback != args[1]) continue;
245 utarray_erase(data->listeners, i, 1);
246 return js_mkundef();
247 }
248
249 return js_mkundef();
250}
251
252// signal.dispatchEvent(event)
253static ant_value_t abort_signal_dispatch_event(ant_t *js, ant_value_t *args, int nargs) {
254 if (nargs < 1) return js_false;
255
256 const char *type = NULL;
257 if (vtype(args[0]) == T_OBJ) type = js_getstr(js, js_get(js, args[0], "type"), NULL);
258 else type = js_getstr(js, args[0], NULL);
259
260 if (!type || strcmp(type, "abort") != 0) return js_true;
261
262 abort_signal_data_t *data = get_signal_data(js_getthis(js));
263 if (!data || data->fired) return js_true;
264
265 signal_do_abort(js, js_getthis(js), data->reason);
266 return js_true;
267}
268
269// signal.throwIfAborted()
270static ant_value_t abort_signal_throw_if_aborted(ant_t *js, ant_value_t *args, int nargs) {
271 abort_signal_data_t *data = get_signal_data(js_getthis(js));
272 if (!data || !data->aborted) return js_mkundef();
273 return js_throw(js, data->reason);
274}
275
276// AbortSignal.abort(reason?)
277static ant_value_t abort_signal_static_abort(ant_t *js, ant_value_t *args, int nargs) {
278 ant_value_t reason = (nargs >= 1 && vtype(args[0]) != T_UNDEF)
279 ? args[0]
280 : make_abort_error(js);
281
282 ant_value_t signal = make_new_signal(js);
283 if (is_err(signal)) return signal;
284
285 signal_mark_aborted(js, signal, reason);
286 return signal;
287}
288
289// AbortSignal.any(signals)
290static ant_value_t abort_signal_static_any(ant_t *js, ant_value_t *args, int nargs) {
291 if (nargs < 1 || vtype(args[0]) != T_ARR)
292 return js_mkerr(js, "AbortSignal.any: argument must be an array of AbortSignal objects");
293
294 ant_value_t composite = make_new_signal(js);
295 if (is_err(composite)) return composite;
296 ant_offset_t len = js_arr_len(js, args[0]);
297
298 for (ant_offset_t i = 0; i < len; i++) {
299 ant_value_t sig = js_arr_get(js, args[0], i);
300 abort_signal_data_t *d = get_signal_data(sig);
301 if (d && d->aborted) {
302 signal_mark_aborted(js, composite, d->reason);
303 return composite;
304 }}
305
306 for (ant_offset_t i = 0; i < len; i++) {
307 ant_value_t sig = js_arr_get(js, args[0], i);
308 abort_signal_data_t *d = get_signal_data(sig);
309 if (!d) continue;
310 if (d->followers) utarray_push_back(d->followers, &composite);
311 }
312
313 return composite;
314}
315
316ant_value_t abort_signal_create_dependent(ant_t *js, ant_value_t source) {
317 ant_value_t composite = make_new_signal(js);
318
319 if (is_err(composite)) return composite;
320 if (vtype(source) != T_OBJ && vtype(source) != T_ARR) return composite;
321
322 abort_signal_data_t *d = get_signal_data(source);
323 if (!d) return composite;
324
325 if (d->aborted) signal_mark_aborted(js, composite, d->reason);
326 else if (d->followers) utarray_push_back(d->followers, &composite);
327
328 return composite;
329}
330
331static void abort_timeout_close_cb(uv_handle_t *h) {
332 abort_timeout_entry_t *entry = (abort_timeout_entry_t *)h->data;
333 if (entry) entry->closed = 1;
334}
335
336static void abort_timeout_fire_cb(uv_timer_t *handle) {
337 abort_timeout_entry_t *entry = (abort_timeout_entry_t *)handle->data;
338 if (!entry || entry->closed) return;
339
340 ant_t *js = entry->js;
341 signal_do_abort(js, entry->signal, make_timeout_error(js));
342 process_microtasks(js);
343
344 if (!uv_is_closing((uv_handle_t *)handle))
345 uv_close((uv_handle_t *)handle, abort_timeout_close_cb);
346}
347
348// AbortSignal.timeout(milliseconds)
349static ant_value_t abort_signal_static_timeout(ant_t *js, ant_value_t *args, int nargs) {
350 if (nargs < 1) return js_mkerr(js, "AbortSignal.timeout: milliseconds argument required");
351
352 double ms = js_getnum(args[0]);
353 if (ms < 0) return js_mkerr(js, "AbortSignal.timeout: milliseconds must be non-negative");
354
355 ant_value_t signal = make_new_signal(js);
356 if (is_err(signal)) return signal;
357
358 abort_timeout_entry_t *entry = ant_calloc(sizeof(abort_timeout_entry_t));
359 if (!entry) return js_mkerr(js, "AbortSignal.timeout: out of memory");
360
361 entry->js = js;
362 entry->signal = signal;
363 entry->closed = 0;
364 entry->next = timeout_entries;
365 timeout_entries = entry;
366
367 uv_timer_init(uv_default_loop(), &entry->handle);
368 entry->handle.data = entry;
369 uv_timer_start(&entry->handle, abort_timeout_fire_cb, (uint64_t)(ms > 0 ? ms : 0), 0);
370
371 return signal;
372}
373
374// new AbortController()
375static ant_value_t abort_controller_ctor(ant_t *js, ant_value_t *args, int nargs) {
376 ant_value_t this_obj = js_getthis(js);
377
378 ant_value_t signal = make_new_signal(js);
379 if (is_err(signal)) return signal;
380
381 js_mkprop_fast(js, this_obj, "signal", 6, signal);
382 js_set_descriptor(js, this_obj, "signal", 6, 0);
383 js_set_sym(js, this_obj, get_toStringTag_sym(), js_mkstr(js, "AbortController", 15));
384
385 return js_mkundef();
386}
387
388// controller.abort(reason?)
389static ant_value_t abort_controller_abort(ant_t *js, ant_value_t *args, int nargs) {
390 ant_value_t signal = js_get(js, js_getthis(js), "signal");
391
392 abort_signal_data_t *data = get_signal_data(signal);
393 if (!data || data->aborted) return js_mkundef();
394
395 ant_value_t reason = (nargs >= 1 && vtype(args[0]) != T_UNDEF)
396 ? args[0]
397 : make_abort_error(js);
398
399 signal_do_abort(js, signal, reason);
400 return js_mkundef();
401}
402
403void init_abort_module(void) {
404 ant_t *js = rt->js;
405 ant_value_t global = js_glob(js);
406
407 ant_value_t signal_proto = js_mkobj(js);
408 g_signal_proto = signal_proto;
409 g_initialized = true;
410
411 js_set(js, signal_proto, "addEventListener", js_mkfun(abort_signal_add_event_listener));
412 js_set(js, signal_proto, "removeEventListener", js_mkfun(abort_signal_remove_event_listener));
413 js_set(js, signal_proto, "dispatchEvent", js_mkfun(abort_signal_dispatch_event));
414 js_set(js, signal_proto, "throwIfAborted", js_mkfun(abort_signal_throw_if_aborted));
415 js_set_sym(js, signal_proto, get_toStringTag_sym(), js_mkstr(js, "AbortSignal", 11));
416
417 ant_value_t signal_ctor = js_mkobj(js);
418 js_mkprop_fast(js, signal_ctor, "prototype", 9, signal_proto);
419 js_mkprop_fast(js, signal_ctor, "name", 4, ANT_STRING("AbortSignal"));
420 js_set_descriptor(js, signal_ctor, "name", 4, 0);
421
422 ant_value_t signal_fn = js_obj_to_func_ex(signal_ctor, SV_CALL_IS_DEFAULT_CTOR);
423 js_set(js, signal_fn, "abort", js_mkfun(abort_signal_static_abort));
424 js_set(js, signal_fn, "timeout", js_mkfun(abort_signal_static_timeout));
425 js_set(js, signal_fn, "any", js_mkfun(abort_signal_static_any));
426
427 js_set(js, signal_proto, "constructor", signal_fn);
428 js_set_descriptor(js, signal_proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
429
430 ant_value_t ctrl_proto = js_mkobj(js);
431 js_set(js, ctrl_proto, "abort", js_mkfun(abort_controller_abort));
432 js_set_sym(js, ctrl_proto, get_toStringTag_sym(), js_mkstr(js, "AbortController", 15));
433
434 ant_value_t ctrl_ctor = js_mkobj(js);
435 js_set_slot(ctrl_ctor, SLOT_CFUNC, js_mkfun(abort_controller_ctor));
436 js_mkprop_fast(js, ctrl_ctor, "prototype", 9, ctrl_proto);
437 js_mkprop_fast(js, ctrl_ctor, "name", 4, ANT_STRING("AbortController"));
438 js_set_descriptor(js, ctrl_ctor, "name", 4, 0);
439
440 ant_value_t ctrl_fn = js_obj_to_func(ctrl_ctor);
441 js_set(js, ctrl_proto, "constructor", ctrl_fn);
442 js_set_descriptor(js, ctrl_proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
443
444 js_set(js, global, "AbortController", ctrl_fn);
445 js_set(js, global, "AbortSignal", signal_fn);
446}
447
448void gc_mark_abort(ant_t *js, gc_mark_fn mark) {
449 if (g_initialized) mark(js, g_signal_proto);
450 for (abort_timeout_entry_t *e = timeout_entries; e; e = e->next)
451 if (!e->closed) mark(js, e->signal);
452}
453
454void gc_mark_abort_signal_object(ant_t *js, ant_value_t signal, gc_mark_fn mark) {
455 abort_signal_data_t *data = get_signal_data_if_signal_object(signal);
456
457 if (!data) return;
458 mark(js, data->reason);
459
460 unsigned int listener_count = abort_array_len(data->listeners);
461 for (unsigned int i = 0; i < listener_count; i++) {
462 abort_listener_t *entry = (abort_listener_t *)utarray_eltptr(data->listeners, i);
463 if (!entry) continue;
464 mark(js, entry->callback);
465 }
466
467 unsigned int follower_count = abort_array_len(data->followers);
468 for (unsigned int i = 0; i < follower_count; i++) {
469 ant_value_t *follower = (ant_value_t *)utarray_eltptr(data->followers, i);
470 if (!follower) continue;
471 mark(js, *follower);
472 }
473}
474
475bool abort_signal_is_aborted(ant_value_t signal) {
476 abort_signal_data_t *data = get_signal_data_if_signal_object(signal);
477 return data && data->aborted;
478}
479
480bool abort_signal_is_signal(ant_value_t signal) {
481 return get_signal_data_if_signal_object(signal) != NULL;
482}
483
484ant_value_t abort_signal_get_reason(ant_value_t signal) {
485 abort_signal_data_t *data = get_signal_data_if_signal_object(signal);
486 return data ? data->reason : js_mkundef();
487}
488
489void abort_signal_add_listener(ant_t *js, ant_value_t signal, ant_value_t callback) {
490 abort_signal_data_t *data = get_signal_data_if_signal_object(signal);
491 if (!data) return;
492
493 if (data->aborted) {
494 ant_value_t event_obj = js_mkobj(js);
495 js_set(js, event_obj, "type", js_mkstr(js, "abort", 5));
496 js_set(js, event_obj, "target", signal);
497 ant_value_t call_args[1] = { event_obj };
498 sv_vm_call(js->vm, js, callback, signal, call_args, 1, NULL, false);
499 return;
500 }
501
502 abort_listener_t entry = { callback, false };
503 if (data->listeners) utarray_push_back(data->listeners, &entry);
504}