From 10a89618c955acee3640335424f16cdb586cb469 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Wed, 14 Aug 2024 11:39:32 +0200 Subject: [PATCH] LibWeb: Implement the "fire a focus event" spec We weren't setting the focus event's composed flag and view field correctly. --- Tests/LibWeb/Text/expected/focus-events.txt | 12 ++--- Tests/LibWeb/Text/input/focus-events.html | 31 +++++-------- Userland/Libraries/LibWeb/HTML/Focus.cpp | 44 +++++++++++-------- .../Libraries/LibWeb/UIEvents/FocusEvent.cpp | 7 ++- .../Libraries/LibWeb/UIEvents/FocusEvent.h | 1 + 5 files changed, 49 insertions(+), 46 deletions(-) diff --git a/Tests/LibWeb/Text/expected/focus-events.txt b/Tests/LibWeb/Text/expected/focus-events.txt index 1fddba0e4c931..9b554119b9f5b 100644 --- a/Tests/LibWeb/Text/expected/focus-events.txt +++ b/Tests/LibWeb/Text/expected/focus-events.txt @@ -1,6 +1,6 @@ - focus target= -focusin target= -blur target= -focusout target= -focus target= -focusin target= + focus target= bubbles=false composed=true +focusin target= bubbles=true composed=true +blur target= bubbles=false composed=true +focusout target= bubbles=true composed=true +focus target= bubbles=false composed=true +focusin target= bubbles=true composed=true diff --git a/Tests/LibWeb/Text/input/focus-events.html b/Tests/LibWeb/Text/input/focus-events.html index bfe053e8705a9..0e4a938feadea 100644 --- a/Tests/LibWeb/Text/input/focus-events.html +++ b/Tests/LibWeb/Text/input/focus-events.html @@ -16,32 +16,21 @@ return element_string; } - document.addEventListener("focusin", function (e) { + function printFocusEvent(e) { const target = elementToString(e.target); - println(`focusin target=${target}`); - }); - document.addEventListener("focusout", function (e) { - const target = elementToString(e.target); - println(`focusout target=${target}`); - }); + println(`${e.type} target=${target} bubbles=${e.bubbles} composed=${e.composed}`); + } + + document.addEventListener('focusin', printFocusEvent); + document.addEventListener('focusout', printFocusEvent); const a = document.getElementById("a"); const b = document.getElementById("b"); - function onFocus(e) { - const target = elementToString(e.target); - println(`focus target=${target}`); - } - - function onBlur(e) { - const target = elementToString(e.target); - println(`blur target=${target}`); - } - - a.addEventListener("focus", onFocus); - a.addEventListener("blur", onBlur); - b.addEventListener("focus", onFocus); - b.addEventListener("blur", onBlur); + a.addEventListener('focus', printFocusEvent); + a.addEventListener('blur', printFocusEvent); + b.addEventListener('focus', printFocusEvent); + b.addEventListener('blur', printFocusEvent); asyncTest(async done => { a.focus(); diff --git a/Userland/Libraries/LibWeb/HTML/Focus.cpp b/Userland/Libraries/LibWeb/HTML/Focus.cpp index 20aee90cc06e9..b1ec2e840df51 100644 --- a/Userland/Libraries/LibWeb/HTML/Focus.cpp +++ b/Userland/Libraries/LibWeb/HTML/Focus.cpp @@ -19,6 +19,24 @@ namespace Web::HTML { +// https://html.spec.whatwg.org/multipage/interaction.html#fire-a-focus-event +static void fire_a_focus_event(JS::GCPtr focus_event_target, JS::GCPtr related_focus_target, FlyString const& event_name, bool bubbles) +{ + // To fire a focus event named e at an element t with a given related target r, fire an event named e at t, using FocusEvent, + // with the relatedTarget attribute initialized to r, the view attribute initialized to t's node document's relevant global + // object, and the composed flag set. + UIEvents::FocusEventInit focus_event_init; + focus_event_init.related_target = related_focus_target; + focus_event_init.view = verify_cast(focus_event_target->realm().global_object()); + + auto focus_event = UIEvents::FocusEvent::create(focus_event_target->realm(), event_name, focus_event_init); + // AD-HOC: support bubbling focus events, used for focusin & focusout. + // See: https://github.com/whatwg/html/issues/3514 + focus_event->set_bubbles(bubbles); + focus_event->set_composed(true); + focus_event_target->dispatch_event(focus_event); +} + // https://html.spec.whatwg.org/multipage/interaction.html#focus-update-steps static void run_focus_update_steps(Vector> old_chain, Vector> new_chain, DOM::Node* new_focus_target) { @@ -74,16 +92,11 @@ static void run_focus_update_steps(Vector> old_chain, Vect // 4. If blur event target is not null, fire a focus event named blur at blur event target, // with related blur target as the related target. if (blur_event_target) { - // FIXME: Implement the "fire a focus event" spec operation. - auto blur_event = UIEvents::FocusEvent::create(blur_event_target->realm(), HTML::EventNames::blur); - blur_event->set_related_target(related_blur_target); - blur_event_target->dispatch_event(blur_event); - } + fire_a_focus_event(blur_event_target, related_blur_target, HTML::EventNames::blur, false); - auto focusout_event = UIEvents::FocusEvent::create(blur_event_target->realm(), HTML::EventNames::focusout); - focusout_event->set_bubbles(true); - focusout_event->set_related_target(related_blur_target); - blur_event_target->dispatch_event(focusout_event); + // AD-HOC: dispatch focusout + fire_a_focus_event(blur_event_target, related_blur_target, HTML::EventNames::focusout, true); + } } // FIXME: 3. Apply any relevant platform-specific conventions for focusing new focus target. @@ -124,15 +137,10 @@ static void run_focus_update_steps(Vector> old_chain, Vect // 4. If focus event target is not null, fire a focus event named focus at focus event target, // with related focus target as the related target. if (focus_event_target) { - // FIXME: Implement the "fire a focus event" spec operation. - auto focus_event = UIEvents::FocusEvent::create(focus_event_target->realm(), HTML::EventNames::focus); - focus_event->set_related_target(related_focus_target); - focus_event_target->dispatch_event(focus_event); - - auto focusin_event = UIEvents::FocusEvent::create(focus_event_target->realm(), HTML::EventNames::focusin); - focusin_event->set_bubbles(true); - focusin_event->set_related_target(related_focus_target); - focus_event_target->dispatch_event(focusin_event); + fire_a_focus_event(focus_event_target, related_focus_target, HTML::EventNames::focus, false); + + // AD-HOC: dispatch focusin + fire_a_focus_event(focus_event_target, related_focus_target, HTML::EventNames::focusin, true); } } } diff --git a/Userland/Libraries/LibWeb/UIEvents/FocusEvent.cpp b/Userland/Libraries/LibWeb/UIEvents/FocusEvent.cpp index a1351b94a1495..d16a7a41463ab 100644 --- a/Userland/Libraries/LibWeb/UIEvents/FocusEvent.cpp +++ b/Userland/Libraries/LibWeb/UIEvents/FocusEvent.cpp @@ -12,11 +12,16 @@ namespace Web::UIEvents { JS_DEFINE_ALLOCATOR(FocusEvent); -WebIDL::ExceptionOr> FocusEvent::construct_impl(JS::Realm& realm, FlyString const& event_name, FocusEventInit const& event_init) +JS::NonnullGCPtr FocusEvent::create(JS::Realm& realm, FlyString const& event_name, FocusEventInit const& event_init) { return realm.heap().allocate(realm, realm, event_name, event_init); } +WebIDL::ExceptionOr> FocusEvent::construct_impl(JS::Realm& realm, FlyString const& event_name, FocusEventInit const& event_init) +{ + return create(realm, event_name, event_init); +} + FocusEvent::FocusEvent(JS::Realm& realm, FlyString const& event_name, FocusEventInit const& event_init) : UIEvent(realm, event_name, event_init) { diff --git a/Userland/Libraries/LibWeb/UIEvents/FocusEvent.h b/Userland/Libraries/LibWeb/UIEvents/FocusEvent.h index e538752e354dd..e3be783076065 100644 --- a/Userland/Libraries/LibWeb/UIEvents/FocusEvent.h +++ b/Userland/Libraries/LibWeb/UIEvents/FocusEvent.h @@ -20,6 +20,7 @@ class FocusEvent final : public UIEvent { JS_DECLARE_ALLOCATOR(FocusEvent); public: + [[nodiscard]] static JS::NonnullGCPtr create(JS::Realm&, FlyString const& event_name, FocusEventInit const& = {}); static WebIDL::ExceptionOr> construct_impl(JS::Realm&, FlyString const& event_name, FocusEventInit const& event_init); virtual ~FocusEvent() override;