LibWeb: Set dirty checkedness flag when setting checked IDL attribute

This matches the behavior of other browsers, which always set the dirty
checkedness flag when setting checkedness, except when setting the
`checked` content attribute.
This commit is contained in:
Tim Ledbetter 2025-01-05 20:50:09 +00:00 committed by Andreas Kling
parent c87bc78d5d
commit 5b6f2bb23a
Notes: github-actions[bot] 2025-01-11 10:25:26 +00:00
5 changed files with 180 additions and 28 deletions

View file

@ -158,15 +158,13 @@ void HTMLInputElement::adjust_computed_style(CSS::ComputedProperties& style)
style.set_property(CSS::PropertyID::LineHeight, CSS::CSSKeywordValue::create(CSS::Keyword::Normal));
}
void HTMLInputElement::set_checked(bool checked, ChangeSource change_source)
void HTMLInputElement::set_checked(bool checked)
{
if (m_checked == checked)
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_dirty_checkedness = true;
if (m_checked == checked)
return;
m_checked = checked;
@ -181,9 +179,9 @@ void HTMLInputElement::set_checked_binding(bool checked)
if (checked)
set_checked_within_group();
else
set_checked(false, ChangeSource::Programmatic);
set_checked(false);
} else {
set_checked(checked, ChangeSource::Programmatic);
set_checked(checked);
}
}
@ -1249,16 +1247,13 @@ void HTMLInputElement::did_lose_focus()
void HTMLInputElement::form_associated_element_attribute_changed(FlyString const& name, Optional<String> const& value, Optional<FlyString> const&)
{
if (name == HTML::AttributeNames::checked) {
if (!value.has_value()) {
// 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);
} else {
// 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);
// https://html.spec.whatwg.org/multipage/input.html#the-input-element:concept-input-checked-dirty-2
// 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;
// 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(value.has_value());
// set_checked() sets the dirty checkedness flag. We reset it here sinceit shouldn't be set when updating the attribute value
m_dirty_checkedness = false;
}
} else if (name == HTML::AttributeNames::type) {
auto new_type_attribute_state = parse_type_attribute(value.value_or(String {}));
@ -1757,7 +1752,7 @@ void HTMLInputElement::set_checked_within_group()
if (checked())
return;
set_checked(true, ChangeSource::User);
set_checked(true);
// No point iterating the tree if we have an empty name.
if (!name().has_value() || name()->is_empty())
@ -1765,7 +1760,7 @@ void HTMLInputElement::set_checked_within_group()
root().for_each_in_inclusive_subtree_of_type<HTML::HTMLInputElement>([&](auto& element) {
if (element.checked() && &element != this && is_in_same_radio_button_group(*this, element))
element.set_checked(false, ChangeSource::User);
element.set_checked(false);
return TraversalDecision::Continue;
});
}
@ -1781,7 +1776,7 @@ void HTMLInputElement::legacy_pre_activation_behavior()
// false, false if it is true) and set this element's indeterminate IDL
// attribute to false.
if (type_state() == TypeAttributeState::Checkbox) {
set_checked(!checked(), ChangeSource::User);
set_checked(!checked());
set_indeterminate(false);
}
@ -1809,7 +1804,7 @@ void HTMLInputElement::legacy_cancelled_activation_behavior()
// element's checkedness and the element's indeterminate IDL attribute back
// to the values they had before the legacy-pre-activation behavior was run.
if (type_state() == TypeAttributeState::Checkbox) {
set_checked(m_before_legacy_pre_activation_behavior_checked, ChangeSource::Programmatic);
set_checked(m_before_legacy_pre_activation_behavior_checked);
set_indeterminate(m_before_legacy_pre_activation_behavior_indeterminate);
}
@ -1834,7 +1829,7 @@ void HTMLInputElement::legacy_cancelled_activation_behavior()
}
if (!did_reselect_previous_element)
set_checked(false, ChangeSource::User);
set_checked(false);
}
}

View file

@ -88,11 +88,7 @@ public:
Optional<String> placeholder_value() const;
bool checked() const { return m_checked; }
enum class ChangeSource {
Programmatic,
User,
};
void set_checked(bool, ChangeSource = ChangeSource::Programmatic);
void set_checked(bool);
bool checked_binding() const { return checked(); }
void set_checked_binding(bool);

View file

@ -0,0 +1,73 @@
Harness status: OK
Found 68 tests
68 Pass
Pass input element's value should be cloned
Pass input element's dirty value flag should be cloned, so setAttribute doesn't affect the cloned input's value
Pass input[type=button] element's indeterminateness should be cloned
Pass input[type=button] element's checkedness should be cloned
Pass input[type=button] element's dirty checkedness should be cloned, so setAttribute doesn't affect the cloned input's checkedness
Pass input[type=checkbox] element's indeterminateness should be cloned
Pass input[type=checkbox] element's checkedness should be cloned
Pass input[type=checkbox] element's dirty checkedness should be cloned, so setAttribute doesn't affect the cloned input's checkedness
Pass input[type=color] element's indeterminateness should be cloned
Pass input[type=color] element's checkedness should be cloned
Pass input[type=color] element's dirty checkedness should be cloned, so setAttribute doesn't affect the cloned input's checkedness
Pass input[type=date] element's indeterminateness should be cloned
Pass input[type=date] element's checkedness should be cloned
Pass input[type=date] element's dirty checkedness should be cloned, so setAttribute doesn't affect the cloned input's checkedness
Pass input[type=datetime-local] element's indeterminateness should be cloned
Pass input[type=datetime-local] element's checkedness should be cloned
Pass input[type=datetime-local] element's dirty checkedness should be cloned, so setAttribute doesn't affect the cloned input's checkedness
Pass input[type=email] element's indeterminateness should be cloned
Pass input[type=email] element's checkedness should be cloned
Pass input[type=email] element's dirty checkedness should be cloned, so setAttribute doesn't affect the cloned input's checkedness
Pass input[type=file] element's indeterminateness should be cloned
Pass input[type=file] element's checkedness should be cloned
Pass input[type=file] element's dirty checkedness should be cloned, so setAttribute doesn't affect the cloned input's checkedness
Pass input[type=hidden] element's indeterminateness should be cloned
Pass input[type=hidden] element's checkedness should be cloned
Pass input[type=hidden] element's dirty checkedness should be cloned, so setAttribute doesn't affect the cloned input's checkedness
Pass input[type=image] element's indeterminateness should be cloned
Pass input[type=image] element's checkedness should be cloned
Pass input[type=image] element's dirty checkedness should be cloned, so setAttribute doesn't affect the cloned input's checkedness
Pass input[type=month] element's indeterminateness should be cloned
Pass input[type=month] element's checkedness should be cloned
Pass input[type=month] element's dirty checkedness should be cloned, so setAttribute doesn't affect the cloned input's checkedness
Pass input[type=number] element's indeterminateness should be cloned
Pass input[type=number] element's checkedness should be cloned
Pass input[type=number] element's dirty checkedness should be cloned, so setAttribute doesn't affect the cloned input's checkedness
Pass input[type=password] element's indeterminateness should be cloned
Pass input[type=password] element's checkedness should be cloned
Pass input[type=password] element's dirty checkedness should be cloned, so setAttribute doesn't affect the cloned input's checkedness
Pass input[type=radio] element's indeterminateness should be cloned
Pass input[type=radio] element's checkedness should be cloned
Pass input[type=radio] element's dirty checkedness should be cloned, so setAttribute doesn't affect the cloned input's checkedness
Pass input[type=range] element's indeterminateness should be cloned
Pass input[type=range] element's checkedness should be cloned
Pass input[type=range] element's dirty checkedness should be cloned, so setAttribute doesn't affect the cloned input's checkedness
Pass input[type=reset] element's indeterminateness should be cloned
Pass input[type=reset] element's checkedness should be cloned
Pass input[type=reset] element's dirty checkedness should be cloned, so setAttribute doesn't affect the cloned input's checkedness
Pass input[type=search] element's indeterminateness should be cloned
Pass input[type=search] element's checkedness should be cloned
Pass input[type=search] element's dirty checkedness should be cloned, so setAttribute doesn't affect the cloned input's checkedness
Pass input[type=submit] element's indeterminateness should be cloned
Pass input[type=submit] element's checkedness should be cloned
Pass input[type=submit] element's dirty checkedness should be cloned, so setAttribute doesn't affect the cloned input's checkedness
Pass input[type=tel] element's indeterminateness should be cloned
Pass input[type=tel] element's checkedness should be cloned
Pass input[type=tel] element's dirty checkedness should be cloned, so setAttribute doesn't affect the cloned input's checkedness
Pass input[type=text] element's indeterminateness should be cloned
Pass input[type=text] element's checkedness should be cloned
Pass input[type=text] element's dirty checkedness should be cloned, so setAttribute doesn't affect the cloned input's checkedness
Pass input[type=time] element's indeterminateness should be cloned
Pass input[type=time] element's checkedness should be cloned
Pass input[type=time] element's dirty checkedness should be cloned, so setAttribute doesn't affect the cloned input's checkedness
Pass input[type=url] element's indeterminateness should be cloned
Pass input[type=url] element's checkedness should be cloned
Pass input[type=url] element's dirty checkedness should be cloned, so setAttribute doesn't affect the cloned input's checkedness
Pass input[type=week] element's indeterminateness should be cloned
Pass input[type=week] element's checkedness should be cloned
Pass input[type=week] element's dirty checkedness should be cloned, so setAttribute doesn't affect the cloned input's checkedness

View file

@ -0,0 +1,64 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Cloning of input elements</title>
<link rel="help" href="https://dom.spec.whatwg.org/#dom-node-clonenode">
<link rel="help" href="https://dom.spec.whatwg.org/#concept-node-clone">
<link rel="help" href="https://dom.spec.whatwg.org/#concept-node-clone-ext">
<link rel="help" href="https://html.spec.whatwg.org/multipage/forms.html#the-input-element:concept-node-clone-ext">
<link rel="author" title="Matthew Phillips" href="mailto:matthew@matthewphillips.info">
<script src="../../../../resources/testharness.js"></script>
<script src="../../../../resources/testharnessreport.js"></script>
<script type=module>
import inputTypes from "./input-types.js";
test(function() {
var input = document.createElement("input");
input.value = "foo bar";
var copy = input.cloneNode();
assert_equals(copy.value, "foo bar");
}, "input element's value should be cloned");
test(function() {
var input = document.createElement("input");
input.value = "foo bar";
var copy = input.cloneNode();
copy.setAttribute("value", "something else");
assert_equals(copy.value, "foo bar");
}, "input element's dirty value flag should be cloned, so setAttribute doesn't affect the cloned input's value");
for (const inputType of inputTypes) {
test(function() {
var input = document.createElement("input");
input.setAttribute("type", inputType);
input.indeterminate = true;
var copy = input.cloneNode();
assert_equals(copy.indeterminate, true);
}, `input[type=${inputType}] element's indeterminateness should be cloned`);
test(function() {
var input = document.createElement("input");
input.setAttribute("type", inputType);
input.checked = true;
var copy = input.cloneNode();
assert_equals(copy.checked, true);
}, `input[type=${inputType}] element's checkedness should be cloned`);
test(function() {
var input = document.createElement("input");
input.setAttribute("type", inputType);
input.checked = false;
var copy = input.cloneNode();
copy.setAttribute("checked", "checked");
assert_equals(copy.checked, false);
}, `input[type=${inputType}] element's dirty checkedness should be cloned, so setAttribute doesn't affect the cloned input's checkedness`);
}
</script>

View file

@ -0,0 +1,24 @@
export default [
"button",
"checkbox",
"color",
"date",
"datetime-local",
"email",
"file",
"hidden",
"image",
"month",
"number",
"password",
"radio",
"range",
"reset",
"search",
"submit",
"tel",
"text",
"time",
"url",
"week",
];