LibWeb: Implement HTMLInputElement::suffering_from_being_missing

This change implements all required “suffering from being missing”
constraints https://html.spec.whatwg.org/#suffering-from-being-missing
for HTMLInputElement.
This commit is contained in:
sideshowbarker 2025-02-22 22:28:02 +09:00 committed by Tim Ledbetter
commit 7da5869b14
Notes: github-actions[bot] 2025-02-26 04:14:30 +00:00
5 changed files with 305 additions and 13 deletions

View file

@ -2526,8 +2526,7 @@ WebIDL::ExceptionOr<void> HTMLInputElement::step_up_or_down(bool is_down, WebIDL
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-cva-checkvalidity
WebIDL::ExceptionOr<bool> HTMLInputElement::check_validity()
{
dbgln("(STUBBED) HTMLInputElement::check_validity(). Called on: {}", debug_description());
return true;
return check_validity_steps();
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-cva-reportvalidity
@ -2874,6 +2873,8 @@ bool HTMLInputElement::is_focusable() const
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-being-missing
bool HTMLInputElement::suffering_from_being_missing() const
{
bool has_checkedness_false_for_all_elements_in_group = true;
bool has_required_element_in_group = false;
switch (type_state()) {
case TypeAttributeState::Checkbox:
// https://html.spec.whatwg.org/multipage/input.html#checkbox-state-(type%3Dcheckbox)%3Asuffering-from-being-missing
@ -2883,14 +2884,25 @@ bool HTMLInputElement::suffering_from_being_missing() const
break;
case TypeAttributeState::RadioButton:
// https://html.spec.whatwg.org/multipage/input.html#radio-button-state-(type%3Dradio)%3Asuffering-from-being-missing
// If an element in the radio button group is required, and all of the input elements in the radio button group have a checkedness that is false, then the element
// is suffering from being missing.
// FIXME: Implement this.
// If an element in the radio button group is required, and all of the input elements in the radio button group
// have a checkedness that is false, then the element is suffering from being missing.
root().for_each_in_inclusive_subtree_of_type<HTML::HTMLInputElement>([&](auto& element) {
if (is_in_same_radio_button_group(*this, element)) {
if (element.checked())
has_checkedness_false_for_all_elements_in_group = false;
if (has_attribute(HTML::AttributeNames::required))
has_required_element_in_group = true;
}
return TraversalDecision::Continue;
});
if (has_checkedness_false_for_all_elements_in_group && has_required_element_in_group)
return true;
break;
case TypeAttributeState::FileUpload:
// https://html.spec.whatwg.org/multipage/input.html#file-upload-state-(type%3Dfile)%3Asuffering-from-being-missing
// If the element is required and the list of selected files is empty, then the element is suffering from being missing.
// FIXME: Implement this.
if (has_attribute(HTML::AttributeNames::required) && const_cast<HTMLInputElement&>(*this).files()->length() == 0)
return true;
break;
default:
break;

View file

@ -0,0 +1,136 @@
Harness status: OK
Found 130 tests
75 Pass
55 Fail
Pass [INPUT in TEXT status] no constraint
Pass [INPUT in TEXT status] no constraint (in a form)
Pass [INPUT in TEXT status] not suffering from being too long
Pass [INPUT in TEXT status] not suffering from being too long (in a form)
Fail [INPUT in TEXT status] suffering from a pattern mismatch
Fail [INPUT in TEXT status] suffering from a pattern mismatch (in a form)
Pass [INPUT in TEXT status] suffering from being missing
Pass [INPUT in TEXT status] suffering from being missing (in a form)
Pass [INPUT in SEARCH status] no constraint
Pass [INPUT in SEARCH status] no constraint (in a form)
Pass [INPUT in SEARCH status] not suffering from being too long
Pass [INPUT in SEARCH status] not suffering from being too long (in a form)
Fail [INPUT in SEARCH status] suffering from a pattern mismatch
Fail [INPUT in SEARCH status] suffering from a pattern mismatch (in a form)
Pass [INPUT in SEARCH status] suffering from being missing
Pass [INPUT in SEARCH status] suffering from being missing (in a form)
Pass [INPUT in TEL status] no constraint
Pass [INPUT in TEL status] no constraint (in a form)
Pass [INPUT in TEL status] not suffering from being too long
Pass [INPUT in TEL status] not suffering from being too long (in a form)
Fail [INPUT in TEL status] suffering from a pattern mismatch
Fail [INPUT in TEL status] suffering from a pattern mismatch (in a form)
Pass [INPUT in TEL status] suffering from being missing
Pass [INPUT in TEL status] suffering from being missing (in a form)
Pass [INPUT in PASSWORD status] no constraint
Pass [INPUT in PASSWORD status] no constraint (in a form)
Pass [INPUT in PASSWORD status] not suffering from being too long
Pass [INPUT in PASSWORD status] not suffering from being too long (in a form)
Fail [INPUT in PASSWORD status] suffering from a pattern mismatch
Fail [INPUT in PASSWORD status] suffering from a pattern mismatch (in a form)
Pass [INPUT in PASSWORD status] suffering from being missing
Pass [INPUT in PASSWORD status] suffering from being missing (in a form)
Pass [INPUT in URL status] no constraint
Pass [INPUT in URL status] no constraint (in a form)
Pass [INPUT in URL status] suffering from being too long
Pass [INPUT in URL status] suffering from being too long (in a form)
Fail [INPUT in URL status] suffering from a pattern mismatch
Fail [INPUT in URL status] suffering from a pattern mismatch (in a form)
Fail [INPUT in URL status] suffering from a type mismatch
Fail [INPUT in URL status] suffering from a type mismatch (in a form)
Pass [INPUT in URL status] suffering from being missing
Pass [INPUT in URL status] suffering from being missing (in a form)
Pass [INPUT in EMAIL status] no constraint
Pass [INPUT in EMAIL status] no constraint (in a form)
Pass [INPUT in EMAIL status] not suffering from being too long
Pass [INPUT in EMAIL status] not suffering from being too long (in a form)
Fail [INPUT in EMAIL status] suffering from a pattern mismatch
Fail [INPUT in EMAIL status] suffering from a pattern mismatch (in a form)
Fail [INPUT in EMAIL status] suffering from a type mismatch
Fail [INPUT in EMAIL status] suffering from a type mismatch (in a form)
Pass [INPUT in EMAIL status] suffering from being missing
Pass [INPUT in EMAIL status] suffering from being missing (in a form)
Pass [INPUT in DATETIME-LOCAL status] no constraint
Pass [INPUT in DATETIME-LOCAL status] no constraint (in a form)
Fail [INPUT in DATETIME-LOCAL status] suffering from an overflow
Fail [INPUT in DATETIME-LOCAL status] suffering from an overflow (in a form)
Fail [INPUT in DATETIME-LOCAL status] suffering from an underflow
Fail [INPUT in DATETIME-LOCAL status] suffering from an underflow (in a form)
Fail [INPUT in DATETIME-LOCAL status] suffering from a step mismatch
Fail [INPUT in DATETIME-LOCAL status] suffering from a step mismatch (in a form)
Pass [INPUT in DATETIME-LOCAL status] suffering from being missing
Pass [INPUT in DATETIME-LOCAL status] suffering from being missing (in a form)
Pass [INPUT in DATE status] no constraint
Pass [INPUT in DATE status] no constraint (in a form)
Fail [INPUT in DATE status] suffering from an overflow
Fail [INPUT in DATE status] suffering from an overflow (in a form)
Fail [INPUT in DATE status] suffering from an underflow
Fail [INPUT in DATE status] suffering from an underflow (in a form)
Fail [INPUT in DATE status] suffering from a step mismatch
Fail [INPUT in DATE status] suffering from a step mismatch (in a form)
Pass [INPUT in DATE status] suffering from being missing
Pass [INPUT in DATE status] suffering from being missing (in a form)
Pass [INPUT in MONTH status] no constraint
Pass [INPUT in MONTH status] no constraint (in a form)
Fail [INPUT in MONTH status] suffering from an overflow
Fail [INPUT in MONTH status] suffering from an overflow (in a form)
Fail [INPUT in MONTH status] suffering from an underflow
Fail [INPUT in MONTH status] suffering from an underflow (in a form)
Fail [INPUT in MONTH status] suffering from a step mismatch
Fail [INPUT in MONTH status] suffering from a step mismatch (in a form)
Pass [INPUT in MONTH status] suffering from being missing
Pass [INPUT in MONTH status] suffering from being missing (in a form)
Pass [INPUT in WEEK status] no constraint
Pass [INPUT in WEEK status] no constraint (in a form)
Fail [INPUT in WEEK status] suffering from an overflow
Fail [INPUT in WEEK status] suffering from an overflow (in a form)
Fail [INPUT in WEEK status] suffering from an underflow
Fail [INPUT in WEEK status] suffering from an underflow (in a form)
Fail [INPUT in WEEK status] suffering from a step mismatch
Fail [INPUT in WEEK status] suffering from a step mismatch (in a form)
Pass [INPUT in WEEK status] suffering from being missing
Pass [INPUT in WEEK status] suffering from being missing (in a form)
Pass [INPUT in TIME status] no constraint
Pass [INPUT in TIME status] no constraint (in a form)
Fail [INPUT in TIME status] suffering from an overflow
Fail [INPUT in TIME status] suffering from an overflow (in a form)
Fail [INPUT in TIME status] suffering from an underflow
Fail [INPUT in TIME status] suffering from an underflow (in a form)
Fail [INPUT in TIME status] suffering from a step mismatch
Fail [INPUT in TIME status] suffering from a step mismatch (in a form)
Pass [INPUT in TIME status] suffering from being missing
Pass [INPUT in TIME status] suffering from being missing (in a form)
Fail [INPUT in NUMBER status] suffering from an overflow
Fail [INPUT in NUMBER status] suffering from an overflow (in a form)
Fail [INPUT in NUMBER status] suffering from an underflow
Fail [INPUT in NUMBER status] suffering from an underflow (in a form)
Fail [INPUT in NUMBER status] suffering from a step mismatch
Fail [INPUT in NUMBER status] suffering from a step mismatch (in a form)
Pass [INPUT in NUMBER status] suffering from being missing
Pass [INPUT in NUMBER status] suffering from being missing (in a form)
Pass [INPUT in CHECKBOX status] no constraint
Pass [INPUT in CHECKBOX status] no constraint (in a form)
Pass [INPUT in CHECKBOX status] suffering from being missing
Pass [INPUT in CHECKBOX status] suffering from being missing (in a form)
Pass [INPUT in RADIO status] no constraint
Pass [INPUT in RADIO status] no constraint (in a form)
Pass [INPUT in RADIO status] suffering from being missing
Pass [INPUT in RADIO status] suffering from being missing (in a form)
Pass [INPUT in FILE status] no constraint
Pass [INPUT in FILE status] no constraint (in a form)
Pass [INPUT in FILE status] suffering from being missing
Pass [INPUT in FILE status] suffering from being missing (in a form)
Fail [select] no constraint
Pass [select] no constraint (in a form)
Fail [select] suffering from being missing
Pass [select] suffering from being missing (in a form)
Pass [textarea] no constraint
Pass [textarea] no constraint (in a form)
Fail [textarea] suffering from being missing
Pass [textarea] suffering from being missing (in a form)

View file

@ -2,8 +2,8 @@ Harness status: OK
Found 78 tests
44 Pass
34 Fail
46 Pass
32 Fail
Pass [INPUT in TEXT status] The required attribute is not set
Pass [INPUT in TEXT status] The value is not empty and required is true
Fail [INPUT in TEXT status] The value is empty and required is true
@ -71,10 +71,10 @@ Pass [INPUT in CHECKBOX status] The checked attribute is true
Pass [INPUT in CHECKBOX status] The checked attribute is false
Pass [INPUT in RADIO status] The required attribute is not set
Pass [INPUT in RADIO status] The checked attribute is true
Fail [INPUT in RADIO status] The checked attribute is false
Pass [INPUT in RADIO status] The checked attribute is false
Pass [INPUT in RADIO status] The checked attribute is false and the name attribute is empty
Pass [INPUT in FILE status] The required attribute is not set
Fail [INPUT in FILE status] The Files attribute is null
Pass [INPUT in FILE status] The Files attribute is null
Pass [select] The required attribute is not set
Pass [select] Selected the option with value equals to 1
Pass [select] Selected the option with value equals to empty

View file

@ -2,8 +2,7 @@ Harness status: OK
Found 12 tests
11 Pass
1 Fail
12 Pass
Pass click on mutable radio fires click event, then input event, then change event
Pass click on non-mutable radio doesn't fire the input event
Pass click on non-mutable radio doesn't fire the change event
@ -12,7 +11,7 @@ Pass only one control of a radio button group can have its checkedness set to tr
Pass radio inputs with non-ASCII name attributes belong to the same radio button group
Pass changing the name of a radio input element and setting its checkedness to true makes all the other elements' checkedness in the same radio button group be set to false
Pass moving radio input element out of or into a form should still work as expected
Fail Radio buttons in an orphan tree should make a group
Pass Radio buttons in an orphan tree should make a group
Pass Radio buttons in different groups (because they have different form owners or no form owner) do not affect each other's checkedness
Pass Radio buttons in different groups (because they are not in the same tree) do not affect each other's checkedness
Pass Radio buttons in different groups (because they have different name attribute values, or no name attribute) do not affect each other's checkedness

View file

@ -0,0 +1,145 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>The constraint validation API Test: element.checkValidity()</title>
<link rel="author" title="Intel" href="http://www.intel.com/">
<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity">
<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-constraint-validation-api">
<script src="../../../../resources/testharness.js"></script>
<script src="../../../../resources/testharnessreport.js"></script>
<script src="support/validator.js"></script>
<div id="log"></div>
<script>
var testElements = [
{
tag: "input",
types: ["text", "search", "tel", "password"],
testData: [
{conditions: {}, expected: true, name: "[target] no constraint"},
{conditions: {maxLength: "4", value: "abcdef"}, expected: true, name: "[target] not suffering from being too long", dirty: true},
{conditions: {pattern: "[A-Z]", value: "abc"}, expected: false, name: "[target] suffering from a pattern mismatch"},
{conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
]
},
{
tag: "input",
types: ["url"],
testData: [
{conditions: {}, expected: true, name: "[target] no constraint"},
{conditions: {maxLength: "20", value: "http://www.example.com"}, expected: true, name: "[target] suffering from being too long", dirty: true},
{conditions: {pattern: "http://www.example.com", value: "http://www.example.net"}, expected: false, name: "[target] suffering from a pattern mismatch"},
{conditions: {value: "abc"}, expected: false, name: "[target] suffering from a type mismatch"},
{conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
]
},
{
tag: "input",
types: ["email"],
testData: [
{conditions: {}, expected: true, name: "[target] no constraint"},
{conditions: {maxLength: "10", value: "test@example.com"}, expected: true, name: "[target] not suffering from being too long", dirty: true},
{conditions: {pattern: "test@example.com", value: "test@example.net"}, expected: false, name: "[target] suffering from a pattern mismatch"},
{conditions: {value: "abc"}, expected: false, name: "[target] suffering from a type mismatch"},
{conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
]
},
{
tag: "input",
types: ["datetime-local"],
testData: [
{conditions: {}, expected: true, name: "[target] no constraint"},
{conditions: {max: "2000-01-01T12:00:00", value: "2001-01-01T12:00:00"}, expected: false, name: "[target] suffering from an overflow"},
{conditions: {min: "2001-01-01T12:00:00", value: "2000-01-01T12:00:00"}, expected: false, name: "[target] suffering from an underflow"},
{conditions: {step: 2 * 60 * 1000, value: "2001-01-01T12:03:00"}, expected: false, name: "[target] suffering from a step mismatch"},
{conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
]
},
{
tag: "input",
types: ["date"],
testData: [
{conditions: {}, expected: true, name: "[target] no constraint"},
{conditions: {max: "2000-01-01", value: "2001-01-01"}, expected: false, name: "[target] suffering from an overflow"},
{conditions: {min: "2001-01-01", value: "2000-01-01"}, expected: false, name: "[target] suffering from an underflow"},
{conditions: {step: 2 * 1 * 86400000, value: "2001-01-03"}, expected: false, name: "[target] suffering from a step mismatch"},
{conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
]
},
{
tag: "input",
types: ["month"],
testData: [
{conditions: {}, expected: true, name: "[target] no constraint"},
{conditions: {max: "2000-01", value: "2001-01"}, expected: false, name: "[target] suffering from an overflow"},
{conditions: {min: "2001-01", value: "2000-01"}, expected: false, name: "[target] suffering from an underflow"},
{conditions: {step: 3 * 1 * 1, value: "2001-03"}, expected: false, name: "[target] suffering from a step mismatch"},
{conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
]
},
{
tag: "input",
types: ["week"],
testData: [
{conditions: {}, expected: true, name: "[target] no constraint"},
{conditions: {max: "2000-W01", value: "2001-W01"}, expected: false, name: "[target] suffering from an overflow"},
{conditions: {min: "2001-W01", value: "2000-W01"}, expected: false, name: "[target] suffering from an underflow"},
{conditions: {step: 2 * 1 * 604800000, value: "2001-W03"}, expected: false, name: "[target] suffering from a step mismatch"},
{conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
]
},
{
tag: "input",
types: ["time"],
testData: [
{conditions: {}, expected: true, name: "[target] no constraint"},
{conditions: {max: "12:00:00", value: "13:00:00"}, expected: false, name: "[target] suffering from an overflow"},
{conditions: {min: "12:00:00", value: "11:00:00"}, expected: false, name: "[target] suffering from an underflow"},
{conditions: {step: 2 * 60 * 1000, value: "12:03:00"}, expected: false, name: "[target] suffering from a step mismatch"},
{conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
]
},
{
tag: "input",
types: ["number"],
testData: [
{conditions: {max: "5", value: "6"}, expected: false, name: "[target] suffering from an overflow"},
{conditions: {min: "5", value: "4"}, expected: false, name: "[target] suffering from an underflow"},
{conditions: {step: 2 * 1 * 1, value: "3"}, expected: false, name: "[target] suffering from a step mismatch"},
{conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
]
},
{
tag: "input",
types: ["checkbox", "radio"],
testData: [
{conditions: {}, expected: true, name: "[target] no constraint"},
{conditions: {required: true, checked: false, name: "test1"}, expected: false, name: "[target] suffering from being missing"}
]
},
{
tag: "input",
types: ["file"],
testData: [
{conditions: {}, expected: true, name: "[target] no constraint"},
{conditions: {required: true, files: null}, expected: false, name: "[target] suffering from being missing"}
]
},
{
tag: "select",
types: [],
testData: [
{conditions: {}, expected: true, name: "[target] no constraint"},
{conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
]
},
{
tag: "textarea",
types: [],
testData: [
{conditions: {}, expected: true, name: "[target] no constraint"},
{conditions: {required: true, value: ""}, expected: false, name: "[target] suffering from being missing"}
]
}
];
validator.run_test(testElements, "checkValidity");
</script>