From 0f17ad9ebcb5b7dc8a3fa171bda2d909c0d9fe87 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Sun, 2 Feb 2025 20:35:29 +0100 Subject: [PATCH] LibWeb: Use fast CSS selector matching in default matches() code path Before this change, checking if fast selector matching could be used was only enabled in style recalculation and hover invalidation. With this change it's enabled for all callers of SelectorEngine::matches() by default. This way APIs like `Element.matches()` and `querySelector()` could take advantage of this optimization. --- Libraries/LibWeb/CSS/Selector.cpp | 46 ++++++++++++++++++++++ Libraries/LibWeb/CSS/Selector.h | 3 ++ Libraries/LibWeb/CSS/SelectorEngine.cpp | 51 +++---------------------- Libraries/LibWeb/CSS/SelectorEngine.h | 5 --- Libraries/LibWeb/CSS/StyleComputer.cpp | 10 +---- Libraries/LibWeb/CSS/StyleComputer.h | 1 - Libraries/LibWeb/DOM/Document.cpp | 9 +---- 7 files changed, 59 insertions(+), 66 deletions(-) diff --git a/Libraries/LibWeb/CSS/Selector.cpp b/Libraries/LibWeb/CSS/Selector.cpp index e413b7f1c01..e387005b6b4 100644 --- a/Libraries/LibWeb/CSS/Selector.cpp +++ b/Libraries/LibWeb/CSS/Selector.cpp @@ -33,6 +33,50 @@ static bool component_value_contains_nesting_selector(Parser::ComponentValue con return false; } +static bool can_selector_use_fast_matches(CSS::Selector const& selector) +{ + for (auto const& compound_selector : selector.compound_selectors()) { + if (compound_selector.combinator != CSS::Selector::Combinator::None + && compound_selector.combinator != CSS::Selector::Combinator::Descendant + && compound_selector.combinator != CSS::Selector::Combinator::ImmediateChild) { + return false; + } + + for (auto const& simple_selector : compound_selector.simple_selectors) { + if (simple_selector.type == CSS::Selector::SimpleSelector::Type::PseudoClass) { + auto const pseudo_class = simple_selector.pseudo_class().type; + if (pseudo_class != CSS::PseudoClass::FirstChild + && pseudo_class != CSS::PseudoClass::LastChild + && pseudo_class != CSS::PseudoClass::OnlyChild + && pseudo_class != CSS::PseudoClass::Hover + && pseudo_class != CSS::PseudoClass::Active + && pseudo_class != CSS::PseudoClass::Focus + && pseudo_class != CSS::PseudoClass::FocusVisible + && pseudo_class != CSS::PseudoClass::FocusWithin + && pseudo_class != CSS::PseudoClass::Link + && pseudo_class != CSS::PseudoClass::AnyLink + && pseudo_class != CSS::PseudoClass::Visited + && pseudo_class != CSS::PseudoClass::LocalLink + && pseudo_class != CSS::PseudoClass::Empty + && pseudo_class != CSS::PseudoClass::Root + && pseudo_class != CSS::PseudoClass::Enabled + && pseudo_class != CSS::PseudoClass::Disabled + && pseudo_class != CSS::PseudoClass::Checked) { + return false; + } + } else if (simple_selector.type != CSS::Selector::SimpleSelector::Type::TagName + && simple_selector.type != CSS::Selector::SimpleSelector::Type::Universal + && simple_selector.type != CSS::Selector::SimpleSelector::Type::Class + && simple_selector.type != CSS::Selector::SimpleSelector::Type::Id + && simple_selector.type != CSS::Selector::SimpleSelector::Type::Attribute) { + return false; + } + } + } + + return true; +} + Selector::Selector(Vector&& compound_selectors) : m_compound_selectors(move(compound_selectors)) { @@ -88,6 +132,8 @@ Selector::Selector(Vector&& compound_selectors) } collect_ancestor_hashes(); + + m_can_use_fast_matches = can_selector_use_fast_matches(*this); } void Selector::collect_ancestor_hashes() diff --git a/Libraries/LibWeb/CSS/Selector.h b/Libraries/LibWeb/CSS/Selector.h index 4b61ea69d10..d8f4679b0fa 100644 --- a/Libraries/LibWeb/CSS/Selector.h +++ b/Libraries/LibWeb/CSS/Selector.h @@ -268,12 +268,15 @@ public: auto const& ancestor_hashes() const { return m_ancestor_hashes; } + bool can_use_fast_matches() const { return m_can_use_fast_matches; } + private: explicit Selector(Vector&&); Vector m_compound_selectors; mutable Optional m_specificity; Optional m_pseudo_element; + bool m_can_use_fast_matches { false }; bool m_contains_the_nesting_selector { false }; bool m_contains_hover_pseudo_class { false }; diff --git a/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Libraries/LibWeb/CSS/SelectorEngine.cpp index 1baff8bc2f6..6fee18ac11e 100644 --- a/Libraries/LibWeb/CSS/SelectorEngine.cpp +++ b/Libraries/LibWeb/CSS/SelectorEngine.cpp @@ -157,7 +157,7 @@ static inline bool matches_link_pseudo_class(DOM::Element const& element) return element.has_attribute(HTML::AttributeNames::href); } -bool matches_hover_pseudo_class(DOM::Element const& element) +static bool matches_hover_pseudo_class(DOM::Element const& element) { auto* hovered_node = element.document().hovered_node(); if (!hovered_node) @@ -899,8 +899,13 @@ bool matches(CSS::Selector const& selector, int component_list_index, DOM::Eleme VERIFY_NOT_REACHED(); } +bool fast_matches(CSS::Selector const& selector, DOM::Element const& element_to_match, GC::Ptr shadow_host, MatchContext& context); + bool matches(CSS::Selector const& selector, DOM::Element const& element, GC::Ptr shadow_host, MatchContext& context, Optional pseudo_element, GC::Ptr scope, SelectorKind selector_kind, GC::Ptr anchor) { + if (selector_kind == SelectorKind::Normal && selector.can_use_fast_matches()) { + return fast_matches(selector, element, shadow_host, context); + } VERIFY(!selector.compound_selectors().is_empty()); if (pseudo_element.has_value() && selector.pseudo_element().has_value() && selector.pseudo_element().value().type() != pseudo_element) return false; @@ -1011,48 +1016,4 @@ bool fast_matches(CSS::Selector const& selector, DOM::Element const& element_to_ } } -bool can_use_fast_matches(CSS::Selector const& selector) -{ - for (auto const& compound_selector : selector.compound_selectors()) { - if (compound_selector.combinator != CSS::Selector::Combinator::None - && compound_selector.combinator != CSS::Selector::Combinator::Descendant - && compound_selector.combinator != CSS::Selector::Combinator::ImmediateChild) { - return false; - } - - for (auto const& simple_selector : compound_selector.simple_selectors) { - if (simple_selector.type == CSS::Selector::SimpleSelector::Type::PseudoClass) { - auto const pseudo_class = simple_selector.pseudo_class().type; - if (pseudo_class != CSS::PseudoClass::FirstChild - && pseudo_class != CSS::PseudoClass::LastChild - && pseudo_class != CSS::PseudoClass::OnlyChild - && pseudo_class != CSS::PseudoClass::Hover - && pseudo_class != CSS::PseudoClass::Active - && pseudo_class != CSS::PseudoClass::Focus - && pseudo_class != CSS::PseudoClass::FocusVisible - && pseudo_class != CSS::PseudoClass::FocusWithin - && pseudo_class != CSS::PseudoClass::Link - && pseudo_class != CSS::PseudoClass::AnyLink - && pseudo_class != CSS::PseudoClass::Visited - && pseudo_class != CSS::PseudoClass::LocalLink - && pseudo_class != CSS::PseudoClass::Empty - && pseudo_class != CSS::PseudoClass::Root - && pseudo_class != CSS::PseudoClass::Enabled - && pseudo_class != CSS::PseudoClass::Disabled - && pseudo_class != CSS::PseudoClass::Checked) { - return false; - } - } else if (simple_selector.type != CSS::Selector::SimpleSelector::Type::TagName - && simple_selector.type != CSS::Selector::SimpleSelector::Type::Universal - && simple_selector.type != CSS::Selector::SimpleSelector::Type::Class - && simple_selector.type != CSS::Selector::SimpleSelector::Type::Id - && simple_selector.type != CSS::Selector::SimpleSelector::Type::Attribute) { - return false; - } - } - } - - return true; -} - } diff --git a/Libraries/LibWeb/CSS/SelectorEngine.h b/Libraries/LibWeb/CSS/SelectorEngine.h index 37430aa414f..e62c0a298e9 100644 --- a/Libraries/LibWeb/CSS/SelectorEngine.h +++ b/Libraries/LibWeb/CSS/SelectorEngine.h @@ -25,9 +25,4 @@ struct MatchContext { bool matches(CSS::Selector const&, DOM::Element const&, GC::Ptr shadow_host, MatchContext& context, Optional = {}, GC::Ptr scope = {}, SelectorKind selector_kind = SelectorKind::Normal, GC::Ptr anchor = nullptr); -[[nodiscard]] bool fast_matches(CSS::Selector const&, DOM::Element const&, GC::Ptr shadow_host, MatchContext& context); -[[nodiscard]] bool can_use_fast_matches(CSS::Selector const&); - -[[nodiscard]] bool matches_hover_pseudo_class(DOM::Element const&); - } diff --git a/Libraries/LibWeb/CSS/StyleComputer.cpp b/Libraries/LibWeb/CSS/StyleComputer.cpp index cfc3816ee1d..9836dc78e17 100644 --- a/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -595,13 +595,8 @@ Vector StyleComputer::collect_matching_rules(DOM::Element c if (context.did_match_any_hover_rules) did_match_any_hover_rules = true; }; - if (rule_to_run.can_use_fast_matches) { - if (!SelectorEngine::fast_matches(selector, element, shadow_host_to_use, context)) - continue; - } else { - if (!SelectorEngine::matches(selector, element, shadow_host_to_use, context, pseudo_element)) - continue; - } + if (!SelectorEngine::matches(selector, element, shadow_host_to_use, context, pseudo_element)) + continue; matching_rules.append(&rule_to_run); } @@ -2686,7 +2681,6 @@ void StyleComputer::make_rule_cache_for_cascade_origin(CascadeOrigin cascade_ori selector.specificity(), cascade_origin, false, - SelectorEngine::can_use_fast_matches(selector), false, }; diff --git a/Libraries/LibWeb/CSS/StyleComputer.h b/Libraries/LibWeb/CSS/StyleComputer.h index c2c62c041d5..07631eb4b68 100644 --- a/Libraries/LibWeb/CSS/StyleComputer.h +++ b/Libraries/LibWeb/CSS/StyleComputer.h @@ -86,7 +86,6 @@ struct MatchingRule { u32 specificity { 0 }; CascadeOrigin cascade_origin; bool contains_pseudo_element { false }; - bool can_use_fast_matches { false }; bool must_be_hovered { false }; // Helpers to deal with the fact that `rule` might be a CSSStyleRule or a CSSNestedDeclarations diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index fe739d8098a..2e4df27997c 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -1728,13 +1728,8 @@ void Document::invalidate_style_for_elements_affected_by_hover_change(Node& old_ SelectorEngine::MatchContext context; bool selector_matched = false; - if (rule.can_use_fast_matches) { - if (SelectorEngine::fast_matches(selector, element, {}, context)) - selector_matched = true; - } else { - if (SelectorEngine::matches(selector, element, {}, context, {})) - selector_matched = true; - } + if (SelectorEngine::matches(selector, element, {}, context, {})) + selector_matched = true; if (element.has_pseudo_elements()) { if (SelectorEngine::matches(selector, element, {}, context, CSS::Selector::PseudoElement::Type::Before)) selector_matched = true;