diff --git a/Tests/LibWeb/Text/expected/focus-events.txt b/Tests/LibWeb/Text/expected/focus-events.txt
index 1fddba0e4c9..9b554119b9f 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 bfe053e8705..0e4a938fead 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 20aee90cc06..4409e6d369b 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);
+ fire_a_focus_event(focus_event_target, related_focus_target, HTML::EventNames::focus, false);
- 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);
+ // 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 a1351b94a14..d16a7a41463 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 e538752e354..e3be7830760 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;