LibWeb: Implement CSS validity pseudo-classes

This commit is contained in:
Psychpsyo 2025-02-02 20:41:23 +01:00 committed by Sam Atkins
commit bad7324307
Notes: github-actions[bot] 2025-02-05 12:52:56 +00:00
15 changed files with 745 additions and 29 deletions

View file

@ -40,6 +40,8 @@ static void fire_a_focus_event(GC::Ptr<DOM::EventTarget> focus_event_target, GC:
// https://html.spec.whatwg.org/multipage/interaction.html#focus-update-steps
static void run_focus_update_steps(Vector<GC::Root<DOM::Node>> old_chain, Vector<GC::Root<DOM::Node>> new_chain, DOM::Node* new_focus_target)
{
// The focus update steps, given an old chain, a new chain, and a new focus target respectively, are as follows:
// 1. If the last entry in old chain and the last entry in new chain are the same,
// pop the last entry from old chain and the last entry from new chain and redo this step.
while (!old_chain.is_empty()
@ -51,18 +53,21 @@ static void run_focus_update_steps(Vector<GC::Root<DOM::Node>> old_chain, Vector
// 2. For each entry entry in old chain, in order, run these substeps:
for (auto& entry : old_chain) {
// 1. If entry is an input element, and the change event applies to the element, and the element does not have
// a defined activation behavior, and the user has changed the element's value or its list of selected files
// while the control was focused without committing that change (such that it is different to what it was
// when the control was first focused), then fire an event named change at the element, with the bubbles
// attribute initialized to true.
// 1. If entry is an input element
if (is<HTMLInputElement>(*entry)) {
auto& input_element = static_cast<HTMLInputElement&>(*entry);
// FIXME: Spec issue: It doesn't make sense to check if the element has a defined activation behavior, as
// that is always true. Instead, we check if it has an *input* activation behavior.
// https://github.com/whatwg/html/issues/9973
if (input_element.change_event_applies() && !input_element.has_input_activation_behavior()) {
// and the change event applies to the element, and the element does not have a defined activation behavior, and the user has changed the
// element's value or its list of selected files while the control was focused without committing that change (such that it is different to what it was when the control was first
// focused), then:
if (input_element.change_event_applies() && !input_element.has_input_activation_behavior() && input_element.has_uncommitted_changes()) {
// 1. Set entry's user validity to true.
input_element.set_user_validity(true);
// 2. Fire an event named change at the element, with the bubbles attribute initialized to true.
input_element.commit_pending_changes();
}
}

View file

@ -15,6 +15,7 @@
#include <LibWeb/DOM/SelectionchangeEventDispatching.h>
#include <LibWeb/HTML/FormAssociatedElement.h>
#include <LibWeb/HTML/HTMLButtonElement.h>
#include <LibWeb/HTML/HTMLDataListElement.h>
#include <LibWeb/HTML/HTMLFieldSetElement.h>
#include <LibWeb/HTML/HTMLFormElement.h>
#include <LibWeb/HTML/HTMLInputElement.h>
@ -52,13 +53,13 @@ bool FormAssociatedElement::enabled() const
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fe-disabled
auto const& html_element = form_associated_element_to_html_element();
// A form control is disabled if any of the following conditions are met:
// 1. The element is a button, input, select, textarea, or form-associated custom element, and the disabled attribute is specified on this element (regardless of its value).
// A form control is disabled if any of the following are true:
// - The element is a button, input, select, textarea, or form-associated custom element, and the disabled attribute is specified on this element (regardless of its value); or
// FIXME: This doesn't check for form-associated custom elements.
if ((is<HTMLButtonElement>(html_element) || is<HTMLInputElement>(html_element) || is<HTMLSelectElement>(html_element) || is<HTMLTextAreaElement>(html_element)) && html_element.has_attribute(HTML::AttributeNames::disabled))
return false;
// 2. The element is a descendant of a fieldset element whose disabled attribute is specified, and is not a descendant of that fieldset element's first legend element child, if any.
// - The element is a descendant of a fieldset element whose disabled attribute is specified, and is not a descendant of that fieldset element's first legend element child, if any.
for (auto* fieldset_ancestor = html_element.first_ancestor_of_type<HTMLFieldSetElement>(); fieldset_ancestor; fieldset_ancestor = fieldset_ancestor->first_ancestor_of_type<HTMLFieldSetElement>()) {
if (fieldset_ancestor->has_attribute(HTML::AttributeNames::disabled)) {
auto* first_legend_element_child = fieldset_ancestor->first_child_of_type<HTMLLegendElement>();
@ -202,6 +203,98 @@ 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#candidate-for-constraint-validation
bool FormAssociatedElement::is_candidate_for_constraint_validation() const
{
// A submittable element is a candidate for constraint validation except when a condition has barred the element from constraint validation.
if (!is_submittable())
return false;
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#enabling-and-disabling-form-controls%3A-the-disabled-attribute%3Abarred-from-constraint-validation
// If an element is disabled, it is barred from constraint validation.
if (!enabled())
return false;
auto const& html_element = form_associated_element_to_html_element();
// https://html.spec.whatwg.org/multipage/form-elements.html#the-datalist-element%3Abarred-from-constraint-validation
// If an element has a datalist element ancestor, it is barred from constraint validation.
if (html_element.first_ancestor_of_type<HTMLDataListElement>())
return false;
if (is<HTMLInputElement>(html_element)) {
auto const& input_element = as<HTMLInputElement>(html_element);
// https://html.spec.whatwg.org/multipage/input.html#hidden-state-(type%3Dhidden)%3Abarred-from-constraint-validation
// If an input element's type attribute is in the Hidden state, it is barred from constraint validation.
if (input_element.type_state() == HTMLInputElement::TypeAttributeState::Hidden)
return false;
// https://html.spec.whatwg.org/multipage/input.html#reset-button-state-(type%3Dreset)%3Abarred-from-constraint-validation
// When an input element's type attribute is in the Reset Button state, the rules in this section apply.
// The element is barred from constraint validation.
if (input_element.type_state() == HTMLInputElement::TypeAttributeState::ResetButton)
return false;
// https://html.spec.whatwg.org/multipage/input.html#button-state-(type%3Dbutton)%3Abarred-from-constraint-validation
// When an input element's type attribute is in the Button state, the rules in this section apply.
// The element is barred from constraint validation.
if (input_element.type_state() == HTMLInputElement::TypeAttributeState::Button)
return false;
// https://html.spec.whatwg.org/multipage/input.html#the-readonly-attribute%3Abarred-from-constraint-validation
// If the readonly attribute is specified on an input element, the element is barred from constraint validation.
if (input_element.has_attribute(HTML::AttributeNames::readonly))
return false;
}
if (is<HTMLButtonElement>(html_element)) {
auto const& button_element = as<HTMLButtonElement>(html_element);
// https://html.spec.whatwg.org/multipage/form-elements.html#the-button-element%3Abarred-from-constraint-validation
// If the type attribute is in the Reset Button state or the Button state, the element is barred from constraint validation.
if (button_element.type_state() == HTMLButtonElement::TypeAttributeState::Button || button_element.type_state() == HTMLButtonElement::TypeAttributeState::Reset)
return false;
}
if (is<HTMLTextAreaElement>(html_element)) {
// https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element%3Abarred-from-constraint-validation
// If the readonly attribute is specified on a textarea element, the element is barred from constraint validation.
if (html_element.has_attribute(HTML::AttributeNames::readonly))
return false;
}
return true;
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fv-valid
bool FormAssociatedElement::satisfies_its_constraints() const
{
return !(
suffering_from_being_missing() || suffering_from_a_type_mismatch() || suffering_from_a_pattern_mismatch() || suffering_from_being_too_long() || suffering_from_being_too_short() || suffering_from_an_underflow() || suffering_from_an_overflow() || suffering_from_a_step_mismatch() || suffering_from_bad_input() || suffering_from_a_custom_error());
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#limiting-user-input-length%3A-the-maxlength-attribute%3Asuffering-from-being-too-long
bool FormAssociatedElement::suffering_from_being_too_long() const
{
// FIXME: Implement this.
return false;
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#setting-minimum-input-length-requirements%3A-the-minlength-attribute%3Asuffering-from-being-too-short
bool FormAssociatedElement::suffering_from_being_too_short() const
{
// FIXME: Implement this.
return false;
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-a-custom-error
bool FormAssociatedElement::suffering_from_a_custom_error() const
{
// FIXME: Implement this.
return false;
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-textarea/input-relevant-value
void FormAssociatedTextControlElement::relevant_value_was_changed()
{

View file

@ -91,6 +91,24 @@ 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/#candidate-for-constraint-validation
bool is_candidate_for_constraint_validation() const;
// https://html.spec.whatwg.org/#concept-fv-valid
bool satisfies_its_constraints() const;
// https://html.spec.whatwg.org/#definitions
virtual bool suffering_from_being_missing() const { return false; }
virtual bool suffering_from_a_type_mismatch() const { return false; }
virtual bool suffering_from_a_pattern_mismatch() const { return false; }
bool suffering_from_being_too_long() const;
bool suffering_from_being_too_short() const;
virtual bool suffering_from_an_underflow() const { return false; }
virtual bool suffering_from_an_overflow() const { return false; }
virtual bool suffering_from_a_step_mismatch() const { return false; }
virtual bool suffering_from_bad_input() const { return false; }
bool suffering_from_a_custom_error() const;
virtual String value() const { return String {}; }
virtual HTMLElement& form_associated_element_to_html_element() = 0;

View file

@ -125,18 +125,31 @@ WebIDL::ExceptionOr<void> HTMLFormElement::submit_form(GC::Ref<HTMLElement> subm
// 2. Set form's firing submission events to true.
m_firing_submission_events = true;
// FIXME: 3. If the submitter element's no-validate state is false, then interactively validate the constraints
// 3. For each element field in the list of submittable elements whose form owner is form, set field's user validity to true.
for (auto& element : get_submittable_elements()) {
// NOTE: Only input, select and textarea elements have a user validity flag.
// See https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#user-validity
if (is<HTMLInputElement>(*element)) {
(&as<HTMLInputElement>(*element))->set_user_validity(false);
} else if (is<HTMLSelectElement>(*element)) {
(&as<HTMLSelectElement>(*element))->set_user_validity(false);
} else if (is<HTMLTextAreaElement>(*element)) {
(&as<HTMLTextAreaElement>(*element))->set_user_validity(false);
}
}
// FIXME: 4. If the submitter element's no-validate state is false, then interactively validate the constraints
// of form and examine the result. If the result is negative (i.e., the constraint validation concluded
// that there were invalid fields and probably informed the user of this), then:
// 1. Set form's firing submission events to false.
// 2. Return.
// 4. Let submitterButton be null if submitter is form. Otherwise, let submitterButton be submitter.
// 5. Let submitterButton be null if submitter is form. Otherwise, let submitterButton be submitter.
GC::Ptr<HTMLElement> submitter_button;
if (submitter != this)
submitter_button = submitter;
// 5. Let shouldContinue be the result of firing an event named submit at form using SubmitEvent, with the
// 6. Let shouldContinue be the result of firing an event named submit at form using SubmitEvent, with the
// submitter attribute initialized to submitterButton, the bubbles attribute initialized to true, and the
// cancelable attribute initialized to true.
SubmitEventInit event_init {};
@ -146,14 +159,14 @@ WebIDL::ExceptionOr<void> HTMLFormElement::submit_form(GC::Ref<HTMLElement> subm
submit_event->set_cancelable(true);
bool should_continue = dispatch_event(*submit_event);
// 6. Set form's firing submission events to false.
// 7. Set form's firing submission events to false.
m_firing_submission_events = false;
// 7. If shouldContinue is false, then return.
// 8. If shouldContinue is false, then return.
if (!should_continue)
return {};
// 8. If form cannot navigate, then return.
// 9. If form cannot navigate, then return.
// Spec Note: Cannot navigate is run again as dispatching the submit event could have changed the outcome.
if (cannot_navigate())
return {};

View file

@ -468,7 +468,8 @@ void HTMLInputElement::did_pick_color(Optional<Color> picked_color, ColorPickerU
if (state == ColorPickerUpdateState::Closed) {
queue_an_element_task(HTML::Task::Source::UserInteraction, [this] {
// given the input element
// FIXME: to set its user interacted to true
// to set its user validity to true
m_user_validity = true;
// and fire an event named change at the input element, with the bubbles attribute initialized to true.
auto change_event = DOM::Event::create(realm(), HTML::EventNames::change);
change_event->set_bubbles(true);
@ -1214,15 +1215,16 @@ void HTMLInputElement::user_interaction_did_change_input_value()
// For input elements without a defined input activation behavior, but to which these events apply,
// and for which the user interface involves both interactive manipulation and an explicit commit action,
// then when the user changes the element's value, the user agent must queue an element task on the user interaction task source
// given the input element to fire an event named input at the input element, with the bubbles and composed attributes initialized to true,
// and any time the user commits the change, the user agent must queue an element task on the user interaction task source given the input
// element to set its user validity to true and fire an event named change at the input element, with the bubbles attribute initialized to true.
// given the input element to fire an event named input at the input element, with the bubbles and composed attributes initialized to true
queue_an_element_task(HTML::Task::Source::UserInteraction, [this] {
auto input_event = DOM::Event::create(realm(), HTML::EventNames::input);
input_event->set_bubbles(true);
input_event->set_composed(true);
dispatch_event(*input_event);
});
// and any time the user commits the change, the user agent must queue an element task on the user interaction task source given the input
// element to set its user validity to true and fire an event named change at the input element, with the bubbles attribute initialized to true.
// FIXME: Does this need to happen here?
}
void HTMLInputElement::update_slider_shadow_tree_elements()
@ -1583,7 +1585,8 @@ String HTMLInputElement::value_sanitization_algorithm(String const& value) const
// https://html.spec.whatwg.org/multipage/input.html#the-input-element:concept-form-reset-control
void HTMLInputElement::reset_algorithm()
{
// The reset algorithm for input elements is to set the dirty value flag and dirty checkedness flag back to false,
// The reset algorithm for input elements is to set its user validity, dirty value flag, and dirty checkedness flag back to false,
m_user_validity = false;
m_dirty_value = false;
m_dirty_checkedness = false;
@ -2765,4 +2768,169 @@ bool HTMLInputElement::is_focusable() const
return m_type != TypeAttributeState::Hidden && enabled();
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-being-missing
bool HTMLInputElement::suffering_from_being_missing() const
{
switch (type_state()) {
case TypeAttributeState::Checkbox:
// https://html.spec.whatwg.org/multipage/input.html#checkbox-state-(type%3Dcheckbox)%3Asuffering-from-being-missing
// If the element is required and its checkedness is false, then the element is suffering from being missing.
if (has_attribute(HTML::AttributeNames::required) && !checked())
return true;
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.
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.
break;
default:
break;
}
// https://html.spec.whatwg.org/multipage/input.html#the-required-attribute%3Asuffering-from-being-missing
// If the element is required, and its value IDL attribute applies and is in the mode value, and the element is mutable, and the element's value is the empty
// string, then the element is suffering from being missing.
if (has_attribute(HTML::AttributeNames::required) && value_attribute_mode() == ValueAttributeMode::Value && is_mutable() && m_value.is_empty())
return true;
return false;
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-a-type-mismatch
bool HTMLInputElement::suffering_from_a_type_mismatch() const
{
switch (type_state()) {
case TypeAttributeState::URL:
// https://html.spec.whatwg.org/multipage/input.html#url-state-(type%3Durl)%3Asuffering-from-a-type-mismatch
// While the value of the element is neither the empty string nor a valid absolute URL, the element is suffering from a type mismatch.
// FIXME: Implement this.
break;
case TypeAttributeState::Email:
// https://html.spec.whatwg.org/multipage/input.html#email-state-(type%3Demail)%3Asuffering-from-a-type-mismatch
// While the value of the element is neither the empty string nor a single valid email address, the element is suffering from a type mismatch.
// FIXME: Implement this.
break;
default:
break;
}
return false;
}
// https://html.spec.whatwg.org/multipage/input.html#the-pattern-attribute%3Asuffering-from-a-pattern-mismatch
bool HTMLInputElement::suffering_from_a_pattern_mismatch() const
{
// If the element's value is not the empty string, and either the element's multiple attribute is not specified or it does not apply to the input element given its
// type attribute's current state, and the element has a compiled pattern regular expression but that regular expression does not match the element's value, then the element is
// suffering from a pattern mismatch.
// FIXME: Implement this.
return false;
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-an-underflow
bool HTMLInputElement::suffering_from_an_underflow() const
{
// https://html.spec.whatwg.org/multipage/input.html#the-min-and-max-attributes%3Asuffering-from-an-underflow-2
// When the element has a minimum and does not have a reversed range, and the result of applying the algorithm to convert a string to a number to the string
// given by the element's value is a number, and the number obtained from that algorithm is less than the minimum, the element is suffering from an underflow.
// FIXME: Implement this.
// https://html.spec.whatwg.org/multipage/input.html#the-min-and-max-attributes%3Asuffering-from-an-underflow-3
// When an element has a reversed range, and the result of applying the algorithm to convert a string to a number to the string given by the element's value is a
// number, and the number obtained from that algorithm is more than the maximum and less than the minimum, the element is simultaneously suffering from an underflow and
// suffering from an overflow.
// FIXME: Implement this.
return false;
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-an-overflow
bool HTMLInputElement::suffering_from_an_overflow() const
{
// https://html.spec.whatwg.org/multipage/input.html#the-min-and-max-attributes%3Asuffering-from-an-overflow-2
// When the element has a maximum and does not have a reversed range, and the result of applying the algorithm to convert a string to a number to the string
// given by the element's value is a number, and the number obtained from that algorithm is more than the maximum, the element is suffering from an overflow.
// https://html.spec.whatwg.org/multipage/input.html#the-min-and-max-attributes%3Asuffering-from-an-underflow-3
// When an element has a reversed range, and the result of applying the algorithm to convert a string to a number to the string given by the element's value is a
// number, and the number obtained from that algorithm is more than the maximum and less than the minimum, the element is simultaneously suffering from an underflow and
// suffering from an overflow.
// FIXME: Implement this.
return false;
}
// https://html.spec.whatwg.org/multipage/input.html#the-step-attribute%3Asuffering-from-a-step-mismatch
bool HTMLInputElement::suffering_from_a_step_mismatch() const
{
// When the element has an allowed value step, and the result of applying the algorithm to convert a string to a number to the string given by the element's
// value is a number, and that number subtracted from the step base is not an integral multiple of the allowed value step, the element is suffering from a step mismatch.
// FIXME: Implement this.
return false;
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-bad-input
bool HTMLInputElement::suffering_from_bad_input() const
{
switch (type_state()) {
case TypeAttributeState::Email:
// https://html.spec.whatwg.org/multipage/input.html#email-state-(type%3Demail)%3Asuffering-from-bad-input
// While the user interface is representing input that the user agent cannot convert to punycode, the control is suffering from bad input.
// FIXME: Implement this.
// https://html.spec.whatwg.org/multipage/input.html#email-state-(type%3Demail)%3Asuffering-from-bad-input-2
// While the user interface describes a situation where an individual value contains a U+002C COMMA (,) or is representing input that the user agent
// cannot convert to punycode, the control is suffering from bad input.
// FIXME: Implement this.
break;
case TypeAttributeState::Date:
// https://html.spec.whatwg.org/multipage/input.html#date-state-(type%3Ddate)%3Asuffering-from-bad-input
// While the user interface describes input that the user agent cannot convert to a valid date string, the control is suffering from bad input.
// FIXME: Implement this.
break;
case TypeAttributeState::Month:
// https://html.spec.whatwg.org/multipage/input.html#month-state-(type%3Dmonth)%3Asuffering-from-bad-input
// While the user interface describes input that the user agent cannot convert to a valid month string, the control is suffering from bad input.
// FIXME: Implement this.
break;
case TypeAttributeState::Week:
// https://html.spec.whatwg.org/multipage/input.html#week-state-(type%3Dweek)%3Asuffering-from-bad-input
// While the user interface describes input that the user agent cannot convert to a valid week string, the control is suffering from bad input.
// FIXME: Implement this.
break;
case TypeAttributeState::Time:
// https://html.spec.whatwg.org/multipage/#time-state-(type=time):suffering-from-bad-input
// While the user interface describes input that the user agent cannot convert to a valid time string, the control is suffering from bad input.
// FIXME: Implement this.
break;
case TypeAttributeState::LocalDateAndTime:
// https://html.spec.whatwg.org/multipage/input.html#local-date-and-time-state-(type%3Ddatetime-local)%3Asuffering-from-bad-input
// While the user interface describes input that the user agent cannot convert to a valid normalized local date and time string, the control is suffering from bad
// input.
// FIXME: Implement this.
break;
case TypeAttributeState::Number:
// https://html.spec.whatwg.org/multipage/input.html#number-state-(type%3Dnumber)%3Asuffering-from-bad-input
// While the user interface describes input that the user agent cannot convert to a valid floating-point number, the control is suffering from bad input.
// FIXME: Implement this.
break;
case TypeAttributeState::Range:
// https://html.spec.whatwg.org/multipage/input.html#range-state-(type%3Drange)%3Asuffering-from-bad-input
// While the user interface describes input that the user agent cannot convert to a valid floating-point number, the control is suffering from bad input.
// FIXME: Implement this.
break;
case TypeAttributeState::Color:
// https://html.spec.whatwg.org/multipage/input.html#color-state-(type%3Dcolor)%3Asuffering-from-bad-input
// While the element's value is not the empty string and parsing it returns failure, the control is suffering from bad input.
// FIXME: Implement this.
break;
default:
break;
}
return false;
}
}

View file

@ -84,7 +84,11 @@ public:
virtual void set_dirty_value_flag(bool flag) override { m_dirty_value = flag; }
bool user_validity() const { return m_user_validity; }
void set_user_validity(bool flag) { m_user_validity = flag; }
void commit_pending_changes();
bool has_uncommitted_changes() { return m_has_uncommitted_changes; }
String placeholder() const;
Optional<String> placeholder_value() const;
@ -220,6 +224,15 @@ public:
virtual void did_edit_text_node() override;
virtual GC::Ptr<DOM::Text> form_associated_element_to_text_node() override { return m_text_node; }
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#definitions
virtual bool suffering_from_being_missing() const override;
virtual bool suffering_from_a_type_mismatch() const override;
virtual bool suffering_from_a_pattern_mismatch() const override;
virtual bool suffering_from_an_underflow() const override;
virtual bool suffering_from_an_overflow() const override;
virtual bool suffering_from_a_step_mismatch() const override;
virtual bool suffering_from_bad_input() const override;
private:
HTMLInputElement(DOM::Document&, DOM::QualifiedName);
@ -338,6 +351,9 @@ private:
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fe-dirty
bool m_dirty_value { false };
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#user-validity
bool m_user_validity { false };
// https://html.spec.whatwg.org/multipage/input.html#the-input-element:legacy-pre-activation-behavior
bool m_before_legacy_pre_activation_behavior_checked { false };
bool m_before_legacy_pre_activation_behavior_indeterminate { false };

View file

@ -241,16 +241,21 @@ Vector<GC::Root<HTMLOptionElement>> HTMLSelectElement::list_of_options() const
void HTMLSelectElement::reset_algorithm()
{
update_cached_list_of_options();
// The reset algorithm for a select element selectElement is:
// The reset algorithm for select elements is to go through all the option elements in the element's list of options,
// 1. Set selectElement's user validity to false.
m_user_validity = false;
// 2. For each optionElement of selectElement's list of options:
for (auto const& option_element : m_cached_list_of_options) {
// set their selectedness to true if the option element has a selected attribute, and false otherwise,
// 1. If optionElement has a selected attribute, then set optionElement's selectedness to true; otherwise set it to false.
option_element->set_selected_internal(option_element->has_attribute(AttributeNames::selected));
// set their dirtiness to false,
// 2. Set optionElement's dirtiness to false.
option_element->m_dirty = false;
// and then have the option elements ask for a reset.
option_element->ask_for_a_reset();
}
// 3. Run the selectedness setting algorithm given selectElement.
update_selectedness();
}
// https://html.spec.whatwg.org/multipage/form-elements.html#dom-select-selectedindex
@ -379,7 +384,8 @@ void HTMLSelectElement::queue_input_and_change_events()
{
// When the user agent is to send select update notifications, queue an element task on the user interaction task source given the select element to run these steps:
queue_an_element_task(HTML::Task::Source::UserInteraction, [this] {
// FIXME: 1. Set the select element's user interacted to true.
// 1. Set the select element's user validity to true.
m_user_validity = true;
// 2. Fire an event named input at the select element, with the bubbles and composed attributes initialized to true.
auto input_event = DOM::Event::create(realm(), HTML::EventNames::input);
@ -691,4 +697,28 @@ bool HTMLSelectElement::is_focusable() const
return enabled();
}
// https://html.spec.whatwg.org/multipage/form-elements.html#placeholder-label-option
HTMLOptionElement* HTMLSelectElement::placeholder_label_option() const
{
// If a select element has a required attribute specified, does not have a multiple attribute specified, and has a display size of 1;
if (has_attribute(HTML::AttributeNames::required) && !has_attribute(HTML::AttributeNames::multiple) && display_size() == 1) {
// and if the value of the first option element in the
// select element's list of options (if any) is the empty string, and that option element's parent node is the select element (and not an optgroup element), then that option is the
// select element's placeholder label option.
auto first_option_element = list_of_options()[0];
if (first_option_element->value().is_empty() && first_option_element->parent() == this)
return first_option_element;
}
return {};
}
// https://html.spec.whatwg.org/multipage/form-elements.html#the-select-element%3Asuffering-from-being-missing
bool HTMLSelectElement::suffering_from_being_missing() const
{
// If the element has its required attribute specified, and either none of the option elements in the select element's list of options have their selectedness
// set to true, or the only option element in the select element's list of options with its selectedness set to true is the placeholder label option, then the element is suffering from being
// missing.
return has_attribute(HTML::AttributeNames::required) && (m_selected_options->length() == 0 || (m_selected_options->length() == 1 && m_selected_options->item(0) == placeholder_label_option()));
}
}

View file

@ -100,6 +100,15 @@ public:
bool can_skip_selectedness_update_for_inserted_option(HTMLOptionElement const&) const;
bool user_validity() const { return m_user_validity; }
void set_user_validity(bool flag) { m_user_validity = flag; }
// https://html.spec.whatwg.org/multipage/form-elements.html#placeholder-label-option
HTMLOptionElement* placeholder_label_option() const;
// https://html.spec.whatwg.org/multipage/form-elements.html#the-select-element%3Asuffering-from-being-missing
virtual bool suffering_from_being_missing() const override;
private:
HTMLSelectElement(DOM::Document&, DOM::QualifiedName);
@ -132,6 +141,9 @@ private:
Vector<SelectItem> m_select_items;
GC::Ptr<DOM::Element> m_inner_text_element;
GC::Ptr<DOM::Element> m_chevron_icon_element;
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#user-validity
bool m_user_validity { false };
};
}

View file

@ -110,7 +110,8 @@ i32 HTMLTextAreaElement::default_tab_index_value() const
// https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element:concept-form-reset-control
void HTMLTextAreaElement::reset_algorithm()
{
// The reset algorithm for textarea elements is to set the dirty value flag back to false,
// The reset algorithm for textarea elements is to set the user validity to false, dirty value flag back to false,
m_user_validity = false;
m_dirty_value = false;
// and set the raw value of element to its child text content.
set_raw_value(child_text_content());
@ -486,4 +487,12 @@ bool HTMLTextAreaElement::is_focusable() const
return enabled();
}
// https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element%3Asuffering-from-being-missing
bool HTMLTextAreaElement::suffering_from_being_missing() const
{
// If the element has its required attribute specified, and the element is mutable, and the element's value is the empty string, then the element is suffering from
// being missing.
return has_attribute(HTML::AttributeNames::required) && is_mutable() && value().is_empty();
}
}

View file

@ -90,6 +90,9 @@ public:
virtual void set_dirty_value_flag(bool flag) override { m_dirty_value = flag; }
bool user_validity() const { return m_user_validity; }
void set_user_validity(bool flag) { m_user_validity = flag; }
u32 text_length() const;
bool check_validity();
@ -126,6 +129,9 @@ public:
virtual void did_edit_text_node() override;
virtual GC::Ptr<DOM::Text> form_associated_element_to_text_node() override { return m_text_node; }
// https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element%3Asuffering-from-being-missing
virtual bool suffering_from_being_missing() const override;
private:
HTMLTextAreaElement(DOM::Document&, DOM::QualifiedName);
@ -157,6 +163,9 @@ private:
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fe-dirty
bool m_dirty_value { false };
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#user-validity
bool m_user_validity { false };
// https://html.spec.whatwg.org/multipage/form-elements.html#concept-textarea-raw-value
String m_raw_value;