Skip to content

Commit

Permalink
LibWeb: Implement the "fire a focus event" spec
Browse files Browse the repository at this point in the history
We weren't setting the focus event's composed flag and view field
correctly.
  • Loading branch information
gmta committed Aug 14, 2024
1 parent 50dfaf8 commit 10a8961
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 46 deletions.
12 changes: 6 additions & 6 deletions Tests/LibWeb/Text/expected/focus-events.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
focus target=<INPUT id="a">
focusin target=<INPUT id="a">
blur target=<INPUT id="a">
focusout target=<INPUT id="a">
focus target=<INPUT id="b">
focusin target=<INPUT id="b">
focus target=<INPUT id="a"> bubbles=false composed=true
focusin target=<INPUT id="a"> bubbles=true composed=true
blur target=<INPUT id="a"> bubbles=false composed=true
focusout target=<INPUT id="a"> bubbles=true composed=true
focus target=<INPUT id="b"> bubbles=false composed=true
focusin target=<INPUT id="b"> bubbles=true composed=true
31 changes: 10 additions & 21 deletions Tests/LibWeb/Text/input/focus-events.html
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
44 changes: 26 additions & 18 deletions Userland/Libraries/LibWeb/HTML/Focus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<DOM::EventTarget> focus_event_target, JS::GCPtr<DOM::EventTarget> 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<HTML::Window>(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<JS::Handle<DOM::Node>> old_chain, Vector<JS::Handle<DOM::Node>> new_chain, DOM::Node* new_focus_target)
{
Expand Down Expand Up @@ -74,16 +92,11 @@ static void run_focus_update_steps(Vector<JS::Handle<DOM::Node>> 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.
Expand Down Expand Up @@ -124,15 +137,10 @@ static void run_focus_update_steps(Vector<JS::Handle<DOM::Node>> 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);
}
}
}
Expand Down
7 changes: 6 additions & 1 deletion Userland/Libraries/LibWeb/UIEvents/FocusEvent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,16 @@ namespace Web::UIEvents {

JS_DEFINE_ALLOCATOR(FocusEvent);

WebIDL::ExceptionOr<JS::NonnullGCPtr<FocusEvent>> FocusEvent::construct_impl(JS::Realm& realm, FlyString const& event_name, FocusEventInit const& event_init)
JS::NonnullGCPtr<FocusEvent> FocusEvent::create(JS::Realm& realm, FlyString const& event_name, FocusEventInit const& event_init)
{
return realm.heap().allocate<FocusEvent>(realm, realm, event_name, event_init);
}

WebIDL::ExceptionOr<JS::NonnullGCPtr<FocusEvent>> 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)
{
Expand Down
1 change: 1 addition & 0 deletions Userland/Libraries/LibWeb/UIEvents/FocusEvent.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class FocusEvent final : public UIEvent {
JS_DECLARE_ALLOCATOR(FocusEvent);

public:
[[nodiscard]] static JS::NonnullGCPtr<FocusEvent> create(JS::Realm&, FlyString const& event_name, FocusEventInit const& = {});
static WebIDL::ExceptionOr<JS::NonnullGCPtr<FocusEvent>> construct_impl(JS::Realm&, FlyString const& event_name, FocusEventInit const& event_init);

virtual ~FocusEvent() override;
Expand Down

0 comments on commit 10a8961

Please sign in to comment.