LibWeb: Cache intrinsic sizes across layout runs

This change moves intrinsic sizes cache from
LayoutState, which is local to current layout run,
to layout nodes, so it could be reused between
layout runs. This optimization is possible because
we can guarantee that these measurements will
remain unchanged unless the style of the element
or any of its descendants changes.

For now, invalidation is implemented simply by
resetting cache on whole ancestors chain once we
figured that element needs layout update.
The case when layout is invalidated by DOM's
structural changes is covered by layout tree
invalidation that drops intrinsic sizes cache
along with layout nodes.

I measured improvement on couple websites:
- Mail list on GMail 28ms -> 6ms
- GitHub large code page 47ms -> 36ms
- Discord chat history 15ms -> 8ms
(Time does not include `commit()`)
This commit is contained in:
Aliaksandr Kalenik 2025-03-07 20:37:04 +01:00 committed by Andreas Kling
parent 180a58b3d2
commit 12c6ac78e2
Notes: github-actions[bot] 2025-03-08 10:46:30 +00:00
13 changed files with 108 additions and 102 deletions

View file

@ -1442,11 +1442,9 @@ CSSPixels FormattingContext::calculate_min_content_width(Layout::Box const& box)
if (box.has_natural_width())
return *box.natural_width();
auto& root_state = m_state.m_root;
auto& cache = *root_state.intrinsic_sizes.ensure(&box, [] { return adopt_own(*new LayoutState::IntrinsicSizes); });
if (cache.min_content_width.has_value())
return *cache.min_content_width;
auto& cache = box.cached_intrinsic_sizes().min_content_width;
if (cache.has_value())
return cache.value();
LayoutState throwaway_state(&m_state);
@ -1466,8 +1464,9 @@ CSSPixels FormattingContext::calculate_min_content_width(Layout::Box const& box)
context->run(AvailableSpace(available_width, available_height));
cache.min_content_width = clamp_to_max_dimension_value(context->automatic_content_width());
return *cache.min_content_width;
auto min_content_width = clamp_to_max_dimension_value(context->automatic_content_width());
cache.emplace(min_content_width);
return min_content_width;
}
CSSPixels FormattingContext::calculate_max_content_width(Layout::Box const& box) const
@ -1475,11 +1474,9 @@ CSSPixels FormattingContext::calculate_max_content_width(Layout::Box const& box)
if (box.has_natural_width())
return *box.natural_width();
auto& root_state = m_state.m_root;
auto& cache = *root_state.intrinsic_sizes.ensure(&box, [] { return adopt_own(*new LayoutState::IntrinsicSizes); });
if (cache.max_content_width.has_value())
return *cache.max_content_width;
auto& cache = box.cached_intrinsic_sizes().max_content_width;
if (cache.has_value())
return cache.value();
LayoutState throwaway_state(&m_state);
@ -1499,8 +1496,9 @@ CSSPixels FormattingContext::calculate_max_content_width(Layout::Box const& box)
context->run(AvailableSpace(available_width, available_height));
cache.max_content_width = clamp_to_max_dimension_value(context->automatic_content_width());
return *cache.max_content_width;
auto max_content_width = clamp_to_max_dimension_value(context->automatic_content_width());
cache.emplace(max_content_width);
return max_content_width;
}
// https://www.w3.org/TR/css-sizing-3/#min-content-block-size
@ -1513,14 +1511,9 @@ CSSPixels FormattingContext::calculate_min_content_height(Layout::Box const& box
if (box.has_natural_height())
return *box.natural_height();
auto get_cache_slot = [&]() -> Optional<CSSPixels>* {
auto& root_state = m_state.m_root;
auto& cache = *root_state.intrinsic_sizes.ensure(&box, [] { return adopt_own(*new LayoutState::IntrinsicSizes); });
return &cache.min_content_height.ensure(width);
};
if (auto* cache_slot = get_cache_slot(); cache_slot && cache_slot->has_value())
return cache_slot->value();
auto cache_slot = box.cached_intrinsic_sizes().min_content_height.ensure(width);
if (cache_slot.has_value())
return cache_slot.value();
LayoutState throwaway_state(&m_state);
@ -1537,9 +1530,7 @@ CSSPixels FormattingContext::calculate_min_content_height(Layout::Box const& box
context->run(AvailableSpace(AvailableSize::make_definite(width), AvailableSize::make_min_content()));
auto min_content_height = clamp_to_max_dimension_value(context->automatic_content_height());
if (auto* cache_slot = get_cache_slot()) {
*cache_slot = min_content_height;
}
cache_slot.emplace(min_content_height);
return min_content_height;
}
@ -1551,14 +1542,9 @@ CSSPixels FormattingContext::calculate_max_content_height(Layout::Box const& box
if (box.has_natural_height())
return *box.natural_height();
auto get_cache_slot = [&]() -> Optional<CSSPixels>* {
auto& root_state = m_state.m_root;
auto& cache = *root_state.intrinsic_sizes.ensure(&box, [] { return adopt_own(*new LayoutState::IntrinsicSizes); });
return &cache.max_content_height.ensure(width);
};
if (auto* cache_slot = get_cache_slot(); cache_slot && cache_slot->has_value())
return cache_slot->value();
auto& cache_slot = box.cached_intrinsic_sizes().max_content_height.ensure(width);
if (cache_slot.has_value())
return cache_slot.value();
LayoutState throwaway_state(&m_state);
@ -1575,11 +1561,7 @@ CSSPixels FormattingContext::calculate_max_content_height(Layout::Box const& box
context->run(AvailableSpace(AvailableSize::make_definite(width), AvailableSize::make_max_content()));
auto max_content_height = clamp_to_max_dimension_value(context->automatic_content_height());
if (auto* cache_slot = get_cache_slot()) {
*cache_slot = max_content_height;
}
cache_slot.emplace(max_content_height);
return max_content_height;
}