LibWeb: Implement the MouseEvent.relatedTarget attribute

This returns the secondary target of a mouse event. For `onmouseenter`
and `onmouseover` events, this is the EventTarget the mouse exited
from. For `onmouseleave` and `onmouseout` events, this is the
EventTarget the mouse entered to.
This commit is contained in:
Tim Ledbetter 2024-05-20 06:02:22 +01:00 committed by Andreas Kling
parent ddc622233f
commit a6d6729034
6 changed files with 46 additions and 21 deletions

View file

@ -1,13 +1,13 @@
> move pointer over #inner
mouseover target.id=(inner) currentTarget.id=(inner)
mouseover target.id=(inner) currentTarget.id=(outer)
mouseenter target.id=(inner) currentTarget.id=(inner)
mouseenter target.id=(outer) currentTarget.id=(outer)
mouseover target.id=(inner) currentTarget.id=(inner), relatedTarget.id=(body)
mouseover target.id=(inner) currentTarget.id=(outer), relatedTarget.id=(body)
mouseenter target.id=(inner) currentTarget.id=(inner), relatedTarget.id=(body)
mouseenter target.id=(outer) currentTarget.id=(outer), relatedTarget.id=(body)
> move pointer over #outer
mouseout target.id=(inner) currentTarget.id=(inner)
mouseout target.id=(inner) currentTarget.id=(outer)
mouseleave target.id=(inner) currentTarget.id=(inner)
mouseover target.id=(outer) currentTarget.id=(outer)
mouseout target.id=(inner) currentTarget.id=(inner), relatedTarget.id=(outer)
mouseout target.id=(inner) currentTarget.id=(outer), relatedTarget.id=(outer)
mouseleave target.id=(inner) currentTarget.id=(inner), relatedTarget.id=(outer)
mouseover target.id=(outer) currentTarget.id=(outer), relatedTarget.id=(inner)
> click document.body
mouseout target.id=(outer) currentTarget.id=(outer)
mouseleave target.id=(outer) currentTarget.id=(outer)
mouseout target.id=(outer) currentTarget.id=(outer), relatedTarget.id=(body)
mouseleave target.id=(outer) currentTarget.id=(outer), relatedTarget.id=(body)

View file

@ -3,6 +3,8 @@
body {
margin: 0px;
padding: 5px;
width: 200px;
height: 200px;
}
#outer {
@ -17,22 +19,23 @@ body {
background-color: magenta;
}
</style>
<div id="outer"><div id="inner"></div></div>
<body id="body"><div id="outer"><div id="inner"></div></div></body>
<script src="../include.js"></script>
<script>
function handleMouseOver(e) {
println(`mouseover target.id=(${e.target.id}) currentTarget.id=(${e.currentTarget.id})`);
println(`mouseover target.id=(${e.target.id}) currentTarget.id=(${e.currentTarget.id}), relatedTarget.id=(${e.relatedTarget.id})`);
}
function handleMouseOut(e) {
println(`mouseout target.id=(${e.target.id}) currentTarget.id=(${e.currentTarget.id})`);
println(`mouseout target.id=(${e.target.id}) currentTarget.id=(${e.currentTarget.id}), relatedTarget.id=(${e.relatedTarget.id})`);
}
function handleMouseEnter(e) {
println(`mouseenter target.id=(${e.target.id}) currentTarget.id=(${e.currentTarget.id})`);
println(`mouseenter target.id=(${e.target.id}) currentTarget.id=(${e.currentTarget.id}), relatedTarget.id=(${e.relatedTarget.id})`);
}
function handleMouseLeave(e) {
println(`mouseleave target.id=(${e.target.id}) currentTarget.id=(${e.currentTarget.id})`);
println(`mouseleave target.id=(${e.target.id}) currentTarget.id=(${e.currentTarget.id}), relatedTarget.id=(${e.relatedTarget.id})`);
}
outer.onmouseover = handleMouseOver;
outer.onmouseout = handleMouseOut;
outer.onmouseenter = handleMouseEnter;
@ -51,6 +54,8 @@ const clickOnBody = () => {
}
asyncTest(async done => {
// First move the mouse outside #outer to populate the MouseEvent.relatedTarget property
internals.movePointerTo(150, 150);
println("> move pointer over #inner");
internals.movePointerTo(10, 10);
println("> move pointer over #outer");

View file

@ -1337,7 +1337,9 @@ void Document::set_hovered_node(Node* node)
// https://w3c.github.io/uievents/#mouseout
if (old_hovered_node && old_hovered_node != m_hovered_node) {
auto event = UIEvents::MouseEvent::create(realm(), UIEvents::EventNames::mouseout);
UIEvents::MouseEventInit mouse_event_init {};
mouse_event_init.related_target = m_hovered_node;
auto event = UIEvents::MouseEvent::create(realm(), UIEvents::EventNames::mouseout, mouse_event_init);
old_hovered_node->dispatch_event(event);
}
@ -1346,13 +1348,17 @@ void Document::set_hovered_node(Node* node)
// FIXME: Check if we need to dispatch these events in a specific order.
for (auto target = old_hovered_node; target && target.ptr() != common_ancestor; target = target->parent()) {
// FIXME: Populate the event with mouse coordinates, etc.
target->dispatch_event(UIEvents::MouseEvent::create(realm(), UIEvents::EventNames::mouseleave));
UIEvents::MouseEventInit mouse_event_init {};
mouse_event_init.related_target = m_hovered_node;
target->dispatch_event(UIEvents::MouseEvent::create(realm(), UIEvents::EventNames::mouseleave, mouse_event_init));
}
}
// https://w3c.github.io/uievents/#mouseover
if (m_hovered_node && m_hovered_node != old_hovered_node) {
auto event = UIEvents::MouseEvent::create(realm(), UIEvents::EventNames::mouseover);
UIEvents::MouseEventInit mouse_event_init {};
mouse_event_init.related_target = old_hovered_node;
auto event = UIEvents::MouseEvent::create(realm(), UIEvents::EventNames::mouseover, mouse_event_init);
m_hovered_node->dispatch_event(event);
}
@ -1361,7 +1367,9 @@ void Document::set_hovered_node(Node* node)
// FIXME: Check if we need to dispatch these events in a specific order.
for (auto target = m_hovered_node; target && target.ptr() != common_ancestor; target = target->parent()) {
// FIXME: Populate the event with mouse coordinates, etc.
target->dispatch_event(UIEvents::MouseEvent::create(realm(), UIEvents::EventNames::mouseenter));
UIEvents::MouseEventInit mouse_event_init {};
mouse_event_init.related_target = old_hovered_node;
target->dispatch_event(UIEvents::MouseEvent::create(realm(), UIEvents::EventNames::mouseenter, mouse_event_init));
}
}
}

View file

@ -45,6 +45,7 @@ MouseEvent::MouseEvent(JS::Realm& realm, FlyString const& event_name, MouseEvent
, 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_event_characteristics();
}
@ -57,6 +58,12 @@ void MouseEvent::initialize(JS::Realm& realm)
WEB_SET_PROTOTYPE_FOR_INTERFACE(MouseEvent);
}
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")

View file

@ -22,6 +22,7 @@ struct MouseEventInit : public EventModifierInit {
double movement_y = 0;
i16 button = 0;
u16 buttons = 0;
JS::GCPtr<DOM::EventTarget> related_target = nullptr;
};
class MouseEvent : public UIEvent {
@ -61,6 +62,8 @@ public:
i16 button() const { return m_button; }
u16 buttons() const { return m_buttons; }
JS::GCPtr<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; }
@ -69,6 +72,7 @@ protected:
MouseEvent(JS::Realm&, FlyString const& event_name, 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;
private:
virtual bool is_mouse_event() const override { return true; }
@ -101,6 +105,7 @@ private:
double m_movement_y { 0 };
i16 m_button { 0 };
u16 m_buttons { 0 };
JS::GCPtr<DOM::EventTarget> m_related_target { nullptr };
};
}

View file

@ -29,7 +29,7 @@ interface MouseEvent : UIEvent {
readonly attribute short button;
readonly attribute unsigned short buttons;
[FIXME] readonly attribute EventTarget? relatedTarget;
readonly attribute EventTarget? relatedTarget;
boolean getModifierState(DOMString keyArg);
};
@ -48,5 +48,5 @@ dictionary MouseEventInit : EventModifierInit {
short button = 0;
unsigned short buttons = 0;
// FIXME: EventTarget? relatedTarget = null;
EventTarget? relatedTarget = null;
};