The runtime installs the DOM Standard's abort primitives as globals in every
isolate (main and workers): AbortController, and AbortSignal with the
abort, timeout and any statics. The implementation is
test-app/runtime/src/main/cpp/js/abort-signal.js, evaluated during Events::Init
right after the Event/EventTarget builtin it is layered on, so the
interfaces exist before any user code runs. AbortSignal extends the
runtime's EventTarget; new AbortSignal() throws TypeError: Illegal constructor — instances come from a controller or one of the statics.
new AbortController()—controller.signal(stable identity) andcontroller.abort(reason?).signal.aborted,signal.reason,signal.throwIfAborted(), and theabortevent (addEventListener("abort", …)or theonaborthandler attribute with HTML event-handler semantics).AbortSignal.abort(reason?)— an already-aborted signal; no event fires.AbortSignal.timeout(delay)— aborts with aTimeoutError-named reason afterdelayms.delaymust be an integer in[0, 2^32 − 1](TypeErrorfor non-numbers,RangeErrorotherwise), matching Node's validation.AbortSignal.any(signals)— a composite signal that aborts with the first source's reason. Accepts any iterable whose members are allAbortSignals (TypeErrorotherwise). Composites are flattened:any([any([a]), b])followsaandbdirectly. Per spec, every affected signal'saborted/reasonflips before the firstabortevent fires.
The implementation is GC-transparent the way Node's is: internal references never keep an unobservable signal alive, and never let an observable abort be dropped.
- A
timeout()timer closes over aWeakRef, so a signal nobody can observe is collectable before it fires; aFinalizationRegistrycancels the pending native timer when that happens. any()links areWeakRefs in both directions (source → dependent and dependent → source), with prune registries clearing dead entries — so per-request composites never accumulate on a long-lived source, and a collected source leaves its composites' source lists (a composite whose sources are all gone can never abort and stops being retained).- Weakness alone would silently drop the abort of a signal that is
listened-to but otherwise unreachable, so a strong
gcPersistentSignalsset holds exactly the signals whose abort someone can still observe: live timeout signals and live non-empty composites while they haveabortlisteners (onabortcounts — it registers a real listener), plus timeout sources a composite follows, until their timer fires. The listener accounting comes from an internal symbol-keyed hook the events builtin calls from every listener-list mutation path (add, remove, andonceremoval during dispatch); the key travels only through the builtin-onlyrequire("internal/events")tier (seetest-app/runtime/src/main/cpp/js/README.md) and never reaches app code, so the accounting cannot be bypassed via a capturedEventTarget.prototype.addEventListener.
Entries leave the persistent set on abort, on the last abort-listener removal, or when a composite loses its last source.
Default reasons are real DOMExceptions — "AbortError" for a plain abort,
"TimeoutError" for timeout() — so both reason.name and
instanceof DOMException checks work.
- Abort events carry no
isTrustedflag (the runtime'sEventdoesn't model it).
Listener errors during the abort dispatch go through the runtime's standard listener-error pipeline (see error handling); a throwing listener never prevents the remaining listeners from running.