LibWeb: Bucket ancestor-filtered rules by ancestor hash
Add a late rule-cache bucket for selectors that do not expose a current-element bucket but do expose ancestor-filter metadata. These rules previously stayed in the global other bucket and relied on the ancestor filter after broad enumeration. Visit the new buckets only when the current ancestor bloom filter may contain the bucket hash. Keep pseudo-class invalidation caches opted out because those caches answer targeted invalidation questions rather than normal style collection. On the GitHub profile, other-bucket candidates fell from about 5.50k to about 1.07k per 1k rule collection calls.
This commit is contained in:
parent
f39890bdc8
commit
f45bf0e770
4 changed files with 65 additions and 20 deletions
|
|
@ -71,7 +71,8 @@ static bool element_may_match_rule_containing_pseudo_class_in_style_scope(DOM::E
|
|||
{
|
||||
bool may_match = false;
|
||||
auto abstract_element = DOM::AbstractElement { element };
|
||||
style_scope.get_pseudo_class_rule_cache(pseudo_class).for_each_matching_rules(abstract_element, [&](auto const& matching_rules) {
|
||||
Function<bool(u32)> const may_contain_ancestor_hash = [](u32) { return true; };
|
||||
style_scope.get_pseudo_class_rule_cache(pseudo_class).for_each_matching_rules(abstract_element, may_contain_ancestor_hash, [&](auto const& matching_rules) {
|
||||
for (auto const& matching_rule : matching_rules) {
|
||||
if (pseudo_class_subject_may_match_element(element, matching_rule.selector, pseudo_class)) {
|
||||
may_match = true;
|
||||
|
|
|
|||
|
|
@ -507,17 +507,16 @@ Vector<StyleComputer::ScopedMatchingRule> StyleComputer::collect_matching_rules_
|
|||
};
|
||||
|
||||
auto add_rules_from_cache = [&](RuleCache const& rule_cache, GC::Ptr<DOM::ShadowRoot const> rule_root) {
|
||||
rule_cache.for_each_matching_rules(abstract_element, [&](auto const& matching_rules) {
|
||||
Function<bool(u32)> may_contain_ancestor_hash = [&](u32 hash) { return m_ancestor_filter->may_contain(hash); };
|
||||
rule_cache.for_each_matching_rules(abstract_element, may_contain_ancestor_hash, [&](auto const& matching_rules) {
|
||||
add_rules_to_run(matching_rules, rule_root);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
if (!abstract_element.pseudo_element().has_value() && matching_pseudo_element_styles) {
|
||||
for (auto const& pseudo_element_rules : rule_cache.rules_by_pseudo_element) {
|
||||
(void)for_each_matching_rule_bucket(abstract_element, pseudo_element_rules, [&](auto const& matching_rules) {
|
||||
add_rules_to_run(matching_rules, rule_root);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
rule_cache.for_each_matching_pseudo_element_rules(abstract_element, may_contain_ancestor_hash, [&](auto const& matching_rules) {
|
||||
add_rules_to_run(matching_rules, rule_root);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -3222,6 +3221,19 @@ static Optional<PseudoClass> subject_pseudo_class_bucket_for_is_or_where_selecto
|
|||
return common_bucket;
|
||||
}
|
||||
|
||||
static Optional<u32> ancestor_hash_bucket_for_selector(Selector const& selector)
|
||||
{
|
||||
if (!selector.can_use_ancestor_filter())
|
||||
return {};
|
||||
|
||||
for (auto hash : selector.ancestor_hashes()) {
|
||||
if (hash == 0)
|
||||
break;
|
||||
return hash;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static bool matches_hover_pseudo_class_for_rule_bucket(DOM::Element const& element)
|
||||
{
|
||||
auto* hovered_node = element.document().hovered_node();
|
||||
|
|
@ -4001,7 +4013,7 @@ void StyleComputer::pop_ancestor(DOM::Element const& element)
|
|||
}
|
||||
|
||||
template<typename RuleBuckets>
|
||||
static void add_rule_to_rule_buckets(RuleBuckets& rule_buckets, MatchingRule const& matching_rule, Selector::CompoundSelector const& bucket_compound_selector, bool contains_root_pseudo_class, SubjectPseudoClassBuckets subject_pseudo_class_buckets)
|
||||
static void add_rule_to_rule_buckets(RuleBuckets& rule_buckets, MatchingRule const& matching_rule, Selector::CompoundSelector const& bucket_compound_selector, bool contains_root_pseudo_class, SubjectPseudoClassBuckets subject_pseudo_class_buckets, AncestorHashBuckets ancestor_hash_buckets)
|
||||
{
|
||||
// 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.
|
||||
|
|
@ -4091,12 +4103,18 @@ static void add_rule_to_rule_buckets(RuleBuckets& rule_buckets, MatchingRule con
|
|||
add_to_subject_pseudo_class_bucket(subject_pseudo_class_bucket.value());
|
||||
return;
|
||||
}
|
||||
if (ancestor_hash_buckets == AncestorHashBuckets::Yes) {
|
||||
if (auto ancestor_hash = ancestor_hash_bucket_for_selector(matching_rule.selector); ancestor_hash.has_value()) {
|
||||
rule_buckets.rules_by_ancestor_hash.ensure(ancestor_hash.value()).append(matching_rule);
|
||||
return;
|
||||
}
|
||||
}
|
||||
rule_buckets.other_rules.append(matching_rule);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename RuleBuckets>
|
||||
static IterationDecision for_each_matching_rule_bucket(DOM::AbstractElement abstract_element, RuleBuckets const& rule_buckets, Function<IterationDecision(Vector<MatchingRule> const&)> const& callback)
|
||||
static IterationDecision for_each_matching_rule_bucket(DOM::AbstractElement abstract_element, RuleBuckets const& rule_buckets, Function<bool(u32)> const& may_contain_ancestor_hash, Function<IterationDecision(Vector<MatchingRule> const&)> const& callback)
|
||||
{
|
||||
for (auto const& class_name : abstract_element.element().class_names()) {
|
||||
if (auto it = rule_buckets.rules_by_class.find(class_name); it != rule_buckets.rules_by_class.end()) {
|
||||
|
|
@ -4156,10 +4174,17 @@ static IterationDecision for_each_matching_rule_bucket(DOM::AbstractElement abst
|
|||
return IterationDecision::Break;
|
||||
}
|
||||
|
||||
for (auto const& [hash, rules] : rule_buckets.rules_by_ancestor_hash) {
|
||||
if (!may_contain_ancestor_hash(hash))
|
||||
continue;
|
||||
if (callback(rules) == IterationDecision::Break)
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
|
||||
return callback(rule_buckets.other_rules);
|
||||
}
|
||||
|
||||
void RuleCache::add_rule(MatchingRule const& matching_rule, Optional<PseudoElement> pseudo_element, bool contains_root_pseudo_class, SubjectPseudoClassBuckets subject_pseudo_class_buckets)
|
||||
void RuleCache::add_rule(MatchingRule const& matching_rule, Optional<PseudoElement> pseudo_element, bool contains_root_pseudo_class, SubjectPseudoClassBuckets subject_pseudo_class_buckets, AncestorHashBuckets ancestor_hash_buckets)
|
||||
{
|
||||
if (matching_rule.slotted) {
|
||||
slotted_rules.append(matching_rule);
|
||||
|
|
@ -4181,26 +4206,35 @@ void RuleCache::add_rule(MatchingRule const& matching_rule, Optional<PseudoEleme
|
|||
}
|
||||
return matching_rule.selector.compound_selectors().last();
|
||||
}();
|
||||
add_rule_to_rule_buckets(rules_by_pseudo_element[to_underlying(pseudo_element.value())], matching_rule, bucket_compound_selector, contains_root_pseudo_class, subject_pseudo_class_buckets);
|
||||
add_rule_to_rule_buckets(rules_by_pseudo_element[to_underlying(pseudo_element.value())], matching_rule, bucket_compound_selector, contains_root_pseudo_class, subject_pseudo_class_buckets, ancestor_hash_buckets);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
add_rule_to_rule_buckets(*this, matching_rule, matching_rule.selector.compound_selectors().last(), contains_root_pseudo_class, subject_pseudo_class_buckets);
|
||||
add_rule_to_rule_buckets(*this, matching_rule, matching_rule.selector.compound_selectors().last(), contains_root_pseudo_class, subject_pseudo_class_buckets, ancestor_hash_buckets);
|
||||
}
|
||||
|
||||
void RuleCache::for_each_matching_rules(DOM::AbstractElement abstract_element, Function<IterationDecision(Vector<MatchingRule> const&)> callback) const
|
||||
void RuleCache::for_each_matching_rules(DOM::AbstractElement abstract_element, Function<bool(u32)> const& may_contain_ancestor_hash, Function<IterationDecision(Vector<MatchingRule> const&)> callback) const
|
||||
{
|
||||
if (abstract_element.pseudo_element().has_value()) {
|
||||
if (Selector::PseudoElementSelector::is_known_pseudo_element_type(abstract_element.pseudo_element().value())) {
|
||||
(void)for_each_matching_rule_bucket(abstract_element, rules_by_pseudo_element.at(to_underlying(abstract_element.pseudo_element().value())), callback);
|
||||
(void)for_each_matching_rule_bucket(abstract_element, rules_by_pseudo_element.at(to_underlying(abstract_element.pseudo_element().value())), may_contain_ancestor_hash, callback);
|
||||
} else {
|
||||
// NOTE: We don't cache rules for unknown pseudo-elements. They can't match anything anyway.
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
(void)for_each_matching_rule_bucket(abstract_element, *this, callback);
|
||||
(void)for_each_matching_rule_bucket(abstract_element, *this, may_contain_ancestor_hash, callback);
|
||||
}
|
||||
|
||||
void RuleCache::for_each_matching_pseudo_element_rules(DOM::AbstractElement abstract_element, Function<bool(u32)> const& may_contain_ancestor_hash, Function<IterationDecision(Vector<MatchingRule> const&)> callback) const
|
||||
{
|
||||
VERIFY(!abstract_element.pseudo_element().has_value());
|
||||
for (auto const& pseudo_element_rules : rules_by_pseudo_element) {
|
||||
if (for_each_matching_rule_bucket(abstract_element, pseudo_element_rules, may_contain_ancestor_hash, callback) == IterationDecision::Break)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void StyleComputer::ScopedMatchingRule::visit_edges(GC::Cell::Visitor& visitor)
|
||||
|
|
|
|||
|
|
@ -105,12 +105,14 @@ void RuleCache::visit_edges(GC::Cell::Visitor& visitor)
|
|||
visit_map(rules_by_tag_name);
|
||||
visit_map(rules_by_attribute_name);
|
||||
visit_pseudo_class_buckets(rules_by_subject_pseudo_class);
|
||||
visit_map(rules_by_ancestor_hash);
|
||||
for (auto& rules : rules_by_pseudo_element) {
|
||||
visit_map(rules.rules_by_id);
|
||||
visit_map(rules.rules_by_class);
|
||||
visit_map(rules.rules_by_tag_name);
|
||||
visit_map(rules.rules_by_attribute_name);
|
||||
visit_pseudo_class_buckets(rules.rules_by_subject_pseudo_class);
|
||||
visit_map(rules.rules_by_ancestor_hash);
|
||||
visit_vector(rules.root_rules);
|
||||
visit_vector(rules.other_rules);
|
||||
}
|
||||
|
|
@ -551,11 +553,11 @@ void StyleScope::make_rule_cache_for_cascade_origin(CascadeOrigin cascade_origin
|
|||
continue;
|
||||
if (selector.contains_pseudo_class(pseudo_class)) {
|
||||
// For pseudo class rule caches we intentionally pass no pseudo-element, because we don't want to bucket pseudo class rules by pseudo-element type.
|
||||
rule_cache.pseudo_class_rule_cache[i]->add_rule(matching_rule, {}, contains_root_pseudo_class, SubjectPseudoClassBuckets::No);
|
||||
rule_cache.pseudo_class_rule_cache[i]->add_rule(matching_rule, {}, contains_root_pseudo_class, SubjectPseudoClassBuckets::No, AncestorHashBuckets::No);
|
||||
}
|
||||
}
|
||||
|
||||
matching_rule_cache.add_rule(matching_rule, selector.target_pseudo_element(), contains_root_pseudo_class, SubjectPseudoClassBuckets::Yes);
|
||||
matching_rule_cache.add_rule(matching_rule, selector.target_pseudo_element(), contains_root_pseudo_class, SubjectPseudoClassBuckets::Yes, AncestorHashBuckets::Yes);
|
||||
}
|
||||
++rule_index;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -59,12 +59,18 @@ enum class SubjectPseudoClassBuckets {
|
|||
Yes,
|
||||
};
|
||||
|
||||
enum class AncestorHashBuckets {
|
||||
No,
|
||||
Yes,
|
||||
};
|
||||
|
||||
struct RuleCache {
|
||||
HashMap<FlyString, Vector<MatchingRule>> rules_by_id;
|
||||
HashMap<FlyString, Vector<MatchingRule>> rules_by_class;
|
||||
HashMap<FlyString, Vector<MatchingRule>> rules_by_tag_name;
|
||||
HashMap<FlyString, Vector<MatchingRule>, AK::ASCIICaseInsensitiveFlyStringTraits> rules_by_attribute_name;
|
||||
Array<Vector<MatchingRule>, to_underlying(PseudoClass::__Count)> rules_by_subject_pseudo_class;
|
||||
HashMap<u32, Vector<MatchingRule>> rules_by_ancestor_hash;
|
||||
Vector<MatchingRule> root_rules;
|
||||
Vector<MatchingRule> slotted_rules;
|
||||
Vector<MatchingRule> part_rules;
|
||||
|
|
@ -76,6 +82,7 @@ struct RuleCache {
|
|||
HashMap<FlyString, Vector<MatchingRule>> rules_by_tag_name;
|
||||
HashMap<FlyString, Vector<MatchingRule>, AK::ASCIICaseInsensitiveFlyStringTraits> rules_by_attribute_name;
|
||||
Array<Vector<MatchingRule>, to_underlying(PseudoClass::__Count)> rules_by_subject_pseudo_class;
|
||||
HashMap<u32, Vector<MatchingRule>> rules_by_ancestor_hash;
|
||||
Vector<MatchingRule> root_rules;
|
||||
Vector<MatchingRule> other_rules;
|
||||
};
|
||||
|
|
@ -83,8 +90,9 @@ struct RuleCache {
|
|||
|
||||
HashMap<FlyString, NonnullRefPtr<Animations::KeyframeEffect::KeyFrameSet>> rules_by_animation_keyframes;
|
||||
|
||||
void add_rule(MatchingRule const&, Optional<PseudoElement>, bool contains_root_pseudo_class, SubjectPseudoClassBuckets);
|
||||
void for_each_matching_rules(DOM::AbstractElement, Function<IterationDecision(Vector<MatchingRule> const&)> callback) const;
|
||||
void add_rule(MatchingRule const&, Optional<PseudoElement>, bool contains_root_pseudo_class, SubjectPseudoClassBuckets, AncestorHashBuckets);
|
||||
void for_each_matching_rules(DOM::AbstractElement, Function<bool(u32)> const& may_contain_ancestor_hash, Function<IterationDecision(Vector<MatchingRule> const&)> callback) const;
|
||||
void for_each_matching_pseudo_element_rules(DOM::AbstractElement, Function<bool(u32)> const& may_contain_ancestor_hash, Function<IterationDecision(Vector<MatchingRule> const&)> callback) const;
|
||||
|
||||
void visit_edges(GC::Cell::Visitor&);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue