mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-21 15:10:19 +00:00
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:
parent
8a07131229
commit
226259dd7c
6 changed files with 97 additions and 11 deletions
|
@ -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();
|
||||
|
@ -357,6 +367,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, client_offset, offset, {}, button, buttons, modifiers).release_value_but_fixme_should_propagate_errors());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -179,7 +179,6 @@ public:
|
|||
virtual DispatchEventOfSameName handle_mousedown(Badge<EventHandler>, CSSPixelPoint, unsigned button, unsigned modifiers);
|
||||
virtual DispatchEventOfSameName handle_mouseup(Badge<EventHandler>, CSSPixelPoint, unsigned button, unsigned modifiers);
|
||||
virtual DispatchEventOfSameName handle_mousemove(Badge<EventHandler>, CSSPixelPoint, unsigned buttons, unsigned modifiers);
|
||||
virtual DOM::Node* mouse_event_target() const { return nullptr; }
|
||||
|
||||
virtual bool handle_mousewheel(Badge<EventHandler>, CSSPixelPoint, unsigned buttons, unsigned modifiers, int wheel_delta_x, int wheel_delta_y);
|
||||
|
||||
|
|
|
@ -30,13 +30,6 @@ bool TextPaintable::wants_mouse_events() const
|
|||
return layout_node().first_ancestor_of_type<Layout::Label>();
|
||||
}
|
||||
|
||||
DOM::Node* TextPaintable::mouse_event_target() const
|
||||
{
|
||||
if (auto const* label = layout_node().first_ancestor_of_type<Layout::Label>())
|
||||
return label->dom_node().control().ptr();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TextPaintable::DispatchEventOfSameName TextPaintable::handle_mousedown(Badge<EventHandler>, CSSPixelPoint position, unsigned button, unsigned)
|
||||
{
|
||||
auto* label = layout_node().first_ancestor_of_type<Layout::Label>();
|
||||
|
|
|
@ -21,7 +21,6 @@ public:
|
|||
Layout::TextNode const& layout_node() const { return static_cast<Layout::TextNode const&>(Paintable::layout_node()); }
|
||||
|
||||
virtual bool wants_mouse_events() const override;
|
||||
virtual DOM::Node* mouse_event_target() const override;
|
||||
virtual DispatchEventOfSameName handle_mousedown(Badge<EventHandler>, CSSPixelPoint, unsigned button, unsigned modifiers) override;
|
||||
virtual DispatchEventOfSameName handle_mouseup(Badge<EventHandler>, CSSPixelPoint, unsigned button, unsigned modifiers) override;
|
||||
virtual DispatchEventOfSameName handle_mousemove(Badge<EventHandler>, CSSPixelPoint, unsigned button, unsigned modifiers) override;
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
mouseup event on box
|
||||
click event on box
|
||||
click event on input#radio1
|
||||
click event on input#radio2
|
|
@ -0,0 +1,75 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.box {
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: purple;
|
||||
}
|
||||
|
||||
input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
border: 1px solid black;
|
||||
}
|
||||
</style>
|
||||
<script src="../include.js"></script>
|
||||
<body>
|
||||
<form>
|
||||
<label>
|
||||
<input type="radio" name="fruit" value="apple" id="radio1">
|
||||
<span class="box"></span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="fruit" value="banana" id="radio2">
|
||||
hey
|
||||
</label>
|
||||
</form>
|
||||
</body>
|
||||
<script>
|
||||
asyncTest(done => {
|
||||
let eventCount = 0;
|
||||
function endTestIfGotAllEvents() {
|
||||
eventCount++;
|
||||
if (eventCount == 4) {
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelectorAll('input').forEach(function (element) {
|
||||
element.addEventListener('click', function (event) {
|
||||
println(`click event on input#${event.target.id}`);
|
||||
endTestIfGotAllEvents();
|
||||
});
|
||||
element.addEventListener('mouseup', function (event) {
|
||||
println(`mouseup event on input#${event.target.id}`);
|
||||
endTestIfGotAllEvents();
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('.box').forEach(function (element) {
|
||||
element.addEventListener('click', function (event) {
|
||||
println('click event on box');
|
||||
endTestIfGotAllEvents();
|
||||
});
|
||||
element.addEventListener('mouseup', function (event) {
|
||||
println('mouseup event on box');
|
||||
endTestIfGotAllEvents();
|
||||
});
|
||||
});
|
||||
|
||||
internals.click(50, 50);
|
||||
internals.click(50, 250);
|
||||
});
|
||||
</script>
|
||||
</html>
|
Loading…
Reference in a new issue