Files
ladybird/Libraries/LibWeb/HTML/HTMLLabelElement.cpp
Shannon Booth fd44da6829 LibWeb/Bindings: Emit one bindings header and cpp per IDL
Previously, the LibWeb bindings generator would output multiple per
interface files like Prototype/Constructor/Namespace/GlobalMixin
depending on the contents of that IDL file.

This complicates the build system as it means that it does not know
what files will be generated without knowledge of the contents of that
IDL file.

Instead, for each IDL file only generate a single Bindings/<IDLFile>.h
and Bindings/<IDLFile>.cpp.
2026-04-21 07:36:13 +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 const* 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();
}
}