LibWeb: Defer entire-subtree style invalidations

Instead of traversing the entire DOM subtrees and marking nodes for
style update, this patch adds a new mechanism where we can mark a
subtree root as "entire subtree needs style update".

A new pass in Document::update_style() then takes care of coalescing
all these invalidations in a single traversal of the DOM.

This shaves *minutes* of loading time off of https://wpt.fyi/ subpages.
This commit is contained in:
Andreas Kling 2025-01-26 19:00:51 +01:00 committed by Andreas Kling
commit f35152cf61
Notes: github-actions[bot] 2025-01-26 21:59:34 +00:00
3 changed files with 45 additions and 18 deletions

View file

@ -429,6 +429,12 @@ void Node::invalidate_style(StyleInvalidationReason reason)
return;
}
// If any ancestor is already marked for an entire subtree update, there's no need to do anything here.
for (auto* ancestor = this; ancestor; ancestor = ancestor->parent_or_shadow_host()) {
if (ancestor->entire_subtree_needs_style_update())
return;
}
// When invalidating style for a node, we actually invalidate:
// - the node itself
// - all of its descendants
@ -436,37 +442,23 @@ void Node::invalidate_style(StyleInvalidationReason reason)
// - all of its subsequent siblings and their descendants
// FIXME: This is a lot of invalidation and we should implement more sophisticated invalidation to do less work!
auto invalidate_entire_subtree = [&](Node& subtree_root) {
subtree_root.for_each_in_inclusive_subtree([&](Node& node) {
node.m_needs_style_update = true;
if (node.has_children())
node.m_child_needs_style_update = true;
if (auto shadow_root = node.is_element() ? static_cast<DOM::Element&>(node).shadow_root() : nullptr) {
node.m_child_needs_style_update = true;
shadow_root->m_needs_style_update = true;
if (shadow_root->has_children())
shadow_root->m_child_needs_style_update = true;
}
return TraversalDecision::Continue;
});
};
invalidate_entire_subtree(*this);
set_entire_subtree_needs_style_update(true);
if (reason == StyleInvalidationReason::NodeInsertBefore || reason == StyleInvalidationReason::NodeRemove) {
for (auto* sibling = previous_sibling(); sibling; sibling = sibling->previous_sibling()) {
if (sibling->is_element())
invalidate_entire_subtree(*sibling);
sibling->set_entire_subtree_needs_style_update(true);
}
}
for (auto* sibling = next_sibling(); sibling; sibling = sibling->next_sibling()) {
if (sibling->is_element())
invalidate_entire_subtree(*sibling);
sibling->set_entire_subtree_needs_style_update(true);
}
for (auto* ancestor = parent_or_shadow_host(); ancestor; ancestor = ancestor->parent_or_shadow_host())
ancestor->m_child_needs_style_update = true;
document().schedule_style_update();
}