diff --git a/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Libraries/LibWeb/CSS/SelectorEngine.cpp index 474ed55608e..233b773c23c 100644 --- a/Libraries/LibWeb/CSS/SelectorEngine.cpp +++ b/Libraries/LibWeb/CSS/SelectorEngine.cpp @@ -16,8 +16,6 @@ #include #include #include -#include -#include #include #include #include @@ -25,8 +23,6 @@ #include #include #include -#include -#include #include #include #include @@ -155,15 +151,6 @@ static inline bool matches_has_pseudo_class(CSS::Selector const& selector, DOM:: return matches_relative_selector(selector, 0, anchor, shadow_host, context, anchor); } -// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-link -static inline bool matches_link_pseudo_class(DOM::Element const& element) -{ - // All a elements that have an href attribute, and all area elements that have an href attribute, must match one of :link and :visited. - if (!is(element) && !is(element) && !is(element)) - return false; - return element.has_attribute(HTML::AttributeNames::href); -} - static bool matches_hover_pseudo_class(DOM::Element const& element) { auto* hovered_node = element.document().hovered_node(); @@ -174,30 +161,6 @@ static bool matches_hover_pseudo_class(DOM::Element const& element) return element.is_shadow_including_ancestor_of(*hovered_node); } -// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-checked -static inline bool matches_checked_pseudo_class(DOM::Element const& element) -{ - // The :checked pseudo-class must match any element falling into one of the following categories: - // - input elements whose type attribute is in the Checkbox state and whose checkedness state is true - // - input elements whose type attribute is in the Radio Button state and whose checkedness state is true - if (is(element)) { - auto const& input_element = static_cast(element); - switch (input_element.type_state()) { - case HTML::HTMLInputElement::TypeAttributeState::Checkbox: - case HTML::HTMLInputElement::TypeAttributeState::RadioButton: - return static_cast(element).checked(); - default: - return false; - } - } - - // - option elements whose selectedness is true - if (is(element)) { - return static_cast(element).selected(); - } - return false; -} - // https://html.spec.whatwg.org/multipage/semantics-other.html#selector-indeterminate static inline bool matches_indeterminate_pseudo_class(DOM::Element const& element) { @@ -463,25 +426,9 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla case CSS::PseudoClass::AnyLink: // NOTE: AnyLink should match whether the link is visited or not, so if we ever start matching // :visited, we'll need to handle these differently. - return matches_link_pseudo_class(element); + return element.matches_link_pseudo_class(); case CSS::PseudoClass::LocalLink: { - // The :local-link pseudo-class allows authors to style hyperlinks based on the users current location - // within a site. It represents an element that is the source anchor of a hyperlink whose target’s - // absolute URL matches the element’s own document URL. If the hyperlink’s target includes a fragment - // URL, then the fragment URL of the current URL must also match; if it does not, then the fragment - // URL portion of the current URL is not taken into account in the comparison. - if (!matches_link_pseudo_class(element)) - return false; - auto document_url = element.document().url(); - auto maybe_href = element.attribute(HTML::AttributeNames::href); - if (!maybe_href.has_value()) - return false; - auto target_url = element.document().encoding_parse_url(*maybe_href); - if (!target_url.has_value()) - return false; - if (target_url->fragment().has_value()) - return document_url.equals(*target_url, URL::ExcludeFragment::No); - return document_url.equals(*target_url, URL::ExcludeFragment::Yes); + return element.matches_local_link_pseudo_class(); } case CSS::PseudoClass::Visited: // FIXME: Maybe match this selector sometimes? @@ -544,16 +491,11 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla case CSS::PseudoClass::Lang: return matches_lang_pseudo_class(element, pseudo_class.languages); case CSS::PseudoClass::Disabled: - // https://html.spec.whatwg.org/multipage/semantics-other.html#selector-disabled - // The :disabled pseudo-class must match any element that is actually disabled. - return element.is_actually_disabled(); + return element.matches_disabled_pseudo_class(); case CSS::PseudoClass::Enabled: - // https://html.spec.whatwg.org/multipage/semantics-other.html#selector-enabled - // The :enabled pseudo-class must match any button, input, select, textarea, optgroup, option, fieldset element, or form-associated custom element that is not actually disabled. - return (is(element) || is(element) || is(element) || is(element) || is(element) || is(element) || is(element)) - && !element.is_actually_disabled(); + return element.matches_enabled_pseudo_class(); case CSS::PseudoClass::Checked: - return matches_checked_pseudo_class(element); + return element.matches_checked_pseudo_class(); case CSS::PseudoClass::Indeterminate: return matches_indeterminate_pseudo_class(element); case CSS::PseudoClass::Defined: diff --git a/Libraries/LibWeb/DOM/Element.cpp b/Libraries/LibWeb/DOM/Element.cpp index 3b4d2cde631..da14daa7cd6 100644 --- a/Libraries/LibWeb/DOM/Element.cpp +++ b/Libraries/LibWeb/DOM/Element.cpp @@ -1160,6 +1160,88 @@ bool Element::affected_by_hover() const return false; } +// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-enabled +bool Element::matches_enabled_pseudo_class() const +{ + // The :enabled pseudo-class must match any button, input, select, textarea, optgroup, option, fieldset element, or form-associated custom element that is not actually disabled. + return (is(*this) || is(*this) || is(*this) || is(*this) || is(*this) || is(*this) || is(*this)) + && !is_actually_disabled(); +} + +// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-disabled +bool Element::matches_disabled_pseudo_class() const +{ + // The :disabled pseudo-class must match any element that is actually disabled. + return is_actually_disabled(); +} + +// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-checked +bool Element::matches_checked_pseudo_class() const +{ + // The :checked pseudo-class must match any element falling into one of the following categories: + // - input elements whose type attribute is in the Checkbox state and whose checkedness state is true + // - input elements whose type attribute is in the Radio Button state and whose checkedness state is true + if (is(*this)) { + auto const& input_element = static_cast(*this); + switch (input_element.type_state()) { + case HTML::HTMLInputElement::TypeAttributeState::Checkbox: + case HTML::HTMLInputElement::TypeAttributeState::RadioButton: + return static_cast(*this).checked(); + default: + return false; + } + } + + // - option elements whose selectedness is true + if (is(*this)) { + return static_cast(*this).selected(); + } + return false; +} + +// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-placeholder-shown +bool Element::matches_placeholder_shown_pseudo_class() const +{ + // The :placeholder-shown pseudo-class must match any element falling into one of the following categories: + // - input elements that have a placeholder attribute whose value is currently being presented to the user. + if (is(*this) && has_attribute(HTML::AttributeNames::placeholder)) { + auto const& input_element = static_cast(*this); + return input_element.placeholder_element() && input_element.placeholder_value().has_value(); + } + // - FIXME: textarea elements that have a placeholder attribute whose value is currently being presented to the user. + return false; +} + +// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-link +bool Element::matches_link_pseudo_class() const +{ + // All a elements that have an href attribute, and all area elements that have an href attribute, must match one of :link and :visited. + if (!is(*this) && !is(*this) && !is(*this)) + return false; + return has_attribute(HTML::AttributeNames::href); +} + +bool Element::matches_local_link_pseudo_class() const +{ + // The :local-link pseudo-class allows authors to style hyperlinks based on the users current location + // within a site. It represents an element that is the source anchor of a hyperlink whose target’s + // absolute URL matches the element’s own document URL. If the hyperlink’s target includes a fragment + // URL, then the fragment URL of the current URL must also match; if it does not, then the fragment + // URL portion of the current URL is not taken into account in the comparison. + if (!matches_link_pseudo_class()) + return false; + auto document_url = document().url(); + auto maybe_href = attribute(HTML::AttributeNames::href); + if (!maybe_href.has_value()) + return false; + auto target_url = document().encoding_parse_url(*maybe_href); + if (!target_url.has_value()) + return false; + if (target_url->fragment().has_value()) + return document_url.equals(*target_url, URL::ExcludeFragment::No); + return document_url.equals(*target_url, URL::ExcludeFragment::Yes); +} + bool Element::includes_properties_from_invalidation_set(CSS::InvalidationSet const& set) const { auto includes_property = [&](CSS::InvalidationSet::Property const& property) { @@ -1180,35 +1262,25 @@ bool Element::includes_properties_from_invalidation_set(CSS::InvalidationSet con case CSS::PseudoClass::Has: return true; case CSS::PseudoClass::Enabled: { - return (is(*this) || is(*this) || is(*this) || is(*this) || is(*this) || is(*this) || is(*this)) - && !is_actually_disabled(); + return matches_enabled_pseudo_class(); } case CSS::PseudoClass::Disabled: { - return is_actually_disabled(); + return matches_disabled_pseudo_class(); } case CSS::PseudoClass::Defined: { return is_defined(); } case CSS::PseudoClass::Checked: { - // FIXME: This could be narrowed down to return true only if element is actually checked. - return is(*this) || is(*this); + return matches_checked_pseudo_class(); } case CSS::PseudoClass::PlaceholderShown: { - if (is(*this) && has_attribute(HTML::AttributeNames::placeholder)) { - auto const& input_element = static_cast(*this); - return input_element.placeholder_element() && input_element.placeholder_value().has_value(); - } - // - FIXME: textarea elements that have a placeholder attribute whose value is currently being presented to the user. - return false; + return matches_placeholder_shown_pseudo_class(); } case CSS::PseudoClass::AnyLink: case CSS::PseudoClass::Link: + return matches_link_pseudo_class(); case CSS::PseudoClass::LocalLink: { - if (!is(*this) && !is(*this) && !is(*this)) - return false; - if (!has_attribute(HTML::AttributeNames::href)) - return false; - return true; + return matches_local_link_pseudo_class(); } default: VERIFY_NOT_REACHED(); diff --git a/Libraries/LibWeb/DOM/Element.h b/Libraries/LibWeb/DOM/Element.h index db9f2442d4c..2ab8f78b2ca 100644 --- a/Libraries/LibWeb/DOM/Element.h +++ b/Libraries/LibWeb/DOM/Element.h @@ -420,6 +420,13 @@ public: bool has_style_containment() const; bool has_paint_containment() const; + bool matches_enabled_pseudo_class() const; + bool matches_disabled_pseudo_class() const; + bool matches_checked_pseudo_class() const; + bool matches_placeholder_shown_pseudo_class() const; + bool matches_link_pseudo_class() const; + bool matches_local_link_pseudo_class() const; + bool affected_by_has_pseudo_class_in_subject_position() const { return m_affected_by_has_pseudo_class_in_subject_position; } void set_affected_by_has_pseudo_class_in_subject_position(bool value) { m_affected_by_has_pseudo_class_in_subject_position = value; }