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%.
This commit is contained in:
Andreas Kling 2024-09-09 14:59:09 +02:00 committed by Andreas Kling
parent 49d2b11085
commit 5bb0f43b90
Notes: github-actions[bot] 2024-09-09 18:13:12 +00:00

View file

@ -2646,7 +2646,12 @@ void StyleComputer::build_rule_cache_if_needed() const
const_cast<StyleComputer&>(*this).build_rule_cache();
}
static Optional<FlyString> is_roundabout_selector_bucketable_as_class(CSS::Selector::SimpleSelector const& simple_selector)
struct SimplifiedSelectorForBucketing {
CSS::Selector::SimpleSelector::Type type;
FlyString name;
};
static Optional<SimplifiedSelectorForBucketing> 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<FlyString> 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::RuleCache> StyleComputer::make_rule_cache_for_cascade_origin(CascadeOrigin cascade_origin)
@ -2723,32 +2734,53 @@ NonnullOwnPtr<StyleComputer::RuleCache> 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;
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));
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) {
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) {