layout: Skip box tree construction when possible (#37957)

When a style change does not chang the structure of the box tree, it is
possible to skip box tree rebuilding for an element. This change adds
support for reusing old box trees when no element has that type of
damage. In order to make this happen, there needs to be a type of
"empty" `LayoutDamage` that just indicates that a fragment tree layout
is necessary.

This is the first step toward incremental fragment tree layout.

Testing: This should not change observable behavior and thus is covered
by
existing WPT tests. Performance numbers to follow.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Martin Robinson 2025-07-09 19:33:09 +02:00 committed by GitHub
parent d5d131c172
commit 436c9072c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 181 additions and 78 deletions

View file

@ -26,7 +26,7 @@ use fonts_traits::StylesheetWebFontLoadFinishedCallback;
use fxhash::FxHashMap;
use ipc_channel::ipc::IpcSender;
use layout_api::{
IFrameSizes, Layout, LayoutConfig, LayoutFactory, NodesFromPointQueryType,
IFrameSizes, Layout, LayoutConfig, LayoutDamage, LayoutFactory, NodesFromPointQueryType,
OffsetParentResponse, QueryMsg, ReflowGoal, ReflowRequest, ReflowRequestRestyle, ReflowResult,
TrustedNodeAddress,
};
@ -849,29 +849,38 @@ impl LayoutThread {
}
let root_node = root_element.as_node();
let mut damage = compute_damage_and_repair_style(&layout_context.style_context, root_node);
if viewport_changed {
damage = RestyleDamage::RELAYOUT;
} else if !damage.contains(RestyleDamage::RELAYOUT) {
let damage_from_environment = viewport_changed
.then_some(RestyleDamage::RELAYOUT)
.unwrap_or_default();
let damage = compute_damage_and_repair_style(
&layout_context.style_context,
root_node,
damage_from_environment,
);
if !damage.contains(RestyleDamage::RELAYOUT) {
layout_context.style_context.stylist.rule_tree().maybe_gc();
return (damage, IFrameSizes::default());
}
let mut box_tree = self.box_tree.borrow_mut();
let box_tree = &mut *box_tree;
let mut build_box_tree = || {
if !BoxTree::update(recalc_style_traversal.context(), dirty_root) {
*box_tree = Some(Arc::new(BoxTree::construct(
recalc_style_traversal.context(),
root_node,
)));
}
};
if let Some(pool) = rayon_pool {
pool.install(build_box_tree)
} else {
build_box_tree()
};
let layout_damage: LayoutDamage = damage.into();
if box_tree.is_none() || layout_damage.has_box_damage() {
let mut build_box_tree = || {
if !BoxTree::update(recalc_style_traversal.context(), dirty_root) {
*box_tree = Some(Arc::new(BoxTree::construct(
recalc_style_traversal.context(),
root_node,
)));
}
};
if let Some(pool) = rayon_pool {
pool.install(build_box_tree)
} else {
build_box_tree()
};
}
let viewport_size = self.stylist.device().au_viewport_size();
let run_layout = || {