LibWeb: Use Event storage for MouseEvent relatedTarget

Store MouseEvent's relatedTarget in the inherited Event field instead of
keeping a second slot on MouseEvent.

Event dispatch retargets and updates the inherited field while building
the event path. The second slot left JS listeners observing stale or
null relatedTarget values during mouse and pointer boundary events.

Add coverage for boundary events between sibling elements.
This commit is contained in:
Andreas Kling 2026-06-18 00:15:31 +02:00 committed by Andreas Kling
parent 8ebdd6d41e
commit 4daec8f6e0
4 changed files with 37 additions and 13 deletions

View file

@ -46,8 +46,8 @@ MouseEvent::MouseEvent(JS::Realm& realm, FlyString const& event_name, Bindings::
, m_movement_y(event_init.movement_y)
, m_button(event_init.button)
, m_buttons(event_init.buttons)
, m_related_target(event_init.related_target)
{
set_related_target(event_init.related_target.ptr());
}
MouseEvent::~MouseEvent() = default;
@ -58,12 +58,6 @@ void MouseEvent::initialize(JS::Realm& realm)
Base::initialize(realm);
}
void MouseEvent::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_related_target);
}
bool MouseEvent::get_modifier_state(String const& key_arg) const
{
if (key_arg == "Control")
@ -121,7 +115,7 @@ void MouseEvent::init_mouse_event(String const& type, bool bubbles, bool cancela
m_alt_key = alt_key;
m_meta_key = meta_key;
m_button = button;
m_related_target = related_target;
set_related_target(related_target);
}
GC::Ref<MouseEvent> MouseEvent::create(JS::Realm& realm, FlyString const& event_name, Bindings::MouseEventInit const& event_init, double page_x, double page_y, double offset_x, double offset_y)
@ -149,7 +143,7 @@ GC::Ref<MouseEvent> MouseEvent::clone() const
init.movement_y = m_movement_y;
init.button = m_button;
init.buttons = m_buttons;
init.related_target = m_related_target;
init.related_target = related_target();
init.ctrl_key = m_ctrl_key;
init.shift_key = m_shift_key;
init.alt_key = m_alt_key;

View file

@ -61,8 +61,6 @@ public:
i16 button() const { return m_button; }
u16 buttons() const { return m_buttons; }
GC::Ptr<DOM::EventTarget> related_target() const { return m_related_target; }
bool get_modifier_state(String const& key_arg) const;
virtual u32 which() const override { return m_button + 1; }
@ -78,7 +76,6 @@ protected:
MouseEvent(JS::Realm&, FlyString const& event_name, Bindings::MouseEventInit const& event_init, double page_x, double page_y, double offset_x, double offset_y);
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
double m_screen_x { 0 };
double m_screen_y { 0 };
@ -110,7 +107,6 @@ private:
double m_movement_y { 0 };
i16 m_button { 0 };
u16 m_buttons { 0 };
GC::Ptr<DOM::EventTarget> m_related_target { nullptr };
};
}

View file

@ -0,0 +1,6 @@
pointerover: target=left, related=null
mouseover: target=left, related=null
pointerout: target=left, related=right
mouseout: target=left, related=right
pointerover: target=right, related=left
mouseover: target=right, related=left

View file

@ -0,0 +1,28 @@
<!DOCTYPE html>
<script src="../include.js"></script>
<style>
body {
margin: 0;
}
#left,
#right {
float: left;
width: 100px;
height: 100px;
}
</style>
<div id="left"></div>
<div id="right"></div>
<script>
test(() => {
for (const type of ["pointerover", "mouseover", "pointerout", "mouseout"]) {
document.body.addEventListener(type, event => {
println(`${type}: target=${event.target.id}, related=${event.relatedTarget?.id ?? "null"}`);
});
}
internals.mouseMove(50, 50);
internals.mouseMove(150, 50);
});
</script>