diff --git a/Tests/LibWeb/Text/expected/HTML/click-label-with-display-none-checkbox.txt b/Tests/LibWeb/Text/expected/HTML/click-label-with-display-none-checkbox.txt
new file mode 100644
index 00000000000..77e954e5355
--- /dev/null
+++ b/Tests/LibWeb/Text/expected/HTML/click-label-with-display-none-checkbox.txt
@@ -0,0 +1 @@
+Label Checkbox changed
diff --git a/Tests/LibWeb/Text/input/HTML/click-label-with-display-none-checkbox.html b/Tests/LibWeb/Text/input/HTML/click-label-with-display-none-checkbox.html
new file mode 100644
index 00000000000..a2a9e832ce3
--- /dev/null
+++ b/Tests/LibWeb/Text/input/HTML/click-label-with-display-none-checkbox.html
@@ -0,0 +1,21 @@
+
+
+
+
+
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLLabelElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLLabelElement.cpp
index 411a4279b8f..8375b2771c5 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLLabelElement.cpp
+++ b/Userland/Libraries/LibWeb/HTML/HTMLLabelElement.cpp
@@ -30,4 +30,39 @@ JS::GCPtr HTMLLabelElement::create_layout_node(NonnullRefPtr(document(), this, move(style));
}
+// https://html.spec.whatwg.org/multipage/forms.html#labeled-control
+JS::GCPtr HTMLLabelElement::control() const
+{
+ JS::GCPtr control;
+
+ // The for attribute may be specified to indicate a form control with which the caption is
+ // to be associated. If the attribute is specified, the attribute's value must be the ID of
+ // a labelable element in the same tree as the label element. If the attribute is specified
+ // and there is an element in the tree whose ID is equal to the value of the for attribute,
+ // and the first such element in tree order is a labelable element, then that element is the
+ // label element's labeled control.
+ if (for_().has_value()) {
+ for_each_in_inclusive_subtree_of_type([&](auto& element) {
+ if (element.id() == *for_() && element.is_labelable()) {
+ control = &const_cast(element);
+ return IterationDecision::Break;
+ }
+ return IterationDecision::Continue;
+ });
+ return control;
+ }
+
+ // If the for attribute is not specified, but the label element has a labelable element descendant,
+ // then the first such descendant in tree order is the label element's labeled control.
+ for_each_in_subtree_of_type([&](auto& element) {
+ if (element.is_labelable()) {
+ control = &const_cast(element);
+ return IterationDecision::Break;
+ }
+ return IterationDecision::Continue;
+ });
+
+ return control;
+}
+
}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLLabelElement.h b/Userland/Libraries/LibWeb/HTML/HTMLLabelElement.h
index 5b47a9cda8b..698061ceb25 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLLabelElement.h
+++ b/Userland/Libraries/LibWeb/HTML/HTMLLabelElement.h
@@ -21,6 +21,8 @@ public:
Optional for_() const { return attribute(HTML::AttributeNames::for_); }
+ JS::GCPtr control() const;
+
private:
HTMLLabelElement(DOM::Document&, DOM::QualifiedName);
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLLabelElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLLabelElement.idl
index f650b7a9bcb..f54ba425aac 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLLabelElement.idl
+++ b/Userland/Libraries/LibWeb/HTML/HTMLLabelElement.idl
@@ -8,6 +8,6 @@ interface HTMLLabelElement : HTMLElement {
// FIXME: readonly attribute HTMLFormElement? form;
[CEReactions, Reflect=for] attribute DOMString htmlFor;
- // FIXME: readonly attribute HTMLElement? control;
+ readonly attribute HTMLElement? control;
};
diff --git a/Userland/Libraries/LibWeb/Layout/Label.cpp b/Userland/Libraries/LibWeb/Layout/Label.cpp
index ec68fffe960..78f94ae14e4 100644
--- a/Userland/Libraries/LibWeb/Layout/Label.cpp
+++ b/Userland/Libraries/LibWeb/Layout/Label.cpp
@@ -27,8 +27,10 @@ void Label::handle_mousedown_on_label(Badge, CSSPixelPo
if (button != GUI::MouseButton::Primary)
return;
- if (auto* control = labeled_control(); control)
- control->paintable()->handle_associated_label_mousedown({});
+ if (auto control = dom_node().control(); control && control->paintable()) {
+ auto& labelable_paintable = verify_cast(*control->paintable());
+ labelable_paintable.handle_associated_label_mousedown({});
+ }
m_tracking_mouse = true;
}
@@ -38,12 +40,13 @@ void Label::handle_mouseup_on_label(Badge, CSSPixelPoin
if (!m_tracking_mouse || button != GUI::MouseButton::Primary)
return;
- if (auto* control = labeled_control(); control) {
+ if (auto control = dom_node().control(); control && control->paintable()) {
bool is_inside_control = control->paintable_box()->absolute_rect().contains(position);
bool is_inside_label = paintable_box()->absolute_rect().contains(position);
-
- if (is_inside_control || is_inside_label)
- control->paintable()->handle_associated_label_mouseup({});
+ if (is_inside_control || is_inside_label) {
+ auto& labelable_paintable = verify_cast(*control->paintable());
+ labelable_paintable.handle_associated_label_mouseup({});
+ }
}
m_tracking_mouse = false;
@@ -54,11 +57,11 @@ void Label::handle_mousemove_on_label(Badge, CSSPixelPo
if (!m_tracking_mouse)
return;
- if (auto* control = labeled_control(); control) {
+ if (auto control = dom_node().control(); control && control->paintable()) {
bool is_inside_control = control->paintable_box()->absolute_rect().contains(position);
bool is_inside_label = paintable_box()->absolute_rect().contains(position);
-
- control->paintable()->handle_associated_label_mousemove({}, is_inside_control || is_inside_label);
+ auto& labelable_paintable = verify_cast(*control->paintable());
+ labelable_paintable.handle_associated_label_mousemove({}, is_inside_control || is_inside_label);
}
}
@@ -113,37 +116,4 @@ Label const* Label::label_for_control_node(LabelableNode const& control)
return control.first_ancestor_of_type