LibWeb: Skip :has() invalidation in update_style when nothing is pending

Add a document-level boolean flag that tracks whether any :has()
invalidations have been scheduled. This avoids iterating over all
shadow roots just to check is_empty() on each style scope when no
:has() invalidations are pending, which is the common case during
scrolling on complex pages like Reddit.

Results in ~10% reduction of is_empty() calls in profiles when
scrolling on Reddit.
This commit is contained in:
Aliaksandr Kalenik 2026-02-10 21:50:24 +01:00 committed by Alexander Kalenik
parent 38e53b5600
commit eea9837438
4 changed files with 17 additions and 5 deletions

View file

@ -468,6 +468,12 @@ void StyleScope::for_each_active_css_style_sheet(Function<void(CSS::CSSStyleShee
}
}
void StyleScope::schedule_ancestors_style_invalidation_due_to_presence_of_has(DOM::Node& node)
{
m_pending_nodes_for_style_invalidation_due_to_presence_of_has.set(node);
document().set_needs_invalidation_of_elements_affected_by_has();
}
void StyleScope::invalidate_style_of_elements_affected_by_has()
{
if (m_pending_nodes_for_style_invalidation_due_to_presence_of_has.is_empty()) {

View file

@ -107,7 +107,7 @@ public:
void invalidate_style_of_elements_affected_by_has();
void schedule_ancestors_style_invalidation_due_to_presence_of_has(DOM::Node& node) { m_pending_nodes_for_style_invalidation_due_to_presence_of_has.set(node); }
void schedule_ancestors_style_invalidation_due_to_presence_of_has(DOM::Node& node);
void visit_edges(GC::Cell::Visitor&);

View file

@ -1690,10 +1690,13 @@ void Document::update_style()
// style change event. [CSS-Transitions-2]
m_transition_generation++;
style_scope().invalidate_style_of_elements_affected_by_has();
for_each_shadow_root([&](auto& shadow_root) {
shadow_root.style_scope().invalidate_style_of_elements_affected_by_has();
});
if (m_needs_invalidation_of_elements_affected_by_has) {
m_needs_invalidation_of_elements_affected_by_has = false;
style_scope().invalidate_style_of_elements_affected_by_has();
for_each_shadow_root([&](auto& shadow_root) {
shadow_root.style_scope().invalidate_style_of_elements_affected_by_has();
});
}
if (!m_style_invalidator->has_pending_invalidations() && !needs_full_style_update() && !needs_style_update() && !child_needs_style_update())
return;

View file

@ -813,6 +813,8 @@ public:
void set_needs_to_resolve_paint_only_properties() { m_needs_to_resolve_paint_only_properties = true; }
void set_needs_animated_style_update() { m_needs_animated_style_update = true; }
void set_needs_invalidation_of_elements_affected_by_has() { m_needs_invalidation_of_elements_affected_by_has = true; }
void set_needs_accumulated_visual_contexts_update(bool value) { m_needs_accumulated_visual_contexts_update = value; }
bool needs_accumulated_visual_contexts_update() const { return m_needs_accumulated_visual_contexts_update; }
@ -1308,6 +1310,7 @@ private:
bool m_needs_to_resolve_paint_only_properties { true };
bool m_needs_accumulated_visual_contexts_update { false };
bool m_needs_invalidation_of_elements_affected_by_has { false };
mutable GC::Ptr<WebIDL::ObservableArray> m_adopted_style_sheets;