WHATWG DOM Events
Elide implements the WHATWG [DOM Living Standard][spec] event interfaces as globals: Event,
CustomEvent, and EventTarget. They are also returned by built-in sources (e.g. AbortSignal
extends EventTarget).
[spec]: https://dom.spec.whatwg.org/
Event
js
Calling new Event(type, eventInitDict = {})
// eventInitDict: { bubbles: boolean = false, cancelable: boolean = false, composed: boolean = false }Event(...) without new throws TypeError — per WebIDL, interface objects with
constructors must be invoked with new. The required first argument is coerced via ToString;
a missing argument throws TypeError.
| Member | Type | Notes | |
|---|---|---|---|
type | string | Readonly; coerced from the constructor argument. | |
target | `EventTarget \ | null` | Readonly; set during dispatch, null before / after. |
currentTarget | `EventTarget \ | null` | Readonly; set during listener invocation only. |
eventPhase | number | Readonly; NONE (0) or AT_TARGET (2) in this non-tree runtime. | |
bubbles / cancelable / composed | boolean | Readonly; from the init dict, default false. | |
defaultPrevented | boolean | Readonly; true after preventDefault() on a cancelable event. | |
isTrusted | boolean | Readonly; always false for script-created events. | |
timeStamp | number | Readonly; milliseconds since the Unix epoch at construction. | |
cancelBubble | boolean | Historical alias — getter returns the stop-propagation flag; setter with true calls stopPropagation(), false is a no-op. | |
composedPath() | EventTarget[] | [currentTarget] during dispatch; [] otherwise (single-target runtime). | |
stopPropagation() | undefined | Sets the stop-propagation flag. | |
stopImmediatePropagation() | undefined | Sets both stop flags — later listeners on the same target are skipped. | |
preventDefault() | undefined | No-op if cancelable is false or the active listener is passive. |
js
Event.NONE === 0
Event.CAPTURING_PHASE === 1
Event.AT_TARGET === 2
Event.BUBBLING_PHASE === 3CustomEvent
js
Subclass of new CustomEvent(type, customEventInitDict = {})
// customEventInitDict: { bubbles, cancelable, composed, detail: any = null }Event: customEvt instanceof Event is true, and every Event member works on a
CustomEvent. Adds one readonly attribute:
| Member | Type | Notes |
|---|---|---|
detail | any | Readonly; default null. The value is stored by reference, not cloned. |
EventTarget
js
new EventTarget()js
target.addEventListener(type, callback, options = {})
target.removeEventListener(type, callback, options = {})
target.dispatchEvent(event) // → boolean (!event.defaultPrevented)callbackmay be a function or any object exposing a callablehandleEventproperty. Functions are invoked withthis = currentTarget;handleEventobjects withthis = callback.null/undefinedcallbacks are silently ignored.optionsaccepts{ capture, passive, once, signal }(booleans; plussignal: AbortSignal) or a plain boolean (interpreted as thecaptureshortcut).- Duplicate
(type, callback, capture)registrations are silently ignored. passive: truelisteners' calls topreventDefault()are no-ops.once: truelisteners are removed immediately after their first invocation.dispatchEvent(event)throwsInvalidStateError DOMExceptionif the event is already being dispatched. Listeners added or removed during dispatch do not affect the current dispatch (a snapshot is taken at entry); they take effect on the next one.- Exceptions thrown by a listener are written to the realm's error stream and do not abort the remaining listeners (the DOM "report the exception" algorithm).
Non-browser scope
Elide does not have a DOM tree, so every dispatch has exactly one invocation target:
composedPath()returns[currentTarget]during dispatch and[]otherwise.eventPhasetransitionsNONE → AT_TARGET → NONE.- Capture/bubble flags are recorded (and honoured for listener dedup / removal) but do not affect the invocation order on a single target.
The signal: AbortSignal option on addEventListener is wired up: passing an AbortSignal as
options.signal registers the listener normally and automatically removes it when the signal
aborts. Passing an already-aborted signal skips registration entirely. Non-AbortSignal values
are ignored, matching the behaviour of the AbortSignal option documented in
WHATWG DOM Abort.
Example
js
const bus = new EventTarget();
bus.addEventListener('tick', (ev) => {
console.log(ev.type, ev.detail); // "tick" 42
});
bus.dispatchEvent(new CustomEvent('tick', { detail: 42 }));
// once + cancelable + preventDefault
const target = new EventTarget();
target.addEventListener('submit', (ev) => ev.preventDefault(), { once: true });
const ok = target.dispatchEvent(new Event('submit', { cancelable: true }));
console.log(ok); // false — cancelled
console.log(target.dispatchEvent(new Event('submit', { cancelable: true })));
// true — the `once` listener is goneNotes on the implementation
- Plain-call
Event(...)/CustomEvent(...)/EventTarget(...)are routed toTypeErrorin the generatedJSEventClassBase.java/JSCustomEventClassBase.java/JSEventTargetClassBase.javaunderpackages/generated/, with the same manual patch Elide uses forDOMExceptionand the encoding classes. Upstreamelide-dev/bindgen(JSConstructorCodegen.kt) emits the same branch on regeneration. - The shared per-event state (type, flags, targets, dispatch phase) lives in
EventState, which is referenced by bothEventInstance extends JSEventObjectandCustomEventInstance extends JSCustomEventObject. The two concrete types implement anEventAccessinterface so the prototype operations onJSEventwork against either without duplicated logic. - Listener lists are kept per event type in an insertion-ordered
ArrayList. The dispatch loop iterates a snapshot taken at entry, so mutations inside a listener don't disturb the current round.