Files
ladybird/Libraries/LibWeb/HTML/HTMLLabelElement.cpp
Aliaksandr Kalenik 568b7ce7ea LibWeb: Make Paintable tree ref-counted
The Paintable tree and its supplemental painting data structures were
GC allocated because that was the easiest way to manage it and avoid
leaks introduced by ref cycles. This included the Paintable subclasses
themselves plus StackingContext, ChromeWidget, Scrollbar, ResizeHandle,
and scroll-frame state.

We are now trying to reduce GC allocation churn on layout and painting
updates, so keeping this short-lived rendering tree outside the JS heap
is a better fit. Move Paintable to RefCountedTreeNode, make painting
helpers ref-counted or weakly reference Paintables, and update the
layout and event-handler call sites to use RefPtr/WeakPtr ownership.
2026-05-07 15:03:44 +02:00

162 lines
6.3 KiB
C++

/*
* Copyright (c) 2020, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ScopeGuard.h>
#include <LibWeb/Bindings/HTMLLabelElement.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/HTML/Focus.h>
#include <LibWeb/HTML/FormAssociatedElement.h>
#include <LibWeb/HTML/HTMLLabelElement.h>
#include <LibWeb/HTML/Navigable.h>
#include <LibWeb/Painting/Paintable.h>
#include <LibWeb/Selection/Selection.h>
#include <LibWeb/UIEvents/MouseEvent.h>
namespace Web::HTML {
GC_DEFINE_ALLOCATOR(HTMLLabelElement);
HTMLLabelElement::HTMLLabelElement(DOM::Document& document, DOM::QualifiedName qualified_name)
: HTMLElement(document, move(qualified_name))
{
}
HTMLLabelElement::~HTMLLabelElement() = default;
void HTMLLabelElement::initialize(JS::Realm& realm)
{
WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLLabelElement);
Base::initialize(realm);
}
void HTMLLabelElement::set_being_activated(bool activated)
{
Base::set_being_activated(activated);
if (auto labeled_control = control())
labeled_control->set_being_activated(activated);
}
bool HTMLLabelElement::has_activation_behavior() const
{
return true;
}
// https://html.spec.whatwg.org/multipage/forms.html#the-label-element:activation-behaviour
void HTMLLabelElement::activation_behavior(DOM::Event const& event)
{
// The label element's exact default presentation and behavior, in particular what its activation behavior might be,
// if anything, should match the platform's label behavior. The activation behavior of a label element for events
// targeted at interactive content descendants of a label element, and any descendants of those interactive content
// descendants, must be to do nothing.
// AD-HOC: Click and focus the control, matching typical platform behavior.
// This matches the behavior of HTMLElement::click(), but the original event properties are preserved.
if (m_click_in_progress)
return;
auto control_element = control();
if (!control_element)
return;
// NB: If a click was fired after a drag selection was made on the label element, do not propagate the click event
// to the input element. This allows the user to e.g. copy the label's text.
if (event.type() == EventNames::click) {
auto const& mouse_event = as<UIEvents::MouseEvent>(event);
auto selection = document().get_selection();
if (mouse_event.detail() == 1 && selection && !selection->is_collapsed())
return;
}
if (auto* form_control = as_if<FormAssociatedElement>(*control_element)) {
if (!form_control->enabled())
return;
}
{
m_click_in_progress = true;
ScopeGuard guard { [this] { m_click_in_progress = false; } };
auto const& mouse_event = as<UIEvents::MouseEvent>(event);
auto click_event = mouse_event.clone();
// NB: Ensure layout is up to date before accessing the control's paintable.
document().update_layout(DOM::UpdateLayoutReason::HTMLLabelElementActivationBehavior);
// Recompute offsetX/offsetY relative to the control element, since the original values are relative to the label.
if (auto paintable = control_element->paintable(); paintable && document().navigable()) {
auto scroll_offset = document().navigable()->viewport_scroll_offset();
auto page_position = CSSPixelPoint { CSSPixels(mouse_event.client_x()) + scroll_offset.x(), CSSPixels(mouse_event.client_y()) + scroll_offset.y() };
auto box_position = paintable->box_type_agnostic_position();
click_event->set_offset_x(AK::round((page_position.x() - box_position.x()).to_double()));
click_event->set_offset_y(AK::round((page_position.y() - box_position.y()).to_double()));
}
click_event->set_bubbles(true);
click_event->set_cancelable(true);
click_event->set_composed(true);
click_event->set_is_trusted(event.is_trusted());
control_element->dispatch_event(click_event);
}
if (control_element->is_focusable())
HTML::run_focusing_steps(control_element);
}
// https://html.spec.whatwg.org/multipage/forms.html#labeled-control
GC::Ptr<HTMLElement> HTMLLabelElement::control() const
{
GC::Ptr<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()) {
root().for_each_in_inclusive_subtree_of_type<HTMLElement>([&](auto& element) {
if (element.id() == *for_() && element.is_labelable()) {
control = &const_cast<HTMLElement&>(element);
return TraversalDecision::Break;
}
return TraversalDecision::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 TraversalDecision::Break;
}
return TraversalDecision::Continue;
});
return control;
}
// https://html.spec.whatwg.org/multipage/forms.html#dom-label-form
GC::Ptr<HTMLFormElement> HTMLLabelElement::form() const
{
auto labeled_control = control();
// 1. If the label element has no labeled control, then return null.
if (!labeled_control)
return {};
// 2. If the label element's labeled control is not a form-associated element, then return null.
auto* form_associated_element = as_if<FormAssociatedElement>(*labeled_control);
if (!form_associated_element)
return {};
// 3. Return the label element's labeled control's form owner (which can still be null).
return form_associated_element->form();
}
}