From 5cbe05366bf11de3bb38e89a3d2ae2aacfadf2b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Jim=C3=A9nez=20Moreno?= Date: Tue, 11 Feb 2020 18:50:54 +0100 Subject: [PATCH 01/10] Add layout debugger support to layout_2020 --- Cargo.lock | 1 + components/layout_2020/Cargo.toml | 1 + components/layout_2020/flow/root.rs | 1 + components/layout_2020/fragments.rs | 40 ++++++++- components/layout_2020/geom.rs | 6 +- components/layout_2020/layout_debug.rs | 110 +++++++++++++++++++++++++ components/layout_2020/lib.rs | 5 ++ components/layout_thread_2020/lib.rs | 35 ++++++-- 8 files changed, 186 insertions(+), 13 deletions(-) create mode 100644 components/layout_2020/layout_debug.rs diff --git a/Cargo.lock b/Cargo.lock index 83d69e12ebc..d5d51efdc46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2927,6 +2927,7 @@ dependencies = [ "script_layout_interface", "script_traits", "serde", + "serde_json", "servo_arc", "servo_geometry", "servo_url", diff --git a/components/layout_2020/Cargo.toml b/components/layout_2020/Cargo.toml index d75d12e20c8..8d4a6d62f63 100644 --- a/components/layout_2020/Cargo.toml +++ b/components/layout_2020/Cargo.toml @@ -33,6 +33,7 @@ rayon_croissant = "0.2.0" script_layout_interface = {path = "../script_layout_interface"} script_traits = {path = "../script_traits"} serde = "1.0" +serde_json = "1.0" servo_arc = { path = "../servo_arc" } servo_geometry = {path = "../geometry"} servo_url = {path = "../url"} diff --git a/components/layout_2020/flow/root.rs b/components/layout_2020/flow/root.rs index 028dfa317d1..ed880a3d9ce 100644 --- a/components/layout_2020/flow/root.rs +++ b/components/layout_2020/flow/root.rs @@ -30,6 +30,7 @@ use style_traits::CSSPixel; pub struct BoxTreeRoot(BlockFormattingContext); +#[derive(Serialize)] pub struct FragmentTreeRoot { /// The children of the root of the fragment tree. children: Vec, diff --git a/components/layout_2020/fragments.rs b/components/layout_2020/fragments.rs index c46b332dec5..87b362c14f0 100644 --- a/components/layout_2020/fragments.rs +++ b/components/layout_2020/fragments.rs @@ -6,6 +6,7 @@ use crate::geom::flow_relative::{Rect, Sides, Vec2}; use crate::geom::{PhysicalPoint, PhysicalRect}; use gfx::text::glyph::GlyphStore; use gfx_traits::print_tree::PrintTree; +use serde::ser::{Serialize, SerializeStruct, Serializer}; use servo_arc::Arc as ServoArc; use std::sync::Arc; use style::computed_values::overflow_x::T as ComputedOverflow; @@ -16,6 +17,7 @@ use style::values::computed::Length; use style::Zero; use webrender_api::{FontInstanceKey, ImageKey}; +#[derive(Serialize)] pub(crate) enum Fragment { Box(BoxFragment), Anonymous(AnonymousFragment), @@ -43,19 +45,21 @@ pub(crate) struct BoxFragment { pub scrollable_overflow_from_children: PhysicalRect, } +#[derive(Serialize)] pub(crate) struct CollapsedBlockMargins { pub collapsed_through: bool, pub start: CollapsedMargin, pub end: CollapsedMargin, } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Serialize)] pub(crate) struct CollapsedMargin { max_positive: Length, min_negative: Length, } /// Can contain child fragments with relative coordinates, but does not contribute to painting itself. +#[derive(Serialize)] pub(crate) struct AnonymousFragment { pub rect: Rect, pub children: Vec, @@ -342,3 +346,37 @@ impl CollapsedMargin { self.max_positive + self.min_negative } } + +impl Serialize for BoxFragment { + fn serialize(&self, serializer: S) -> Result { + let mut serializer = serializer.serialize_struct("BoxFragment", 6)?; + serializer.serialize_field("content_rect", &self.content_rect)?; + serializer.serialize_field("padding", &self.padding)?; + serializer.serialize_field("border", &self.border)?; + serializer.serialize_field("margin", &self.margin)?; + serializer.serialize_field( + "block_margins_collapsed_with_children", + &self.block_margins_collapsed_with_children, + )?; + serializer.serialize_field("children", &self.children)?; + serializer.end() + } +} + +impl Serialize for TextFragment { + fn serialize(&self, serializer: S) -> Result { + let mut serializer = serializer.serialize_struct("TextFragment", 3)?; + serializer.serialize_field("rect", &self.rect)?; + serializer.serialize_field("ascent", &self.ascent)?; + serializer.serialize_field("glyphs", &self.glyphs)?; + serializer.end() + } +} + +impl Serialize for ImageFragment { + fn serialize(&self, serializer: S) -> Result { + let mut serializer = serializer.serialize_struct("ImageFragment", 1)?; + serializer.serialize_field("rect", &self.rect)?; + serializer.end() + } +} diff --git a/components/layout_2020/geom.rs b/components/layout_2020/geom.rs index a3b60bd5cef..5499836c004 100644 --- a/components/layout_2020/geom.rs +++ b/components/layout_2020/geom.rs @@ -18,19 +18,19 @@ pub type PhysicalRect = euclid::Rect; pub type PhysicalSides = euclid::SideOffsets2D; pub(crate) mod flow_relative { - #[derive(Clone)] + #[derive(Clone, Serialize)] pub(crate) struct Vec2 { pub inline: T, pub block: T, } - #[derive(Clone)] + #[derive(Clone, Serialize)] pub(crate) struct Rect { pub start_corner: Vec2, pub size: Vec2, } - #[derive(Clone, Debug)] + #[derive(Clone, Serialize)] pub(crate) struct Sides { pub inline_start: T, pub inline_end: T, diff --git a/components/layout_2020/layout_debug.rs b/components/layout_2020/layout_debug.rs new file mode 100644 index 00000000000..32ab13fc484 --- /dev/null +++ b/components/layout_2020/layout_debug.rs @@ -0,0 +1,110 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! Supports writing a trace file created during each layout scope +//! that can be viewed by an external tool to make layout debugging easier. + +use crate::flow::FragmentTreeRoot; +use serde_json::{to_string, to_value, Value}; +use std::cell::RefCell; +use std::fs::File; +use std::io::Write; +use std::sync::Arc; + +thread_local!(static STATE_KEY: RefCell> = RefCell::new(None)); + +pub struct Scope; + +#[macro_export] +macro_rules! layout_debug_scope( + ($($arg:tt)*) => ( + if cfg!(debug_assertions) { + layout_debug::Scope::new(format!($($arg)*)) + } else { + layout_debug::Scope + } + ) +); + +#[derive(Serialize)] +struct ScopeData { + name: String, + pre: Value, + post: Value, + children: Vec>, +} + +impl ScopeData { + fn new(name: String, pre: Value) -> ScopeData { + ScopeData { + name: name, + pre: pre, + post: Value::Null, + children: vec![], + } + } +} + +struct State { + fragment: Arc, + scope_stack: Vec>, +} + +/// A layout debugging scope. The entire state of the fragment tree +/// will be output at the beginning and end of this scope. +impl Scope { + pub fn new(name: String) -> Scope { + STATE_KEY.with(|ref r| { + if let Some(ref mut state) = *r.borrow_mut() { + let fragment_tree = to_value(&state.fragment).unwrap(); + let data = Box::new(ScopeData::new(name.clone(), fragment_tree)); + state.scope_stack.push(data); + } + }); + Scope + } +} + +#[cfg(debug_assertions)] +impl Drop for Scope { + fn drop(&mut self) { + STATE_KEY.with(|ref r| { + if let Some(ref mut state) = *r.borrow_mut() { + let mut current_scope = state.scope_stack.pop().unwrap(); + current_scope.post = to_value(&state.fragment).unwrap(); + let previous_scope = state.scope_stack.last_mut().unwrap(); + previous_scope.children.push(current_scope); + } + }); + } +} + +/// Begin a layout debug trace. If this has not been called, +/// creating debug scopes has no effect. +pub fn begin_trace(root: Arc) { + assert!(STATE_KEY.with(|ref r| r.borrow().is_none())); + + STATE_KEY.with(|ref r| { + let root_trace = to_value(&root).unwrap(); + let state = State { + scope_stack: vec![Box::new(ScopeData::new("root".to_owned(), root_trace))], + fragment: root.clone(), + }; + *r.borrow_mut() = Some(state); + }); +} + +/// End the debug layout trace. This will write the layout +/// trace to disk in the current directory. The output +/// file can then be viewed with an external tool. +pub fn end_trace(generation: u32) { + let mut thread_state = STATE_KEY.with(|ref r| r.borrow_mut().take().unwrap()); + assert_eq!(thread_state.scope_stack.len(), 1); + let mut root_scope = thread_state.scope_stack.pop().unwrap(); + root_scope.post = to_value(&thread_state.fragment).unwrap(); + + let result = to_string(&root_scope).unwrap(); + let mut file = File::create(format!("layout_trace-{}.json", generation)).unwrap(); + file.write_all(result.as_bytes()).unwrap(); +} diff --git a/components/layout_2020/lib.rs b/components/layout_2020/lib.rs index 28ad31ae146..62e1f1b9470 100644 --- a/components/layout_2020/lib.rs +++ b/components/layout_2020/lib.rs @@ -5,6 +5,9 @@ #![deny(unsafe_code)] #![feature(exact_size_is_empty)] +#[macro_use] +extern crate serde; + pub mod context; pub mod data; pub mod display_list; @@ -14,6 +17,8 @@ mod flow; mod formatting_contexts; mod fragments; mod geom; +#[macro_use] +pub mod layout_debug; mod opaque_node; mod positioned; pub mod query; diff --git a/components/layout_thread_2020/lib.rs b/components/layout_thread_2020/lib.rs index be24d768595..6a2ba3b706b 100644 --- a/components/layout_thread_2020/lib.rs +++ b/components/layout_thread_2020/lib.rs @@ -36,6 +36,7 @@ use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use ipc_channel::router::ROUTER; use layout::context::LayoutContext; use layout::display_list::{DisplayListBuilder, WebRenderImageInfo}; +use layout::layout_debug; use layout::query::{ process_content_box_request, process_content_boxes_request, LayoutRPCImpl, LayoutThreadData, }; @@ -175,7 +176,7 @@ pub struct LayoutThread { box_tree_root: RefCell>, /// The root of the fragment tree. - fragment_tree_root: RefCell>, + fragment_tree_root: RefCell>>, /// The document-specific shared lock used for author-origin stylesheets document_shared_lock: Option, @@ -234,6 +235,10 @@ pub struct LayoutThread { /// Emits notifications when there is a relayout. relayout_event: bool, + + /// True if each step of layout is traced to an external JSON file + /// for debugging purposes. + trace_layout: bool, } impl LayoutThreadFactory for LayoutThread { @@ -266,7 +271,7 @@ impl LayoutThreadFactory for LayoutThread { dump_rule_tree: bool, relayout_event: bool, _nonincremental_layout: bool, - _trace_layout: bool, + trace_layout: bool, dump_flow_tree: bool, ) { thread::Builder::new() @@ -314,6 +319,7 @@ impl LayoutThreadFactory for LayoutThread { dump_style_tree, dump_rule_tree, dump_flow_tree, + trace_layout, ); let reporter_name = format!("layout-reporter-{}", id); @@ -482,6 +488,7 @@ impl LayoutThread { dump_style_tree: bool, dump_rule_tree: bool, dump_flow_tree: bool, + trace_layout: bool, ) -> LayoutThread { // Let webrender know about this pipeline by sending an empty display list. webrender_api_sender.send_initial_transaction(webrender_document, id.to_webrender()); @@ -567,6 +574,7 @@ impl LayoutThread { dump_style_tree, dump_rule_tree, dump_flow_tree, + trace_layout, } } @@ -866,9 +874,9 @@ impl LayoutThread { self.dump_style_tree, self.dump_rule_tree, self.relayout_event, - true, // nonincremental_layout - false, // trace_layout - self.dump_flow_tree, + true, // nonincremental_layout + self.trace_layout, // trace_layout + self.dump_flow_tree, // dump_flow_tree ); } @@ -1165,7 +1173,7 @@ impl LayoutThread { run_layout() }; *self.box_tree_root.borrow_mut() = Some(box_tree); - *self.fragment_tree_root.borrow_mut() = Some(fragment_tree); + *self.fragment_tree_root.borrow_mut() = Some(Arc::new(fragment_tree)); } for element in elements_with_snapshot { @@ -1195,7 +1203,7 @@ impl LayoutThread { // Perform post-style recalculation layout passes. if let Some(root) = &*self.fragment_tree_root.borrow() { self.perform_post_style_recalc_layout_passes( - root, + root.clone(), &data.reflow_goal, Some(&document), &mut layout_context, @@ -1358,7 +1366,7 @@ impl LayoutThread { let mut layout_context = self.build_layout_context(guards, false, &snapshots, origin); self.perform_post_style_recalc_layout_passes( - root, + root.clone(), &ReflowGoal::TickAnimations, None, &mut layout_context, @@ -1369,7 +1377,7 @@ impl LayoutThread { fn perform_post_style_recalc_layout_passes( &self, - fragment_tree: &FragmentTreeRoot, + fragment_tree: Arc, reflow_goal: &ReflowGoal, document: Option<&ServoLayoutDocument>, context: &mut LayoutContext, @@ -1384,6 +1392,11 @@ impl LayoutThread { .needs_paint_from_layout(); return; } + + if self.trace_layout { + layout_debug::begin_trace(fragment_tree.clone()); + } + if let Some(document) = document { document.will_paint(); } @@ -1426,6 +1439,10 @@ impl LayoutThread { display_list.wr.finalize(), ); + if self.trace_layout { + layout_debug::end_trace(self.generation.get()); + } + self.generation.set(self.generation.get() + 1); } From cce74e814d9fd906f1bf87fbc6556d84c5f142d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Jim=C3=A9nez=20Moreno?= Date: Tue, 11 Feb 2020 18:52:06 +0100 Subject: [PATCH 02/10] Ignore layout debugger trace files --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index f778f67ea83..3f306a88921 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,6 @@ support/hololens/ServoApp/Release/ support/hololens/packages/ support/hololens/AppPackages/ support/hololens/.vs/ + +# Layout debugger trace files +layout_trace* From 84dd334160ad51b623c42cf637892709ce348de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Jim=C3=A9nez=20Moreno?= Date: Wed, 12 Feb 2020 11:21:04 +0100 Subject: [PATCH 03/10] Display fragment tree on layout viewer --- etc/layout_viewer/viewer.html | 59 ++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/etc/layout_viewer/viewer.html b/etc/layout_viewer/viewer.html index 2deded56f8e..a3e0e2b1db0 100644 --- a/etc/layout_viewer/viewer.html +++ b/etc/layout_viewer/viewer.html @@ -100,22 +100,23 @@ } function create_flow_tree(trace_node) { - var base = get_base(trace_node); - var node = { - text: trace_node.class + " (" + base.id + ")", - id: base.id, - icon: "dummy", - href: "#diff-" + base.id + text: Object.keys(trace_node)[0], +// id: base.id, +// icon: "dummy", +// href: "#diff-" + base.id }; - var children = []; - for (var i=0 ; i < base.children.length ; ++i) { - children.push(create_flow_tree(base.children[i])); - } + var fragment = Object.values(trace_node)[0]; + if (fragment.children) { + var children = []; + for (var i=0 ; i < fragment.children.length ; ++i) { + children.push(create_flow_tree(fragment.children[i])); + } - if (children.length > 0) { - node.nodes = children; + if (children.length > 0) { + node.nodes = children; + } } return node; @@ -125,22 +126,22 @@ var base = get_base(trace_node); flow_hash[base.id] = trace_node; - for (var i=0 ; i < base.children.length ; ++i) { + for (var i=0 ; i < trace_node.children.length ; ++i) { create_flow_hash(base.children[i], flow_hash); } - delete base.children; + delete trace_node.children; } function flatten_trace(trace_node) { - var flow_tree = create_flow_tree(trace_node.children[0]); + var flow_tree = create_flow_tree(Object.values(trace_node)[0]); - var flow_hash = {}; - create_flow_hash(trace_node.children[0], flow_hash); + //var flow_hash = {}; + //create_flow_hash(trace_node[0], flow_hash); return { tree: flow_tree, - flows: flow_hash, + //flows: flow_hash, } } @@ -149,21 +150,23 @@ var post_trace = flatten_trace(trace_node.post); var tree_node = { - text: trace_node.name, + text: Object.keys(trace_node)[0], icon: "dummy", flow_tree: pre_trace.tree, // assume pre/post trace always have same tree! - pre: pre_trace.flows, - post: post_trace.flows, + //pre: pre_trace.flows, + //post: post_trace.flows, }; - var children = []; + var trace_node = Object.values(trace_node)[0]; + if (trace_node.children) { + var children = []; + for (var i=0 ; i < trace_node.children.length ; ++i) { + children.push(create_tree_node(trace_node.children[i])); + } - for (var i=0 ; i < trace_node.children.length ; ++i) { - children.push(create_tree_node(trace_node.children[i])); - } - - if (children.length > 0) { - tree_node.nodes = children; + if (children.length > 0) { + tree_node.nodes = children; + } } return tree_node; From 67706f9c0b7fdb07c6ba12bf7ce6de557a4e81de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Jim=C3=A9nez=20Moreno?= Date: Wed, 12 Feb 2020 12:24:20 +0100 Subject: [PATCH 04/10] Add fragment debug id --- components/layout_2020/flow/inline.rs | 3 +- components/layout_2020/flow/mod.rs | 3 +- components/layout_2020/fragments.rs | 53 ++++++++++++++++++++++++-- components/layout_2020/layout_debug.rs | 11 ++++++ components/layout_2020/positioned.rs | 2 +- components/layout_2020/replaced.rs | 3 +- etc/layout_viewer/viewer.html | 53 +++++++++++--------------- 7 files changed, 90 insertions(+), 38 deletions(-) diff --git a/components/layout_2020/flow/inline.rs b/components/layout_2020/flow/inline.rs index 58dfb6581db..ae75b8f055b 100644 --- a/components/layout_2020/flow/inline.rs +++ b/components/layout_2020/flow/inline.rs @@ -7,7 +7,7 @@ use crate::flow::float::FloatBox; use crate::flow::FlowLayout; use crate::formatting_contexts::IndependentFormattingContext; use crate::fragments::CollapsedBlockMargins; -use crate::fragments::{AnonymousFragment, BoxFragment, Fragment, TextFragment}; +use crate::fragments::{AnonymousFragment, BoxFragment, DebugId, Fragment, TextFragment}; use crate::geom::flow_relative::{Rect, Sides, Vec2}; use crate::positioned::{relative_adjustement, AbsolutelyPositionedBox, PositioningContext}; use crate::sizing::ContentSizes; @@ -713,6 +713,7 @@ impl TextRun { .fragments_so_far .push(Fragment::Text(TextFragment { tag: self.tag, + debug_id: DebugId::new(), parent_style: self.parent_style.clone(), rect, ascent: font_ascent.into(), diff --git a/components/layout_2020/flow/mod.rs b/components/layout_2020/flow/mod.rs index b047fee78df..f407b4ffd0e 100644 --- a/components/layout_2020/flow/mod.rs +++ b/components/layout_2020/flow/mod.rs @@ -8,8 +8,9 @@ use crate::context::LayoutContext; use crate::flow::float::{FloatBox, FloatContext}; use crate::flow::inline::InlineFormattingContext; use crate::formatting_contexts::{IndependentFormattingContext, IndependentLayout, NonReplacedIFC}; -use crate::fragments::{AnonymousFragment, BoxFragment, Fragment}; +use crate::fragments::{AnonymousFragment, BoxFragment}; use crate::fragments::{CollapsedBlockMargins, CollapsedMargin}; +use crate::fragments::{DebugId, Fragment}; use crate::geom::flow_relative::{Rect, Sides, Vec2}; use crate::positioned::{AbsolutelyPositionedBox, PositioningContext}; use crate::replaced::ReplacedContent; diff --git a/components/layout_2020/fragments.rs b/components/layout_2020/fragments.rs index 87b362c14f0..5dddb18ca49 100644 --- a/components/layout_2020/fragments.rs +++ b/components/layout_2020/fragments.rs @@ -4,6 +4,7 @@ use crate::geom::flow_relative::{Rect, Sides, Vec2}; use crate::geom::{PhysicalPoint, PhysicalRect}; +use crate::layout_debug; use gfx::text::glyph::GlyphStore; use gfx_traits::print_tree::PrintTree; use serde::ser::{Serialize, SerializeStruct, Serializer}; @@ -27,6 +28,7 @@ pub(crate) enum Fragment { pub(crate) struct BoxFragment { pub tag: OpaqueNode, + pub debug_id: DebugId, pub style: ServoArc, pub children: Vec, @@ -61,6 +63,7 @@ pub(crate) struct CollapsedMargin { /// Can contain child fragments with relative coordinates, but does not contribute to painting itself. #[derive(Serialize)] pub(crate) struct AnonymousFragment { + pub debug_id: DebugId, pub rect: Rect, pub children: Vec, pub mode: WritingMode, @@ -70,6 +73,7 @@ pub(crate) struct AnonymousFragment { } pub(crate) struct TextFragment { + pub debug_id: DebugId, pub tag: OpaqueNode, pub parent_style: ServoArc, pub rect: Rect, @@ -79,6 +83,7 @@ pub(crate) struct TextFragment { } pub(crate) struct ImageFragment { + pub debug_id: DebugId, pub style: ServoArc, pub rect: Rect, pub image_key: ImageKey, @@ -123,6 +128,7 @@ impl Fragment { impl AnonymousFragment { pub fn no_op(mode: WritingMode) -> Self { Self { + debug_id: DebugId::new(), children: vec![], rect: Rect::zero(), mode, @@ -140,6 +146,7 @@ impl AnonymousFragment { ) }); AnonymousFragment { + debug_id: DebugId::new(), rect, children, mode, @@ -179,6 +186,7 @@ impl BoxFragment { }); BoxFragment { tag, + debug_id: DebugId::new(), style, children, content_rect, @@ -349,7 +357,8 @@ impl CollapsedMargin { impl Serialize for BoxFragment { fn serialize(&self, serializer: S) -> Result { - let mut serializer = serializer.serialize_struct("BoxFragment", 6)?; + let mut serializer = serializer.serialize_struct("BoxFragment", 7)?; + serializer.serialize_field("debug_id", &self.debug_id)?; serializer.serialize_field("content_rect", &self.content_rect)?; serializer.serialize_field("padding", &self.padding)?; serializer.serialize_field("border", &self.border)?; @@ -365,7 +374,8 @@ impl Serialize for BoxFragment { impl Serialize for TextFragment { fn serialize(&self, serializer: S) -> Result { - let mut serializer = serializer.serialize_struct("TextFragment", 3)?; + let mut serializer = serializer.serialize_struct("TextFragment", 4)?; + serializer.serialize_field("debug_id", &self.debug_id)?; serializer.serialize_field("rect", &self.rect)?; serializer.serialize_field("ascent", &self.ascent)?; serializer.serialize_field("glyphs", &self.glyphs)?; @@ -375,8 +385,45 @@ impl Serialize for TextFragment { impl Serialize for ImageFragment { fn serialize(&self, serializer: S) -> Result { - let mut serializer = serializer.serialize_struct("ImageFragment", 1)?; + let mut serializer = serializer.serialize_struct("ImageFragment", 2)?; + serializer.serialize_field("debug_id", &self.debug_id)?; serializer.serialize_field("rect", &self.rect)?; serializer.end() } } + +#[cfg(not(debug_assertions))] +#[derive(Clone)] +pub struct DebugId; + +#[cfg(debug_assertions)] +#[derive(Clone)] +pub struct DebugId(u16); + +#[cfg(not(debug_assertions))] +impl DebugId { + pub fn new() -> DebugId { + DebugId + } +} + +#[cfg(debug_assertions)] +impl DebugId { + pub fn new() -> DebugId { + DebugId(layout_debug::generate_unique_debug_id()) + } +} + +#[cfg(not(debug_assertions))] +impl Serialize for DebugId { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str(&format!("{:p}", &self)) + } +} + +#[cfg(debug_assertions)] +impl Serialize for DebugId { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_u16(self.0) + } +} diff --git a/components/layout_2020/layout_debug.rs b/components/layout_2020/layout_debug.rs index 32ab13fc484..4e28ce8ffeb 100644 --- a/components/layout_2020/layout_debug.rs +++ b/components/layout_2020/layout_debug.rs @@ -10,10 +10,15 @@ use serde_json::{to_string, to_value, Value}; use std::cell::RefCell; use std::fs::File; use std::io::Write; +#[cfg(debug_assertions)] +use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; thread_local!(static STATE_KEY: RefCell> = RefCell::new(None)); +#[cfg(debug_assertions)] +static DEBUG_ID_COUNTER: AtomicUsize = AtomicUsize::new(0); + pub struct Scope; #[macro_export] @@ -80,6 +85,12 @@ impl Drop for Scope { } } +/// Generate a unique ID for Fragments. +#[cfg(debug_assertions)] +pub fn generate_unique_debug_id() -> u16 { + DEBUG_ID_COUNTER.fetch_add(1, Ordering::SeqCst) as u16 +} + /// Begin a layout debug trace. If this has not been called, /// creating debug scopes has no effect. pub fn begin_trace(root: Arc) { diff --git a/components/layout_2020/positioned.rs b/components/layout_2020/positioned.rs index 7b7d67bd122..3c4ec876fc7 100644 --- a/components/layout_2020/positioned.rs +++ b/components/layout_2020/positioned.rs @@ -5,7 +5,7 @@ use crate::context::LayoutContext; use crate::dom_traversal::{Contents, NodeExt}; use crate::formatting_contexts::IndependentFormattingContext; -use crate::fragments::{AnonymousFragment, BoxFragment, CollapsedBlockMargins, Fragment}; +use crate::fragments::{AnonymousFragment, BoxFragment, CollapsedBlockMargins, DebugId, Fragment}; use crate::geom::flow_relative::{Rect, Sides, Vec2}; use crate::sizing::ContentSizesRequest; use crate::style_ext::{ComputedValuesExt, DisplayInside}; diff --git a/components/layout_2020/replaced.rs b/components/layout_2020/replaced.rs index 2157d07f0b4..d979c9df481 100644 --- a/components/layout_2020/replaced.rs +++ b/components/layout_2020/replaced.rs @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use crate::dom_traversal::NodeExt; -use crate::fragments::{Fragment, ImageFragment}; +use crate::fragments::{DebugId, Fragment, ImageFragment}; use crate::geom::flow_relative::{Rect, Vec2}; use crate::geom::PhysicalSize; use crate::sizing::ContentSizes; @@ -113,6 +113,7 @@ impl ReplacedContent { .and_then(|image| image.id) .map(|image_key| { Fragment::Image(ImageFragment { + debug_id: DebugId::new(), style: style.clone(), rect: Rect { start_corner: Vec2::zero(), diff --git a/etc/layout_viewer/viewer.html b/etc/layout_viewer/viewer.html index a3e0e2b1db0..2dd339b0bda 100644 --- a/etc/layout_viewer/viewer.html +++ b/etc/layout_viewer/viewer.html @@ -57,8 +57,8 @@
-
Flow Tree
-
+
Fragment Tree
+
@@ -90,28 +90,19 @@ - - - - - - - + + + + + + + + + - - + reader.readAsText(file); + return false; + }; + register_toggle_unchanaged_code_handler(); + register_prev_next_trace_node(); + }); + + From f81a2f021e630b6d086a83fc90b917dbef89e888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Jim=C3=A9nez=20Moreno?= Date: Wed, 19 Feb 2020 12:55:37 +0100 Subject: [PATCH 08/10] Show box tree node info and clean up unused code --- etc/layout_viewer/css/main.css | 4 + etc/layout_viewer/viewer.html | 256 +++++++++++++-------------------- 2 files changed, 103 insertions(+), 157 deletions(-) diff --git a/etc/layout_viewer/css/main.css b/etc/layout_viewer/css/main.css index 9522c2ccb15..c591e866c91 100644 --- a/etc/layout_viewer/css/main.css +++ b/etc/layout_viewer/css/main.css @@ -27,3 +27,7 @@ font-weight: bold; text-align: right; } + +.tree-collapse { + float: right; +} diff --git a/etc/layout_viewer/viewer.html b/etc/layout_viewer/viewer.html index e2df391b0ad..76b85641e38 100644 --- a/etc/layout_viewer/viewer.html +++ b/etc/layout_viewer/viewer.html @@ -62,15 +62,32 @@
-
Box Tree
+
+ Box Tree + +
+
+
+
-
Fragment Tree
+
+ Fragment Tree + +
@@ -80,16 +97,6 @@
-
- < Prev step - | - Next step > -
- - -
- Back to top -
@@ -144,15 +151,28 @@ } nodes = null; } + + let info; + if ( + box_type != "BlockLevelBoxes" && + box_type != "InlineFormattingContext" + ) { + info = Object.assign({}, Object.values(container)[0]); + delete info.children; + delete info.contents; + delete info.tag; + } + return { text, - nodes + nodes, + info }; } function box_tree_from_bfc(bfc) { const { contains_floats, contents } = bfc; - var block_container = Object.values(contents)[0]; + let block_container = Object.values(contents)[0]; return { text: "BlockFormattingContext", info: { @@ -163,17 +183,16 @@ } function create_fragment_tree(root) { - var fragment = Object.values(root)[0]; - var node = { + let fragment = Object.values(root)[0]; + let node = { text: Object.keys(root)[0], id: fragment.debug_id, - icon: "dummy", href: "#diff-" + fragment.debug_id }; if (fragment.children) { - var children = []; - for (var i = 0; i < fragment.children.length; ++i) { + let children = []; + for (let i = 0; i < fragment.children.length; ++i) { children.push(create_fragment_tree(fragment.children[i])); } @@ -182,58 +201,38 @@ } } + node.info = Object.assign({}, fragment); + delete node.info.children; + delete node.info.debug_id; + return node; } - function get_fragments_info(node, fragments_info) { - const fragmentType = Object.keys(node)[0]; - const trace_node = Object.assign({}, Object.values(node)[0]); - let data = Object.assign({}, trace_node); - delete data.children; - delete data.debug_id; - fragments_info[trace_node.debug_id] = { - class: fragmentType, - data - }; - - if (trace_node.children) { - for (var i = 0; i < trace_node.children.length; ++i) { - get_fragments_info(trace_node.children[i], fragments_info); - } - } - } - function flatten_trace(trace_node) { - const fragment_tree_root = Object.values(trace_node.fragment_tree.children)[0]; - var fragments_info = {}; - get_fragments_info(fragment_tree_root, fragments_info); + const fragment_tree_root = Object.values( + trace_node.fragment_tree.children + )[0]; return { fragment_tree: create_fragment_tree(fragment_tree_root), - box_tree: box_tree_from_bfc(trace_node.box_tree), - fragments_info + box_tree: box_tree_from_bfc(trace_node.box_tree) }; } function create_trace_tree_node(trace_node) { - var pre_trace = flatten_trace(trace_node.pre); - var post_trace = flatten_trace(trace_node.post); + const trace = flatten_trace(trace_node.pre); - var tree_node = { + let tree_node = { text: trace_node.name, icon: "dummy", - box_tree: pre_trace.box_tree, - fragment_tree: pre_trace.fragment_tree, - pre_boxes_info: pre_trace.boxes_info, - post_boxes_info: post_trace.boxes_info, - pre_fragments_info: pre_trace.fragments_info, - post_fragments_info: post_trace.fragments_info + box_tree: trace.box_tree, + fragment_tree: trace.fragment_tree }; - var trace_node = Object.values(trace_node)[0]; - if (trace_node.children) { - var children = []; - for (var i = 0; i < trace_node.children.length; ++i) { - children.push(create_trace_tree_node(trace_node.children[i])); + let node = Object.values(trace_node)[0]; + if (node.children) { + let children = []; + for (let i = 0; i < node.children.length; ++i) { + children.push(create_trace_tree_node(node.children[i])); } if (children.length > 0) { @@ -244,27 +243,11 @@ return tree_node; } - function update_fragment_tree_bgcolor( - fragment_tree_node, - node_color_hash - ) { - fragment_tree_node.backColor = - node_color_hash[fragment_tree_node.debug_id]; - if (fragment_tree_node.nodes !== undefined) { - for (var i = 0; i < fragment_tree_node.nodes.length; ++i) { - update_fragment_tree_bgcolor( - fragment_tree_node.nodes[i], - node_color_hash - ); - } - } - } - function new_data_loaded(data) { jsondiffpatch.formatters.html.hideUnchanged(); - var node_color_hash = {}; - var tree = [create_trace_tree_node(data)]; + let node_color_hash = {}; + let tree = [create_trace_tree_node(data)]; $("#trace-tree").treeview({ data: tree, levels: 3 }); $("#trace-tree").on("nodeSelected", function(event, node) { $("#fragment-diffs").empty(); @@ -272,67 +255,62 @@ .treeview(true) .revealNode(node); - for (var key in node.pre_fragments_info) { - var fragment_info_left = node.pre_fragments_info[key]; - var fragment_info_right = node.post_fragments_info[key]; - - var delta = jsondiffpatch + const on_tree_node_selected = tree => (event, data) => { + $(`#${tree}-diffs`).empty(); + if (!data.info) return; + // XXX(ferjm) no diff for now. + const delta = jsondiffpatch .create({ objectHash: function(obj) { return JSON.stringify(obj); } }) - .diff(fragment_info_left, fragment_info_right); + .diff({}, data.info); - if (!delta) { - delta = jsondiffpatch - .create({ - objectHash: function(obj) { - return JSON.stringify(obj); - } - }) - .diff({}, fragment_info_right); - } + const json = jsondiffpatch.formatters.html.format(delta, data.info); - if (delta !== undefined) { - var diff_id = "diff-" + key; - $("#fragment-diffs").append( - "
" + - fragment_info_left.class + - " (" + - key + - ")" + - "
" - ); + $(`#${tree}-diffs`).append( + "
" + + data.text + + "
" + + json + + "
" + ); + }; - document - .getElementById(diff_id) - .getElementsByClassName( - "panel-body" - )[0].innerHTML = jsondiffpatch.formatters.html.format( - delta, - fragment_info_left - ); - node_color_hash[key] = "rgba(255, 0, 0, 0.7)"; - } else { - node_color_hash[key] = "rgb(212, 248, 212)"; - } - } + const on_fragment_tree_node_selected = on_tree_node_selected( + "fragment" + ); + const on_box_tree_node_selected = on_tree_node_selected("box"); - update_fragment_tree_bgcolor(node.fragment_tree, node_color_hash); $("#fragment-tree").treeview({ data: [node.fragment_tree], levels: 100, - enableLinks: true, - emptyIcon: "glyphicon glyphicon-unchecked hidden-glyphicon" + enableLinks: false, + emptyIcon: "glyphicon glyphicon-unchecked hidden-glyphicon", + onNodeSelected: on_fragment_tree_node_selected }); + $("#box-tree").treeview({ data: [node.box_tree], levels: 100, - enableLinks: true, - emptyIcon: "glyphicon glyphicon-unchecked hidden-glyphicon" + enableLinks: false, + emptyIcon: "glyphicon glyphicon-unchecked hidden-glyphicon", + onNodeSelected: on_box_tree_node_selected + }); + + ["fragment", "box"].forEach(key => { + const collapsable = $(`#${key}-tree-collapse`); + collapsable.html("Collapse all").on("click", () => { + const collapsed = collapsable.data("collapsed"); + if (collapsed == 0) { + $(`#${key}-tree`).treeview("collapseAll"); + } else { + $(`#${key}-tree`).treeview("expandAll"); + } + collapsable.html(collapsed == 0 ? "Expand all" : "Collapse all"); + collapsable.data("collapsed", collapsed == 0 ? 1 : 0); + }); }); }); @@ -341,46 +319,12 @@ .selectNode(0); } - function register_toggle_unchanaged_code_handler() { - var show_unchange_box = document.getElementById("show_unchanged"); - show_unchange_box.addEventListener("change", function(evt) { - jsondiffpatch.formatters.html.showUnchanged( - show_unchange_box.checked, - null, - 800 - ); - }); - } - - function register_prev_next_trace_node() { - var prev_btn = document.getElementById("prev_trace"); - var next_btn = document.getElementById("next_trace"); - prev_btn.addEventListener("click", function(evt) { - var node_id = $("#trace-tree") - .treeview(true) - .getSelected()[0].nodeId; - // We deliberatly choose to ignore the node_id out of bound case, - // since it won't break the UI usability - $("#trace-tree") - .treeview(true) - .selectNode(node_id - 1); - }); - next_btn.addEventListener("click", function(evt) { - var node_id = $("#trace-tree") - .treeview(true) - .getSelected()[0].nodeId; - $("#trace-tree") - .treeview(true) - .selectNode(node_id + 1); - }); - } - $(document).ready(function() { - var upload = document.getElementsByTagName("input")[0]; + let upload = document.getElementsByTagName("input")[0]; upload.onchange = function(e) { e.preventDefault(); - var file = upload.files[0], + let file = upload.files[0], reader = new FileReader(); reader.onload = function(event) { new_data_loaded(JSON.parse(event.target.result)); @@ -389,8 +333,6 @@ reader.readAsText(file); return false; }; - register_toggle_unchanaged_code_handler(); - register_prev_next_trace_node(); }); From c33a5173870990aa4a91cbf0677f266309305e71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Jim=C3=A9nez=20Moreno?= Date: Wed, 19 Feb 2020 17:18:25 +0100 Subject: [PATCH 09/10] Keep layout viewer versions for both 2013 and 2020 engines --- etc/layout_viewer/viewer.html | 567 +++++++++++++---------------- etc/layout_viewer/viewer_2020.html | 339 +++++++++++++++++ 2 files changed, 591 insertions(+), 315 deletions(-) create mode 100644 etc/layout_viewer/viewer_2020.html diff --git a/etc/layout_viewer/viewer.html b/etc/layout_viewer/viewer.html index 76b85641e38..2deded56f8e 100644 --- a/etc/layout_viewer/viewer.html +++ b/etc/layout_viewer/viewer.html @@ -1,339 +1,276 @@ - - - - - Servo Layout Debugger + + + + + Servo Layout Debugger - - + + - - + + - - + + - - + + - - - -
-
-
-

Servo Layout Viewer

-

- Check the - README - for instructions. -

-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
    -
    -
    -
    -
    -
    -
    -
    -
    - Box Tree - + + + +
    +
    +
    +

    Servo Layout Viewer

    +

    Check the README for instructions.

    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - Fragment Tree - +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
      +
    +
    +
    +
    +
    +
    +
    +
    +
    Flow Tree
    +
    +
    +
    +
    +
    +
    +
    -
    -
    -
    -
    +
    + < Prev step + | + Next step > +
    + + +
    + Back to top
    -
    -
    -
    - - - - - - - - - + + + + + + + + + - - + + diff --git a/etc/layout_viewer/viewer_2020.html b/etc/layout_viewer/viewer_2020.html new file mode 100644 index 00000000000..76b85641e38 --- /dev/null +++ b/etc/layout_viewer/viewer_2020.html @@ -0,0 +1,339 @@ + + + + + + + Servo Layout Debugger + + + + + + + + + + + + + + + + +
    +
    +
    +

    Servo Layout Viewer

    +

    + Check the + README + for instructions. +

    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
      +
      +
      +
      +
      +
      +
      +
      +
      + Box Tree + +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      + Fragment Tree + +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      + + + + + + + + + + + + + + From 5e76c93cc79613d5845fc726be5e420e431fea04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Jim=C3=A9nez=20Moreno?= Date: Fri, 21 Feb 2020 16:34:32 +0100 Subject: [PATCH 10/10] Minor improvements to layout_debug and associated code --- components/layout_2020/fragments.rs | 59 ++++++-------------------- components/layout_2020/layout_debug.rs | 10 +++-- 2 files changed, 19 insertions(+), 50 deletions(-) diff --git a/components/layout_2020/fragments.rs b/components/layout_2020/fragments.rs index 5dddb18ca49..fcf0bf21d84 100644 --- a/components/layout_2020/fragments.rs +++ b/components/layout_2020/fragments.rs @@ -4,10 +4,12 @@ use crate::geom::flow_relative::{Rect, Sides, Vec2}; use crate::geom::{PhysicalPoint, PhysicalRect}; +#[cfg(debug_assertions)] use crate::layout_debug; use gfx::text::glyph::GlyphStore; use gfx_traits::print_tree::PrintTree; -use serde::ser::{Serialize, SerializeStruct, Serializer}; +#[cfg(not(debug_assertions))] +use serde::ser::{Serialize, Serializer}; use servo_arc::Arc as ServoArc; use std::sync::Arc; use style::computed_values::overflow_x::T as ComputedOverflow; @@ -26,9 +28,11 @@ pub(crate) enum Fragment { Image(ImageFragment), } +#[derive(Serialize)] pub(crate) struct BoxFragment { pub tag: OpaqueNode, pub debug_id: DebugId, + #[serde(skip_serializing)] pub style: ServoArc, pub children: Vec, @@ -72,20 +76,26 @@ pub(crate) struct AnonymousFragment { pub scrollable_overflow: PhysicalRect, } +#[derive(Serialize)] pub(crate) struct TextFragment { pub debug_id: DebugId, pub tag: OpaqueNode, + #[serde(skip_serializing)] pub parent_style: ServoArc, pub rect: Rect, pub ascent: Length, + #[serde(skip_serializing)] pub font_key: FontInstanceKey, pub glyphs: Vec>, } +#[derive(Serialize)] pub(crate) struct ImageFragment { pub debug_id: DebugId, + #[serde(skip_serializing)] pub style: ServoArc, pub rect: Rect, + #[serde(skip_serializing)] pub image_key: ImageKey, } @@ -355,49 +365,13 @@ impl CollapsedMargin { } } -impl Serialize for BoxFragment { - fn serialize(&self, serializer: S) -> Result { - let mut serializer = serializer.serialize_struct("BoxFragment", 7)?; - serializer.serialize_field("debug_id", &self.debug_id)?; - serializer.serialize_field("content_rect", &self.content_rect)?; - serializer.serialize_field("padding", &self.padding)?; - serializer.serialize_field("border", &self.border)?; - serializer.serialize_field("margin", &self.margin)?; - serializer.serialize_field( - "block_margins_collapsed_with_children", - &self.block_margins_collapsed_with_children, - )?; - serializer.serialize_field("children", &self.children)?; - serializer.end() - } -} - -impl Serialize for TextFragment { - fn serialize(&self, serializer: S) -> Result { - let mut serializer = serializer.serialize_struct("TextFragment", 4)?; - serializer.serialize_field("debug_id", &self.debug_id)?; - serializer.serialize_field("rect", &self.rect)?; - serializer.serialize_field("ascent", &self.ascent)?; - serializer.serialize_field("glyphs", &self.glyphs)?; - serializer.end() - } -} - -impl Serialize for ImageFragment { - fn serialize(&self, serializer: S) -> Result { - let mut serializer = serializer.serialize_struct("ImageFragment", 2)?; - serializer.serialize_field("debug_id", &self.debug_id)?; - serializer.serialize_field("rect", &self.rect)?; - serializer.end() - } -} - #[cfg(not(debug_assertions))] #[derive(Clone)] pub struct DebugId; #[cfg(debug_assertions)] -#[derive(Clone)] +#[derive(Clone, Serialize)] +#[serde(transparent)] pub struct DebugId(u16); #[cfg(not(debug_assertions))] @@ -420,10 +394,3 @@ impl Serialize for DebugId { serializer.serialize_str(&format!("{:p}", &self)) } } - -#[cfg(debug_assertions)] -impl Serialize for DebugId { - fn serialize(&self, serializer: S) -> Result { - serializer.serialize_u16(self.0) - } -} diff --git a/components/layout_2020/layout_debug.rs b/components/layout_2020/layout_debug.rs index d7c59e6cf3c..571aa28eee9 100644 --- a/components/layout_2020/layout_debug.rs +++ b/components/layout_2020/layout_debug.rs @@ -8,8 +8,7 @@ use crate::flow::{BoxTreeRoot, FragmentTreeRoot}; use serde_json::{to_string, to_value, Value}; use std::cell::RefCell; -use std::fs::File; -use std::io::Write; +use std::fs; #[cfg(debug_assertions)] use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; @@ -141,6 +140,9 @@ pub fn end_trace(generation: u32) { fragment_tree: to_value(&thread_state.fragment_tree).unwrap_or(Value::Null), }; let result = to_string(&root_scope).unwrap(); - let mut file = File::create(format!("layout_trace-{}.json", generation)).unwrap(); - file.write_all(result.as_bytes()).unwrap(); + fs::write( + format!("layout_trace-{}.json", generation), + result.as_bytes(), + ) + .unwrap(); }