LibWeb: Only update paint-only properties in affected subtrees

Before this change, we always updated paint-only properties for every
single paintable after layout or style changes.

This could get very expensive in large documents, so this patch makes
it something we can do partially based on "repaint" invalidations.

This cuts down time spent in paint-only property update when scrolling
https://imdb.com/ from 19% to 5%.
This commit is contained in:
Andreas Kling 2025-09-23 23:28:11 +02:00 committed by Alexander Kalenik
parent beb70d2112
commit eff9989aeb
6 changed files with 41 additions and 7 deletions

View File

@ -694,8 +694,9 @@ AnimationUpdateContext::~AnimationUpdateContext()
}
}
if (invalidation.repaint) {
if (target->paintable())
target->paintable()->set_needs_paint_only_properties_update(true);
element.document().set_needs_display();
element.document().set_needs_to_resolve_paint_only_properties();
}
if (invalidation.rebuild_stacking_context_tree)
element.document().invalidate_stacking_context_tree();

View File

@ -10,6 +10,7 @@
#include <LibWeb/DOMURL/DOMURL.h>
#include <LibWeb/Fetch/Fetching/Fetching.h>
#include <LibWeb/HTML/SharedResourceRequest.h>
#include <LibWeb/Painting/ViewportPaintable.h>
namespace Web::CSS {
@ -131,7 +132,8 @@ GC::Ptr<HTML::SharedResourceRequest> fetch_an_external_image_for_a_stylesheet(St
if (auto navigable = document->navigable()) {
// Once the image has loaded, we need to re-resolve CSS properties that depend on the image's dimensions.
document->set_needs_to_resolve_paint_only_properties();
if (auto paintable = document->paintable())
paintable->set_needs_paint_only_properties_update(true);
// FIXME: Do less than a full repaint if possible?
document->set_needs_display();

View File

@ -762,14 +762,16 @@ CSS::RequiredInvalidationAfterStyleChange Element::recompute_style(bool& did_cha
if (invalidation.is_none())
return invalidation;
if (invalidation.repaint)
document().set_needs_to_resolve_paint_only_properties();
if (invalidation.repaint && paintable())
paintable()->set_needs_paint_only_properties_update(true);
if (!invalidation.rebuild_layout_tree && layout_node()) {
// If we're keeping the layout tree, we can just apply the new style to the existing layout tree.
layout_node()->apply_style(*m_computed_properties);
if (invalidation.repaint && paintable())
if (invalidation.repaint && paintable()) {
paintable()->set_needs_paint_only_properties_update(true);
paintable()->set_needs_display();
}
// Do the same for pseudo-elements.
for (auto i = 0; i < to_underlying(CSS::PseudoElement::KnownPseudoElementCount); i++) {
@ -784,8 +786,10 @@ CSS::RequiredInvalidationAfterStyleChange Element::recompute_style(bool& did_cha
if (auto node_with_style = pseudo_element->layout_node()) {
node_with_style->apply_style(*pseudo_element_style);
if (invalidation.repaint && node_with_style->first_paintable())
if (invalidation.repaint && node_with_style->first_paintable()) {
node_with_style->first_paintable()->set_needs_paint_only_properties_update(true);
node_with_style->first_paintable()->set_needs_display();
}
}
}
}

View File

@ -303,4 +303,16 @@ Painting::BorderRadiiData normalize_border_radii_data(Layout::Node const& node,
return radii_px;
}
void Paintable::set_needs_paint_only_properties_update(bool needs_update)
{
if (needs_update == m_needs_paint_only_properties_update)
return;
m_needs_paint_only_properties_update = needs_update;
if (needs_update) {
document().set_needs_to_resolve_paint_only_properties();
}
}
}

View File

@ -111,6 +111,8 @@ public:
GC::Ptr<HTML::Navigable> navigable() const;
virtual void set_needs_display(InvalidateDisplayList = InvalidateDisplayList::Yes);
void set_needs_paint_only_properties_update(bool);
[[nodiscard]] bool needs_paint_only_properties_update() const { return m_needs_paint_only_properties_update; }
PaintableBox* containing_block() const;
@ -177,6 +179,7 @@ private:
bool m_absolutely_positioned : 1 { false };
bool m_floating : 1 { false };
bool m_inline : 1 { false };
bool m_needs_paint_only_properties_update : 1 { true };
};
inline DOM::Node* HitTestResult::dom_node()

View File

@ -302,6 +302,15 @@ void ViewportPaintable::refresh_scroll_state()
});
}
static void resolve_paint_only_properties_in_subtree(Paintable& root)
{
root.for_each_in_inclusive_subtree([&](auto& paintable) {
paintable.resolve_paint_properties();
paintable.set_needs_paint_only_properties_update(false);
return TraversalDecision::Continue;
});
}
void ViewportPaintable::resolve_paint_only_properties()
{
// Resolves layout-dependent properties not handled during layout and stores them in the paint tree.
@ -313,7 +322,10 @@ void ViewportPaintable::resolve_paint_only_properties()
// - Transform origins
// - Outlines
for_each_in_inclusive_subtree([&](Paintable& paintable) {
paintable.resolve_paint_properties();
if (paintable.needs_paint_only_properties_update()) {
resolve_paint_only_properties_in_subtree(paintable);
return TraversalDecision::SkipChildrenAndContinue;
}
return TraversalDecision::Continue;
});
}