Skip to content

Commit

Permalink
LibWeb: Set dirty checkedness flag when setting checked IDL attribute
Browse files Browse the repository at this point in the history
This matches the behavior of other browsers, which always set the dirty
checkedness flag when setting checkedness, except when setting the
`checked` content attribute.
  • Loading branch information
tcl3 authored and awesomekling committed Jan 11, 2025
1 parent c87bc78 commit 5b6f2bb
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 28 deletions.
41 changes: 18 additions & 23 deletions Libraries/LibWeb/HTML/HTMLInputElement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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);
}
}

Expand Down Expand Up @@ -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 {}));
Expand Down Expand Up @@ -1757,15 +1752,15 @@ 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())
return;

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;
});
}
Expand All @@ -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);
}

Expand Down Expand Up @@ -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);
}

Expand All @@ -1834,7 +1829,7 @@ void HTMLInputElement::legacy_cancelled_activation_behavior()
}

if (!did_reselect_previous_element)
set_checked(false, ChangeSource::User);
set_checked(false);
}
}

Expand Down
6 changes: 1 addition & 5 deletions Libraries/LibWeb/HTML/HTMLInputElement.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,7 @@ class HTMLInputElement final
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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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",
];

0 comments on commit 5b6f2bb

Please sign in to comment.