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