#include #include #include #include #include "ant.h" #include "errors.h" #include "internal.h" #include "runtime.h" #include "descriptors.h" #include "gc/modules.h" #include "modules/symbol.h" #include "modules/timer.h" #include "modules/abort.h" #include "modules/domexception.h" #include "silver/engine.h" typedef struct { ant_value_t callback; bool once; } abort_listener_t; typedef struct { bool aborted; bool fired; ant_value_t reason; UT_array *listeners; UT_array *followers; } abort_signal_data_t; typedef struct abort_timeout_entry { uv_timer_t handle; ant_t *js; ant_value_t signal; int closed; struct abort_timeout_entry *next; } abort_timeout_entry_t; static const UT_icd abort_listener_icd = { sizeof(abort_listener_t), NULL, NULL, NULL }; static const UT_icd abort_value_icd = { sizeof(ant_value_t), NULL, NULL, NULL }; static abort_timeout_entry_t *timeout_entries = NULL; static ant_value_t g_signal_proto = 0; static bool g_initialized = false; static inline unsigned int abort_array_len(UT_array *arr) { return arr ? utarray_len(arr) : 0; } static abort_signal_data_t *get_signal_data(ant_value_t obj) { ant_value_t slot = js_get_slot(obj, SLOT_DATA); if (vtype(slot) != T_NUM) return NULL; return (abort_signal_data_t *)(uintptr_t)js_getnum(slot); } static abort_signal_data_t *get_signal_data_if_signal_object(ant_value_t obj) { if (!g_initialized || !is_object_type(obj)) return NULL; if (!js_check_brand(obj, BRAND_ABORT_SIGNAL)) return NULL; return get_signal_data(obj); } static ant_value_t make_abort_error(ant_t *js) { return make_dom_exception(js, "signal is aborted without reason", "AbortError"); } static ant_value_t make_timeout_error(ant_t *js) { return make_dom_exception(js, "signal timed out", "TimeoutError"); } static void signal_mark_aborted(ant_t *js, ant_value_t signal_obj, ant_value_t reason) { abort_signal_data_t *data = get_signal_data(signal_obj); if (!data || data->aborted) return; data->aborted = true; data->fired = true; data->reason = reason; js_set(js, signal_obj, "aborted", js_true); js_set(js, signal_obj, "reason", reason); } void signal_do_abort(ant_t *js, ant_value_t signal_obj, ant_value_t reason) { abort_signal_data_t *data = get_signal_data(signal_obj); if (!data || data->aborted) return; UT_array *queue = NULL; UT_array *to_fire = NULL; utarray_new(queue, &abort_value_icd); utarray_new(to_fire, &abort_value_icd); if (!queue || !to_fire) { if (queue) utarray_free(queue); if (to_fire) utarray_free(to_fire); signal_mark_aborted(js, signal_obj, reason); return; } utarray_push_back(queue, &signal_obj); for (unsigned int qi = 0; qi < utarray_len(queue); qi++) { ant_value_t *cur = (ant_value_t *)utarray_eltptr(queue, qi); abort_signal_data_t *d = get_signal_data(*cur); if (!d || d->aborted) continue; d->aborted = true; d->fired = true; d->reason = reason; js_set(js, *cur, "aborted", js_true); js_set(js, *cur, "reason", reason); utarray_push_back(to_fire, cur); unsigned int nf = abort_array_len(d->followers); for (unsigned int i = 0; i < nf; i++) { ant_value_t *sig = (ant_value_t *)utarray_eltptr(d->followers, i); utarray_push_back(queue, sig); }} utarray_free(queue); for (unsigned int qi = 0; qi < utarray_len(to_fire); qi++) { ant_value_t *cur = (ant_value_t *)utarray_eltptr(to_fire, qi); abort_signal_data_t *d = get_signal_data(*cur); if (!d) continue; ant_value_t event_obj = js_mkobj(js); js_set(js, event_obj, "type", js_mkstr(js, "abort", 5)); js_set(js, event_obj, "target", *cur); ant_value_t call_args[1] = { event_obj }; ant_value_t onabort = js_get(js, *cur, "onabort"); if (is_callable(onabort)) { sv_vm_call(js->vm, js, onabort, *cur, call_args, 1, NULL, false); process_microtasks(js); } for (unsigned int i = 0;;) { unsigned int n = abort_array_len(d->listeners); if (i >= n) break; abort_listener_t *entry = (abort_listener_t *)utarray_eltptr(d->listeners, i); if (!entry) break; ant_value_t cb = entry->callback; bool once = entry->once; if (once) { utarray_erase(d->listeners, i, 1); n--; } else i++; if (!is_callable(cb)) continue; sv_vm_call(js->vm, js, cb, *cur, call_args, 1, NULL, false); process_microtasks(js); }} utarray_free(to_fire); } void abort_signal_remove_listener(ant_t *js, ant_value_t signal, ant_value_t callback) { abort_signal_data_t *data = get_signal_data_if_signal_object(signal); if (!data) return; unsigned int n = abort_array_len(data->listeners); for (unsigned int i = 0; i < n; i++) { abort_listener_t *entry = (abort_listener_t *)utarray_eltptr(data->listeners, i); if (entry->callback != callback) continue; utarray_erase(data->listeners, i, 1); return; } } static ant_value_t make_new_signal(ant_t *js) { abort_signal_data_t *data = ant_calloc(sizeof(abort_signal_data_t)); if (!data) return js_mkerr(js, "AbortSignal: out of memory"); data->aborted = false; data->fired = false; data->reason = js_mkundef(); utarray_new(data->listeners, &abort_listener_icd); utarray_new(data->followers, &abort_value_icd); if (!data->listeners || !data->followers) { if (data->listeners) utarray_free(data->listeners); if (data->followers) utarray_free(data->followers); free(data); return js_mkerr(js, "AbortSignal: out of memory"); } ant_value_t obj = js_mkobj(js); js_set_slot(obj, SLOT_DATA, ANT_PTR(data)); js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_ABORT_SIGNAL)); if (g_initialized) js_set_slot_wb(js, obj, SLOT_PROTO, g_signal_proto); js_set(js, obj, "aborted", js_false); js_set(js, obj, "reason", js_mkundef()); js_set(js, obj, "onabort", js_mkundef()); js_set_sym(js, obj, get_toStringTag_sym(), js_mkstr(js, "AbortSignal", 11)); return obj; } // signal.addEventListener(type, listener, options?) static ant_value_t abort_signal_add_event_listener(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 2) return js_mkundef(); const char *type = js_getstr(js, args[0], NULL); if (!type || strcmp(type, "abort") != 0) return js_mkundef(); if (!is_callable(args[1])) return js_mkundef(); abort_signal_data_t *data = get_signal_data(js_getthis(js)); if (!data) return js_mkundef(); if (data->aborted) return js_mkundef(); bool once = false; if (nargs >= 3 && vtype(args[2]) == T_OBJ) { ant_value_t once_val = js_get(js, args[2], "once"); if (vtype(once_val) != T_UNDEF) once = js_truthy(js, once_val); } else if (nargs >= 3 && vtype(args[2]) == T_BOOL) once = js_truthy(js, args[2]); unsigned int n = abort_array_len(data->listeners); for (unsigned int i = 0; i < n; i++) { abort_listener_t *e = (abort_listener_t *)utarray_eltptr(data->listeners, i); if (e->callback == args[1] && e->once == once) return js_mkundef(); } abort_listener_t entry = { args[1], once }; if (data->listeners) utarray_push_back(data->listeners, &entry); return js_mkundef(); } // signal.removeEventListener(type, listener) static ant_value_t abort_signal_remove_event_listener(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 2) return js_mkundef(); const char *type = js_getstr(js, args[0], NULL); if (!type || strcmp(type, "abort") != 0) return js_mkundef(); abort_signal_data_t *data = get_signal_data(js_getthis(js)); if (!data) return js_mkundef(); unsigned int n = abort_array_len(data->listeners); for (unsigned int i = 0; i < n; i++) { abort_listener_t *e = (abort_listener_t *)utarray_eltptr(data->listeners, i); if (e->callback != args[1]) continue; utarray_erase(data->listeners, i, 1); return js_mkundef(); } return js_mkundef(); } // signal.dispatchEvent(event) static ant_value_t abort_signal_dispatch_event(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_false; const char *type = NULL; if (vtype(args[0]) == T_OBJ) type = js_getstr(js, js_get(js, args[0], "type"), NULL); else type = js_getstr(js, args[0], NULL); if (!type || strcmp(type, "abort") != 0) return js_true; abort_signal_data_t *data = get_signal_data(js_getthis(js)); if (!data || data->fired) return js_true; signal_do_abort(js, js_getthis(js), data->reason); return js_true; } // signal.throwIfAborted() static ant_value_t abort_signal_throw_if_aborted(ant_t *js, ant_value_t *args, int nargs) { abort_signal_data_t *data = get_signal_data(js_getthis(js)); if (!data || !data->aborted) return js_mkundef(); return js_throw(js, data->reason); } // AbortSignal.abort(reason?) static ant_value_t abort_signal_static_abort(ant_t *js, ant_value_t *args, int nargs) { ant_value_t reason = (nargs >= 1 && vtype(args[0]) != T_UNDEF) ? args[0] : make_abort_error(js); ant_value_t signal = make_new_signal(js); if (is_err(signal)) return signal; signal_mark_aborted(js, signal, reason); return signal; } // AbortSignal.any(signals) static ant_value_t abort_signal_static_any(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1 || vtype(args[0]) != T_ARR) return js_mkerr(js, "AbortSignal.any: argument must be an array of AbortSignal objects"); ant_value_t composite = make_new_signal(js); if (is_err(composite)) return composite; ant_offset_t len = js_arr_len(js, args[0]); for (ant_offset_t i = 0; i < len; i++) { ant_value_t sig = js_arr_get(js, args[0], i); abort_signal_data_t *d = get_signal_data(sig); if (d && d->aborted) { signal_mark_aborted(js, composite, d->reason); return composite; }} for (ant_offset_t i = 0; i < len; i++) { ant_value_t sig = js_arr_get(js, args[0], i); abort_signal_data_t *d = get_signal_data(sig); if (!d) continue; if (d->followers) utarray_push_back(d->followers, &composite); } return composite; } ant_value_t abort_signal_create_dependent(ant_t *js, ant_value_t source) { ant_value_t composite = make_new_signal(js); if (is_err(composite)) return composite; if (vtype(source) != T_OBJ && vtype(source) != T_ARR) return composite; abort_signal_data_t *d = get_signal_data(source); if (!d) return composite; if (d->aborted) signal_mark_aborted(js, composite, d->reason); else if (d->followers) utarray_push_back(d->followers, &composite); return composite; } static void abort_timeout_close_cb(uv_handle_t *h) { abort_timeout_entry_t *entry = (abort_timeout_entry_t *)h->data; if (entry) entry->closed = 1; } static void abort_timeout_fire_cb(uv_timer_t *handle) { abort_timeout_entry_t *entry = (abort_timeout_entry_t *)handle->data; if (!entry || entry->closed) return; ant_t *js = entry->js; signal_do_abort(js, entry->signal, make_timeout_error(js)); process_microtasks(js); if (!uv_is_closing((uv_handle_t *)handle)) uv_close((uv_handle_t *)handle, abort_timeout_close_cb); } // AbortSignal.timeout(milliseconds) static ant_value_t abort_signal_static_timeout(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkerr(js, "AbortSignal.timeout: milliseconds argument required"); double ms = js_getnum(args[0]); if (ms < 0) return js_mkerr(js, "AbortSignal.timeout: milliseconds must be non-negative"); ant_value_t signal = make_new_signal(js); if (is_err(signal)) return signal; abort_timeout_entry_t *entry = ant_calloc(sizeof(abort_timeout_entry_t)); if (!entry) return js_mkerr(js, "AbortSignal.timeout: out of memory"); entry->js = js; entry->signal = signal; entry->closed = 0; entry->next = timeout_entries; timeout_entries = entry; uv_timer_init(uv_default_loop(), &entry->handle); entry->handle.data = entry; uv_timer_start(&entry->handle, abort_timeout_fire_cb, (uint64_t)(ms > 0 ? ms : 0), 0); return signal; } // new AbortController() static ant_value_t abort_controller_ctor(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); ant_value_t signal = make_new_signal(js); if (is_err(signal)) return signal; js_mkprop_fast(js, this_obj, "signal", 6, signal); js_set_descriptor(js, this_obj, "signal", 6, 0); js_set_sym(js, this_obj, get_toStringTag_sym(), js_mkstr(js, "AbortController", 15)); return js_mkundef(); } // controller.abort(reason?) static ant_value_t abort_controller_abort(ant_t *js, ant_value_t *args, int nargs) { ant_value_t signal = js_get(js, js_getthis(js), "signal"); abort_signal_data_t *data = get_signal_data(signal); if (!data || data->aborted) return js_mkundef(); ant_value_t reason = (nargs >= 1 && vtype(args[0]) != T_UNDEF) ? args[0] : make_abort_error(js); signal_do_abort(js, signal, reason); return js_mkundef(); } void init_abort_module(void) { ant_t *js = rt->js; ant_value_t global = js_glob(js); ant_value_t signal_proto = js_mkobj(js); g_signal_proto = signal_proto; g_initialized = true; js_set(js, signal_proto, "addEventListener", js_mkfun(abort_signal_add_event_listener)); js_set(js, signal_proto, "removeEventListener", js_mkfun(abort_signal_remove_event_listener)); js_set(js, signal_proto, "dispatchEvent", js_mkfun(abort_signal_dispatch_event)); js_set(js, signal_proto, "throwIfAborted", js_mkfun(abort_signal_throw_if_aborted)); js_set_sym(js, signal_proto, get_toStringTag_sym(), js_mkstr(js, "AbortSignal", 11)); ant_value_t signal_ctor = js_mkobj(js); js_mkprop_fast(js, signal_ctor, "prototype", 9, signal_proto); js_mkprop_fast(js, signal_ctor, "name", 4, ANT_STRING("AbortSignal")); js_set_descriptor(js, signal_ctor, "name", 4, 0); ant_value_t signal_fn = js_obj_to_func_ex(signal_ctor, SV_CALL_IS_DEFAULT_CTOR); js_set(js, signal_fn, "abort", js_mkfun(abort_signal_static_abort)); js_set(js, signal_fn, "timeout", js_mkfun(abort_signal_static_timeout)); js_set(js, signal_fn, "any", js_mkfun(abort_signal_static_any)); js_set(js, signal_proto, "constructor", signal_fn); js_set_descriptor(js, signal_proto, "constructor", 11, JS_DESC_W | JS_DESC_C); ant_value_t ctrl_proto = js_mkobj(js); js_set(js, ctrl_proto, "abort", js_mkfun(abort_controller_abort)); js_set_sym(js, ctrl_proto, get_toStringTag_sym(), js_mkstr(js, "AbortController", 15)); ant_value_t ctrl_ctor = js_mkobj(js); js_set_slot(ctrl_ctor, SLOT_CFUNC, js_mkfun(abort_controller_ctor)); js_mkprop_fast(js, ctrl_ctor, "prototype", 9, ctrl_proto); js_mkprop_fast(js, ctrl_ctor, "name", 4, ANT_STRING("AbortController")); js_set_descriptor(js, ctrl_ctor, "name", 4, 0); ant_value_t ctrl_fn = js_obj_to_func(ctrl_ctor); js_set(js, ctrl_proto, "constructor", ctrl_fn); js_set_descriptor(js, ctrl_proto, "constructor", 11, JS_DESC_W | JS_DESC_C); js_set(js, global, "AbortController", ctrl_fn); js_set(js, global, "AbortSignal", signal_fn); } void gc_mark_abort(ant_t *js, gc_mark_fn mark) { if (g_initialized) mark(js, g_signal_proto); for (abort_timeout_entry_t *e = timeout_entries; e; e = e->next) if (!e->closed) mark(js, e->signal); } void gc_mark_abort_signal_object(ant_t *js, ant_value_t signal, gc_mark_fn mark) { abort_signal_data_t *data = get_signal_data_if_signal_object(signal); if (!data) return; mark(js, data->reason); unsigned int listener_count = abort_array_len(data->listeners); for (unsigned int i = 0; i < listener_count; i++) { abort_listener_t *entry = (abort_listener_t *)utarray_eltptr(data->listeners, i); if (!entry) continue; mark(js, entry->callback); } unsigned int follower_count = abort_array_len(data->followers); for (unsigned int i = 0; i < follower_count; i++) { ant_value_t *follower = (ant_value_t *)utarray_eltptr(data->followers, i); if (!follower) continue; mark(js, *follower); } } bool abort_signal_is_aborted(ant_value_t signal) { abort_signal_data_t *data = get_signal_data_if_signal_object(signal); return data && data->aborted; } bool abort_signal_is_signal(ant_value_t signal) { return get_signal_data_if_signal_object(signal) != NULL; } ant_value_t abort_signal_get_reason(ant_value_t signal) { abort_signal_data_t *data = get_signal_data_if_signal_object(signal); return data ? data->reason : js_mkundef(); } void abort_signal_add_listener(ant_t *js, ant_value_t signal, ant_value_t callback) { abort_signal_data_t *data = get_signal_data_if_signal_object(signal); if (!data) return; if (data->aborted) { ant_value_t event_obj = js_mkobj(js); js_set(js, event_obj, "type", js_mkstr(js, "abort", 5)); js_set(js, event_obj, "target", signal); ant_value_t call_args[1] = { event_obj }; sv_vm_call(js->vm, js, callback, signal, call_args, 1, NULL, false); return; } abort_listener_t entry = { callback, false }; if (data->listeners) utarray_push_back(data->listeners, &entry); }