1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 21:47:46 +00:00

LibWeb: Make <input type=checkbox> honor the "checked" attribute

Implemented according to spec, although it's very possible that I missed
one or two details. :^)
This commit is contained in:
Andreas Kling 2022-02-15 19:16:57 +01:00
parent 05c9fd962d
commit 8a89a7bd95
4 changed files with 70 additions and 8 deletions

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org> * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -61,14 +61,37 @@ RefPtr<Layout::Node> HTMLInputElement::create_layout_node(NonnullRefPtr<CSS::Sty
return layout_node; return layout_node;
} }
void HTMLInputElement::set_checked(bool checked) void HTMLInputElement::set_checked(bool checked, ChangeSource change_source, ShouldRunActivationBehavior should_run_activation_behavior)
{ {
if (m_checked == checked) if (m_checked == checked)
return; return;
// The dirty checkedness flag must be initially set to false when the element is created,
// and must be set to true whenever the user interacts with the control in a way that changes the checkedness.
if (change_source == ChangeSource::User)
m_dirty_checkedness = true;
m_checked = checked; m_checked = checked;
if (layout_node()) if (layout_node())
layout_node()->set_needs_display(); layout_node()->set_needs_display();
if (should_run_activation_behavior == ShouldRunActivationBehavior::Yes)
run_activation_behavior();
}
void HTMLInputElement::run_activation_behavior()
{
// The activation behavior for input elements are these steps:
// FIXME: 1. If this element is not mutable and is not in the Checkbox state and is not in the Radio state, then return.
// 2. Run this element's input activation behavior, if any, and do nothing otherwise.
run_input_activation_behavior();
}
// https://html.spec.whatwg.org/multipage/input.html#input-activation-behavior
void HTMLInputElement::run_input_activation_behavior()
{
dispatch_event(DOM::Event::create(EventNames::change)); dispatch_event(DOM::Event::create(EventNames::change));
} }
@ -139,4 +162,26 @@ void HTMLInputElement::removed_from(DOM::Node* old_parent)
set_form(nullptr); set_form(nullptr);
} }
void HTMLInputElement::parse_attribute(FlyString const& name, String const& value)
{
FormAssociatedElement::parse_attribute(name, value);
if (name == HTML::AttributeNames::checked) {
// When the checked content attribute is added, if the control does not have dirty checkedness,
// the user agent must set the checkedness of the element to true
if (!m_dirty_checkedness)
set_checked(true, ChangeSource::Programmatic, ShouldRunActivationBehavior::No);
}
}
void HTMLInputElement::did_remove_attribute(FlyString const& name)
{
FormAssociatedElement::did_remove_attribute(name);
if (name == HTML::AttributeNames::checked) {
// When the checked content attribute is removed, if the control does not have dirty checkedness,
// the user agent must set the checkedness of the element to false.
if (!m_dirty_checkedness)
set_checked(false, ChangeSource::Programmatic, ShouldRunActivationBehavior::No);
}
}
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org> * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -28,7 +28,15 @@ public:
void set_value(String); void set_value(String);
bool checked() const { return m_checked; } bool checked() const { return m_checked; }
void set_checked(bool); enum class ChangeSource {
Programmatic,
User,
};
enum class ShouldRunActivationBehavior {
No,
Yes,
};
void set_checked(bool, ChangeSource = ChangeSource::Programmatic, ShouldRunActivationBehavior = ShouldRunActivationBehavior::Yes);
bool enabled() const; bool enabled() const;
@ -36,6 +44,9 @@ public:
virtual bool is_focusable() const override; virtual bool is_focusable() const override;
virtual void parse_attribute(FlyString const&, String const&) override;
virtual void did_remove_attribute(FlyString const&) override;
private: private:
// ^DOM::Node // ^DOM::Node
virtual void inserted() override; virtual void inserted() override;
@ -43,11 +54,16 @@ private:
// ^DOM::EventTarget // ^DOM::EventTarget
virtual void did_receive_focus() override; virtual void did_receive_focus() override;
virtual void run_activation_behavior() override;
void create_shadow_tree_if_needed(); void create_shadow_tree_if_needed();
void run_input_activation_behavior();
RefPtr<DOM::Text> m_text_node; RefPtr<DOM::Text> m_text_node;
bool m_checked { false }; bool m_checked { false };
// https://html.spec.whatwg.org/multipage/input.html#concept-input-checked-dirty-flag
bool m_dirty_checkedness { false };
}; };
} }

View file

@ -9,6 +9,7 @@
#include <LibGfx/Painter.h> #include <LibGfx/Painter.h>
#include <LibGfx/StylePainter.h> #include <LibGfx/StylePainter.h>
#include <LibWeb/HTML/BrowsingContext.h> #include <LibWeb/HTML/BrowsingContext.h>
#include <LibWeb/HTML/HTMLInputElement.h>
#include <LibWeb/Layout/CheckBox.h> #include <LibWeb/Layout/CheckBox.h>
#include <LibWeb/Layout/Label.h> #include <LibWeb/Layout/Label.h>
@ -62,7 +63,7 @@ void CheckBox::handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint& position
is_inside_node_or_label = Label::is_inside_associated_label(*this, position); is_inside_node_or_label = Label::is_inside_associated_label(*this, position);
if (is_inside_node_or_label) if (is_inside_node_or_label)
dom_node().set_checked(!dom_node().checked()); dom_node().set_checked(!dom_node().checked(), HTML::HTMLInputElement::ChangeSource::User);
m_being_pressed = false; m_being_pressed = false;
m_tracking_mouse = false; m_tracking_mouse = false;
@ -102,7 +103,7 @@ void CheckBox::handle_associated_label_mouseup(Badge<Label>)
// NOTE: Changing the checked state of the DOM node may run arbitrary JS, which could disappear this node. // NOTE: Changing the checked state of the DOM node may run arbitrary JS, which could disappear this node.
NonnullRefPtr protect = *this; NonnullRefPtr protect = *this;
dom_node().set_checked(!dom_node().checked()); dom_node().set_checked(!dom_node().checked(), HTML::HTMLInputElement::ChangeSource::User);
m_being_pressed = false; m_being_pressed = false;
} }

View file

@ -114,12 +114,12 @@ void RadioButton::set_checked_within_group()
if (dom_node().checked()) if (dom_node().checked())
return; return;
dom_node().set_checked(true); dom_node().set_checked(true, HTML::HTMLInputElement::ChangeSource::User);
String name = dom_node().name(); String name = dom_node().name();
document().for_each_in_inclusive_subtree_of_type<HTML::HTMLInputElement>([&](auto& element) { document().for_each_in_inclusive_subtree_of_type<HTML::HTMLInputElement>([&](auto& element) {
if (element.checked() && (element.layout_node() != this) && (element.name() == name)) if (element.checked() && (element.layout_node() != this) && (element.name() == name))
element.set_checked(false); element.set_checked(false, HTML::HTMLInputElement::ChangeSource::User);
return IterationDecision::Continue; return IterationDecision::Continue;
}); });
} }