// Copyright (c) 2017-2022 Cloudflare, Inc. // Licensed under the Apache 2.0 license found in the LICENSE file or at: // https://opensource.org/licenses/Apache-2.0 #pragma once // This file defines Event- and EventTarget-related APIs. // // TODO(cleanup): Rename to events.h? #include #include #include #include #include #include #include #include namespace workerd::api { class EventTarget; class AbortSignal; class AbortController; class ActorState; // An implementation of the Web Platform Standard Event API class Event: public jsg::Object { public: struct Init final { jsg::Optional bubbles; jsg::Optional cancelable; jsg::Optional composed; JSG_STRUCT(bubbles, cancelable, composed); }; inline explicit Event(kj::String ownType, Init init = {}, bool trusted = true) : ownType(kj::mv(ownType)), type(this->ownType) { flags.trusted = trusted; flags.bubbles = init.bubbles.orDefault(false); flags.cancelable = init.cancelable.orDefault(false); flags.composed = init.composed.orDefault(false); } inline explicit Event(kj::StringPtr type, Init init = {}, bool trusted = true): type(type) { flags.trusted = trusted; flags.bubbles = init.bubbles.orDefault(false); flags.cancelable = init.cancelable.orDefault(false); flags.composed = init.composed.orDefault(false); } inline bool isPreventDefault() const { return flags.preventedDefault; } inline void clearPreventDefault() { flags.preventedDefault = false; } void beginDispatch(jsg::Ref target); inline void endDispatch() { flags.isBeingDispatched = false; } inline bool isStopped() const { return flags.stopped; } static jsg::Ref constructor(jsg::Lock& js, kj::String type, jsg::Optional init); kj::StringPtr getType(); inline void stopImmediatePropagation() { flags.stopped = true; } inline void preventDefault() { flags.preventedDefault = true; } // The only phases we actually use are NONE and AT_TARGET but we provide // all of them to meet spec compliance. enum Phase { NONE, CAPTURING_PHASE, AT_TARGET, BUBBLING_PHASE, }; inline int getEventPhase() const { return flags.isBeingDispatched ? AT_TARGET : NONE; } // Much of the following is not used in our implementation of Event // simply because we do not support the notion of bubbled events // (events propagated up through a hierarchy of objects). They are // provided to fill-out Event spec compliance. inline bool getCancelBubble() const { return flags.propagationStopped; } inline void setCancelBubble(bool stopped) { flags.propagationStopped = stopped; } inline void stopPropagation() { flags.propagationStopped = true; } inline bool getComposed() const { return flags.composed; } inline bool getBubbles() const { return flags.bubbles; } inline bool getCancelable() const { return flags.cancelable; } inline bool getDefaultPrevented() const { return getCancelable() && flags.preventedDefault; } inline bool getReturnValue() const { return !getDefaultPrevented(); } // We provide the timeStamp property for spec compliance but we force // the value to 0.0 always because we really don't want users to rely // on this property for timing details. inline double getTimestamp() const { return 0.0; } // What makes an Event trusted? It's pretty simple... any Event created // by EW internally is Trusted, any Event created using new Event() in JS // is not trusted. inline bool getIsTrusted() const { return flags.trusted; } // The currentTarget is the EventTarget on which the Event is being // dispatched. This will be set every time dispatchEvent() is called // successfully and will be null after dispatchEvent returns. kj::Maybe> getCurrentTarget(); // Because we don't support hierarchical EventTargets, this function // will always return the same value as getCurrentTarget(). jsg::Optional> getTarget(); // For our implementation, since we do not support hierarchical EventTargets, // the composedPath is always either an empty array if the Event is currently // not being dispatched, or an array containing only the currentTarget if // it is being dispatched. kj::Array> composedPath(); JSG_RESOURCE_TYPE(Event, CompatibilityFlags::Reader flags) { // Previously, we were setting all properties as instance properties, // which broke the ability to subclass the Event object. With the // compatibility flag set, we instead attach the properties to the // prototype. if (flags.getJsgPropertyOnPrototypeTemplate()) { JSG_READONLY_PROTOTYPE_PROPERTY(type, getType); JSG_READONLY_PROTOTYPE_PROPERTY(eventPhase, getEventPhase); JSG_READONLY_PROTOTYPE_PROPERTY(composed, getComposed); JSG_READONLY_PROTOTYPE_PROPERTY(bubbles, getBubbles); JSG_READONLY_PROTOTYPE_PROPERTY(cancelable, getCancelable); JSG_READONLY_PROTOTYPE_PROPERTY(defaultPrevented, getDefaultPrevented); JSG_READONLY_PROTOTYPE_PROPERTY(returnValue, getReturnValue); if (flags.getPedanticWpt()) { JSG_READONLY_PROTOTYPE_PROPERTY(currentTarget, getCurrentTarget); } else { // The original implementation had getTarget simply deferring to // getCurrentTarget, the new impl moves the original impl into // getTarget here so having currentTarget point to getTarget // preserves the original behavior. JSG_READONLY_PROTOTYPE_PROPERTY(currentTarget, getTarget); } JSG_READONLY_PROTOTYPE_PROPERTY(target, getTarget); JSG_READONLY_PROTOTYPE_PROPERTY(srcElement, getTarget); JSG_READONLY_PROTOTYPE_PROPERTY(timeStamp, getTimestamp); if (flags.getPedanticWpt()) { JSG_READONLY_INSTANCE_PROPERTY(isTrusted, getIsTrusted); } else { JSG_READONLY_PROTOTYPE_PROPERTY(isTrusted, getIsTrusted); } JSG_PROTOTYPE_PROPERTY(cancelBubble, getCancelBubble, setCancelBubble); } else { JSG_READONLY_INSTANCE_PROPERTY(type, getType); JSG_READONLY_INSTANCE_PROPERTY(eventPhase, getEventPhase); JSG_READONLY_INSTANCE_PROPERTY(composed, getComposed); JSG_READONLY_INSTANCE_PROPERTY(bubbles, getBubbles); JSG_READONLY_INSTANCE_PROPERTY(cancelable, getCancelable); JSG_READONLY_INSTANCE_PROPERTY(defaultPrevented, getDefaultPrevented); JSG_READONLY_INSTANCE_PROPERTY(returnValue, getReturnValue); if (flags.getPedanticWpt()) { JSG_READONLY_INSTANCE_PROPERTY(currentTarget, getCurrentTarget); } else { JSG_READONLY_INSTANCE_PROPERTY(currentTarget, getTarget); } JSG_READONLY_INSTANCE_PROPERTY(target, getTarget); JSG_READONLY_INSTANCE_PROPERTY(srcElement, getCurrentTarget); JSG_READONLY_INSTANCE_PROPERTY(timeStamp, getTimestamp); JSG_READONLY_INSTANCE_PROPERTY(isTrusted, getIsTrusted); JSG_INSTANCE_PROPERTY(cancelBubble, getCancelBubble, setCancelBubble); } JSG_METHOD(stopImmediatePropagation); JSG_METHOD(preventDefault); JSG_METHOD(stopPropagation); JSG_METHOD(composedPath); JSG_STATIC_CONSTANT(NONE); JSG_STATIC_CONSTANT(CAPTURING_PHASE); JSG_STATIC_CONSTANT(AT_TARGET); JSG_STATIC_CONSTANT(BUBBLING_PHASE); } void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { tracker.trackField("type", ownType); tracker.trackField("target", target); } private: // listing ownType first so type can be initialized with it in constructor kj::String ownType; kj::StringPtr type; kj::Maybe> target; struct Flags { uint8_t trusted : 1 = 1; uint8_t stopped : 1 = 0; uint8_t preventedDefault : 1 = 0; uint8_t isBeingDispatched : 1 = 0; uint8_t propagationStopped : 1 = 0; uint8_t composed : 1 = 0; uint8_t bubbles : 1 = 0; uint8_t cancelable : 1 = 0; }; Flags flags{}; void visitForGc(jsg::GcVisitor& visitor) { visitor.visit(target); } }; class ExtendableEvent: public Event { public: using Event::Event; // While ExtendableEvent is defined by the spec to be constructable, there's really not a // lot of reason currently to do so, especially with the restriction that waitUntil can // only be called on trusted events (which have to originate from within the system). static jsg::Ref constructor(kj::String type) = delete; void waitUntil(kj::Promise promise); jsg::Optional> getActorState(jsg::Lock& js); JSG_RESOURCE_TYPE(ExtendableEvent) { JSG_INHERIT(Event); JSG_METHOD(waitUntil); #if !WORKERD_API_BASICS_TEST JSG_LAZY_READONLY_INSTANCE_PROPERTY(actorState, getActorState); #endif JSG_TS_OVERRIDE({ actorState: never }); // Omit `actorState` from definitions } }; // An implementation of the Web Platform Standard CustomEvent API class CustomEvent: public Event { public: struct CustomEventInit final { jsg::Optional bubbles; jsg::Optional cancelable; jsg::Optional composed; jsg::Optional> detail; JSG_STRUCT(bubbles, cancelable, composed, detail); operator Event::Init(); }; explicit CustomEvent(kj::String ownType, CustomEventInit init = CustomEventInit()); static jsg::Ref constructor( jsg::Lock& js, kj::String type, jsg::Optional init); jsg::Optional getDetail(jsg::Lock& js); JSG_RESOURCE_TYPE(CustomEvent) { JSG_INHERIT(Event); JSG_READONLY_PROTOTYPE_PROPERTY(detail, getDetail); JSG_TS_OVERRIDE( { get detail(): T; }); } void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { tracker.trackField("detail", detail); } private: jsg::Optional> detail; }; // An implementation of the Web Platform Standard EventTarget API class EventTarget: public jsg::Object { public: ~EventTarget() noexcept(false); size_t getHandlerCount(kj::StringPtr type) const; kj::Array getHandlerNames() const; bool dispatchEventImpl(jsg::Lock& js, jsg::Ref event); inline void removeAllHandlers() { typeMap.clear(); } inline void enableWarningOnSpecialEvents() { flags.warnOnSpecialEvents = true; } // The EventListenerCallback, if given, is called whenever addEventListener // or removeEventListener is invoked to report the number of registered // handlers for the event. using EventListenerCallback = jsg::Function; // --------------------------------------------------------------------------- // JS API struct EventListenerOptions { jsg::Optional capture; JSG_STRUCT(capture); }; struct AddEventListenerOptions { jsg::Optional capture; jsg::Optional passive; jsg::Optional once; jsg::Optional> signal; JSG_STRUCT(capture, passive, once, signal); // A following signal is used when the EventTarget is an AbortSignal // that is being followed by another AbortSignal via the AbortSignal.any. // This is used to keep the following signal alive until either the // signal is triggered or this AbortSignal is destroyed. jsg::Optional> followingSignal; }; using AddEventListenerOpts = kj::OneOf; using EventListenerOpts = kj::OneOf; using HandlerFunction = jsg::Function(jsg::Ref)>; struct HandlerObject { HandlerFunction handleEvent; jsg::SelfRef self; JSG_STRUCT(handleEvent, self); // TODO(cleanup): Get rid of this override and parse the type directly in param-extractor.rs JSG_STRUCT_TS_OVERRIDE({ handleEvent: (event: Event) => any | undefined; }); }; using Handler = kj::OneOf; void addEventListener(jsg::Lock& js, kj::String type, kj::Maybe> maybeHandler, jsg::Optional maybeOptions, const jsg::TypeHandler>& eventTargetHandler); void removeEventListener(jsg::Lock& js, kj::String type, kj::Maybe> maybeHandler, jsg::Optional options); bool dispatchEvent(jsg::Lock& js, jsg::Ref event); JSG_RESOURCE_TYPE(EventTarget) { JSG_METHOD(addEventListener); JSG_METHOD(removeEventListener); JSG_METHOD(dispatchEvent); JSG_TS_DEFINE( type EventListener = (event: EventType) => void; interface EventListenerObject { handleEvent(event: EventType): void; } type EventListenerOrEventListenerObject = EventListener | EventListenerObject; ); JSG_TS_OVERRIDE( = Record> { addEventListener(type: Type, handler: EventListenerOrEventListenerObject, options?: EventTargetAddEventListenerOptions | boolean): void; removeEventListener(type: Type, handler: EventListenerOrEventListenerObject, options?: EventTargetEventListenerOptions | boolean): void; dispatchEvent(event: EventMap[keyof EventMap]): boolean; }); } JSG_REFLECTION(onEvents); static jsg::Ref constructor(jsg::Lock& js); // Registers a lambda that will be called when the given event type is emitted. // The handler will be registered for as long as the returned kj::Own // handle is held. If the EventTarget is destroyed while the native handler handle // is held, it will be automatically detached. // // The caller must not do anything with the returned Own except drop it. This is why it // is Own and not Own. kj::Own newNativeHandler( jsg::Lock& js, kj::String type, jsg::Function)> func, bool once = false); void visitForMemoryInfo(jsg::MemoryTracker& tracker) const; protected: void setEventListenerCallback(EventListenerCallback&& callback) { maybeListenerCallback = kj::mv(callback); } private: // RAII-style listener that can be attached to an EventTarget. class NativeHandler { public: using Signature = void(jsg::Ref); NativeHandler(jsg::Lock& js, EventTarget& target, kj::String type, jsg::Function func, bool once = false); ~NativeHandler() noexcept(false); KJ_DISALLOW_COPY_AND_MOVE(NativeHandler); void operator()(jsg::Lock& js, jsg::Ref event); uint hashCode() const; void visitForGc(jsg::GcVisitor& visitor); private: void detach(); kj::String type; struct State { // target's destructor will null out `state`, so this is OK to be a bare reference. EventTarget& target; jsg::Function func; }; kj::Maybe state; bool once; friend class EventTarget; }; void addNativeListener(jsg::Lock& js, NativeHandler& handler); bool removeNativeListener(NativeHandler& handler); struct EventHandler { struct JavaScriptHandler { jsg::HashableV8Ref identity; HandlerFunction callback; // If the event handler is registered with an AbortSignal, then the abortHandler points // at the NativeHandler representing that registration, so that if this object is GC'ed before // the AbortSignal is signalled, we unregister ourselves from listening on it. Note that // this is Own for the same reason newNativeHandler() returns Own: We are not // supposed to do anything with this except drop it. kj::Maybe> abortHandler; void visitForGc(jsg::GcVisitor& visitor) { visitor.visit(identity, callback); // Note that we intentionally do NOT visit `abortHandler`. This is because the JS handles // held by `abortHandler` are not ever accessed by this path. Instead, they are accessed // by the AbortSignal, if and when it fires. So it is the AbortSignal's responsibility to // visit the NativeHandler's content. } kj::StringPtr jsgGetMemoryName() const { return "JavaScriptHandler"_kjc; } size_t jsgGetMemorySelfSize() const; void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const; }; struct NativeHandlerRef { NativeHandler& handler; }; // An EventHandler can be backed by either a JavaScript Handler (which is either a // function or an object) or a native handler. The insertion order matters here so // we maintain a single table. using Handler = kj::OneOf; Handler handler; // When once is true, the handler will be removed after it is invoked one time. bool once = false; EventHandler(Handler handler, bool once): handler(kj::mv(handler)), once(once) {} KJ_DISALLOW_COPY_AND_MOVE(EventHandler); kj::StringPtr jsgGetMemoryName() const { return "EventHandler"_kjc; } size_t jsgGetMemorySelfSize() const; void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const; }; struct EventHandlerHashCallbacks { const EventHandler::Handler& keyForRow(const kj::Own& row) const; bool matches(const kj::Own& a, const jsg::HashableV8Ref& b) const; bool matches(const kj::Own& a, const NativeHandler& b) const; bool matches(const kj::Own& a, const EventHandler::NativeHandlerRef& b) const; bool matches(const kj::Own& a, const EventHandler::Handler& b) const; uint hashCode(const jsg::HashableV8Ref& obj) const; uint hashCode(const NativeHandler& handler) const; uint hashCode(const EventHandler::NativeHandlerRef& handler) const; uint hashCode(const EventHandler::JavaScriptHandler& handler) const; uint hashCode(const EventHandler::Handler& handler) const; }; struct EventHandlerSet { kj::Table, kj::HashIndex, kj::InsertionOrderIndex> handlers; EventHandlerSet(): handlers(EventHandlerHashCallbacks(), {}) {} kj::StringPtr jsgGetMemoryName() const { return "EventHandlerSet"_kjc; } size_t jsgGetMemorySelfSize() const; void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const; }; EventHandlerSet& getOrCreate(kj::StringPtr str) KJ_LIFETIMEBOUND; jsg::PropertyReflection> onEvents; kj::HashMap typeMap; kj::Maybe maybeListenerCallback; struct Flags { // When using module syntax, the "fetch", "scheduled", "trace", etc. // events are handled by exports rather than events. When warnOnSpecialEvents is true, // when using module syntax, attempts to register event handlers for these special // types of events will result in a warning being emitted. uint8_t warnOnSpecialEvents : 1 = 0; // Event handlers are not supposed to return values. The first time one does, we'll // emit a warning to help users debug things but we'll otherwise ignore it. uint8_t warnOnHandlerReturn : 1 = 1; }; Flags flags; void visitForGc(jsg::GcVisitor& visitor); friend class NativeHandler; }; // An implementation of the Web Platform Standard AbortSignal API class AbortTriggerRpcClient; class AbortSignal final: public EventTarget { public: enum class Flag { NONE, NEVER_ABORTS, IGNORE_FOR_SUBREQUESTS }; AbortSignal(kj::Maybe exception = kj::none, jsg::Optional> maybeReason = kj::none, Flag flag = Flag::NONE); using PendingReason = ExternalPusherImpl::PendingAbortReason; // The AbortSignal explicitly does not expose a constructor(). It is // illegal for user code to create an AbortSignal directly. static jsg::Ref constructor() = delete; bool getAborted(jsg::Lock& js); jsg::JsValue getReason(jsg::Lock& js); // Will synchronously throw an error if the abort signal has been triggered. void throwIfAborted(jsg::Lock& js); inline bool getNeverAborts() const { return flag == Flag::NEVER_ABORTS; } // The static abort() function here returns an AbortSignal that // has been pre-emptively aborted. It's useful when it might still // be desirable to kick off an async process while communicating // that it shouldn't continue. static jsg::Ref abort(jsg::Lock& js, jsg::Optional reason); // Returns an AbortSignal that is triggered after delay milliseconds. static jsg::Ref timeout(jsg::Lock& js, double delay); void triggerAbort( jsg::Lock& js, jsg::Optional> maybeReason); static jsg::Ref any(jsg::Lock& js, kj::Array> signals, const jsg::TypeHandler& handler, const jsg::TypeHandler>& eventTargetHandler); // While AbortSignal extends EventTarget, and our EventTarget implementation will // automatically support onabort being set as an own property, the spec defines // onabort as a prototype property on the AbortSignal prototype. Therefore, we // need to explicitly set it as a prototype property here. kj::Maybe getOnAbort(jsg::Lock& js); void setOnAbort(jsg::Lock& js, jsg::Optional handler); void addEventListener(jsg::Lock& js, kj::String type, jsg::Identified handler, jsg::Optional maybeOptions, const jsg::TypeHandler>& eventTargetHandler); JSG_RESOURCE_TYPE(AbortSignal, CompatibilityFlags::Reader flags) { JSG_INHERIT(EventTarget); JSG_STATIC_METHOD(abort); JSG_STATIC_METHOD(timeout); JSG_STATIC_METHOD(any); if (flags.getJsgPropertyOnPrototypeTemplate()) { JSG_READONLY_PROTOTYPE_PROPERTY(aborted, getAborted); JSG_READONLY_PROTOTYPE_PROPERTY(reason, getReason); } else { JSG_READONLY_INSTANCE_PROPERTY(aborted, getAborted); JSG_READONLY_INSTANCE_PROPERTY(reason, getReason); } JSG_PROTOTYPE_PROPERTY(onabort, getOnAbort, setOnAbort); JSG_METHOD(throwIfAborted); if (flags.getWorkerdExperimental()) { JSG_METHOD(skipReleaseForTest); JSG_TS_OVERRIDE({ skipReleaseForTest: never }); } } // Allows this AbortSignal to also serve as a kj::Canceler template kj::Promise wrap(jsg::Lock& js, kj::Promise promise) { subscribeToRpcAbort(js); JSG_REQUIRE(!canceler->isCanceled(), TypeError, "The AbortSignal has already been triggered"); return canceler->wrap(kj::mv(promise)); } template static kj::Promise maybeCancelWrap( jsg::Lock& js, kj::Maybe>& signal, kj::Promise promise) { KJ_IF_SOME(s, signal) { return s->wrap(js, kj::mv(promise)); } else { return kj::mv(promise); } } RefcountedCanceler& getCanceler(); void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { EventTarget::visitForMemoryInfo(tracker); tracker.trackInlineFieldWithSize( "IoOwn", sizeof(IoOwn)); tracker.trackField("reason", reason); } void serialize(jsg::Lock& js, jsg::Serializer& serializer); // To test what happens if a capability is dropped before invoking release on the cloned abort // signal, this method will tell every rpcClient to skip this step before destruction. void skipReleaseForTest(); static jsg::Ref deserialize( jsg::Lock& js, rpc::SerializationTag tag, jsg::Deserializer& deserializer); JSG_SERIALIZABLE(rpc::SerializationTag::ABORT_SIGNAL); // True if this is a signal on the request of an incoming fetch. When the compat flag // `requestSignalPassthrough` is set, this flag has no effect. But to ensure backwards // compatibility, when this flag is not set, this signal will not be passed through to // subrequests derived from the incoming request. bool isIgnoredForSubrequests(jsg::Lock& js) const; private: IoOwn canceler; Flag flag; kj::Maybe> reason; kj::Maybe> onAbortHandler; static kj::Exception abortException( jsg::Lock& js, const jsg::Optional>& reason); void visitForGc(jsg::GcVisitor& visitor); friend class AbortController; // ------------------------------------------------------------- // RPC client functionality. Used if this signal was serialized. // A collection of rpcClients, which will be notified if this signal is triggered and when this // signal is destroyed. kj::Vector> rpcClients; // Trigger an abort on all associated clients kj::Promise sendToRpc(kj::Array&& reason); // --------------------------------------------------------------- // RPC server functionality. Used if this signal was deserialized. // A promise that is fulfilled if an abort() message is received over RPC. kj::Maybe>> rpcAbortPromise; // A refcounted object used to receive a serialized abort reason // The abort reason is required in asynchronous event handlers as well as synchronous methods // like getReason(). As a result, we can't pass the abort reason in the above promise, and both // sync and async methods will need to check this value. kj::Maybe> pendingReason; // Synchronously check if an abort reason was sent over RPC bool hasPendingReason(); kj::Maybe deserializePendingReason(jsg::Lock& js); // Wait for abort over RPC. // We invoke this once at least one event handler is attached to the AbortSignal void subscribeToRpcAbort(jsg::Lock& js); }; // An implementation of the Web Platform Standard AbortController API class AbortController final: public jsg::Object { public: explicit AbortController( jsg::Lock& js, AbortSignal::Flag abortSignalFlag = AbortSignal::Flag::NONE) : signal(js.alloc( kj::none /* exception */, kj::none /* maybeReason */, abortSignalFlag)) {} static jsg::Ref constructor(jsg::Lock& js) { return js.alloc(js); } jsg::Ref getSignal() { return signal.addRef(); } void abort(jsg::Lock& js, jsg::Optional reason); JSG_RESOURCE_TYPE(AbortController, CompatibilityFlags::Reader flags) { if (flags.getJsgPropertyOnPrototypeTemplate()) { JSG_READONLY_PROTOTYPE_PROPERTY(signal, getSignal); } else { JSG_READONLY_INSTANCE_PROPERTY(signal, getSignal); } JSG_METHOD(abort); } void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { tracker.trackField("signal", signal); } private: jsg::Ref signal; void visitForGc(jsg::GcVisitor& visitor) { // We have to be careful with GC here. The event listeners added to the AbortSignal // could hold a circular reference to the AbortController. visitor.visit(signal); } }; // The scheduler class is an emerging web platform standard API that is meant // to be global and provides task scheduling APIs. We currently only implement // a subset of the API that is being defined. class Scheduler final: public jsg::Object { public: struct WaitOptions { jsg::Optional> signal; JSG_STRUCT(signal); }; // Returns a promise that resolves after the `delay` milliseconds. // Essentially an awaitable alternative to setTimeout(). The wait // can be canceled using an AbortSignal. kj::Promise wait(jsg::Lock& js, double delay, jsg::Optional maybeOptions); JSG_RESOURCE_TYPE(Scheduler) { JSG_METHOD(wait); } private: }; #define EW_BASICS_ISOLATE_TYPES \ api::Event, api::Event::Init, api::EventTarget, api::EventTarget::EventListenerOptions, \ api::EventTarget::AddEventListenerOptions, api::EventTarget::HandlerObject, \ api::AbortController, api::AbortSignal, api::Scheduler, api::Scheduler::WaitOptions, \ api::ExtendableEvent, api::CustomEvent, api::CustomEvent::CustomEventInit // The list of basics.h types that are added to worker.c++'s JSG_DECLARE_ISOLATE_TYPE } // namespace workerd::api