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:
parent
05c9fd962d
commit
8a89a7bd95
4 changed files with 70 additions and 8 deletions
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 };
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue