LibWeb: Postpone invalidating style of elements affected by :has()

...until Document::update_style(). This allows to avoid doing full
document DOM tree traversal on each Node::invalidate_style() call.

Fixes performance regression on wpt.fyi
This commit is contained in:
Aliaksandr Kalenik 2025-01-29 15:39:15 +01:00 committed by Andreas Kling
parent 8877a4f691
commit 7da3b06e3e
Notes: github-actions[bot] 2025-01-29 16:22:40 +00:00
4 changed files with 48 additions and 37 deletions

View file

@ -401,47 +401,13 @@ GC::Ptr<HTML::Navigable> Node::navigable() const
}
}
void Node::invalidate_elements_affected_by_has()
{
Vector<CSS::InvalidationSet::Property, 1> changed_properties;
changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::PseudoClass, .value = CSS::PseudoClass::Has });
auto invalidation_set = document().style_computer().invalidation_set_for_properties(changed_properties);
for_each_shadow_including_inclusive_descendant([&](Node& node) {
if (!node.is_element())
return TraversalDecision::Continue;
auto& element = static_cast<Element&>(node);
bool needs_style_recalculation = false;
// There are two cases in which an element must be invalidated, depending on the position of :has() in a selector:
// 1) In the subject position, i.e., ".a:has(.b)". In that case, invalidation sets are not helpful
// for narrowing down the set of elements that need to be invalidated. Instead, we invalidate
// all elements that were tested against selectors with :has() in the subject position during
// selector matching.
// 2) In the non-subject position, i.e., ".a:has(.b) > .c". Here, invalidation sets can be used to
// determine that only elements with the "c" class have to be invalidated.
if (element.affected_by_has_pseudo_class_in_subject_position()) {
needs_style_recalculation = true;
} else if (invalidation_set.needs_invalidate_whole_subtree()) {
needs_style_recalculation = true;
} else if (element.includes_properties_from_invalidation_set(invalidation_set)) {
needs_style_recalculation = true;
}
if (needs_style_recalculation) {
element.set_needs_style_update(true);
} else {
element.set_needs_inherited_style_update(true);
}
return TraversalDecision::Continue;
});
}
void Node::invalidate_style(StyleInvalidationReason reason)
{
if (is_character_data())
return;
if (document().style_computer().may_have_has_selectors()) {
document().invalidate_elements_affected_by_has();
document().set_needs_invalidate_elements_affected_by_has(true);
}
if (!needs_style_update() && !document().needs_full_style_update()) {
@ -503,7 +469,7 @@ void Node::invalidate_style(StyleInvalidationReason, Vector<CSS::InvalidationSet
properties_used_in_has_selectors |= document().style_computer().invalidation_property_used_in_has_selector(property);
}
if (properties_used_in_has_selectors) {
document().invalidate_elements_affected_by_has();
document().set_needs_invalidate_elements_affected_by_has(true);
}
auto invalidation_set = document().style_computer().invalidation_set_for_properties(properties);