Skip to content

Commit

Permalink
LibWeb: Correctly implement event listeners default passive attribute
Browse files Browse the repository at this point in the history
This commit implements the default value of the passive attribute of
event listeners according to the spec.
  • Loading branch information
skyz1 committed Dec 24, 2024
1 parent 910ff8b commit 1cda49d
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 48 deletions.
5 changes: 3 additions & 2 deletions Libraries/LibWeb/DOM/DOMEventListener.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2022, Andreas Kling <[email protected]>
* Copyright (c) 2024, Glenn Skrzypczak <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
Expand Down Expand Up @@ -35,8 +36,8 @@ class DOMEventListener : public JS::Cell {
// capture (a boolean, initially false)
bool capture { false };

// passive (a boolean, initially false)
bool passive { false };
// passive (null or a boolean, initially null)
Optional<bool> passive;

// once (a boolean, initially false)
bool once { false };
Expand Down
3 changes: 2 additions & 1 deletion Libraries/LibWeb/DOM/EventDispatcher.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2020-2022, Andreas Kling <[email protected]>
* Copyright (c) 2024, Glenn Skrzypczak <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
Expand Down Expand Up @@ -81,7 +82,7 @@ bool EventDispatcher::inner_invoke(Event& event, Vector<GC::Root<DOM::DOMEventLi
}

// 9. If listener’s passive is true, then set event’s in passive listener flag.
if (listener->passive)
if (listener->passive == true)
event.set_in_passive_listener(true);

// FIXME: 10. If global is a Window object, then record timing info for event listener given event and listener.
Expand Down
49 changes: 40 additions & 9 deletions Libraries/LibWeb/DOM/EventTarget.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/*
* Copyright (c) 2020-2022, Andreas Kling <[email protected]>
* Copyright (c) 2022, Luke Wilde <[email protected]>
* Copyright (c) 2024, Glenn Skrzypczak <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
Expand Down Expand Up @@ -110,7 +111,7 @@ static bool flatten_event_listener_options(Variant<AddEventListenerOptions, bool

struct FlattenedAddEventListenerOptions {
bool capture { false };
bool passive { false };
Optional<bool> passive { false };
bool once { false };
GC::Ptr<AbortSignal> signal;
};
Expand All @@ -121,21 +122,24 @@ static FlattenedAddEventListenerOptions flatten_add_event_listener_options(Varia
// 1. Let capture be the result of flattening options.
bool capture = flatten_event_listener_options(options);

// 2. Let once and passive be false.
// 2. Let once be false.
bool once = false;
bool passive = false;

// 3. Let signal be null.
// 3. Let passive and signal be null.
Optional<bool> passive;
GC::Ptr<AbortSignal> signal;

// 4. If options is a dictionary, then:
if (options.has<AddEventListenerOptions>()) {
auto& add_event_listener_options = options.get<AddEventListenerOptions>();
auto const& add_event_listener_options = options.get<AddEventListenerOptions>();

// 1. Set passive to options["passive"] and once to options["once"].
passive = add_event_listener_options.passive;
// 1. Set once to options["once"].
once = add_event_listener_options.once;

// 2. If options["passive"] exists, then set passive to options["passive"].
if (add_event_listener_options.passive.has_value())
passive = add_event_listener_options.passive;

// 2. If options["signal"] exists, then set signal to options["signal"].
if (add_event_listener_options.signal)
signal = add_event_listener_options.signal;
Expand All @@ -145,6 +149,28 @@ static FlattenedAddEventListenerOptions flatten_add_event_listener_options(Varia
return FlattenedAddEventListenerOptions { .capture = capture, .passive = passive, .once = once, .signal = signal.ptr() };
}

// https://dom.spec.whatwg.org/#default-passive-value
static bool default_passive_value(FlyString const& type, EventTarget* event_target)
{
// 1. Return true if all of the following are true:
// - type is one of "touchstart", "touchmove", "wheel", or "mousewheel".
// - eventTarget is a Window object, or is a node whose node document is eventTarget, or is a node whose node document’s document element is eventTarget,
// or is a node whose node document’s body element is eventTarget.
if (type == "touchstart" || type == "touchmove" || type == "wheel" || type == "mousewheel") {
if (is<HTML::Window>(event_target))
return true;

if (is<Node>(event_target)) {
auto* node = verify_cast<Node>(event_target);
if (&node->document() == event_target || node->document().document_element() == event_target || node->document().body() == event_target)
return true;
}
}

// 2. Return false.
return false;
}

// https://dom.spec.whatwg.org/#dom-eventtarget-addeventlistener
void EventTarget::add_event_listener(FlyString const& type, IDLEventListener* callback, Variant<AddEventListenerOptions, bool> const& options)
{
Expand Down Expand Up @@ -186,7 +212,12 @@ void EventTarget::add_an_event_listener(DOMEventListener& listener)
if (!listener.callback)
return;

// 4. If eventTarget’s event listener list does not contain an event listener whose type is listener’s type, callback is listener’s callback,
// 4. If listener’s passive is null, then set it to the default passive value given listener’s type and eventTarget.
if (!listener.passive.has_value()) {
listener.passive = default_passive_value(listener.type, this);
}

// 5. If eventTarget’s event listener list does not contain an event listener whose type is listener’s type, callback is listener’s callback,
// and capture is listener’s capture, then append listener to eventTarget’s event listener list.
auto it = event_listener_list.find_if([&](auto& entry) {
return entry->type == listener.type
Expand All @@ -196,7 +227,7 @@ void EventTarget::add_an_event_listener(DOMEventListener& listener)
if (it == event_listener_list.end())
event_listener_list.append(listener);

// 5. If listener’s signal is not null, then add the following abort steps to it:
// 6. If listener’s signal is not null, then add the following abort steps to it:
if (listener.signal) {
// NOTE: `this` and `listener` are protected by AbortSignal using GC::HeapFunction.
listener.signal->add_abort_algorithm([this, &listener] {
Expand Down
2 changes: 1 addition & 1 deletion Libraries/LibWeb/DOM/EventTarget.idl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ dictionary EventListenerOptions {
};

dictionary AddEventListenerOptions : EventListenerOptions {
boolean passive = false;
boolean passive;
boolean once = false;
AbortSignal signal;
};
3 changes: 2 additions & 1 deletion Libraries/LibWeb/DOM/IDLEventListener.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2020-2022, Andreas Kling <[email protected]>
* Copyright (c) 2024, Glenn Skrzypczak <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
Expand All @@ -19,7 +20,7 @@ struct EventListenerOptions {
};

struct AddEventListenerOptions : public EventListenerOptions {
bool passive { false };
Optional<bool> passive;
bool once { false };
GC::Ptr<AbortSignal> signal;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,82 +2,81 @@ Harness status: OK

Found 100 tests

68 Pass
32 Fail
Fail touchstart listener is passive by default for Window
Fail touchstart listener is passive with {passive:undefined} for Window
100 Pass
Pass touchstart listener is passive by default for Window
Pass touchstart listener is passive with {passive:undefined} for Window
Pass touchstart listener is non-passive with {passive:false} for Window
Pass touchstart listener is passive with {passive:true} for Window
Fail touchstart listener is passive by default for HTMLDocument
Fail touchstart listener is passive with {passive:undefined} for HTMLDocument
Pass touchstart listener is passive by default for HTMLDocument
Pass touchstart listener is passive with {passive:undefined} for HTMLDocument
Pass touchstart listener is non-passive with {passive:false} for HTMLDocument
Pass touchstart listener is passive with {passive:true} for HTMLDocument
Fail touchstart listener is passive by default for HTMLHtmlElement
Fail touchstart listener is passive with {passive:undefined} for HTMLHtmlElement
Pass touchstart listener is passive by default for HTMLHtmlElement
Pass touchstart listener is passive with {passive:undefined} for HTMLHtmlElement
Pass touchstart listener is non-passive with {passive:false} for HTMLHtmlElement
Pass touchstart listener is passive with {passive:true} for HTMLHtmlElement
Fail touchstart listener is passive by default for HTMLBodyElement
Fail touchstart listener is passive with {passive:undefined} for HTMLBodyElement
Pass touchstart listener is passive by default for HTMLBodyElement
Pass touchstart listener is passive with {passive:undefined} for HTMLBodyElement
Pass touchstart listener is non-passive with {passive:false} for HTMLBodyElement
Pass touchstart listener is passive with {passive:true} for HTMLBodyElement
Pass touchstart listener is non-passive by default for HTMLDivElement
Pass touchstart listener is non-passive with {passive:undefined} for HTMLDivElement
Pass touchstart listener is non-passive with {passive:false} for HTMLDivElement
Pass touchstart listener is passive with {passive:true} for HTMLDivElement
Fail touchmove listener is passive by default for Window
Fail touchmove listener is passive with {passive:undefined} for Window
Pass touchmove listener is passive by default for Window
Pass touchmove listener is passive with {passive:undefined} for Window
Pass touchmove listener is non-passive with {passive:false} for Window
Pass touchmove listener is passive with {passive:true} for Window
Fail touchmove listener is passive by default for HTMLDocument
Fail touchmove listener is passive with {passive:undefined} for HTMLDocument
Pass touchmove listener is passive by default for HTMLDocument
Pass touchmove listener is passive with {passive:undefined} for HTMLDocument
Pass touchmove listener is non-passive with {passive:false} for HTMLDocument
Pass touchmove listener is passive with {passive:true} for HTMLDocument
Fail touchmove listener is passive by default for HTMLHtmlElement
Fail touchmove listener is passive with {passive:undefined} for HTMLHtmlElement
Pass touchmove listener is passive by default for HTMLHtmlElement
Pass touchmove listener is passive with {passive:undefined} for HTMLHtmlElement
Pass touchmove listener is non-passive with {passive:false} for HTMLHtmlElement
Pass touchmove listener is passive with {passive:true} for HTMLHtmlElement
Fail touchmove listener is passive by default for HTMLBodyElement
Fail touchmove listener is passive with {passive:undefined} for HTMLBodyElement
Pass touchmove listener is passive by default for HTMLBodyElement
Pass touchmove listener is passive with {passive:undefined} for HTMLBodyElement
Pass touchmove listener is non-passive with {passive:false} for HTMLBodyElement
Pass touchmove listener is passive with {passive:true} for HTMLBodyElement
Pass touchmove listener is non-passive by default for HTMLDivElement
Pass touchmove listener is non-passive with {passive:undefined} for HTMLDivElement
Pass touchmove listener is non-passive with {passive:false} for HTMLDivElement
Pass touchmove listener is passive with {passive:true} for HTMLDivElement
Fail wheel listener is passive by default for Window
Fail wheel listener is passive with {passive:undefined} for Window
Pass wheel listener is passive by default for Window
Pass wheel listener is passive with {passive:undefined} for Window
Pass wheel listener is non-passive with {passive:false} for Window
Pass wheel listener is passive with {passive:true} for Window
Fail wheel listener is passive by default for HTMLDocument
Fail wheel listener is passive with {passive:undefined} for HTMLDocument
Pass wheel listener is passive by default for HTMLDocument
Pass wheel listener is passive with {passive:undefined} for HTMLDocument
Pass wheel listener is non-passive with {passive:false} for HTMLDocument
Pass wheel listener is passive with {passive:true} for HTMLDocument
Fail wheel listener is passive by default for HTMLHtmlElement
Fail wheel listener is passive with {passive:undefined} for HTMLHtmlElement
Pass wheel listener is passive by default for HTMLHtmlElement
Pass wheel listener is passive with {passive:undefined} for HTMLHtmlElement
Pass wheel listener is non-passive with {passive:false} for HTMLHtmlElement
Pass wheel listener is passive with {passive:true} for HTMLHtmlElement
Fail wheel listener is passive by default for HTMLBodyElement
Fail wheel listener is passive with {passive:undefined} for HTMLBodyElement
Pass wheel listener is passive by default for HTMLBodyElement
Pass wheel listener is passive with {passive:undefined} for HTMLBodyElement
Pass wheel listener is non-passive with {passive:false} for HTMLBodyElement
Pass wheel listener is passive with {passive:true} for HTMLBodyElement
Pass wheel listener is non-passive by default for HTMLDivElement
Pass wheel listener is non-passive with {passive:undefined} for HTMLDivElement
Pass wheel listener is non-passive with {passive:false} for HTMLDivElement
Pass wheel listener is passive with {passive:true} for HTMLDivElement
Fail mousewheel listener is passive by default for Window
Fail mousewheel listener is passive with {passive:undefined} for Window
Pass mousewheel listener is passive by default for Window
Pass mousewheel listener is passive with {passive:undefined} for Window
Pass mousewheel listener is non-passive with {passive:false} for Window
Pass mousewheel listener is passive with {passive:true} for Window
Fail mousewheel listener is passive by default for HTMLDocument
Fail mousewheel listener is passive with {passive:undefined} for HTMLDocument
Pass mousewheel listener is passive by default for HTMLDocument
Pass mousewheel listener is passive with {passive:undefined} for HTMLDocument
Pass mousewheel listener is non-passive with {passive:false} for HTMLDocument
Pass mousewheel listener is passive with {passive:true} for HTMLDocument
Fail mousewheel listener is passive by default for HTMLHtmlElement
Fail mousewheel listener is passive with {passive:undefined} for HTMLHtmlElement
Pass mousewheel listener is passive by default for HTMLHtmlElement
Pass mousewheel listener is passive with {passive:undefined} for HTMLHtmlElement
Pass mousewheel listener is non-passive with {passive:false} for HTMLHtmlElement
Pass mousewheel listener is passive with {passive:true} for HTMLHtmlElement
Fail mousewheel listener is passive by default for HTMLBodyElement
Fail mousewheel listener is passive with {passive:undefined} for HTMLBodyElement
Pass mousewheel listener is passive by default for HTMLBodyElement
Pass mousewheel listener is passive with {passive:undefined} for HTMLBodyElement
Pass mousewheel listener is non-passive with {passive:false} for HTMLBodyElement
Pass mousewheel listener is passive with {passive:true} for HTMLBodyElement
Pass mousewheel listener is non-passive by default for HTMLDivElement
Expand Down

0 comments on commit 1cda49d

Please sign in to comment.