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 e413b7f1c0..e387005b6b 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 4b61ea69d1..d8f4679b0f 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 1baff8bc2f..6fee18ac11 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 37430aa414..e62c0a298e 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 cfc3816ee1..9836dc78e1 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 c2c62c041d..07631eb4b6 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 fe739d8098..2e4df27997 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;