Skip to content
File

Blob: src/workerd/api/basics.c++

42.7 KB
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5#include "basics.h"
6 
7#include "actor-state.h"
8#include "global-scope.h"
9 
10#include <workerd/io/features.h>
11#include <workerd/io/io-context.h>
12 
13#include <capnp/message.h>
14#include <kj/async.h>
15#include <kj/vector.h>
16 
17namespace workerd::api {
18 
19namespace {
20// "Special" events are the global addEventListener(...) events that the runtime itself
21// will emit for various things (e.g. the "fetch" event). When using module syntax, these
22// are not emitted as events and instead should be registered as functions on the exported
23// handler. To help make that clearer, if user code calls addEventListener() using one of
24// these special types (only when using module syntax), a warning will be logged to the
25// console.
26// It's important to keep this list in sync with any other top level events that are emitted
27// when in worker syntax but called as exports in module syntax.
28constexpr bool isSpecialEventType(kj::StringPtr type) {
29 // TODO(someday): How should we cover custom events here? Since it's just for a warning I'm
30 // leaving them out for now.
31 return type == "fetch" || type == "scheduled" || type == "tail" || type == "trace" ||
32 type == "alarm";
33}
34} // namespace
35 
36EventTarget::NativeHandler::NativeHandler(
37 jsg::Lock& js, EventTarget& target, kj::String type, jsg::Function<Signature> func, bool once)
38 : type(kj::mv(type)),
39 state(State{
40 .target = target,
41 .func = kj::mv(func),
42 }),
43 once(once) {
44 target.addNativeListener(js, *this);
45}
46 
47EventTarget::NativeHandler::~NativeHandler() noexcept(false) {
48 detach();
49}
50 
51void EventTarget::NativeHandler::operator()(jsg::Lock& js, jsg::Ref<Event> event) {
52 KJ_IF_SOME(s, state) {
53 if (once) {
54 auto fn = kj::mv(s.func);
55 detach();
56 fn(js, kj::mv(event));
57 // Note that the function may have detached itself and caused the NativeHandler
58 // to be destroyed. Let's be careful not to touch it after this point.
59 } else {
60 s.func(js, kj::mv(event));
61 }
62 return;
63 }
64}
65 
66uint EventTarget::NativeHandler::hashCode() const {
67 return kj::hashCode(this);
68}
69 
70void EventTarget::NativeHandler::visitForGc(jsg::GcVisitor& visitor) {
71 KJ_IF_SOME(s, state) {
72 visitor.visit(s.func);
73 }
74}
75 
76void EventTarget::NativeHandler::detach() {
77 KJ_IF_SOME(s, state) {
78 s.target.removeNativeListener(*this);
79 state = kj::none;
80 }
81}
82 
83kj::Own<void> EventTarget::newNativeHandler(
84 jsg::Lock& js, kj::String type, jsg::Function<void(jsg::Ref<Event>)> func, bool once) {
85 return kj::heap<EventTarget::NativeHandler>(js, *this, kj::mv(type), kj::mv(func), once);
86}
87 
88const EventTarget::EventHandler::Handler& EventTarget::EventHandlerHashCallbacks::keyForRow(
89 const kj::Own<EventHandler>& row) const {
90 // The key for each EventHandler struct is the handler, which is a kj::OneOf
91 // of either a JavaScriptHandler or NativeHandler.
92 return row->handler;
93}
94 
95bool EventTarget::EventHandlerHashCallbacks::matches(
96 const kj::Own<EventHandler>& a, const jsg::HashableV8Ref<v8::Object>& b) const {
97 KJ_IF_SOME(jsA, a->handler.tryGet<EventHandler::JavaScriptHandler>()) {
98 return jsA.identity == b;
99 }
100 return false;
101}
102 
103bool EventTarget::EventHandlerHashCallbacks::matches(
104 const kj::Own<EventHandler>& a, const NativeHandler& b) const {
105 KJ_IF_SOME(ref, a->handler.tryGet<EventHandler::NativeHandlerRef>()) {
106 return &ref.handler == &b;
107 }
108 return false;
109}
110 
111bool EventTarget::EventHandlerHashCallbacks::matches(
112 const kj::Own<EventHandler>& a, const EventHandler::NativeHandlerRef& b) const {
113 return matches(a, b.handler);
114}
115 
116bool EventTarget::EventHandlerHashCallbacks::matches(
117 const kj::Own<EventHandler>& a, const EventHandler::Handler& b) const {
118 KJ_SWITCH_ONEOF(b) {
119 KJ_CASE_ONEOF(jsB, EventHandler::JavaScriptHandler) {
120 return matches(a, jsB.identity);
121 }
122 KJ_CASE_ONEOF(nativeB, EventHandler::NativeHandlerRef) {
123 return matches(a, nativeB);
124 }
125 }
126 KJ_UNREACHABLE;
127}
128 
129uint EventTarget::EventHandlerHashCallbacks::hashCode(
130 const jsg::HashableV8Ref<v8::Object>& obj) const {
131 return obj.hashCode();
132}
133 
134uint EventTarget::EventHandlerHashCallbacks::hashCode(const NativeHandler& handler) const {
135 return handler.hashCode();
136}
137 
138uint EventTarget::EventHandlerHashCallbacks::hashCode(
139 const EventHandler::NativeHandlerRef& handler) const {
140 return hashCode(handler.handler);
141}
142 
143uint EventTarget::EventHandlerHashCallbacks::hashCode(
144 const EventHandler::JavaScriptHandler& handler) const {
145 return hashCode(handler.identity);
146}
147 
148uint EventTarget::EventHandlerHashCallbacks::hashCode(const EventHandler::Handler& handler) const {
149 KJ_SWITCH_ONEOF(handler) {
150 KJ_CASE_ONEOF(js, EventHandler::JavaScriptHandler) {
151 return hashCode(js);
152 }
153 KJ_CASE_ONEOF(native, EventHandler::NativeHandlerRef) {
154 return hashCode(native);
155 }
156 }
157 KJ_UNREACHABLE;
158}
159 
160jsg::Ref<Event> Event::constructor(jsg::Lock& js, kj::String type, jsg::Optional<Init> init) {
161 static const Init defaultInit;
162 return js.alloc<Event>(kj::mv(type), init.orDefault(defaultInit), false /* not trusted */);
163}
164 
165kj::StringPtr Event::getType() {
166 return type;
167}
168 
169kj::Maybe<jsg::Ref<EventTarget>> Event::getCurrentTarget() {
170 if (flags.isBeingDispatched) {
171 return getTarget();
172 }
173 return kj::none;
174}
175 
176jsg::Optional<jsg::Ref<EventTarget>> Event::getTarget() {
177 return target.map([&](jsg::Ref<EventTarget>& t) { return t.addRef(); });
178}
179 
180kj::Array<jsg::Ref<EventTarget>> Event::composedPath() {
181 if (flags.isBeingDispatched) {
182 // When isBeingDispatched is true, target should always be non-null.
183 // If it's not, there's a bug that we need to know about.
184 return kj::arr(KJ_ASSERT_NONNULL(target).addRef());
185 }
186 return kj::Array<jsg::Ref<EventTarget>>();
187}
188 
189void Event::beginDispatch(jsg::Ref<EventTarget> target) {
190 JSG_REQUIRE(
191 !flags.isBeingDispatched, DOMInvalidStateError, "The event is already being dispatched.");
192 flags.isBeingDispatched = true;
193 this->target = kj::mv(target);
194}
195 
196jsg::Ref<EventTarget> EventTarget::constructor(jsg::Lock& js) {
197 return js.alloc<EventTarget>();
198}
199 
200EventTarget::~EventTarget() noexcept(false) {
201 for (auto& entry: typeMap) {
202 for (auto& handler: entry.value.handlers) {
203 KJ_IF_SOME(native, handler->handler.tryGet<EventHandler::NativeHandlerRef>()) {
204 // Note: Can't call `detach()` here because it would loop back and call
205 // `removeNativeListener()` on us, invalidating the `typeMap` iterator. We'll directly
206 // null out the state.
207 native.handler.state = kj::none;
208 }
209 }
210 }
211}
212 
213size_t EventTarget::getHandlerCount(kj::StringPtr type) const {
214 KJ_IF_SOME(handlerSet, typeMap.find(type)) {
215 return handlerSet.handlers.size();
216 } else {
217 return 0;
218 }
219}
220 
221kj::Array<kj::StringPtr> EventTarget::getHandlerNames() const {
222 return KJ_MAP(entry, typeMap) { return entry.key.asPtr(); };
223}
224 
225void EventTarget::addEventListener(jsg::Lock& js,
226 kj::String type,
227 kj::Maybe<jsg::Identified<Handler>> maybeHandler,
228 jsg::Optional<AddEventListenerOpts> maybeOptions,
229 const jsg::TypeHandler<jsg::Ref<EventTarget>>& eventTargetHandler) {
230 if (flags.warnOnSpecialEvents && isSpecialEventType(type)) {
231 js.logWarning(kj::str("When using module syntax, the '", type,
232 "' event handler should be "
233 "declared as an exported function on the root module as opposed to using "
234 "the global addEventListener()."));
235 }
236 
237 KJ_IF_SOME(handler, maybeHandler) {
238 js.withinHandleScope([&] {
239 // Per the spec, the handler can be either a Function, or an object with a
240 // handleEvent member function.
241 HandlerFunction handlerFn = ([&]() {
242 KJ_SWITCH_ONEOF(handler.unwrapped) {
243 KJ_CASE_ONEOF(fn, HandlerFunction) {
244 if (FeatureFlags::get(js).getSetEventTargetThis()) {
245 fn.setReceiver(js.v8Ref(eventTargetHandler.wrap(js, JSG_THIS)));
246 }
247 return kj::mv(fn);
248 }
249 KJ_CASE_ONEOF(obj, HandlerObject) {
250 if (FeatureFlags::get(js).getSetEventTargetThis()) {
251 obj.handleEvent.setReceiver(obj.self.asValue(js));
252 }
253 return kj::mv(obj.handleEvent);
254 }
255 }
256 KJ_UNREACHABLE;
257 })();
258 
259 bool once = false;
260 kj::Maybe<jsg::Ref<AbortSignal>> maybeSignal;
261 kj::Maybe<jsg::Ref<AbortSignal>> maybeFollowingSignal;
262 KJ_IF_SOME(value, maybeOptions) {
263 KJ_SWITCH_ONEOF(value) {
264 KJ_CASE_ONEOF(b, bool) {
265 JSG_REQUIRE(!b, TypeError, "addEventListener(): useCapture must be false.");
266 }
267 KJ_CASE_ONEOF(opts, AddEventListenerOptions) {
268 JSG_REQUIRE(!opts.capture.orDefault(false), TypeError,
269 "addEventListener(): options.capture must be false.");
270 JSG_REQUIRE(!opts.passive.orDefault(false), TypeError,
271 "addEventListener(): options.passive must be false.");
272 once = opts.once.orDefault(false);
273 maybeSignal = kj::mv(opts.signal);
274 maybeFollowingSignal = kj::mv(opts.followingSignal);
275 }
276 }
277 }
278 KJ_IF_SOME(signal, maybeSignal) {
279 // If the AbortSignal has already been triggered, then we need to stop here.
280 // Return without adding the event listener.
281 if (signal->getAborted(js)) {
282 return;
283 }
284 }
285 
286 auto& set = getOrCreate(type);
287 
288 auto maybeAbortHandler = maybeSignal.map([&](jsg::Ref<AbortSignal>& signal) {
289 // The returned native handler captures a bare reference to signal and
290 // will be held by this EventTarget. The signal is the only thing that
291 // triggers it. If signal is gc'd the native handler created here could
292 // still be alive which means *technically* it will be holding a bare
293 // reference for something that is already destroyed. However, there's
294 // nothing else that would trigger it so it's generally safe-ish. That
295 // said, it's still a potential UAF so let's guard against it by attaching
296 // a strong reference to the signal to the event handler. This will mean
297 // likely keeping the signal in memory longer if it can otherwise be
298 // gc'd but that's ok, the impact should be minimal.
299 auto func =
300 JSG_VISITABLE_LAMBDA((this, type = kj::mv(type), handler = handler.identity.addRef(js),
301 signal = signal.addRef()),
302 (handler, signal), (jsg::Lock& js, jsg::Ref<Event>) {
303 removeEventListener(js, kj::mv(type), kj::mv(handler), kj::none);
304 });
305 
306 return signal->newNativeHandler(js, kj::str("abort"), kj::mv(func), true);
307 });
308 
309 auto eventHandler = kj::heap<EventHandler>(
310 EventHandler::JavaScriptHandler{
311 .identity = kj::mv(handler.identity),
312 .callback = kj::mv(handlerFn),
313 .abortHandler = kj::mv(maybeAbortHandler),
314 },
315 once);
316 
317 // If maybeFollowingSignal is set, we need to attach it to the event handler
318 // in order to keep it alive. This is used only for AbortSignal.any() where
319 // the followed signal (this) is being followed by another signal. We need
320 // to make sure the following signal stays alive until either the followed
321 // signal is triggered or destroyed.
322 KJ_IF_SOME(following, maybeFollowingSignal) {
323 eventHandler = eventHandler.attach(kj::mv(following));
324 }
325 
326 set.handlers.upsert(kj::mv(eventHandler), [&](auto&&...) {});
327 });
328 }
329}
330 
331void EventTarget::removeEventListener(jsg::Lock& js,
332 kj::String type,
333 kj::Maybe<jsg::HashableV8Ref<v8::Object>> maybeHandler,
334 jsg::Optional<EventListenerOpts> maybeOptions) {
335 KJ_IF_SOME(value, maybeOptions) {
336 KJ_SWITCH_ONEOF(value) {
337 KJ_CASE_ONEOF(b, bool) {
338 JSG_REQUIRE(!b, TypeError, "removeEventListener(): useCapture must be false.");
339 }
340 KJ_CASE_ONEOF(opts, EventListenerOptions) {
341 JSG_REQUIRE(!opts.capture.orDefault(false), TypeError,
342 "removeEventListener(): options.capture must be false.");
343 }
344 }
345 }
346 
347 KJ_IF_SOME(handler, maybeHandler) {
348 js.withinHandleScope([&] {
349 KJ_IF_SOME(handlerSet, typeMap.find(type)) {
350 handlerSet.handlers.eraseMatch(handler);
351 }
352 });
353 }
354}
355 
356void EventTarget::addNativeListener(jsg::Lock& js, NativeHandler& handler) {
357 auto& set = getOrCreate(handler.type);
358 
359 auto eventHandler = kj::heap<EventHandler>(
360 EventHandler::NativeHandlerRef{
361 .handler = handler,
362 },
363 handler.once);
364 
365 set.handlers.upsert(kj::mv(eventHandler), [&](auto&&...) {});
366}
367 
368bool EventTarget::removeNativeListener(EventTarget::NativeHandler& handler) {
369 KJ_IF_SOME(handlerSet, typeMap.find(handler.type)) {
370 return handlerSet.handlers.eraseMatch(handler);
371 }
372 return false;
373}
374 
375EventTarget::EventHandlerSet& EventTarget::getOrCreate(kj::StringPtr type) {
376 return typeMap.upsert(kj::str(type), EventHandlerSet(), [&](auto&&...) {}).value;
377}
378 
379bool EventTarget::dispatchEventImpl(jsg::Lock& js, jsg::Ref<Event> event) {
380 event->beginDispatch(JSG_THIS);
381 KJ_DEFER(event->endDispatch());
382 
383 event->clearPreventDefault();
384 
385 // First, gather all the function handles that we plan to call. This is important to ensure that
386 // the callback can add or remove listeners without affecting the current event's processing.
387 
388 return js.withinHandleScope([&] {
389 struct Callback {
390 EventHandler::Handler handler;
391 bool once = false;
392 bool oldStyle = false;
393 };
394 
395 kj::Vector<Callback> callbacks;
396 
397 // Check if there is an `on<event>` property on this object. If so, we treat that as an event
398 // handler, in addition to the ones registered with addEventListener().
399 KJ_IF_SOME(onProp, onEvents.get(js, kj::str("on", event->getType()))) {
400 // If the on-event is not a function, we silently ignore it rather than raise an error.
401 KJ_IF_SOME(cb, onProp.tryGet<HandlerFunction>()) {
402 callbacks.add(Callback{
403 .handler =
404 EventHandler::JavaScriptHandler{
405 .identity = nullptr, // won't be used below if oldStyle is true and once is false
406 .callback = kj::mv(cb),
407 },
408 .oldStyle = true,
409 });
410 }
411 }
412 
413 KJ_IF_SOME(handlerSet, typeMap.find(event->getType())) {
414 callbacks.reserve(handlerSet.handlers.size());
415 for (auto& handler: handlerSet.handlers.ordered<kj::InsertionOrderIndex>()) {
416 KJ_SWITCH_ONEOF(handler->handler) {
417 KJ_CASE_ONEOF(jsh, EventHandler::JavaScriptHandler) {
418 callbacks.add(Callback{
419 .handler = EventHandler::JavaScriptHandler{.identity = jsh.identity.addRef(js),
420 .callback = jsh.callback.addRef(js)},
421 .once = handler->once,
422 });
423 }
424 KJ_CASE_ONEOF(native, EventHandler::NativeHandlerRef) {
425 callbacks.add(Callback{
426 .handler =
427 EventHandler::NativeHandlerRef{
428 .handler = native.handler,
429 },
430 .once = handler->once,
431 });
432 }
433 }
434 }
435 }
436 
437 const auto isRemoved = [&](auto& handler) {
438 // This is not the most efficient way to do this but it's what works right now.
439 // Instead of capturing direct references to the handler structs, we copy those
440 // into the Callbacks vector, which means we need to look up the actual handler
441 // again to see if it still exists in the list. The entire way the storage of the
442 // handlers is done here can be improved to make this more efficient.
443 
444 KJ_IF_SOME(handlerSet, typeMap.find(event->getType())) {
445 KJ_SWITCH_ONEOF(handler) {
446 KJ_CASE_ONEOF(js, EventHandler::JavaScriptHandler) {
447 return handlerSet.handlers.find(js.identity) == kj::none;
448 }
449 KJ_CASE_ONEOF(native, EventHandler::NativeHandlerRef) {
450 return handlerSet.handlers.find(native.handler) == kj::none;
451 }
452 }
453 } else {
454 return true;
455 }
456 KJ_UNREACHABLE;
457 };
458 
459 for (auto& callback: callbacks) {
460 if (event->isStopped()) {
461 // stopImmediatePropagation() was called; don't call any further listeners
462 break;
463 }
464 
465 // If the handler gets removed by an earlier run handler, then we need to
466 // make sure we don't run it. Skip over and continue.
467 if (!callback.oldStyle && isRemoved(callback.handler)) {
468 continue;
469 }
470 
471 if (callback.once) {
472 KJ_SWITCH_ONEOF(callback.handler) {
473 KJ_CASE_ONEOF(jsh, EventHandler::JavaScriptHandler) {
474 removeEventListener(js, kj::str(event->getType()), jsh.identity.addRef(js), kj::none);
475 }
476 KJ_CASE_ONEOF(native, EventHandler::NativeHandlerRef) {
477 // The native handler will handle detaching itself when invoked
478 }
479 }
480 }
481 
482 KJ_SWITCH_ONEOF(callback.handler) {
483 KJ_CASE_ONEOF(jsh, EventHandler::JavaScriptHandler) {
484 // Per the standard, the event listener is not supposed to return any value, and if it
485 // does, that value is ignored. That can be somewhat problematic if the user passes an
486 // async function as the event handler. Doing so counts as undefined behavior and can
487 // introduce subtle and difficult to diagnose bugs. Here, if the handler does return a
488 // value, we're going to emit a warning but otherwise ignore it. The warning will only
489 // be emitted at most once per EventEmitter instance.
490 auto ret = jsh.callback(js, event.addRef());
491 // Note: We used to run each handler in its own v8::TryCatch. However, due to a
492 // misunderstanding of the V8 API, we incorrectly believed that TryCatch mishandled
493 // termination (or maybe it actually did at the time), so we changed things such that
494 // we don't catch exceptions so the first handler to throw an exception terminates the
495 // loop, and the exception flows out of dispatchEvent(). In theory if multiple
496 // handlers were registered then maybe we ought to be running all of them even if one
497 // fails. This isn't entirely clear, though: in the case of 'fetch' handlers, in
498 // fail-closed mode, an exception from any handler should make the whole request fail,
499 // but then who cares if the remaining handlers run? Meanwhile, in fail-open mode, for
500 // consistency, we should probably trigger fallback behavior if any handler throws, so
501 // again it doesn't matter. For other types of handlers, e.g. WebSocket 'message', it's
502 // not clear why one would ever register multiple handlers.
503 KJ_IF_SOME(r, ret) {
504 auto handle = r.getHandle(js);
505 // Returning true is the same as calling preventDefault() on the event.
506 if (handle->IsTrue()) {
507 event->preventDefault();
508 }
509 if (flags.warnOnHandlerReturn && !handle->IsBoolean()) {
510 flags.warnOnHandlerReturn = false;
511 // To help make debugging easier, let's tailor the warning a bit if it was a promise.
512 if (handle->IsPromise()) {
513 js.logWarning(kj::str(
514 "An event handler returned a promise that will be ignored. Event handlers "
515 "should not have a return value and should not be async functions."));
516 } else {
517 js.logWarning(kj::str("An event handler returned a value of type \"",
518 handle->TypeOf(js.v8Isolate),
519 "\" that will be ignored. Event handlers should not have a return value."));
520 }
521 }
522 }
523 }
524 KJ_CASE_ONEOF(native, EventHandler::NativeHandlerRef) {
525 native.handler(js, event.addRef());
526 }
527 }
528 }
529 
530 return !event->isPreventDefault();
531 });
532}
533 
534bool EventTarget::dispatchEvent(jsg::Lock& js, jsg::Ref<Event> event) {
535 return dispatchEventImpl(js, kj::mv(event));
536}
537 
538// A wrapper for the AbortTrigger jsrpc client, that automatically sends a release() message once
539// the client is destroyed, informing the server that an abort will not be triggered in the future.
540class AbortTriggerRpcClient final {
541 public:
542 AbortTriggerRpcClient(rpc::AbortTrigger::Client&& client): client(kj::mv(client)) {}
543 
544 kj::Promise<void> abort(kj::ArrayPtr<kj::byte> reason) {
545 auto req = client.abortRequest(capnp::MessageSize{reason.size() / sizeof(capnp::word) + 8, 0});
546 auto field = req.initReason();
547 field.setV8Serialized(reason);
548 return req.sendIgnoringResult();
549 }
550 
551 ~AbortTriggerRpcClient() noexcept(false) {
552 if (skipReleaseForTest) {
553 return;
554 }
555 
556 auto req = client.releaseRequest(capnp::MessageSize{4, 0});
557 // We call detach() on the resulting promise so that we can perform RPC in a destructor
558 req.sendIgnoringResult().detach([](kj::Exception exc) {
559 if (exc.getType() == kj::Exception::Type::DISCONNECTED) {
560 // It's possible we can't send the release message because we're already disconnected.
561 return;
562 };
563 
564 // Other exceptions could be more interesting
565 LOG_EXCEPTION("abortTriggerReleaseRpc", exc);
566 });
567 }
568 
569 bool skipReleaseForTest = false;
570 
571 private:
572 rpc::AbortTrigger::Client client;
573};
574 
575namespace {
576// The jsrpc handler that receives aborts from the remote and triggers them locally
577//
578// TODO(cleanup): This class has been copied to external-pusher.c++. The copy here can be
579// deleted as soon as we've switched from StreamSink to ExternalPusher and can delete all the
580// StreamSink-related code. For now I'm not trying to avoid duplication.
581class AbortTriggerRpcServer final: public rpc::AbortTrigger::Server {
582 public:
583 AbortTriggerRpcServer(kj::Own<kj::PromiseFulfiller<void>> fulfiller,
584 kj::Own<AbortSignal::PendingReason>&& pendingReason)
585 : fulfiller(kj::mv(fulfiller)),
586 pendingReason(kj::mv(pendingReason)) {}
587 
588 kj::Promise<void> abort(AbortContext abortCtx) override {
589 auto params = abortCtx.getParams();
590 auto reason = params.getReason().getV8Serialized();
591 
592 pendingReason->getWrapped() = kj::heapArray(reason.asBytes());
593 fulfiller->fulfill();
594 return kj::READY_NOW;
595 }
596 
597 kj::Promise<void> release(ReleaseContext releaseCtx) override {
598 released = true;
599 return kj::READY_NOW;
600 }
601 
602 ~AbortTriggerRpcServer() noexcept(false) {
603 if (pendingReason->getWrapped() != nullptr) {
604 // Already triggered
605 return;
606 }
607 
608 if (!released) {
609 pendingReason->getWrapped() = JSG_KJ_EXCEPTION(FAILED, DOMAbortError,
610 "An AbortSignal received over RPC was implicitly aborted because the connection back to "
611 "its trigger was lost.");
612 }
613 
614 // Always fulfill the promise in case the AbortSignal was waiting
615 fulfiller->fulfill();
616 }
617 
618 private:
619 kj::Own<kj::PromiseFulfiller<void>> fulfiller;
620 kj::Own<AbortSignal::PendingReason> pendingReason;
621 bool released = false;
622};
623} // namespace
624 
625AbortSignal::AbortSignal(kj::Maybe<kj::Exception> exception,
626 jsg::Optional<jsg::JsRef<jsg::JsValue>> maybeReason,
627 Flag flag)
628 : canceler(IoContext::current().addObject(kj::refcounted<RefcountedCanceler>(
629 exception.map([](const kj::Exception& e) { return e.clone(); })))),
630 flag(flag),
631 reason(kj::mv(maybeReason)) {}
632 
633kj::Maybe<jsg::JsValue> AbortSignal::getOnAbort(jsg::Lock& js) {
634 return onAbortHandler.map(
635 [&](jsg::JsRef<jsg::JsValue>& ref) -> jsg::JsValue { return ref.getHandle(js); });
636}
637 
638void AbortSignal::setOnAbort(jsg::Lock& js, jsg::Optional<jsg::JsValue> handler) {
639 // We only want to accept the handler if it's a valid handler... For anything
640 // else, set it to null.
641 KJ_IF_SOME(h, handler) {
642 if (h.isFunction() || h.isObject()) {
643 onAbortHandler = jsg::JsRef(js, h);
644 subscribeToRpcAbort(js);
645 return;
646 }
647 }
648 onAbortHandler = kj::none;
649}
650 
651void AbortSignal::addEventListener(jsg::Lock& js,
652 kj::String type,
653 jsg::Identified<Handler> handler,
654 jsg::Optional<AddEventListenerOpts> maybeOptions,
655 const jsg::TypeHandler<jsg::Ref<EventTarget>>& eventTargetHandler) {
656 EventTarget::addEventListener(
657 js, kj::mv(type), kj::mv(handler), kj::mv(maybeOptions), eventTargetHandler);
658 subscribeToRpcAbort(js);
659}
660 
661bool AbortSignal::getAborted(jsg::Lock& js) {
662 return canceler->isCanceled() || hasPendingReason();
663}
664 
665jsg::JsValue AbortSignal::getReason(jsg::Lock& js) {
666 KJ_IF_SOME(r, reason) {
667 return r.getHandle(js);
668 }
669 
670 KJ_IF_SOME(r, deserializePendingReason(js)) {
671 return r;
672 }
673 
674 return js.undefined();
675}
676 
677kj::Exception AbortSignal::abortException(
678 jsg::Lock& js, const jsg::Optional<kj::OneOf<kj::Exception, jsg::JsValue>>& maybeReason) {
679 KJ_IF_SOME(reason, maybeReason) {
680 KJ_SWITCH_ONEOF(reason) {
681 KJ_CASE_ONEOF(reason, jsg::JsValue) {
682 return js.exceptionToKj(reason);
683 }
684 KJ_CASE_ONEOF(reason, kj::Exception) {
685 return reason.clone();
686 }
687 }
688 }
689 
690 return JSG_KJ_EXCEPTION(DISCONNECTED, DOMAbortError, "The operation was aborted");
691}
692 
693jsg::Ref<AbortSignal> AbortSignal::abort(jsg::Lock& js, jsg::Optional<jsg::JsValue> maybeReason) {
694 auto exception = abortException(js, maybeReason);
695 KJ_IF_SOME(reason, maybeReason) {
696 return js.alloc<AbortSignal>(kj::mv(exception), reason.addRef(js));
697 }
698 return js.alloc<AbortSignal>(exception.clone(), js.exceptionToJsValue(kj::mv(exception)));
699}
700 
701void AbortSignal::throwIfAborted(jsg::Lock& js) {
702 if (canceler->isCanceled()) {
703 KJ_IF_SOME(r, reason) {
704 js.throwException(r.getHandle(js));
705 } else {
706 js.throwException(abortException(js, kj::none));
707 }
708 }
709 
710 KJ_IF_SOME(r, deserializePendingReason(js)) {
711 js.throwException(r);
712 }
713}
714 
715jsg::Ref<AbortSignal> AbortSignal::timeout(jsg::Lock& js, double delay) {
716 auto signal = js.alloc<AbortSignal>();
717 
718 auto context = js.v8Context();
719 
720 auto& global =
721 jsg::extractInternalPointer<ServiceWorkerGlobalScope, true>(context, context->Global());
722 
723 // It's worth noting that the setTimeout holds a strong pointer to the AbortSignal,
724 // keeping it from being garbage collected before the timer fires or until the request
725 // completes, whichever comes first.
726 
727 global.setTimeoutInternal([signal = signal.addRef()](jsg::Lock& js) mutable {
728 signal->triggerAbort(js,
729 JSG_KJ_EXCEPTION(
730 DISCONNECTED, DOMTimeoutError, "The operation was aborted due to timeout"));
731 }, delay);
732 
733 return kj::mv(signal);
734}
735 
736jsg::Ref<AbortSignal> AbortSignal::any(jsg::Lock& js,
737 kj::Array<jsg::Ref<AbortSignal>> signals,
738 const jsg::TypeHandler<EventTarget::HandlerFunction>& handler,
739 const jsg::TypeHandler<jsg::Ref<EventTarget>>& eventTargetHandler) {
740 // If nothing was passed in, we can just return a signal that never aborts.
741 if (signals.size() == 0) {
742 return js.alloc<AbortSignal>(kj::none, kj::none, AbortSignal::Flag::NEVER_ABORTS);
743 }
744 
745 // Let's check to see if any of the signals are already aborted. If it is, we can
746 // optimize here by skipping the event handler registration.
747 for (auto& sig: signals) {
748 if (sig->getAborted(js)) {
749 return AbortSignal::abort(js, sig->getReason(js));
750 }
751 }
752 
753 // Otherwise we need to create a new signal and register event handlers on all
754 // of the signals that were passed in.
755 auto signal = js.alloc<AbortSignal>();
756 for (auto& sig: signals) {
757 // This is a bit of a hack. We want to call addEventListener, but that requires a
758 // jsg::Identified<EventTarget::Handler>, which we can't create directly yet.
759 // So we create a jsg::Function, wrap that in a v8::Function, then convert that into
760 // the jsg::Identified<EventTarget::Handler>, and voila, we have what we need.
761 auto fn = js.wrapSimpleFunction(
762 js.v8Context(), [&signal = *signal, &self = *sig](jsg::Lock& js, auto&) {
763 // Note that we are not capturing any strong references here to either signal
764 // or sig. This is because we are capturing a strong reference to the signal
765 // when we add the event below. This ensures that we do not have an unbreakable
766 // circular reference. The returned signal will not have any strong references
767 // to any of the signals that are passed in, but each of the signals passed in
768 // will have a strong reference to the new signal.
769 signal.triggerAbort(js, self.getReason(js));
770 });
771 jsg::Identified<EventTarget::Handler> identified = {.identity = {js.v8Isolate, fn},
772 .unwrapped = JSG_REQUIRE_NONNULL(handler.tryUnwrap(js, fn.As<v8::Value>()), TypeError,
773 "Unable to create AbortSignal.any handler")};
774 
775 sig->addEventListener(js, kj::str("abort"), kj::mv(identified),
776 AddEventListenerOptions{// Once the abort is triggered, this handler should remove itself.
777 .once = true,
778 // Each of the followed signals will maintain a strong reference to this new
779 // one that's been created.
780 .followingSignal = signal.addRef()},
781 eventTargetHandler);
782 }
783 return signal;
784}
785 
786void AbortSignal::visitForGc(jsg::GcVisitor& visitor) {
787 visitor.visit(reason, onAbortHandler);
788}
789 
790RefcountedCanceler& AbortSignal::getCanceler() {
791 return *canceler;
792}
793 
794void AbortSignal::triggerAbort(
795 jsg::Lock& js, jsg::Optional<kj::OneOf<kj::Exception, jsg::JsValue>> maybeReason) {
796 KJ_ASSERT(flag != Flag::NEVER_ABORTS);
797 if (canceler->isCanceled()) {
798 return;
799 }
800 auto exception = AbortSignal::abortException(js, maybeReason);
801 KJ_IF_SOME(r, maybeReason) {
802 KJ_SWITCH_ONEOF(r) {
803 KJ_CASE_ONEOF(value, jsg::JsValue) {
804 reason = value.addRef(js);
805 }
806 KJ_CASE_ONEOF(ex, kj::Exception) {
807 reason = js.exceptionToJsValue(kj::mv(ex));
808 }
809 }
810 } else {
811 reason = js.exceptionToJsValue(exception.clone());
812 }
813 
814 canceler->cancel(kj::mv(exception));
815 
816 // 1. Dispatch to RPC clients
817 if (!rpcClients.empty()) {
818 IoContext& ioContext = IoContext::current();
819 jsg::Serializer ser(js);
820 KJ_IF_SOME(r, reason) {
821 ser.write(js, r.getHandle(js));
822 }
823 
824 auto released = ser.release();
825 ioContext.addTask(sendToRpc(kj::mv(released.data)));
826 }
827 
828 // 2. Dispatch to local listeners
829 
830 // This is questionable only because it goes against the spec but it does help prevent
831 // memory leaks. Once the abort signal has been triggered, there's really nothing else
832 // the AbortSignal can be used for and no other events make sense. The user code could
833 // add more, and could even emit their own events on the signal by using it as an
834 // EventTarget directly but that would be rather silly, so stepping outside the lines
835 // of the spec here should be just fine.
836 KJ_DEFER(removeAllHandlers());
837 
838 dispatchEventImpl(js, js.alloc<Event>(kj::str("abort")));
839}
840 
841void AbortSignal::serialize(jsg::Lock& js, jsg::Serializer& serializer) {
842 JSG_REQUIRE(FeatureFlags::get(js).getAbortSignalRpc(), DOMDataCloneError,
843 "AbortSignal serialization is not enabled.");
844 
845 auto& handler = JSG_REQUIRE_NONNULL(serializer.getExternalHandler(), DOMDataCloneError,
846 "AbortSignal can only be serialized for RPC.");
847 
848 auto externalHandler = dynamic_cast<RpcSerializerExternalHandler*>(&handler);
849 JSG_REQUIRE(
850 externalHandler != nullptr, DOMDataCloneError, "AbortSignal can only be serialized for RPC.");
851 
852 serializer.writeRawUint32(static_cast<uint>(canceler->isCanceled()));
853 serializer.writeRawUint32(static_cast<uint>(flag));
854 KJ_IF_SOME(r, reason) {
855 serializer.write(js, r.getHandle(js));
856 } else {
857 serializer.write(js, js.undefined());
858 }
859 
860 if (getAborted(js) || getNeverAborts()) {
861 // This AbortSignal cannot be triggered in the future. No stream is needed.
862 return;
863 }
864 
865 auto triggerCap = [&]() -> rpc::AbortTrigger::Client {
866 KJ_IF_SOME(pusher, externalHandler->getExternalPusher()) {
867 auto pipeline = pusher.pushAbortSignalRequest(capnp::MessageSize{2, 0}).sendForPipeline();
868 
869 externalHandler->write(
870 [signal = pipeline.getSignal()](rpc::JsValue::External::Builder builder) mutable {
871 builder.setAbortSignal(kj::mv(signal));
872 });
873 
874 return pipeline.getTrigger();
875 } else {
876 return externalHandler
877 ->writeStream([&](rpc::JsValue::External::Builder builder) mutable {
878 builder.setAbortTrigger();
879 }).castAs<rpc::AbortTrigger>();
880 }
881 }();
882 
883 auto& ioContext = IoContext::current();
884 // Keep track of every AbortSignal cloned from this one.
885 // If this->triggerAbort(...) is called, each rpcClient will be informed.
886 rpcClients.add(ioContext.addObject(kj::heap<AbortTriggerRpcClient>(kj::mv(triggerCap))));
887}
888 
889jsg::Ref<AbortSignal> AbortSignal::deserialize(
890 jsg::Lock& js, rpc::SerializationTag tag, jsg::Deserializer& deserializer) {
891 auto& handler = KJ_REQUIRE_NONNULL(
892 deserializer.getExternalHandler(), "got AbortSignal on non-RPC serialized object?");
893 auto externalHandler = dynamic_cast<RpcDeserializerExternalHandler*>(&handler);
894 KJ_REQUIRE(externalHandler != nullptr, "got AbortSignal on non-RPC serialized object?");
895 
896 auto isCanceled = static_cast<bool>(deserializer.readRawUint32());
897 auto flag = static_cast<Flag>(deserializer.readRawUint32());
898 auto reason = deserializer.readValue(js);
899 
900 if (isCanceled) {
901 // The signal is already aborted and cannot be triggered again. We don't need to set up RPC.
902 return abort(js, reason);
903 }
904 
905 if (flag == Flag::NEVER_ABORTS) {
906 // The signal can't be aborted. We don't need to setup RPC
907 return js.alloc<AbortSignal>(/* exception */ kj::none, /* maybeReason */ kj::none, flag);
908 }
909 
910 // The AbortSignalImpl will receive any remote triggerAbort requests and fulfill the promise with the reason for abort
911 
912 auto signal = js.alloc<AbortSignal>(/* exception */ kj::none, /* maybeReason */ kj::none, flag);
913 
914 auto& ioctx = IoContext::current();
915 
916 auto reader = externalHandler->read();
917 if (reader.isAbortTrigger()) {
918 // Old-style StreamSink.
919 // TODO(cleanup): Remove this once the ExternalPusher autogate has rolled out.
920 auto paf = kj::newPromiseAndFulfiller<void>();
921 auto pendingReason = ioctx.addObject(kj::refcounted<PendingReason>());
922 
923 externalHandler->setLastStream(
924 kj::heap<AbortTriggerRpcServer>(kj::mv(paf.fulfiller), kj::addRef(*pendingReason)));
925 signal->rpcAbortPromise = ioctx.addObject(kj::heap(kj::mv(paf.promise)));
926 signal->pendingReason = kj::mv(pendingReason);
927 } else {
928 KJ_REQUIRE(reader.isAbortSignal(), "external table slot type does't match serialization tag");
929 
930 auto resolvedSignal = ioctx.getExternalPusher()->unwrapAbortSignal(reader.getAbortSignal());
931 
932 signal->rpcAbortPromise = ioctx.addObject(kj::heap(kj::mv(resolvedSignal.signal)));
933 signal->pendingReason = ioctx.addObject(kj::mv(resolvedSignal.reason));
934 }
935 
936 return signal;
937}
938 
939void AbortSignal::skipReleaseForTest() {
940 for (auto& cap: rpcClients) {
941 cap->skipReleaseForTest = true;
942 }
943 
944 rpcClients.clear();
945}
946 
947kj::Promise<void> AbortSignal::sendToRpc(kj::Array<kj::byte>&& reason) {
948 auto& ioContext = IoContext::current();
949 
950 KJ_IF_SOME(outputLocks, ioContext.waitForOutputLocksIfNecessary()) {
951 co_await outputLocks;
952 }
953 
954 kj::Vector<kj::Promise<void>> promises;
955 for (auto& cap: rpcClients) {
956 promises.add(cap->abort(reason));
957 }
958 
959 co_await kj::joinPromises(promises.releaseAsArray());
960}
961 
962bool AbortSignal::hasPendingReason() {
963 KJ_IF_SOME(pr, pendingReason) {
964 return pr->getWrapped() != nullptr;
965 }
966 
967 return false;
968}
969 
970kj::Maybe<jsg::JsValue> AbortSignal::deserializePendingReason(jsg::Lock& js) {
971 KJ_IF_SOME(pr, pendingReason) {
972 if (pr->getWrapped() == nullptr) {
973 // pendingReason not initialized. This means abort wasn't yet triggered
974 return kj::none;
975 }
976 
977 KJ_SWITCH_ONEOF(pr->getWrapped()) {
978 KJ_CASE_ONEOF(v8Serialized, kj::Array<kj::byte>) {
979 jsg::Deserializer des(js, v8Serialized);
980 return kj::some(des.readValue(js));
981 }
982 
983 KJ_CASE_ONEOF(exception, kj::Exception) {
984 return kj::some(js.exceptionToJsValue(exception.clone()).getHandle(js));
985 }
986 }
987 }
988 
989 return kj::none;
990}
991 
992void AbortSignal::subscribeToRpcAbort(jsg::Lock& js) {
993 // For an AbortSignal received over RPC, the first time someone registers an event on the signal,
994 // we want to arrange to awaitIo() for the underlying RPC signal. If no one is actually listening,
995 // though, we don't want to awaitIo() since it blocks hibernation in actors.
996 
997 KJ_IF_SOME(promise, rpcAbortPromise) {
998 IoContext::current().awaitIo(js, kj::mv(*promise), [this, self = JSG_THIS](jsg::Lock& js) {
999 KJ_IF_SOME(r, deserializePendingReason(js)) {
1000 triggerAbort(js, r);
1001 }
1002 });
1003 
1004 rpcAbortPromise = kj::none;
1005 }
1006}
1007 
1008bool AbortSignal::isIgnoredForSubrequests(jsg::Lock& js) const {
1009 // True if this is a signal on the request of an incoming fetch. When the compat flag
1010 // `requestSignalPassthrough` is set, this flag has no effect. But to ensure backwards
1011 // compatibility, when this flag is not set, this signal will not be passed through to
1012 // subrequests derived from the incoming request.
1013 
1014 return !FeatureFlags::get(js).getRequestSignalPassthrough() &&
1015 flag == Flag::IGNORE_FOR_SUBREQUESTS;
1016}
1017 
1018void AbortController::abort(jsg::Lock& js, jsg::Optional<jsg::JsValue> maybeReason) {
1019 signal->triggerAbort(js, maybeReason);
1020}
1021 
1022void EventTarget::visitForGc(jsg::GcVisitor& visitor) {
1023 for (auto& entry: typeMap) {
1024 for (auto& handler: entry.value.handlers) {
1025 KJ_SWITCH_ONEOF(handler->handler) {
1026 KJ_CASE_ONEOF(js, EventHandler::JavaScriptHandler) {
1027 visitor.visit(js);
1028 }
1029 KJ_CASE_ONEOF(native, EventHandler::NativeHandlerRef) {
1030 // Note that even though `native.handler` is a non-owned reference, we still need to
1031 // visit it. This is because we are the ones that will invoke the handles contained
1032 // in the native handler if it ever fires. The actual owner of the C++ NativeHandler
1033 // object doesn't ever access the JS objects it contains; the ownership relationship
1034 // exists only for RAII reasons, so that the NativeHandler is automatically unregistered
1035 // if the owner is destroyed.
1036 //
1037 // You might say: "Well, it's fine if the owner is responsible for visiting it, because
1038 // if the owner is no longer reachable then it will be destroyed and it will unregister
1039 // itself from here!" That doesn't quite work: V8's GC doesn't necessarily destroy
1040 // objects immediately when they become unreachable. However, it is no longer safe to
1041 // access an object once it is unreachable. Therefore, if we left it to the
1042 // NativeHandler's owner to visit the object, it's possible that the object becomes
1043 // poison some time before it is actually unregistered.
1044 //
1045 // Put another way, this is a very weird case where the C++ ownership and the JavaScript
1046 // ownership are different. We need GC visitation to follow the JavaScript ownership
1047 // graph.
1048 visitor.visit(native.handler);
1049 }
1050 }
1051 }
1052 }
1053}
1054 
1055kj::Promise<void> Scheduler::wait(
1056 jsg::Lock& js, double delay, jsg::Optional<WaitOptions> maybeOptions) {
1057 KJ_IF_SOME(options, maybeOptions) {
1058 KJ_IF_SOME(s, options.signal) {
1059 if (s->getAborted(js)) {
1060 return js.exceptionToKj(s->getReason(js));
1061 }
1062 }
1063 }
1064 
1065 // TODO(cleanup): Use jsg promise and resolver to avoid an unlock/relock. However, we need
1066 // the abort signal to support wrapping jsg promises.
1067 auto paf = kj::newPromiseAndFulfiller<void>();
1068 
1069 auto context = js.v8Context();
1070 
1071 auto& global =
1072 jsg::extractInternalPointer<ServiceWorkerGlobalScope, true>(context, context->Global());
1073 auto timeoutId = global.setTimeoutInternal(
1074 [fulfiller = IoContext::current().addObject(kj::mv(paf.fulfiller))](jsg::Lock& lock) mutable {
1075 fulfiller->fulfill();
1076 }, delay);
1077 
1078 auto promise = kj::mv(paf.promise);
1079 
1080 KJ_IF_SOME(options, maybeOptions) {
1081 KJ_IF_SOME(s, options.signal) {
1082 promise = s->wrap(js, kj::mv(promise));
1083 // When the signal aborts and the canceler drops the promise, clear the underlying
1084 // timeout to free the quota slot. clearTimeoutImpl is a no-op if the timeout has
1085 // already fired, so this is safe on the normal completion path as well.
1086 promise = promise.attach(kj::defer([timeoutId, &ioContext = IoContext::current()]() {
1087 ioContext.clearTimeoutImpl(TimeoutId::fromNumber(timeoutId));
1088 }));
1089 }
1090 }
1091 
1092 return kj::mv(promise);
1093}
1094 
1095void ExtendableEvent::waitUntil(kj::Promise<void> promise) {
1096 JSG_REQUIRE(
1097 getIsTrusted(), DOMInvalidStateError, "waitUntil() can only be called on trusted event.");
1098 IoContext::current().addWaitUntil(kj::mv(promise));
1099}
1100 
1101jsg::Optional<jsg::Ref<ActorState>> ExtendableEvent::getActorState(jsg::Lock& js) {
1102 IoContext& context = IoContext::current();
1103 return context.getActor().map([&](Worker::Actor& actor) {
1104 auto& lock = context.getCurrentLock();
1105 auto persistent = actor.makeStorageForSwSyntax(lock);
1106 return js.alloc<api::ActorState>(actor.cloneId(), actor.getTransient(lock), kj::mv(persistent));
1107 });
1108}
1109 
1110CustomEvent::CustomEvent(kj::String ownType, CustomEventInit init)
1111 : Event(kj::mv(ownType), Event::Init(init)),
1112 detail(kj::mv(init.detail)) {}
1113 
1114jsg::Ref<CustomEvent> CustomEvent::constructor(
1115 jsg::Lock& js, kj::String type, jsg::Optional<CustomEventInit> init) {
1116 return js.alloc<CustomEvent>(kj::mv(type), kj::mv(init).orDefault({}));
1117}
1118 
1119jsg::Optional<jsg::JsValue> CustomEvent::getDetail(jsg::Lock& js) {
1120 return detail.map([&](jsg::JsRef<jsg::JsValue>& val) { return val.getHandle(js); });
1121}
1122 
1123CustomEvent::CustomEventInit::operator Event::Init() {
1124 return {
1125 .bubbles = this->bubbles.map([](auto& val) { return val; }),
1126 .cancelable = this->cancelable.map([](auto& val) { return val; }),
1127 .composed = this->composed.map([](auto& val) { return val; }),
1128 };
1129}
1130 
1131size_t EventTarget::EventHandler::JavaScriptHandler::jsgGetMemorySelfSize() const {
1132 return sizeof(JavaScriptHandler);
1133}
1134 
1135void EventTarget::EventHandler::JavaScriptHandler::jsgGetMemoryInfo(
1136 jsg::MemoryTracker& tracker) const {
1137 tracker.trackField("identity", identity);
1138 tracker.trackField("callback", callback);
1139 if (abortHandler != kj::none) {
1140 tracker.trackFieldWithSize(
1141 "abortHandler", sizeof(kj::Own<NativeHandler>) + sizeof(NativeHandler));
1142 }
1143}
1144 
1145size_t EventTarget::EventHandler::jsgGetMemorySelfSize() const {
1146 return sizeof(EventHandler);
1147}
1148 
1149void EventTarget::EventHandler::jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const {
1150 KJ_SWITCH_ONEOF(handler) {
1151 KJ_CASE_ONEOF(js, JavaScriptHandler) {
1152 tracker.trackField("js", js);
1153 }
1154 KJ_CASE_ONEOF(native, NativeHandlerRef) {
1155 tracker.trackFieldWithSize("native", sizeof(NativeHandlerRef));
1156 }
1157 }
1158}
1159 
1160size_t EventTarget::EventHandlerSet::jsgGetMemorySelfSize() const {
1161 return sizeof(EventHandlerSet);
1162}
1163 
1164void EventTarget::EventHandlerSet::jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const {
1165 for (const auto& handler: handlers) {
1166 tracker.trackField("handler", handler);
1167 }
1168}
1169 
1170void EventTarget::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {
1171 tracker.trackField("typeMap", typeMap);
1172}
1173 
1174} // namespace workerd::api