layout: Do not require restyle information when not restyling (#37722)

This reduces the amount of work necessary when running layout, by making
restyle information optional in the `ReflowRequest`. When restyling
isn't
necessary, the option is `None`.

Testing: This shouldn't change any observable behavior and thus is
covered
by existing WPT tests.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Martin Robinson 2025-06-27 10:08:29 +02:00 committed by GitHub
parent cbb0407ae6
commit 8e2ef5c248
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 67 additions and 47 deletions

View file

@ -27,7 +27,7 @@ use fxhash::FxHashMap;
use ipc_channel::ipc::IpcSender; use ipc_channel::ipc::IpcSender;
use layout_api::{ use layout_api::{
Layout, LayoutConfig, LayoutFactory, NodesFromPointQueryType, OffsetParentResponse, QueryMsg, Layout, LayoutConfig, LayoutFactory, NodesFromPointQueryType, OffsetParentResponse, QueryMsg,
ReflowGoal, ReflowRequest, ReflowResult, TrustedNodeAddress, ReflowGoal, ReflowRequest, ReflowRequestRestyle, ReflowResult, TrustedNodeAddress,
}; };
use log::{debug, error, warn}; use log::{debug, error, warn};
use malloc_size_of::{MallocConditionalSizeOf, MallocSizeOf, MallocSizeOfOps}; use malloc_size_of::{MallocConditionalSizeOf, MallocSizeOf, MallocSizeOfOps};
@ -586,7 +586,7 @@ impl LayoutThread {
/// at all. /// at all.
fn can_skip_reflow_request_entirely(&self, reflow_request: &ReflowRequest) -> bool { fn can_skip_reflow_request_entirely(&self, reflow_request: &ReflowRequest) -> bool {
// If a restyle is necessary, restyle and reflow is a necessity. // If a restyle is necessary, restyle and reflow is a necessity.
if reflow_request.restyle_reason.needs_restyle() { if reflow_request.restyle.is_some() {
return false; return false;
} }
// We always need to at least build a fragment tree. // We always need to at least build a fragment tree.
@ -649,9 +649,11 @@ impl LayoutThread {
}; };
let mut snapshot_map = SnapshotMap::new(); let mut snapshot_map = SnapshotMap::new();
let _snapshot_setter = SnapshotSetter::new(&mut reflow_request, &mut snapshot_map); let mut _snapshot_setter = None;
let mut viewport_changed = false; let mut viewport_changed = false;
if reflow_request.restyle_reason.needs_restyle() { if let Some(restyle) = reflow_request.restyle.as_mut() {
_snapshot_setter = Some(SnapshotSetter::new(restyle, &mut snapshot_map));
viewport_changed = self.viewport_did_change(reflow_request.viewport_details); viewport_changed = self.viewport_did_change(reflow_request.viewport_details);
if self.update_device_if_necessary(&reflow_request, viewport_changed, &guards) { if self.update_device_if_necessary(&reflow_request, viewport_changed, &guards) {
if let Some(mut data) = root_element.mutate_data() { if let Some(mut data) = root_element.mutate_data() {
@ -683,7 +685,7 @@ impl LayoutThread {
&snapshot_map, &snapshot_map,
reflow_request.animation_timeline_value, reflow_request.animation_timeline_value,
&reflow_request.animations, &reflow_request.animations,
match reflow_request.stylesheets_changed { match reflow_request.stylesheets_changed() {
true => TraversalFlags::ForCSSRuleChanges, true => TraversalFlags::ForCSSRuleChanges,
false => TraversalFlags::empty(), false => TraversalFlags::empty(),
}, },
@ -700,9 +702,9 @@ impl LayoutThread {
}; };
let mut damage = RestyleDamage::empty(); let mut damage = RestyleDamage::empty();
if reflow_request.restyle_reason.needs_restyle() { if let Some(restyle) = reflow_request.restyle.as_ref() {
damage = self.restyle_and_build_trees( damage = self.restyle_and_build_trees(
&reflow_request, restyle,
root_element, root_element,
rayon_pool, rayon_pool,
&mut layout_context, &mut layout_context,
@ -794,7 +796,7 @@ impl LayoutThread {
self.have_added_user_agent_stylesheets = true; self.have_added_user_agent_stylesheets = true;
} }
if reflow_request.stylesheets_changed { if reflow_request.stylesheets_changed() {
self.stylist self.stylist
.force_stylesheet_origins_dirty(Origin::Author.into()); .force_stylesheet_origins_dirty(Origin::Author.into());
} }
@ -809,14 +811,14 @@ impl LayoutThread {
#[servo_tracing::instrument(skip_all)] #[servo_tracing::instrument(skip_all)]
fn restyle_and_build_trees( fn restyle_and_build_trees(
&self, &self,
reflow_request: &ReflowRequest, restyle: &ReflowRequestRestyle,
root_element: ServoLayoutElement<'_>, root_element: ServoLayoutElement<'_>,
rayon_pool: Option<&ThreadPool>, rayon_pool: Option<&ThreadPool>,
layout_context: &mut LayoutContext<'_>, layout_context: &mut LayoutContext<'_>,
viewport_changed: bool, viewport_changed: bool,
) -> RestyleDamage { ) -> RestyleDamage {
let dirty_root = unsafe { let dirty_root = unsafe {
ServoLayoutNode::new(&reflow_request.dirty_root.unwrap()) ServoLayoutNode::new(&restyle.dirty_root.unwrap())
.as_element() .as_element()
.unwrap() .unwrap()
}; };
@ -1330,12 +1332,9 @@ struct SnapshotSetter<'dom> {
} }
impl SnapshotSetter<'_> { impl SnapshotSetter<'_> {
fn new(reflow_request: &mut ReflowRequest, snapshot_map: &mut SnapshotMap) -> Self { fn new(restyle: &mut ReflowRequestRestyle, snapshot_map: &mut SnapshotMap) -> Self {
debug!( debug!("Draining restyles: {}", restyle.pending_restyles.len());
"Draining restyles: {}", let restyles = std::mem::take(&mut restyle.pending_restyles);
reflow_request.pending_restyles.len()
);
let restyles = std::mem::take(&mut reflow_request.pending_restyles);
let elements_with_snapshot: Vec<_> = restyles let elements_with_snapshot: Vec<_> = restyles
.iter() .iter()

View file

@ -52,8 +52,8 @@ use js::rust::{
MutableHandleValue, MutableHandleValue,
}; };
use layout_api::{ use layout_api::{
FragmentType, Layout, PendingImageState, QueryMsg, ReflowGoal, ReflowRequest, RestyleReason, FragmentType, Layout, PendingImageState, QueryMsg, ReflowGoal, ReflowRequest,
TrustedNodeAddress, combine_id_with_fragment_type, ReflowRequestRestyle, RestyleReason, TrustedNodeAddress, combine_id_with_fragment_type,
}; };
use malloc_size_of::MallocSizeOf; use malloc_size_of::MallocSizeOf;
use media::WindowGLContext; use media::WindowGLContext;
@ -2143,17 +2143,6 @@ impl Window {
return false; return false;
} }
let restyle_reason = document.restyle_reason();
document.clear_restyle_reasons();
if restyle_reason.needs_restyle() {
debug!("Invalidating layout cache due to reflow condition {restyle_reason:?}",);
// Invalidate any existing cached layout values.
self.layout_marker.borrow().set(false);
// Create a new layout caching token.
*self.layout_marker.borrow_mut() = Rc::new(Cell::new(true));
}
debug!("script: performing reflow for goal {reflow_goal:?}"); debug!("script: performing reflow for goal {reflow_goal:?}");
let marker = if self.need_emit_timeline_marker(TimelineMarkerType::Reflow) { let marker = if self.need_emit_timeline_marker(TimelineMarkerType::Reflow) {
Some(TimelineMarker::start("Reflow".to_owned())) Some(TimelineMarker::start("Reflow".to_owned()))
@ -2166,6 +2155,15 @@ impl Window {
debug_reflow_events(pipeline_id, &reflow_goal); debug_reflow_events(pipeline_id, &reflow_goal);
} }
let restyle_reason = document.restyle_reason();
document.clear_restyle_reasons();
let restyle = if restyle_reason.needs_restyle() {
debug!("Invalidating layout cache due to reflow condition {restyle_reason:?}",);
// Invalidate any existing cached layout values.
self.layout_marker.borrow().set(false);
// Create a new layout caching token.
*self.layout_marker.borrow_mut() = Rc::new(Cell::new(true));
let stylesheets_changed = document.flush_stylesheets_for_reflow(); let stylesheets_changed = document.flush_stylesheets_for_reflow();
let pending_restyles = document.drain_pending_restyles(); let pending_restyles = document.drain_pending_restyles();
let dirty_root = document let dirty_root = document
@ -2174,19 +2172,26 @@ impl Window {
.or_else(|| document.GetDocumentElement()) .or_else(|| document.GetDocumentElement())
.map(|root| root.upcast::<Node>().to_trusted_node_address()); .map(|root| root.upcast::<Node>().to_trusted_node_address());
Some(ReflowRequestRestyle {
reason: restyle_reason,
dirty_root,
stylesheets_changed,
pending_restyles,
})
} else {
None
};
let highlighted_dom_node = document.highlighted_dom_node().map(|node| node.to_opaque()); let highlighted_dom_node = document.highlighted_dom_node().map(|node| node.to_opaque());
// Send new document and relevant styles to layout. // Send new document and relevant styles to layout.
let reflow = ReflowRequest { let reflow = ReflowRequest {
restyle_reason,
document: document.upcast::<Node>().to_trusted_node_address(), document: document.upcast::<Node>().to_trusted_node_address(),
dirty_root, restyle,
stylesheets_changed,
viewport_details: self.viewport_details.get(), viewport_details: self.viewport_details.get(),
origin: self.origin().immutable().clone(), origin: self.origin().immutable().clone(),
reflow_goal, reflow_goal,
dom_count: document.dom_count(), dom_count: document.dom_count(),
pending_restyles,
animation_timeline_value: document.current_animation_timeline_value(), animation_timeline_value: document.current_animation_timeline_value(),
animations: document.animations().sets.clone(), animations: document.animations().sets.clone(),
node_to_animating_image_map: document.image_animation_manager().node_to_image_map(), node_to_animating_image_map: document.image_animation_manager().node_to_image_map(),

View file

@ -395,17 +395,27 @@ pub struct ReflowResult {
pub iframe_sizes: IFrameSizes, pub iframe_sizes: IFrameSizes,
} }
/// Information needed for a script-initiated reflow. /// Information needed for a script-initiated reflow that requires a restyle
/// and reconstruction of box and fragment trees.
#[derive(Debug)] #[derive(Debug)]
pub struct ReflowRequest { pub struct ReflowRequestRestyle {
/// Whether or not (and for what reasons) restyle needs to happen. /// Whether or not (and for what reasons) restyle needs to happen.
pub restyle_reason: RestyleReason, pub reason: RestyleReason,
/// The document node.
pub document: TrustedNodeAddress,
/// The dirty root from which to restyle. /// The dirty root from which to restyle.
pub dirty_root: Option<TrustedNodeAddress>, pub dirty_root: Option<TrustedNodeAddress>,
/// Whether the document's stylesheets have changed since the last script reflow. /// Whether the document's stylesheets have changed since the last script reflow.
pub stylesheets_changed: bool, pub stylesheets_changed: bool,
/// Restyle snapshot map.
pub pending_restyles: Vec<(TrustedNodeAddress, PendingRestyle)>,
}
/// Information needed for a script-initiated reflow.
#[derive(Debug)]
pub struct ReflowRequest {
/// The document node.
pub document: TrustedNodeAddress,
/// If a restyle is necessary, all of the informatio needed to do that restyle.
pub restyle: Option<ReflowRequestRestyle>,
/// The current [`ViewportDetails`] to use for this reflow. /// The current [`ViewportDetails`] to use for this reflow.
pub viewport_details: ViewportDetails, pub viewport_details: ViewportDetails,
/// The goal of this reflow. /// The goal of this reflow.
@ -414,8 +424,6 @@ pub struct ReflowRequest {
pub dom_count: u32, pub dom_count: u32,
/// The current window origin /// The current window origin
pub origin: ImmutableOrigin, pub origin: ImmutableOrigin,
/// Restyle snapshot map.
pub pending_restyles: Vec<(TrustedNodeAddress, PendingRestyle)>,
/// The current animation timeline value. /// The current animation timeline value.
pub animation_timeline_value: f64, pub animation_timeline_value: f64,
/// The set of animations for this document. /// The set of animations for this document.
@ -428,6 +436,14 @@ pub struct ReflowRequest {
pub highlighted_dom_node: Option<OpaqueNode>, pub highlighted_dom_node: Option<OpaqueNode>,
} }
impl ReflowRequest {
pub fn stylesheets_changed(&self) -> bool {
self.restyle
.as_ref()
.is_some_and(|restyle| restyle.stylesheets_changed)
}
}
/// A pending restyle. /// A pending restyle.
#[derive(Debug, Default, MallocSizeOf)] #[derive(Debug, Default, MallocSizeOf)]
pub struct PendingRestyle { pub struct PendingRestyle {