diff --git a/Libraries/LibWeb/CSS/Invalidation/PseudoClassInvalidator.cpp b/Libraries/LibWeb/CSS/Invalidation/PseudoClassInvalidator.cpp index 0ee2cc7aa2..c605b9a947 100644 --- a/Libraries/LibWeb/CSS/Invalidation/PseudoClassInvalidator.cpp +++ b/Libraries/LibWeb/CSS/Invalidation/PseudoClassInvalidator.cpp @@ -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 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; diff --git a/Libraries/LibWeb/CSS/StyleComputer.cpp b/Libraries/LibWeb/CSS/StyleComputer.cpp index 4aaec23d01..39fdf05479 100644 --- a/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -507,17 +507,16 @@ Vector StyleComputer::collect_matching_rules_ }; auto add_rules_from_cache = [&](RuleCache const& rule_cache, GC::Ptr rule_root) { - rule_cache.for_each_matching_rules(abstract_element, [&](auto const& matching_rules) { + Function 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 subject_pseudo_class_bucket_for_is_or_where_selecto return common_bucket; } +static Optional 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 -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 -static IterationDecision for_each_matching_rule_bucket(DOM::AbstractElement abstract_element, RuleBuckets const& rule_buckets, Function const&)> const& callback) +static IterationDecision for_each_matching_rule_bucket(DOM::AbstractElement abstract_element, RuleBuckets const& rule_buckets, Function const& may_contain_ancestor_hash, Function 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 pseudo_element, bool contains_root_pseudo_class, SubjectPseudoClassBuckets subject_pseudo_class_buckets) +void RuleCache::add_rule(MatchingRule const& matching_rule, Optional 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 const&)> callback) const +void RuleCache::for_each_matching_rules(DOM::AbstractElement abstract_element, Function const& may_contain_ancestor_hash, Function 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 const& may_contain_ancestor_hash, Function 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) diff --git a/Libraries/LibWeb/CSS/StyleScope.cpp b/Libraries/LibWeb/CSS/StyleScope.cpp index 579d30ae3c..78f5eaecbc 100644 --- a/Libraries/LibWeb/CSS/StyleScope.cpp +++ b/Libraries/LibWeb/CSS/StyleScope.cpp @@ -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; }); diff --git a/Libraries/LibWeb/CSS/StyleScope.h b/Libraries/LibWeb/CSS/StyleScope.h index 6fdf8886e5..04d4f86223 100644 --- a/Libraries/LibWeb/CSS/StyleScope.h +++ b/Libraries/LibWeb/CSS/StyleScope.h @@ -59,12 +59,18 @@ enum class SubjectPseudoClassBuckets { Yes, }; +enum class AncestorHashBuckets { + No, + Yes, +}; + struct RuleCache { HashMap> rules_by_id; HashMap> rules_by_class; HashMap> rules_by_tag_name; HashMap, AK::ASCIICaseInsensitiveFlyStringTraits> rules_by_attribute_name; Array, to_underlying(PseudoClass::__Count)> rules_by_subject_pseudo_class; + HashMap> rules_by_ancestor_hash; Vector root_rules; Vector slotted_rules; Vector part_rules; @@ -76,6 +82,7 @@ struct RuleCache { HashMap> rules_by_tag_name; HashMap, AK::ASCIICaseInsensitiveFlyStringTraits> rules_by_attribute_name; Array, to_underlying(PseudoClass::__Count)> rules_by_subject_pseudo_class; + HashMap> rules_by_ancestor_hash; Vector root_rules; Vector other_rules; }; @@ -83,8 +90,9 @@ struct RuleCache { HashMap> rules_by_animation_keyframes; - void add_rule(MatchingRule const&, Optional, bool contains_root_pseudo_class, SubjectPseudoClassBuckets); - void for_each_matching_rules(DOM::AbstractElement, Function const&)> callback) const; + void add_rule(MatchingRule const&, Optional, bool contains_root_pseudo_class, SubjectPseudoClassBuckets, AncestorHashBuckets); + void for_each_matching_rules(DOM::AbstractElement, Function const& may_contain_ancestor_hash, Function const&)> callback) const; + void for_each_matching_pseudo_element_rules(DOM::AbstractElement, Function const& may_contain_ancestor_hash, Function const&)> callback) const; void visit_edges(GC::Cell::Visitor&); };