LibWeb: Implement HTMLFormElement::checkValidity (constraint validation)

This change implements the requirements from the HTML spec at
https://html.spec.whatwg.org/#statically-validate-the-constraints
and https://html.spec.whatwg.org/#dom-form-checkvalidity — the parts of
the HTML constraint validation API (aka “client-side form validation”)
https://html.spec.whatwg.org/#the-constraint-validation-api for
HTMLFormElement itself — as well as the code for the requirements at
https://html.spec.whatwg.org/#check-validity-steps, which are the shared
requirements for the checkValidity method for individual form controls.
This commit is contained in:
sideshowbarker 2025-02-22 22:14:44 +09:00 committed by Tim Ledbetter
parent b4e47f198a
commit 7c34746571
Notes: github-actions[bot] 2025-02-26 04:14:35 +00:00
4 changed files with 62 additions and 2 deletions

View file

@ -226,6 +226,20 @@ WebIDL::ExceptionOr<void> FormAssociatedElement::set_form_action(String const& v
return html_element.set_attribute(HTML::AttributeNames::formaction, value);
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#check-validity-steps
bool FormAssociatedElement::check_validity_steps()
{
// 1. If element is a candidate for constraint validation and does not satisfy its constraints
if (is_candidate_for_constraint_validation() && !satisfies_its_constraints()) {
auto& element = form_associated_element_to_html_element();
// 1. Fire an event named invalid at element, with the cancelable attribute initialized to true
element.dispatch_event(DOM::Event::create(element.realm(), EventNames::invalid, { .cancelable = true }));
// 2. Return false.
return false;
}
return true;
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#candidate-for-constraint-validation
bool FormAssociatedElement::is_candidate_for_constraint_validation() const
{

View file

@ -91,6 +91,9 @@ public:
// https://html.spec.whatwg.org/multipage/forms.html#concept-submit-button
virtual bool is_submit_button() const { return false; }
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#check-validity-steps
bool check_validity_steps();
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#candidate-for-constraint-validation
bool is_candidate_for_constraint_validation() const;

View file

@ -561,11 +561,48 @@ unsigned HTMLFormElement::length() const
return elements()->length();
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#statically-validate-the-constraints
HTMLFormElement::StaticValidationResult HTMLFormElement::statically_validate_constraints()
{
// 1. Let controls be a list of all the submittable elements whose form owner is form, in tree order.
auto controls = get_submittable_elements();
// 2. Let invalid controls be an initially empty list of elements.
GC::RootVector<GC::Ref<DOM::Element>> invalid_controls(realm().heap());
// 3. For each element field in controls, in tree order:
for (auto& element : controls) {
auto& field = as<FormAssociatedElement>(*element);
// 1. If field is not a candidate for constraint validation, then move on to the next element.
if (!field.is_candidate_for_constraint_validation())
continue;
// 2. Otherwise, if field satisfies its constraints, then move on to the next element.
if (field.satisfies_its_constraints())
continue;
// 3. Otherwise, add field to invalid controls.
invalid_controls.append(field.form_associated_element_to_html_element());
}
// 4. If invalid controls is empty, then return a positive result.
if (invalid_controls.is_empty())
return { true, invalid_controls };
// 5. Let unhandled invalid controls be an initially empty list of elements.
GC::RootVector<GC::Ref<DOM::Element>> unhandled_invalid_controls(realm().heap());
// 6. For each element field in invalid controls, if any, in tree order:
for (auto& field : invalid_controls) {
// 1. Let notCanceled be the result of firing an event named invalid at field, with the cancelable attribute
// initialized to true.
auto not_canceled = field->dispatch_event(DOM::Event::create(this->realm(),
EventNames::invalid, { .cancelable = true }));
// 2. If notCanceled is true, then add field to unhandled invalid controls.
if (not_canceled)
unhandled_invalid_controls.append(field);
}
// 7. Return a negative result with the list of elements in the unhandled invalid controls list.
return { false, unhandled_invalid_controls };
}
// https://html.spec.whatwg.org/multipage/forms.html#dom-form-checkvalidity
WebIDL::ExceptionOr<bool> HTMLFormElement::check_validity()
{
dbgln("(STUBBED) HTMLFormElement::check_validity(). Called on: {}", debug_description());
return true;
return statically_validate_constraints().result;
}
// https://html.spec.whatwg.org/multipage/forms.html#dom-form-reportvalidity

View file

@ -78,6 +78,12 @@ public:
GC::Ref<HTMLFormControlsCollection> elements() const;
unsigned length() const;
struct StaticValidationResult {
bool result;
GC::RootVector<GC::Ref<DOM::Element>> unhandled_invalid_controls;
};
StaticValidationResult statically_validate_constraints();
WebIDL::ExceptionOr<bool> check_validity();
WebIDL::ExceptionOr<bool> report_validity();