LibWeb: Dispatch "click" event on input control associated with <label>

For example, in the following HTML:
```html
<label>
    <input type="radio" name="fruit" value="apple" id="radio1">
    <span class="box"></span>
</label>
```

When any descendant of a <label> element is clicked, a "click" event
must be dispatched on the <input> element nested within the <label>, in
addition to the "click" event dispatched on the clicked descendant.

Previously, this behavior was implemented only for text node descendants
by "overriding" the mouse event target using `mouse_event_target()` in
the TextPaintable. This approach was incorrect because it was limited to
text nodes, whereas the behavior should apply to any box. Moreover, the
"click" event for the input control must be dispatched *in addition* to
the event on the clicked element, rather than redirecting it.
This commit is contained in:
Aliaksandr Kalenik 2024-11-20 21:31:13 +01:00 committed by Alexander Kalenik
commit c0e90a2a68
Notes: github-actions[bot] 2024-11-21 15:11:58 +00:00
6 changed files with 105 additions and 15 deletions

View file

@ -17,6 +17,7 @@
#include <LibWeb/HTML/HTMLImageElement.h>
#include <LibWeb/HTML/HTMLMediaElement.h>
#include <LibWeb/HTML/HTMLVideoElement.h>
#include <LibWeb/Layout/Label.h>
#include <LibWeb/Layout/Viewport.h>
#include <LibWeb/Page/DragAndDropEventHandler.h>
#include <LibWeb/Page/EventHandler.h>
@ -40,8 +41,6 @@ namespace Web {
static GC::Ptr<DOM::Node> dom_node_for_event_dispatch(Painting::Paintable& paintable)
{
if (auto node = paintable.mouse_event_target())
return node;
if (auto node = paintable.dom_node())
return node;
auto* layout_parent = paintable.layout_node().parent();
@ -53,6 +52,17 @@ static GC::Ptr<DOM::Node> dom_node_for_event_dispatch(Painting::Paintable& paint
return nullptr;
}
static DOM::Node* input_control_associated_with_ancestor_label_element(Painting::Paintable& paintable)
{
if (is<Layout::Label>(paintable.layout_node())) {
auto const& label = verify_cast<Layout::Label>(paintable.layout_node());
return label.dom_node().control().ptr();
}
if (auto const* label = paintable.layout_node().first_ancestor_of_type<Layout::Label>())
return label->dom_node().control().ptr();
return nullptr;
}
static bool parent_element_for_event_dispatch(Painting::Paintable& paintable, GC::Ptr<DOM::Node>& node, Layout::Node*& layout_node)
{
auto* current_ancestor_node = node.ptr();
@ -351,6 +361,12 @@ EventResult EventHandler::handle_mouseup(CSSPixelPoint viewport_position, CSSPix
}
}
}
if (auto* input_control = input_control_associated_with_ancestor_label_element(*paintable)) {
if (button == UIEvents::MouseButton::Primary) {
input_control->dispatch_event(UIEvents::MouseEvent::create_from_platform_event(node->realm(), UIEvents::EventNames::click, screen_position, page_offset, viewport_position, offset, {}, button, buttons, modifiers).release_value_but_fixme_should_propagate_errors());
}
}
}
}
@ -434,10 +450,14 @@ EventResult EventHandler::handle_mousedown(CSSPixelPoint viewport_position, CSSP
if (dom_node) {
// See if we want to focus something.
GC::Ptr<DOM::Node> focus_candidate;
for (auto candidate = node; candidate; candidate = candidate->parent_or_shadow_host()) {
if (candidate->is_focusable()) {
focus_candidate = candidate;
break;
if (auto* input_control = input_control_associated_with_ancestor_label_element(*paintable)) {
focus_candidate = input_control;
} else {
for (auto candidate = node; candidate; candidate = candidate->parent_or_shadow_host()) {
if (candidate->is_focusable()) {
focus_candidate = candidate;
break;
}
}
}