diff --git a/Libraries/LibWeb/CSS/PseudoClasses.json b/Libraries/LibWeb/CSS/PseudoClasses.json index b91702cc9e9..d92a7fe8c48 100644 --- a/Libraries/LibWeb/CSS/PseudoClasses.json +++ b/Libraries/LibWeb/CSS/PseudoClasses.json @@ -116,6 +116,9 @@ "optimal-value": { "argument": "" }, + "optional": { + "argument": "" + }, "popover-open": { "argument": "" }, @@ -134,6 +137,9 @@ "read-write": { "argument": "" }, + "required": { + "argument": "" + }, "root": { "argument": "" }, diff --git a/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Libraries/LibWeb/CSS/SelectorEngine.cpp index 963131dd556..79fd77662c4 100644 --- a/Libraries/LibWeb/CSS/SelectorEngine.cpp +++ b/Libraries/LibWeb/CSS/SelectorEngine.cpp @@ -988,6 +988,55 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla return false; } + case CSS::PseudoClass::Required: { + // https://html.spec.whatwg.org/multipage/semantics-other.html#selector-required + + // The :required pseudo-class must match any element falling into one of the following categories: + // - input elements that are required + if (auto const* input_element = as_if(element)) { + if (input_element->required_applies() && input_element->has_attribute(HTML::AttributeNames::required)) + return true; + } + // - select elements that have a required attribute + else if (auto const* select_element = as_if(element)) { + if (select_element->has_attribute(HTML::AttributeNames::required)) + return true; + } + // - textarea elements that have a required attribute + else if (auto const* textarea_element = as_if(element)) { + if (textarea_element->has_attribute(HTML::AttributeNames::required)) + return true; + } + + return false; + } + case CSS::PseudoClass::Optional: { + // https://html.spec.whatwg.org/multipage/semantics-other.html#selector-optional + + // The :optional pseudo-class must match any element falling into one of the following categories: + // - input elements to which the required attribute applies that are not required + if (auto const* input_element = as_if(element)) { + if (input_element->required_applies() && !input_element->has_attribute(HTML::AttributeNames::required)) + return true; + + // AD-HOC: Chromium and Webkit also match for hidden inputs (and WPT expects this) + // See: https://github.com/whatwg/html/issues/11273 + if (input_element->type_state() == HTML::HTMLInputElement::TypeAttributeState::Hidden) + return true; + } + // - select elements that do not have a required attribute + else if (auto const* select_element = as_if(element)) { + if (!select_element->has_attribute(HTML::AttributeNames::required)) + return true; + } + // - textarea elements that do not have a required attribute + else if (auto const* textarea_element = as_if(element)) { + if (!textarea_element->has_attribute(HTML::AttributeNames::required)) + return true; + } + + return false; + } } return false; diff --git a/Libraries/LibWeb/CSS/StyleInvalidationData.cpp b/Libraries/LibWeb/CSS/StyleInvalidationData.cpp index 27865e2d6bc..f5998387280 100644 --- a/Libraries/LibWeb/CSS/StyleInvalidationData.cpp +++ b/Libraries/LibWeb/CSS/StyleInvalidationData.cpp @@ -141,6 +141,8 @@ static void build_invalidation_sets_for_simple_selector(Selector::SimpleSelector case PseudoClass::Link: case PseudoClass::AnyLink: case PseudoClass::LocalLink: + case PseudoClass::Required: + case PseudoClass::Optional: invalidation_set.set_needs_invalidate_pseudo_class(pseudo_class.type); break; default: diff --git a/Libraries/LibWeb/DOM/Element.cpp b/Libraries/LibWeb/DOM/Element.cpp index e3352434fee..339fd38abcc 100644 --- a/Libraries/LibWeb/DOM/Element.cpp +++ b/Libraries/LibWeb/DOM/Element.cpp @@ -2397,6 +2397,9 @@ void Element::invalidate_style_after_attribute_change(FlyString const& attribute changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::PseudoClass, .value = CSS::PseudoClass::PlaceholderShown }); } else if (attribute_name == HTML::AttributeNames::value) { changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::PseudoClass, .value = CSS::PseudoClass::Checked }); + } else if (attribute_name == HTML::AttributeNames::required) { + changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::PseudoClass, .value = CSS::PseudoClass::Required }); + changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::PseudoClass, .value = CSS::PseudoClass::Optional }); } changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::Attribute, .value = attribute_name }); diff --git a/Libraries/LibWeb/HTML/HTMLInputElement.cpp b/Libraries/LibWeb/HTML/HTMLInputElement.cpp index 2d43f712757..99b0dd25e62 100644 --- a/Libraries/LibWeb/HTML/HTMLInputElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLInputElement.cpp @@ -3002,6 +3002,31 @@ bool HTMLInputElement::multiple_applies() const } } +// https://html.spec.whatwg.org/multipage/input.html#do-not-apply +bool HTMLInputElement::required_applies() const +{ + switch (type_state()) { + case TypeAttributeState::Text: + case TypeAttributeState::Search: + case TypeAttributeState::Telephone: + case TypeAttributeState::URL: + case TypeAttributeState::Email: + case TypeAttributeState::Password: + case TypeAttributeState::Date: + case TypeAttributeState::Month: + case TypeAttributeState::Week: + case TypeAttributeState::Time: + case TypeAttributeState::LocalDateAndTime: + case TypeAttributeState::Number: + case TypeAttributeState::Checkbox: + case TypeAttributeState::RadioButton: + case TypeAttributeState::FileUpload: + return true; + default: + return false; + } +} + bool HTMLInputElement::has_selectable_text() const { // Potential FIXME: Date, Month, Week, Time and LocalDateAndTime are rendered as a basic text input for now, diff --git a/Libraries/LibWeb/HTML/HTMLInputElement.h b/Libraries/LibWeb/HTML/HTMLInputElement.h index d926d1e07ca..63e9c95c734 100644 --- a/Libraries/LibWeb/HTML/HTMLInputElement.h +++ b/Libraries/LibWeb/HTML/HTMLInputElement.h @@ -219,6 +219,7 @@ public: bool selection_direction_applies() const; bool pattern_applies() const; bool multiple_applies() const; + bool required_applies() const; bool has_selectable_text() const; bool supports_a_picker() const; diff --git a/Tests/LibWeb/Ref/expected/required-optional-pseudoclass-ref.html b/Tests/LibWeb/Ref/expected/required-optional-pseudoclass-ref.html new file mode 100644 index 00000000000..2f4b65a907a --- /dev/null +++ b/Tests/LibWeb/Ref/expected/required-optional-pseudoclass-ref.html @@ -0,0 +1,56 @@ + + + + +Should be green + + +
+ +
+ +
+
+ +
+
+ +

Other content

+ + +
+ Purple border + +
+
+ No border + +
diff --git a/Tests/LibWeb/Ref/input/required-optional-pseudoclass.html b/Tests/LibWeb/Ref/input/required-optional-pseudoclass.html new file mode 100644 index 00000000000..576ecdcb690 --- /dev/null +++ b/Tests/LibWeb/Ref/input/required-optional-pseudoclass.html @@ -0,0 +1,68 @@ + + + + + +Should be green + + +
+ +
+ +
+
+ +
+
+ +

Other content

+ + +
+ Purple border + +
+
+ No border + +
+ + + diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-select-element/select-required-attribute.tentative.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-select-element/select-required-attribute.tentative.txt index 5f23242531e..78d8b55c618 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-select-element/select-required-attribute.tentative.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-select-element/select-required-attribute.tentative.txt @@ -2,5 +2,5 @@ Harness status: OK Found 1 tests -1 Fail -Fail Test required attribute \ No newline at end of file +1 Pass +Pass Test required attribute \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/selectors/pseudo-classes/required-optional-hidden.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/selectors/pseudo-classes/required-optional-hidden.txt new file mode 100644 index 00000000000..7ae303f9feb --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/selectors/pseudo-classes/required-optional-hidden.txt @@ -0,0 +1,6 @@ +Harness status: OK + +Found 1 tests + +1 Pass +Pass Evaluation of :required and :optional changes for input type change. \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/selectors/pseudo-classes/required-optional.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/selectors/pseudo-classes/required-optional.txt new file mode 100644 index 00000000000..26ea0f0969f --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/selectors/pseudo-classes/required-optional.txt @@ -0,0 +1,11 @@ +Harness status: OK + +Found 6 tests + +6 Pass +Pass ':required' matches required s, + + +