MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at mir/inline-method 504 lines 17 kB view raw
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}