From b622157c1089e17bbf5c4a8a50568985d52cd7c2 Mon Sep 17 00:00:00 2001 From: Steven Novaryo <65610990+stevennovaryo@users.noreply.github.com> Date: Fri, 20 Jun 2025 15:16:30 +0800 Subject: [PATCH] Layout: Add Debug Print for The Scroll Tree (#37522) Add debug option `dump-scroll-tree` to print the scroll tree that had been stored after each reflow, or log that the scoll tree is not initialized yet.. To reduce the coupling, the debug print operation will process the scroll tree node list that have been constructed in the stacking context tree. It will generate a adjacency list and do preorder traversal. The order of the tree then will depends on the order of the node in the node list that has been constructed. Which, in turn, correspond to the declaration order of the nodes. This would help with the analysis and development of post composite queries and its caching. cc: @xiaochengh Signed-off-by: stevennovaryo --- components/config/opts.rs | 4 + components/layout/layout_impl.rs | 14 +++ components/shared/compositing/display_list.rs | 101 ++++++++++++++++++ 3 files changed, 119 insertions(+) diff --git a/components/config/opts.rs b/components/config/opts.rs index 3db866a7443..072c8cf3e10 100644 --- a/components/config/opts.rs +++ b/components/config/opts.rs @@ -110,6 +110,9 @@ pub struct DebugOptions { /// Print the stacking context tree after each layout. pub dump_stacking_context_tree: bool, + /// Print the scroll tree after each layout. + pub dump_scroll_tree: bool, + /// Print the display list after each layout. pub dump_display_list: bool, @@ -156,6 +159,7 @@ impl DebugOptions { "dump-flow-tree" => self.dump_flow_tree = true, "dump-rule-tree" => self.dump_rule_tree = true, "dump-style-tree" => self.dump_style_tree = true, + "dump-scroll-tree" => self.dump_scroll_tree = true, "gc-profile" => self.gc_profile = true, "profile-script-events" => self.profile_script_events = true, "relayout-event" => self.relayout_event = true, diff --git a/components/layout/layout_impl.rs b/components/layout/layout_impl.rs index fcadda07887..4188da443c4 100644 --- a/components/layout/layout_impl.rs +++ b/components/layout/layout_impl.rs @@ -664,6 +664,20 @@ impl LayoutThread { self.set_scroll_offset_from_script(external_scroll_id, offset); } + if self.debug.dump_scroll_tree { + // Print the [ScrollTree], this is done after display list build so we have + // the information about webrender id. Whether a scroll tree is initialized + // or not depends on the reflow goal. + if let Some(tree) = self.stacking_context_tree.borrow().as_ref() { + tree.compositor_info.scroll_tree.debug_print(); + } else { + println!( + "Scroll Tree -- reflow {:?}: scroll tree is not initialized yet.", + reflow_request.reflow_goal + ); + } + } + let pending_images = std::mem::take(&mut *layout_context.pending_images.lock()); let pending_rasterization_images = std::mem::take(&mut *layout_context.pending_rasterization_images.lock()); diff --git a/components/shared/compositing/display_list.rs b/components/shared/compositing/display_list.rs index 37986b6e91d..d9d4d694413 100644 --- a/components/shared/compositing/display_list.rs +++ b/components/shared/compositing/display_list.rs @@ -7,6 +7,7 @@ use std::collections::HashMap; use base::id::ScrollTreeNodeId; +use base::print_tree::PrintTree; use bitflags::bitflags; use embedder_traits::Cursor; use euclid::SideOffsets2D; @@ -262,6 +263,51 @@ impl ScrollTreeNode { info.scroll_to_webrender_location(scroll_location, context) .map(|location| (info.external_id, location)) } + + pub fn debug_print(&self, print_tree: &mut PrintTree, node_index: usize) { + match &self.info { + SpatialTreeNodeInfo::ReferenceFrame(info) => { + print_tree.new_level(format!( + "Reference Frame({node_index}): webrender_id={:?}\ + \norigin: {:?}\ + \ntransform_style: {:?}\ + \ntransform: {:?}\ + \nkind: {:?}", + self.webrender_id, info.origin, info.transform_style, info.transform, info.kind, + )); + }, + SpatialTreeNodeInfo::Scroll(info) => { + print_tree.new_level(format!( + "Scroll Frame({node_index}): webrender_id={:?}\ + \nexternal_id: {:?}\ + \ncontent_rect: {:?}\ + \nclip_rect: {:?}\ + \nscroll_sensitivity: {:?}\ + \noffset: {:?}", + self.webrender_id, + info.external_id, + info.content_rect, + info.clip_rect, + info.scroll_sensitivity, + info.offset, + )); + }, + SpatialTreeNodeInfo::Sticky(info) => { + print_tree.new_level(format!( + "Sticky Frame({node_index}): webrender_id={:?}\ + \nframe_rect: {:?}\ + \nmargins: {:?}\ + \nhorizontal_offset_bounds: {:?}\ + \nvertical_offset_bounds: {:?}", + self.webrender_id, + info.frame_rect, + info.margins, + info.horizontal_offset_bounds, + info.vertical_offset_bounds, + )); + }, + }; + } } /// A tree of spatial nodes, which mirrors the spatial nodes in the WebRender @@ -380,6 +426,61 @@ impl ScrollTree { } } +/// In order to pretty print the [ScrollTree] structure, we are converting +/// the node list inside the tree to be a adjacency list. The adjacency list +/// then is used for the [ScrollTree::debug_print_traversal] of the tree. +/// +/// This preprocessing helps decouples print logic a lot from its construction. +type AdjacencyListForPrint = Vec>; + +/// Implementation of [ScrollTree] that is related to debugging. +// FIXME: probably we could have a universal trait for this. Especially for +// structures that utilizes PrintTree. +impl ScrollTree { + fn nodes_in_adjacency_list(&self) -> AdjacencyListForPrint { + let mut adjacency_list: AdjacencyListForPrint = vec![Default::default(); self.nodes.len()]; + + for (node_index, node) in self.nodes.iter().enumerate() { + let current_id = ScrollTreeNodeId { index: node_index }; + if let Some(parent_id) = node.parent { + adjacency_list[parent_id.index].push(current_id); + } + } + + adjacency_list + } + + fn debug_print_traversal( + &self, + print_tree: &mut PrintTree, + current_id: &ScrollTreeNodeId, + adjacency_list: &[Vec], + ) { + for node_id in &adjacency_list[current_id.index] { + self.nodes[node_id.index].debug_print(print_tree, node_id.index); + self.debug_print_traversal(print_tree, node_id, adjacency_list); + } + print_tree.end_level(); + } + + /// Print the [ScrollTree]. Particularly, we are printing the node in + /// preorder traversal. The order of the nodes will depends of the + /// index of a node in the [ScrollTree] which corresponds to the + /// declarations of the nodes. + // TODO(stevennovaryo): add information about which fragment that + // defines this node. + pub fn debug_print(&self) { + let mut print_tree = PrintTree::new("Scroll Tree".to_owned()); + + let adj_list = self.nodes_in_adjacency_list(); + let root_id = ScrollTreeNodeId { index: 0 }; + + self.nodes[root_id.index].debug_print(&mut print_tree, root_id.index); + self.debug_print_traversal(&mut print_tree, &root_id, &adj_list); + print_tree.end_level(); + } +} + /// A data structure which stores compositor-side information about /// display lists sent to the compositor. #[derive(Debug, Deserialize, Serialize)]