Skip to content

Implement Event, CustomEvent and EventTarget in TypeScript - #7793

Draft
jasnell wants to merge 4 commits into
mainfrom
jasnell/ts-eventtarget
Draft

jasnell wants to merge 4 commits into
mainfrom
jasnell/ts-eventtarget

Conversation

@jasnell

@jasnell jasnell commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator

First step of implementing EventTarget/Event in TypeScript and moving out of basics.h/basics.c++.

Keeping this draft for now.

Add standalone TypeScript implementations of Event, CustomEvent and
EventTarget to the per-isolate bootstrap (src/per_isolate/events/),
following the TS streams model. Under the new experimental
typescript_implemented_events flag, main.ts installs them as the
Event, CustomEvent and EventTarget globals.

This is the first step of moving these classes out of basics.h.
Nothing native creates, dispatches or listens through the TS classes
yet: the runtime's own events (MessageEvent and the rest), the global
scope and AbortSignal stay C++, and the C++ classes stay registered.
The two implementations accept none of each other's events or
targets.

The TS classes follow the DOM spec and consult none of the C++
implementation's compat flags (pedantic_wpt,
workers_api_getters_setters_on_prototype, set_event_target_this,
spec_compliant_dispatch_exceptions and the rest). Where the C++
classes deviate from the spec without a flag, the TS ones do not:
they support capture and passive listeners, initEvent() and
initCustomEvent(); take timeStamp from Date.now(); define isTrusted
on each instance ([LegacyUnforgeable]); look handleEvent up at every
call; ignore listener results; and have no generic on<type>
reflection. Listener exceptions are reported through the global
reportError(). The {signal} option registers an abort algorithm with
utils.addAbortAlgorithm() that holds the listener's registration
weakly, so a long-lived signal retains no dead target.

primordials.ts gains FunctionPrototypeCall and DateNow.

Compatibility: the TS globals replace the C++ ones only under
typescript_implemented_events, which is experimental and has no
enable date, so no existing Worker's API surface changes.
Add a dom/events-ts WPT target, with the per-isolate bootstrap
autogate and typescript_implemented_events. Its config is the C++
one (dom/events-test.ts) less the expectations the TypeScript classes
clear: passive listeners, {signal} with capture, the Event
constructors (timeStamp) and isTrusted. Removing a capture listener
still fails, because the test does it on the global, which is still
a C++ EventTarget.
Add src/tests/events/event/, which runs the same test modules
against the C++ and the TypeScript Event and CustomEvent, following
the streams suites' conventions (src/tests/streams/AGENTS.md):
event-cpp, event-cpp-pedantic (with pedantic_wpt) and event-ts. A
fourth cell, event-cpp-legacy, guards the C++ behaviors without
workers_api_getters_setters_on_prototype and set_tostring_tag.

The suite covers construction, the attributes, cancelation, the
propagation flags, initEvent(), detail, subclassing, pollution and
the WebIDL shape, with tests ported from WPT's HTML-only
Event-initEvent and CustomEvent. Each place the implementations
differ is a which-impl branch pinning both sides, recorded in the
suite's AGENTS.md ledger: 19 rows, 3 of them gaps between the TS
classes and the rest of the runtime that integration will close.
Add src/tests/events/event-target/, the EventTarget counterpart of
the Event suite: event-target-cpp, event-target-cpp-pedantic,
event-target-ts and event-target-cpp-legacy. The legacy cell guards
the C++ behaviors without dont_substitute_null_on_type_error,
set_event_target_this, workers_api_getters_setters_on_prototype and
set_tostring_tag. legacy-dispatch-exceptions-test.js already guards
the side without spec_compliant_dispatch_exceptions.

The suite covers adding and removing listeners, the options,
dispatch, handleEvent, the {signal} option, listener exceptions
(inside and outside a request), on<type> properties, subclassing,
pollution, garbage collection and the WebIDL shape. Its AGENTS.md
ledger has 23 rows, 4 of them interop gaps: events cannot cross the
implementations, AbortSignal is not an instance of the TS
EventTarget, node:events' helpers reject it, and structuredClone()
differs.

src/tests/events/AGENTS.md describes what the two suites share.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant