LibWeb: Optimize :hover style invalidation

Instead of recalculating styles for all nodes in the common ancestor of
the new and old hovered nodes' subtrees, this change introduces the
following approach:
- While calculating ComputedProperties, a flag is saved if any rule
  applied to an element is affected by the hover state during the
  execution of SelectorEngine::matches().
- When the hovered element changes, styles are marked for recalculation
  only if the flag saved in ComputedProperties indicates that the
  element could be affected by the hover state.
This commit is contained in:
Aliaksandr Kalenik 2025-01-03 20:39:25 +03:00 committed by Andreas Kling
commit e465e922bd
Notes: github-actions[bot] 2025-01-04 19:33:47 +00:00
9 changed files with 124 additions and 77 deletions

View file

@ -422,7 +422,7 @@ bool StyleComputer::should_reject_with_ancestor_filter(Selector const& selector)
return false;
}
Vector<MatchingRule> StyleComputer::collect_matching_rules(DOM::Element const& element, CascadeOrigin cascade_origin, Optional<CSS::Selector::PseudoElement::Type> pseudo_element, FlyString const& qualified_layer_name) const
Vector<MatchingRule> StyleComputer::collect_matching_rules(DOM::Element const& element, CascadeOrigin cascade_origin, Optional<CSS::Selector::PseudoElement::Type> pseudo_element, bool& did_match_any_hover_rules, 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;
@ -435,22 +435,16 @@ Vector<MatchingRule> StyleComputer::collect_matching_rules(DOM::Element const& e
auto const& rule_cache = rule_cache_for_cascade_origin(cascade_origin);
bool is_hovered = SelectorEngine::matches_hover_pseudo_class(element);
Vector<MatchingRule, 512> rules_to_run;
auto add_rules_to_run = [&](Vector<MatchingRule> const& rules) {
rules_to_run.grow_capacity(rules_to_run.size() + rules.size());
if (pseudo_element.has_value()) {
for (auto const& rule : rules) {
if (rule.must_be_hovered && !is_hovered)
continue;
if (rule.contains_pseudo_element && filter_namespace_rule(element, rule) && filter_layer(qualified_layer_name, rule))
rules_to_run.unchecked_append(rule);
}
} else {
for (auto const& rule : rules) {
if (rule.must_be_hovered && !is_hovered)
continue;
if (!rule.contains_pseudo_element && filter_namespace_rule(element, rule) && filter_layer(qualified_layer_name, rule))
rules_to_run.unchecked_append(rule);
}
@ -537,11 +531,16 @@ Vector<MatchingRule> StyleComputer::collect_matching_rules(DOM::Element const& e
auto const& selector = rule_to_run.absolutized_selectors()[rule_to_run.selector_index];
SelectorEngine::MatchContext context { .style_sheet_for_rule = *rule_to_run.sheet };
ScopeGuard guard = [&] {
if (context.did_match_any_hover_rules)
did_match_any_hover_rules = true;
};
if (rule_to_run.can_use_fast_matches) {
if (!SelectorEngine::fast_matches(selector, *rule_to_run.sheet, element, shadow_host_to_use))
if (!SelectorEngine::fast_matches(selector, element, shadow_host_to_use, context))
continue;
} else {
if (!SelectorEngine::matches(selector, *rule_to_run.sheet, element, shadow_host_to_use, pseudo_element))
if (!SelectorEngine::matches(selector, element, shadow_host_to_use, context, pseudo_element))
continue;
}
matching_rules.append(rule_to_run);
@ -1488,24 +1487,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::Selector::PseudoElement::Type> pseudo_element, bool& did_match_any_pseudo_element_rules, ComputeStyleMode mode) const
GC::Ref<CascadedProperties> StyleComputer::compute_cascaded_values(DOM::Element& element, Optional<CSS::Selector::PseudoElement::Type> pseudo_element, bool& did_match_any_pseudo_element_rules, bool& did_match_any_hover_rules, 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);
matching_rule_set.user_agent_rules = collect_matching_rules(element, CascadeOrigin::UserAgent, pseudo_element, did_match_any_hover_rules);
sort_matching_rules(matching_rule_set.user_agent_rules);
matching_rule_set.user_rules = collect_matching_rules(element, CascadeOrigin::User, pseudo_element);
matching_rule_set.user_rules = collect_matching_rules(element, CascadeOrigin::User, pseudo_element, did_match_any_hover_rules);
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, layer_name);
auto layer_rules = collect_matching_rules(element, CascadeOrigin::Author, pseudo_element, did_match_any_hover_rules, 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);
auto unlayered_author_rules = collect_matching_rules(element, CascadeOrigin::Author, pseudo_element, did_match_any_hover_rules);
sort_matching_rules(unlayered_author_rules);
matching_rule_set.author_rules.append({ {}, unlayered_author_rules });
@ -2299,7 +2298,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;
auto cascaded_properties = compute_cascaded_values(element, pseudo_element, did_match_any_pseudo_element_rules, mode);
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);
element.set_cascaded_properties(pseudo_element, cascaded_properties);
@ -2332,7 +2332,10 @@ GC::Ptr<ComputedProperties> StyleComputer::compute_style_impl(DOM::Element& elem
}
}
return compute_properties(element, pseudo_element, cascaded_properties);
auto computed_properties = compute_properties(element, pseudo_element, cascaded_properties);
if (did_match_any_hover_rules)
computed_properties->set_did_match_any_hover_rules();
return computed_properties;
}
GC::Ref<ComputedProperties> StyleComputer::compute_properties(DOM::Element& element, Optional<Selector::PseudoElement::Type> pseudo_element, CascadedProperties& cascaded_properties) const