LibWeb: Look for labeled control in DOM tree instead of layout tree

...because "change" event should be dispatched on control even if it
has "display: none" style.

This change fixes selection in labels dropdown on GitHub's "new issue"
page.
This commit is contained in:
Aliaksandr Kalenik 2024-03-23 12:03:21 +01:00 committed by Andreas Kling
parent 730876fda9
commit f932d5d825
Notes: sideshowbarker 2024-07-17 07:11:12 +09:00
8 changed files with 74 additions and 49 deletions

View file

@ -0,0 +1 @@
Label Checkbox changed

View file

@ -0,0 +1,21 @@
<!DOCTYPE html>
<style>
body {
margin: 0;
}
input {
display: none;
}
</style>
<label id="lbl">Label<input id="checkbox" type="checkbox"/></label>
<script src="../include.js"></script>
<script>
asyncTest((done) => {
checkbox.addEventListener("change", () => {
println("Checkbox changed");
done();
});
internals.click(5, 5);
});
</script>

View file

@ -30,4 +30,39 @@ JS::GCPtr<Layout::Node> HTMLLabelElement::create_layout_node(NonnullRefPtr<CSS::
return heap().allocate_without_realm<Layout::Label>(document(), this, move(style));
}
// https://html.spec.whatwg.org/multipage/forms.html#labeled-control
JS::GCPtr<HTMLElement> HTMLLabelElement::control() const
{
JS::GCPtr<HTMLElement> 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<HTMLElement>([&](auto& element) {
if (element.id() == *for_() && element.is_labelable()) {
control = &const_cast<HTMLElement&>(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<HTMLElement>([&](auto& element) {
if (element.is_labelable()) {
control = &const_cast<HTMLElement&>(element);
return IterationDecision::Break;
}
return IterationDecision::Continue;
});
return control;
}
}

View file

@ -21,6 +21,8 @@ public:
Optional<String> for_() const { return attribute(HTML::AttributeNames::for_); }
JS::GCPtr<HTMLElement> control() const;
private:
HTMLLabelElement(DOM::Document&, DOM::QualifiedName);

View file

@ -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;
};

View file

@ -27,8 +27,10 @@ void Label::handle_mousedown_on_label(Badge<Painting::TextPaintable>, 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<Painting::LabelablePaintable>(*control->paintable());
labelable_paintable.handle_associated_label_mousedown({});
}
m_tracking_mouse = true;
}
@ -38,12 +40,13 @@ void Label::handle_mouseup_on_label(Badge<Painting::TextPaintable>, 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<Painting::LabelablePaintable>(*control->paintable());
labelable_paintable.handle_associated_label_mouseup({});
}
}
m_tracking_mouse = false;
@ -54,11 +57,11 @@ void Label::handle_mousemove_on_label(Badge<Painting::TextPaintable>, 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<Painting::LabelablePaintable>(*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<Label>();
}
// https://html.spec.whatwg.org/multipage/forms.html#labeled-control
LabelableNode* Label::labeled_control()
{
if (!document().layout_node())
return nullptr;
LabelableNode* control = nullptr;
// 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 (auto for_ = dom_node().for_(); for_.has_value()) {
document().layout_node()->for_each_in_inclusive_subtree_of_type<LabelableNode>([&](auto& node) {
if (node.dom_node().id() == for_) {
control = &node;
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<LabelableNode>([&](auto& labelable_node) {
control = &labelable_node;
return IterationDecision::Break;
});
return control;
}
}

View file

@ -28,8 +28,6 @@ public:
void handle_mouseup_on_label(Badge<Painting::TextPaintable>, CSSPixelPoint, unsigned button);
void handle_mousemove_on_label(Badge<Painting::TextPaintable>, CSSPixelPoint, unsigned button);
LabelableNode* labeled_control();
private:
virtual bool is_label() const override { return true; }

View file

@ -30,10 +30,8 @@ bool TextPaintable::wants_mouse_events() const
DOM::Node* TextPaintable::mouse_event_target() const
{
if (auto* label = layout_node().first_ancestor_of_type<Layout::Label>()) {
if (auto* control = const_cast<Layout::Label*>(label)->labeled_control())
return &control->dom_node();
}
if (auto const* label = layout_node().first_ancestor_of_type<Layout::Label>())
return label->dom_node().control().ptr();
return nullptr;
}