LibWeb: Correctly implement event listeners default passive attribute

This commit implements the default value of the passive attribute of
event listeners according to the spec.
This commit is contained in:
Glenn Skrzypczak 2024-12-24 17:52:52 +01:00 committed by Tim Ledbetter
commit 08589741f5
Notes: github-actions[bot] 2024-12-25 14:58:22 +00:00
6 changed files with 82 additions and 49 deletions

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2022, Andreas Kling <andreas@ladybird.org> * Copyright (c) 2022, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2024, Glenn Skrzypczak <glenn.skrzypczak@gmail.com>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -35,8 +36,8 @@ public:
// capture (a boolean, initially false) // capture (a boolean, initially false)
bool capture { false }; bool capture { false };
// passive (a boolean, initially false) // passive (null or a boolean, initially null)
bool passive { false }; Optional<bool> passive;
// once (a boolean, initially false) // once (a boolean, initially false)
bool once { false }; bool once { false };

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2020-2022, Andreas Kling <andreas@ladybird.org> * Copyright (c) 2020-2022, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2024, Glenn Skrzypczak <glenn.skrzypczak@gmail.com>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -81,7 +82,7 @@ bool EventDispatcher::inner_invoke(Event& event, Vector<GC::Root<DOM::DOMEventLi
} }
// 9. If listeners passive is true, then set events in passive listener flag. // 9. If listeners passive is true, then set events in passive listener flag.
if (listener->passive) if (listener->passive == true)
event.set_in_passive_listener(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. // FIXME: 10. If global is a Window object, then record timing info for event listener given event and listener.

View file

@ -1,6 +1,7 @@
/* /*
* Copyright (c) 2020-2022, Andreas Kling <andreas@ladybird.org> * Copyright (c) 2020-2022, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2022, Luke Wilde <lukew@serenityos.org> * Copyright (c) 2022, Luke Wilde <lukew@serenityos.org>
* Copyright (c) 2024, Glenn Skrzypczak <glenn.skrzypczak@gmail.com>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -110,7 +111,7 @@ static bool flatten_event_listener_options(Variant<AddEventListenerOptions, bool
struct FlattenedAddEventListenerOptions { struct FlattenedAddEventListenerOptions {
bool capture { false }; bool capture { false };
bool passive { false }; Optional<bool> passive;
bool once { false }; bool once { false };
GC::Ptr<AbortSignal> signal; GC::Ptr<AbortSignal> signal;
}; };
@ -121,22 +122,25 @@ static FlattenedAddEventListenerOptions flatten_add_event_listener_options(Varia
// 1. Let capture be the result of flattening options. // 1. Let capture be the result of flattening options.
bool capture = flatten_event_listener_options(options); bool capture = flatten_event_listener_options(options);
// 2. Let once and passive be false. // 2. Let once be false.
bool once = 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; GC::Ptr<AbortSignal> signal;
// 4. If options is a dictionary, then: // 4. If options is a dictionary, then:
if (options.has<AddEventListenerOptions>()) { 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"]. // 1. Set once to options["once"].
passive = add_event_listener_options.passive;
once = add_event_listener_options.once; once = add_event_listener_options.once;
// 2. If options["signal"] exists, then set signal to options["signal"]. // 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;
// 3. If options["signal"] exists, then set signal to options["signal"].
if (add_event_listener_options.signal) if (add_event_listener_options.signal)
signal = add_event_listener_options.signal; signal = add_event_listener_options.signal;
} }
@ -145,6 +149,28 @@ static FlattenedAddEventListenerOptions flatten_add_event_listener_options(Varia
return FlattenedAddEventListenerOptions { .capture = capture, .passive = passive, .once = once, .signal = signal.ptr() }; 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 documents document element is eventTarget,
// or is a node whose node documents body element is eventTarget.
if (AK::first_is_one_of(type, "touchstart"sv, "touchmove"sv, "wheel"sv, "mousewheel"sv)) {
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 // https://dom.spec.whatwg.org/#dom-eventtarget-addeventlistener
void EventTarget::add_event_listener(FlyString const& type, IDLEventListener* callback, Variant<AddEventListenerOptions, bool> const& options) void EventTarget::add_event_listener(FlyString const& type, IDLEventListener* callback, Variant<AddEventListenerOptions, bool> const& options)
{ {
@ -186,7 +212,12 @@ void EventTarget::add_an_event_listener(DOMEventListener& listener)
if (!listener.callback) if (!listener.callback)
return; return;
// 4. If eventTargets event listener list does not contain an event listener whose type is listeners type, callback is listeners callback, // 4. If listeners passive is null, then set it to the default passive value given listeners type and eventTarget.
if (!listener.passive.has_value()) {
listener.passive = default_passive_value(listener.type, this);
}
// 5. If eventTargets event listener list does not contain an event listener whose type is listeners type, callback is listeners callback,
// and capture is listeners capture, then append listener to eventTargets event listener list. // and capture is listeners capture, then append listener to eventTargets event listener list.
auto it = event_listener_list.find_if([&](auto& entry) { auto it = event_listener_list.find_if([&](auto& entry) {
return entry->type == listener.type return entry->type == listener.type
@ -196,7 +227,7 @@ void EventTarget::add_an_event_listener(DOMEventListener& listener)
if (it == event_listener_list.end()) if (it == event_listener_list.end())
event_listener_list.append(listener); event_listener_list.append(listener);
// 5. If listeners signal is not null, then add the following abort steps to it: // 6. If listeners signal is not null, then add the following abort steps to it:
if (listener.signal) { if (listener.signal) {
// NOTE: `this` and `listener` are protected by AbortSignal using GC::HeapFunction. // NOTE: `this` and `listener` are protected by AbortSignal using GC::HeapFunction.
listener.signal->add_abort_algorithm([this, &listener] { listener.signal->add_abort_algorithm([this, &listener] {

View file

@ -20,7 +20,7 @@ dictionary EventListenerOptions {
}; };
dictionary AddEventListenerOptions : EventListenerOptions { dictionary AddEventListenerOptions : EventListenerOptions {
boolean passive = false; boolean passive;
boolean once = false; boolean once = false;
AbortSignal signal; AbortSignal signal;
}; };

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2020-2022, Andreas Kling <andreas@ladybird.org> * Copyright (c) 2020-2022, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2024, Glenn Skrzypczak <glenn.skrzypczak@gmail.com>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -19,7 +20,7 @@ struct EventListenerOptions {
}; };
struct AddEventListenerOptions : public EventListenerOptions { struct AddEventListenerOptions : public EventListenerOptions {
bool passive { false }; Optional<bool> passive;
bool once { false }; bool once { false };
GC::Ptr<AbortSignal> signal; GC::Ptr<AbortSignal> signal;
}; };

View file

@ -2,82 +2,81 @@ Harness status: OK
Found 100 tests Found 100 tests
68 Pass 100 Pass
32 Fail Pass touchstart listener is passive by default for Window
Fail touchstart listener is passive by default for Window Pass touchstart listener is passive with {passive:undefined} for Window
Fail touchstart listener is passive with {passive:undefined} for Window
Pass touchstart listener is non-passive with {passive:false} for Window Pass touchstart listener is non-passive with {passive:false} for Window
Pass touchstart listener is passive with {passive:true} for Window Pass touchstart listener is passive with {passive:true} for Window
Fail touchstart listener is passive by default for HTMLDocument Pass touchstart listener is passive by default for HTMLDocument
Fail touchstart listener is passive with {passive:undefined} 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 non-passive with {passive:false} for HTMLDocument
Pass touchstart listener is passive with {passive:true} for HTMLDocument Pass touchstart listener is passive with {passive:true} for HTMLDocument
Fail touchstart listener is passive by default for HTMLHtmlElement Pass touchstart listener is passive by default for HTMLHtmlElement
Fail touchstart listener is passive with {passive:undefined} 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 non-passive with {passive:false} for HTMLHtmlElement
Pass touchstart listener is passive with {passive:true} for HTMLHtmlElement Pass touchstart listener is passive with {passive:true} for HTMLHtmlElement
Fail touchstart listener is passive by default for HTMLBodyElement Pass touchstart listener is passive by default for HTMLBodyElement
Fail touchstart listener is passive with {passive:undefined} 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 non-passive with {passive:false} for HTMLBodyElement
Pass touchstart listener is passive with {passive:true} 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 by default for HTMLDivElement
Pass touchstart listener is non-passive with {passive:undefined} 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 non-passive with {passive:false} for HTMLDivElement
Pass touchstart listener is passive with {passive:true} for HTMLDivElement Pass touchstart listener is passive with {passive:true} for HTMLDivElement
Fail touchmove listener is passive by default for Window Pass touchmove listener is passive by default for Window
Fail touchmove listener is passive with {passive:undefined} 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 non-passive with {passive:false} for Window
Pass touchmove listener is passive with {passive:true} for Window Pass touchmove listener is passive with {passive:true} for Window
Fail touchmove listener is passive by default for HTMLDocument Pass touchmove listener is passive by default for HTMLDocument
Fail touchmove listener is passive with {passive:undefined} 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 non-passive with {passive:false} for HTMLDocument
Pass touchmove listener is passive with {passive:true} for HTMLDocument Pass touchmove listener is passive with {passive:true} for HTMLDocument
Fail touchmove listener is passive by default for HTMLHtmlElement Pass touchmove listener is passive by default for HTMLHtmlElement
Fail touchmove listener is passive with {passive:undefined} 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 non-passive with {passive:false} for HTMLHtmlElement
Pass touchmove listener is passive with {passive:true} for HTMLHtmlElement Pass touchmove listener is passive with {passive:true} for HTMLHtmlElement
Fail touchmove listener is passive by default for HTMLBodyElement Pass touchmove listener is passive by default for HTMLBodyElement
Fail touchmove listener is passive with {passive:undefined} 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 non-passive with {passive:false} for HTMLBodyElement
Pass touchmove listener is passive with {passive:true} 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 by default for HTMLDivElement
Pass touchmove listener is non-passive with {passive:undefined} 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 non-passive with {passive:false} for HTMLDivElement
Pass touchmove listener is passive with {passive:true} for HTMLDivElement Pass touchmove listener is passive with {passive:true} for HTMLDivElement
Fail wheel listener is passive by default for Window Pass wheel listener is passive by default for Window
Fail wheel listener is passive with {passive:undefined} 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 non-passive with {passive:false} for Window
Pass wheel listener is passive with {passive:true} for Window Pass wheel listener is passive with {passive:true} for Window
Fail wheel listener is passive by default for HTMLDocument Pass wheel listener is passive by default for HTMLDocument
Fail wheel listener is passive with {passive:undefined} 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 non-passive with {passive:false} for HTMLDocument
Pass wheel listener is passive with {passive:true} for HTMLDocument Pass wheel listener is passive with {passive:true} for HTMLDocument
Fail wheel listener is passive by default for HTMLHtmlElement Pass wheel listener is passive by default for HTMLHtmlElement
Fail wheel listener is passive with {passive:undefined} 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 non-passive with {passive:false} for HTMLHtmlElement
Pass wheel listener is passive with {passive:true} for HTMLHtmlElement Pass wheel listener is passive with {passive:true} for HTMLHtmlElement
Fail wheel listener is passive by default for HTMLBodyElement Pass wheel listener is passive by default for HTMLBodyElement
Fail wheel listener is passive with {passive:undefined} 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 non-passive with {passive:false} for HTMLBodyElement
Pass wheel listener is passive with {passive:true} 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 by default for HTMLDivElement
Pass wheel listener is non-passive with {passive:undefined} 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 non-passive with {passive:false} for HTMLDivElement
Pass wheel listener is passive with {passive:true} for HTMLDivElement Pass wheel listener is passive with {passive:true} for HTMLDivElement
Fail mousewheel listener is passive by default for Window Pass mousewheel listener is passive by default for Window
Fail mousewheel listener is passive with {passive:undefined} 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 non-passive with {passive:false} for Window
Pass mousewheel listener is passive with {passive:true} for Window Pass mousewheel listener is passive with {passive:true} for Window
Fail mousewheel listener is passive by default for HTMLDocument Pass mousewheel listener is passive by default for HTMLDocument
Fail mousewheel listener is passive with {passive:undefined} 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 non-passive with {passive:false} for HTMLDocument
Pass mousewheel listener is passive with {passive:true} for HTMLDocument Pass mousewheel listener is passive with {passive:true} for HTMLDocument
Fail mousewheel listener is passive by default for HTMLHtmlElement Pass mousewheel listener is passive by default for HTMLHtmlElement
Fail mousewheel listener is passive with {passive:undefined} 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 non-passive with {passive:false} for HTMLHtmlElement
Pass mousewheel listener is passive with {passive:true} for HTMLHtmlElement Pass mousewheel listener is passive with {passive:true} for HTMLHtmlElement
Fail mousewheel listener is passive by default for HTMLBodyElement Pass mousewheel listener is passive by default for HTMLBodyElement
Fail mousewheel listener is passive with {passive:undefined} 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 non-passive with {passive:false} for HTMLBodyElement
Pass mousewheel listener is passive with {passive:true} for HTMLBodyElement Pass mousewheel listener is passive with {passive:true} for HTMLBodyElement
Pass mousewheel listener is non-passive by default for HTMLDivElement Pass mousewheel listener is non-passive by default for HTMLDivElement