diff --git a/Userland/Libraries/LibWeb/CSS/MediaQueryList.cpp b/Userland/Libraries/LibWeb/CSS/MediaQueryList.cpp index 583dff0594..776db6ea0f 100644 --- a/Userland/Libraries/LibWeb/CSS/MediaQueryList.cpp +++ b/Userland/Libraries/LibWeb/CSS/MediaQueryList.cpp @@ -71,7 +71,7 @@ void MediaQueryList::add_listener(RefPtr listener) // callback set to listener, and capture set to false, unless there already is an event listener // in that list with the same type, callback, and capture. // (NOTE: capture is set to false by default) - add_event_listener(HTML::EventNames::change, listener); + add_event_listener_without_options(HTML::EventNames::change, listener); } // https://www.w3.org/TR/cssom-view/#dom-mediaquerylist-removelistener @@ -80,7 +80,7 @@ void MediaQueryList::remove_listener(RefPtr listener) // 1. Remove an event listener from the associated list of event listeners, whose type is change, callback is listener, and capture is false. // NOTE: While the spec doesn't technically use remove_event_listener and instead manipulates the list directly, every major engine uses remove_event_listener. // This means if an event listener removes another event listener that comes after it, the removed event listener will not be invoked. - remove_event_listener(HTML::EventNames::change, listener); + remove_event_listener_without_options(HTML::EventNames::change, listener); } void MediaQueryList::set_onchange(Optional event_handler) diff --git a/Userland/Libraries/LibWeb/DOM/EventTarget.cpp b/Userland/Libraries/LibWeb/DOM/EventTarget.cpp index d2debd03db..8ba9dc6ecc 100644 --- a/Userland/Libraries/LibWeb/DOM/EventTarget.cpp +++ b/Userland/Libraries/LibWeb/DOM/EventTarget.cpp @@ -45,27 +45,87 @@ EventTarget::~EventTarget() { } -// https://dom.spec.whatwg.org/#dom-eventtarget-addeventlistener -void EventTarget::add_event_listener(FlyString const& type, RefPtr callback, bool use_capture) +// https://dom.spec.whatwg.org/#concept-flatten-options +static bool flatten_event_listener_options(Variant const& options) { - // FIXME: 1. Let capture, passive, once, and signal be the result of flattening more options. - bool capture = use_capture; - bool passive = false; + // 1. If options is a boolean, then return options. + if (options.has()) + return options.get(); + + // 2. Return options["capture"]. + return options.get().capture; +} + +static bool flatten_event_listener_options(Variant const& options) +{ + // 1. If options is a boolean, then return options. + if (options.has()) + return options.get(); + + // 2. Return options["capture"]. + return options.get().capture; +} + +struct FlattenedAddEventListenerOptions { + bool capture { false }; + bool passive { false }; + bool once { false }; + RefPtr signal; +}; + +// https://dom.spec.whatwg.org/#event-flatten-more +static FlattenedAddEventListenerOptions flatten_add_event_listener_options(Variant const& options) +{ + // 1. Let capture be the result of flattening options. + bool capture = flatten_event_listener_options(options); + + // 2. Let once and passive be false. bool once = false; - RefPtr signal = nullptr; + bool passive = false; + + // 3. Let signal be null. + RefPtr signal; + + // 4. If options is a dictionary, then: + if (options.has()) { + auto& add_event_listener_options = options.get(); + + // 1. Set passive to options["passive"] and once to options["once"]. + passive = add_event_listener_options.passive; + once = add_event_listener_options.once; + + // 2. If options["signal"] exists, then set signal to options["signal"]. + if (add_event_listener_options.signal.has_value()) + signal = add_event_listener_options.signal.value(); + } + + // 5. Return capture, passive, once, and signal. + return FlattenedAddEventListenerOptions { .capture = capture, .passive = passive, .once = once, .signal = signal }; +} + +// https://dom.spec.whatwg.org/#dom-eventtarget-addeventlistener +void EventTarget::add_event_listener(FlyString const& type, RefPtr callback, Variant const& options) +{ + // 1. Let capture, passive, once, and signal be the result of flattening more options. + auto flattened_options = flatten_add_event_listener_options(options); // 2. Add an event listener with this and an event listener whose type is type, callback is callback, capture is capture, passive is passive, // once is once, and signal is signal. auto event_listener = adopt_ref(*new DOMEventListener); event_listener->type = type; event_listener->callback = move(callback); - event_listener->signal = move(signal); - event_listener->capture = capture; - event_listener->passive = passive; - event_listener->once = once; + event_listener->signal = move(flattened_options.signal); + event_listener->capture = flattened_options.capture; + event_listener->passive = flattened_options.passive; + event_listener->once = flattened_options.once; add_an_event_listener(move(event_listener)); } +void EventTarget::add_event_listener_without_options(FlyString const& type, RefPtr callback) +{ + add_event_listener(type, move(callback), AddEventListenerOptions {}); +} + // https://dom.spec.whatwg.org/#add-an-event-listener void EventTarget::add_an_event_listener(NonnullRefPtr listener) { @@ -91,15 +151,20 @@ void EventTarget::add_an_event_listener(NonnullRefPtr listener if (it == m_event_listener_list.end()) m_event_listener_list.append(listener); - // FIXME: 5. If listener’s signal is not null, then add the following abort steps to it: - // FIXME: 1. Remove an event listener with eventTarget and listener. + // 5. If listener’s signal is not null, then add the following abort steps to it: + if (listener->signal) { + listener->signal->add_abort_algorithm([strong_event_target = NonnullRefPtr(*this), listener]() mutable { + // 1. Remove an event listener with eventTarget and listener. + strong_event_target->remove_an_event_listener(listener); + }); + } } // https://dom.spec.whatwg.org/#dom-eventtarget-removeeventlistener -void EventTarget::remove_event_listener(FlyString const& type, RefPtr callback, bool use_capture) +void EventTarget::remove_event_listener(FlyString const& type, RefPtr callback, Variant const& options) { - // FIXME: 1. Let capture be the result of flattening options. - bool capture = use_capture; + // 1. Let capture be the result of flattening options. + bool capture = flatten_event_listener_options(options); // 2. If this’s event listener list contains an event listener whose type is type, callback is callback, and capture is capture, // then remove an event listener with this and that event listener. @@ -112,6 +177,11 @@ void EventTarget::remove_event_listener(FlyString const& type, RefPtr callback) +{ + remove_event_listener(type, move(callback), EventListenerOptions {}); +} + // https://dom.spec.whatwg.org/#remove-an-event-listener void EventTarget::remove_an_event_listener(DOMEventListener& listener) { diff --git a/Userland/Libraries/LibWeb/DOM/EventTarget.h b/Userland/Libraries/LibWeb/DOM/EventTarget.h index a5c56d9c6c..8d5fb9b47c 100644 --- a/Userland/Libraries/LibWeb/DOM/EventTarget.h +++ b/Userland/Libraries/LibWeb/DOM/EventTarget.h @@ -29,8 +29,12 @@ public: virtual bool is_focusable() const { return false; } - void add_event_listener(FlyString const& type, RefPtr callback, bool use_capture = false); - void remove_event_listener(FlyString const& type, RefPtr callback, bool use_capture = false); + void add_event_listener(FlyString const& type, RefPtr callback, Variant const& options); + void remove_event_listener(FlyString const& type, RefPtr callback, Variant const& options); + + // NOTE: These are for internal use only. They operate as though addEventListener(type, callback) was called instead of addEventListener(type, callback, options). + void add_event_listener_without_options(FlyString const& type, RefPtr callback); + void remove_event_listener_without_options(FlyString const& type, RefPtr callback); virtual bool dispatch_event(NonnullRefPtr); ExceptionOr dispatch_event_binding(NonnullRefPtr); diff --git a/Userland/Libraries/LibWeb/DOM/EventTarget.idl b/Userland/Libraries/LibWeb/DOM/EventTarget.idl index 74b005c182..8913b232b5 100644 --- a/Userland/Libraries/LibWeb/DOM/EventTarget.idl +++ b/Userland/Libraries/LibWeb/DOM/EventTarget.idl @@ -1,9 +1,20 @@ +#import + interface EventTarget { - // FIXME: Both of these should take in options - undefined addEventListener(DOMString type, EventListener? callback, optional boolean useCapture = false); - undefined removeEventListener(DOMString type, EventListener? callback, optional boolean useCapture = false); + undefined addEventListener(DOMString type, EventListener? callback, optional (AddEventListenerOptions or boolean) options = {}); + undefined removeEventListener(DOMString type, EventListener? callback, optional (EventListenerOptions or boolean) options = {}); [ImplementedAs=dispatch_event_binding] boolean dispatchEvent(Event event); }; + +dictionary EventListenerOptions { + boolean capture = false; +}; + +dictionary AddEventListenerOptions : EventListenerOptions { + boolean passive = false; + boolean once = false; + AbortSignal signal; +}; diff --git a/Userland/Libraries/LibWeb/DOM/IDLEventListener.h b/Userland/Libraries/LibWeb/DOM/IDLEventListener.h index ca18d2dab2..2ed2b6c78c 100644 --- a/Userland/Libraries/LibWeb/DOM/IDLEventListener.h +++ b/Userland/Libraries/LibWeb/DOM/IDLEventListener.h @@ -10,9 +10,21 @@ #include #include #include +#include namespace Web::DOM { +// NOTE: Even though these dictionaries are defined in EventTarget.idl, they are here to prevent a circular include between EventTarget.h and AbortSignal.h. +struct EventListenerOptions { + bool capture { false }; +}; + +struct AddEventListenerOptions : public EventListenerOptions { + bool passive { false }; + bool once { false }; + Optional> signal; +}; + class IDLEventListener : public RefCounted , public Bindings::Wrappable { diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index 296a05939c..6f8628b709 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -108,6 +108,8 @@ class Text; class Timer; class Window; enum class QuirksMode; +struct EventListenerOptions; +struct AddEventListenerOptions; template class ExceptionOr;