From 5bb0f43b90a56275a0bfb92116826b2349c69dd3 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Mon, 9 Sep 2024 14:59:09 +0200 Subject: [PATCH] LibWeb: Bucket :is/where() selectors by tag name and ID as well Instead of only bucketing these by class name, let's also bucket by tag name and ID. Reduces the number of selectors evaluated on https://tailwindcss.com/ from 2.9% to 1.9%. --- .../Libraries/LibWeb/CSS/StyleComputer.cpp | 72 +++++++++++++------ 1 file changed, 52 insertions(+), 20 deletions(-) diff --git a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp index 970e90ad05d..0711e3d971c 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -2646,7 +2646,12 @@ void StyleComputer::build_rule_cache_if_needed() const const_cast(*this).build_rule_cache(); } -static Optional is_roundabout_selector_bucketable_as_class(CSS::Selector::SimpleSelector const& simple_selector) +struct SimplifiedSelectorForBucketing { + CSS::Selector::SimpleSelector::Type type; + FlyString name; +}; + +static Optional is_roundabout_selector_bucketable_as_something_simpler(CSS::Selector::SimpleSelector const& simple_selector) { if (simple_selector.type != CSS::Selector::SimpleSelector::Type::PseudoClass) return {}; @@ -2665,10 +2670,16 @@ static Optional is_roundabout_selector_bucketable_as_class(CSS::Selec return {}; auto const& inner_simple_selector = compound_selector.simple_selectors.first(); - if (inner_simple_selector.type != CSS::Selector::SimpleSelector::Type::Class) - return {}; + if (inner_simple_selector.type == CSS::Selector::SimpleSelector::Type::Class + || inner_simple_selector.type == CSS::Selector::SimpleSelector::Type::Id) { + return SimplifiedSelectorForBucketing { inner_simple_selector.type, inner_simple_selector.name() }; + } - return inner_simple_selector.name(); + if (inner_simple_selector.type == CSS::Selector::SimpleSelector::Type::TagName) { + return SimplifiedSelectorForBucketing { inner_simple_selector.type, inner_simple_selector.qualified_name().name.lowercase_name }; + } + + return {}; } NonnullOwnPtr StyleComputer::make_rule_cache_for_cascade_origin(CascadeOrigin cascade_origin) @@ -2723,32 +2734,53 @@ NonnullOwnPtr StyleComputer::make_rule_cache_for_casca // NOTE: We traverse the simple selectors in reverse order to make sure that class/ID buckets are preferred over tag buckets // in the common case of div.foo or div#foo selectors. bool added_to_bucket = false; + + auto add_to_id_bucket = [&](FlyString const& name) { + rule_cache->rules_by_id.ensure(name).append(move(matching_rule)); + ++num_id_rules; + added_to_bucket = true; + }; + + auto add_to_class_bucket = [&](FlyString const& name) { + rule_cache->rules_by_class.ensure(name).append(move(matching_rule)); + ++num_class_rules; + added_to_bucket = true; + }; + + auto add_to_tag_name_bucket = [&](FlyString const& name) { + rule_cache->rules_by_tag_name.ensure(name).append(move(matching_rule)); + ++num_tag_name_rules; + added_to_bucket = true; + }; + for (auto const& simple_selector : selector.compound_selectors().last().simple_selectors.in_reverse()) { if (simple_selector.type == CSS::Selector::SimpleSelector::Type::Id) { - rule_cache->rules_by_id.ensure(simple_selector.name()).append(move(matching_rule)); - ++num_id_rules; - added_to_bucket = true; + add_to_id_bucket(simple_selector.name()); break; } if (simple_selector.type == CSS::Selector::SimpleSelector::Type::Class) { - rule_cache->rules_by_class.ensure(simple_selector.name()).append(move(matching_rule)); - ++num_class_rules; - added_to_bucket = true; - break; - } - // NOTE: Selectors like `:is/where(.foo)` and `:is/where(.foo .bar)` are bucketed as class selectors for `foo` and `bar` respectively. - if (auto class_ = is_roundabout_selector_bucketable_as_class(simple_selector); class_.has_value()) { - rule_cache->rules_by_class.ensure(class_.value()).append(move(matching_rule)); - ++num_class_rules; - added_to_bucket = true; + add_to_class_bucket(simple_selector.name()); break; } if (simple_selector.type == CSS::Selector::SimpleSelector::Type::TagName) { - rule_cache->rules_by_tag_name.ensure(simple_selector.qualified_name().name.lowercase_name).append(move(matching_rule)); - ++num_tag_name_rules; - added_to_bucket = true; + add_to_tag_name_bucket(simple_selector.qualified_name().name.lowercase_name); break; } + // NOTE: Selectors like `:is/where(.foo)` and `:is/where(.foo .bar)` are bucketed as class selectors for `foo` and `bar` respectively. + if (auto simplified = is_roundabout_selector_bucketable_as_something_simpler(simple_selector); simplified.has_value()) { + if (simplified->type == CSS::Selector::SimpleSelector::Type::TagName) { + add_to_tag_name_bucket(simplified->name); + break; + } + if (simplified->type == CSS::Selector::SimpleSelector::Type::Class) { + add_to_class_bucket(simplified->name); + break; + } + if (simplified->type == CSS::Selector::SimpleSelector::Type::Id) { + add_to_id_bucket(simplified->name); + break; + } + } } if (!added_to_bucket) { if (matching_rule.contains_pseudo_element) {