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)]