LibWeb: Make :hover invalidation logic reusable for all pseudo classes

We achieve this by keeping track of all checked pseudo class selectors
in the SelectorEngine code. We also give StyleComputer per-pseudo-class
rule caches.
This commit is contained in:
Andreas Kling 2025-04-17 13:39:30 +02:00 committed by Andreas Kling
parent ed35f9e7c2
commit e1777f6e79
Notes: github-actions[bot] 2025-04-17 17:47:22 +00:00
13 changed files with 134 additions and 59 deletions

View file

@ -422,10 +422,10 @@ RuleCache const* StyleComputer::rule_cache_for_cascade_origin(CascadeOrigin casc
return true;
}
RuleCache const& StyleComputer::get_hover_rules() const
RuleCache const& StyleComputer::get_pseudo_class_rule_cache(PseudoClass pseudo_class) const
{
build_rule_cache_if_needed();
return *m_hover_rule_cache;
return *m_pseudo_class_rule_cache[to_underlying(pseudo_class)];
}
InvalidationSet StyleComputer::invalidation_set_for_properties(Vector<InvalidationSet::Property> const& properties) const
@ -472,7 +472,7 @@ bool StyleComputer::invalidation_property_used_in_has_selector(InvalidationSet::
return false;
}
Vector<MatchingRule const*> StyleComputer::collect_matching_rules(DOM::Element const& element, CascadeOrigin cascade_origin, Optional<CSS::PseudoElement> pseudo_element, bool& did_match_any_hover_rules, FlyString const& qualified_layer_name) const
Vector<MatchingRule const*> StyleComputer::collect_matching_rules(DOM::Element const& element, CascadeOrigin cascade_origin, Optional<CSS::PseudoElement> pseudo_element, PseudoClassBitmap& attempted_pseudo_class_matches, FlyString const& qualified_layer_name) const
{
auto const& root_node = element.root();
auto shadow_root = is<DOM::ShadowRoot>(root_node) ? static_cast<DOM::ShadowRoot const*>(&root_node) : nullptr;
@ -567,8 +567,7 @@ Vector<MatchingRule const*> StyleComputer::collect_matching_rules(DOM::Element c
.collect_per_element_selector_involvement_metadata = true,
};
ScopeGuard guard = [&] {
if (context.did_match_any_hover_rules)
did_match_any_hover_rules = true;
attempted_pseudo_class_matches |= context.attempted_pseudo_class_matches;
};
if (!SelectorEngine::matches(selector, element, shadow_host_to_use, context, pseudo_element))
continue;
@ -1566,24 +1565,24 @@ void StyleComputer::start_needed_transitions(ComputedProperties const& previous_
// https://www.w3.org/TR/css-cascade/#cascading
// https://drafts.csswg.org/css-cascade-5/#layering
GC::Ref<CascadedProperties> StyleComputer::compute_cascaded_values(DOM::Element& element, Optional<CSS::PseudoElement> pseudo_element, bool& did_match_any_pseudo_element_rules, bool& did_match_any_hover_rules, ComputeStyleMode mode) const
GC::Ref<CascadedProperties> StyleComputer::compute_cascaded_values(DOM::Element& element, Optional<CSS::PseudoElement> pseudo_element, bool& did_match_any_pseudo_element_rules, PseudoClassBitmap& attempted_pseudo_class_matches, ComputeStyleMode mode) const
{
auto cascaded_properties = m_document->heap().allocate<CascadedProperties>();
// First, we collect all the CSS rules whose selectors match `element`:
MatchingRuleSet matching_rule_set;
matching_rule_set.user_agent_rules = collect_matching_rules(element, CascadeOrigin::UserAgent, pseudo_element, did_match_any_hover_rules);
matching_rule_set.user_agent_rules = collect_matching_rules(element, CascadeOrigin::UserAgent, pseudo_element, attempted_pseudo_class_matches);
sort_matching_rules(matching_rule_set.user_agent_rules);
matching_rule_set.user_rules = collect_matching_rules(element, CascadeOrigin::User, pseudo_element, did_match_any_hover_rules);
matching_rule_set.user_rules = collect_matching_rules(element, CascadeOrigin::User, pseudo_element, attempted_pseudo_class_matches);
sort_matching_rules(matching_rule_set.user_rules);
// @layer-ed author rules
for (auto const& layer_name : m_qualified_layer_names_in_order) {
auto layer_rules = collect_matching_rules(element, CascadeOrigin::Author, pseudo_element, did_match_any_hover_rules, layer_name);
auto layer_rules = collect_matching_rules(element, CascadeOrigin::Author, pseudo_element, attempted_pseudo_class_matches, layer_name);
sort_matching_rules(layer_rules);
matching_rule_set.author_rules.append({ layer_name, layer_rules });
}
// Un-@layer-ed author rules
auto unlayered_author_rules = collect_matching_rules(element, CascadeOrigin::Author, pseudo_element, did_match_any_hover_rules);
auto unlayered_author_rules = collect_matching_rules(element, CascadeOrigin::Author, pseudo_element, attempted_pseudo_class_matches);
sort_matching_rules(unlayered_author_rules);
matching_rule_set.author_rules.append({ {}, unlayered_author_rules });
@ -2427,8 +2426,8 @@ GC::Ptr<ComputedProperties> StyleComputer::compute_style_impl(DOM::Element& elem
// 1. Perform the cascade. This produces the "specified style"
bool did_match_any_pseudo_element_rules = false;
bool did_match_any_hover_rules = false;
auto cascaded_properties = compute_cascaded_values(element, pseudo_element, did_match_any_pseudo_element_rules, did_match_any_hover_rules, mode);
PseudoClassBitmap attempted_pseudo_class_matches;
auto cascaded_properties = compute_cascaded_values(element, pseudo_element, did_match_any_pseudo_element_rules, attempted_pseudo_class_matches, mode);
element.set_cascaded_properties(pseudo_element, cascaded_properties);
@ -2462,8 +2461,7 @@ GC::Ptr<ComputedProperties> StyleComputer::compute_style_impl(DOM::Element& elem
}
auto computed_properties = compute_properties(element, pseudo_element, cascaded_properties);
if (did_match_any_hover_rules)
computed_properties->set_did_match_any_hover_rules();
computed_properties->set_attempted_pseudo_class_matches(attempted_pseudo_class_matches);
return computed_properties;
}
@ -2834,10 +2832,17 @@ void StyleComputer::make_rule_cache_for_cascade_origin(CascadeOrigin cascade_ori
}
}
if (selector.contains_hover_pseudo_class()) {
// For hover rule cache we intentionally pass pseudo_element as None, because we don't want to bucket hover rules by pseudo element type
m_hover_rule_cache->add_rule(matching_rule, {}, contains_root_pseudo_class);
for (size_t i = 0; i < to_underlying(PseudoClass::__Count); ++i) {
auto pseudo_class = static_cast<PseudoClass>(i);
// If we're not building a rule cache for this pseudo class, just ignore it.
if (!m_pseudo_class_rule_cache[i])
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.
m_pseudo_class_rule_cache[i]->add_rule(matching_rule, {}, contains_root_pseudo_class);
}
}
rule_cache.add_rule(matching_rule, pseudo_element, contains_root_pseudo_class);
}
++rule_index;
@ -2965,7 +2970,14 @@ void StyleComputer::build_rule_cache()
build_qualified_layer_names_cache();
m_hover_rule_cache = make<RuleCache>();
m_pseudo_class_rule_cache[to_underlying(PseudoClass::Hover)] = make<RuleCache>();
m_pseudo_class_rule_cache[to_underlying(PseudoClass::Active)] = make<RuleCache>();
m_pseudo_class_rule_cache[to_underlying(PseudoClass::Focus)] = make<RuleCache>();
m_pseudo_class_rule_cache[to_underlying(PseudoClass::FocusWithin)] = make<RuleCache>();
m_pseudo_class_rule_cache[to_underlying(PseudoClass::FocusVisible)] = make<RuleCache>();
m_pseudo_class_rule_cache[to_underlying(PseudoClass::Target)] = make<RuleCache>();
m_pseudo_class_rule_cache[to_underlying(PseudoClass::TargetWithin)] = make<RuleCache>();
make_rule_cache_for_cascade_origin(CascadeOrigin::Author, *m_selector_insights);
make_rule_cache_for_cascade_origin(CascadeOrigin::User, *m_selector_insights);
make_rule_cache_for_cascade_origin(CascadeOrigin::UserAgent, *m_selector_insights);
@ -2985,7 +2997,7 @@ void StyleComputer::invalidate_rule_cache()
// If we are sure that it's safe, we could keep it as an optimization.
m_user_agent_rule_cache = nullptr;
m_hover_rule_cache = nullptr;
m_pseudo_class_rule_cache = {};
m_style_invalidation_data = nullptr;
}