From adc17c35760970764070858c82ec4080c393812d Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Sun, 9 Feb 2025 17:02:51 +0100 Subject: [PATCH] LibWeb: Deduplicate code for pseudo class selector matching Moves pseudo class matching helpers into Element methods, so they don't have to be duplicated between SelectorEngine and function that checks if element is included in invalidation set. --- Libraries/LibWeb/CSS/SelectorEngine.cpp | 68 ++-------------- Libraries/LibWeb/DOM/Element.cpp | 104 ++++++++++++++++++++---- Libraries/LibWeb/DOM/Element.h | 7 ++ 3 files changed, 100 insertions(+), 79 deletions(-) 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; }