Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Layout viewer for layout 2020 #25803

Merged
merged 10 commits into from Feb 24, 2020
Next

Add layout debugger support to layout_2020

  • Loading branch information
ferjm committed Feb 21, 2020
commit 5cbe05366bf11de3bb38e89a3d2ae2aacfadf2b6

Some generated files are not rendered by default. Learn more.

@@ -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"}
@@ -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<Fragment>,
@@ -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<Length>,
}

#[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<Length>,
pub children: Vec<Fragment>,
@@ -342,3 +346,37 @@ impl CollapsedMargin {
self.max_positive + self.min_negative
}
}

impl Serialize for BoxFragment {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
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<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
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<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut serializer = serializer.serialize_struct("ImageFragment", 1)?;
serializer.serialize_field("rect", &self.rect)?;
serializer.end()
}
}
This conversation was marked as resolved by SimonSapin

This comment has been minimized.

Copy link
@SimonSapin

SimonSapin Feb 20, 2020

Member

For these three impls: why are they not derived? Could they be derived with some attributes from https://serde.rs/field-attrs.html ?

This comment has been minimized.

Copy link
@ferjm

ferjm Feb 21, 2020

Author Member

I think I learnt about #[serde(skip_serializing)] after implementing the serializers. Fixed.

@@ -18,19 +18,19 @@ pub type PhysicalRect<U> = euclid::Rect<U, CSSPixel>;
pub type PhysicalSides<U> = euclid::SideOffsets2D<U, CSSPixel>;

pub(crate) mod flow_relative {
#[derive(Clone)]
#[derive(Clone, Serialize)]
pub(crate) struct Vec2<T> {
pub inline: T,
pub block: T,
}

#[derive(Clone)]
#[derive(Clone, Serialize)]
pub(crate) struct Rect<T> {
pub start_corner: Vec2<T>,
pub size: Vec2<T>,
}

#[derive(Clone, Debug)]
#[derive(Clone, Serialize)]
pub(crate) struct Sides<T> {
pub inline_start: T,
pub inline_end: T,
@@ -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<Option<State>> = 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<Box<ScopeData>>,
}

impl ScopeData {
fn new(name: String, pre: Value) -> ScopeData {
ScopeData {
name: name,
pre: pre,
post: Value::Null,
children: vec![],
}
}
}

struct State {
fragment: Arc<FragmentTreeRoot>,
scope_stack: Vec<Box<ScopeData>>,
}

/// 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<FragmentTreeRoot>) {
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();
Comment on lines +108 to +109

This comment has been minimized.

Copy link
@SimonSapin

SimonSapin Feb 20, 2020

Member

Nit: this can use std::fs::write

}
@@ -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;
@@ -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<Option<BoxTreeRoot>>,

/// The root of the fragment tree.
fragment_tree_root: RefCell<Option<FragmentTreeRoot>>,
fragment_tree_root: RefCell<Option<Arc<FragmentTreeRoot>>>,

/// The document-specific shared lock used for author-origin stylesheets
document_shared_lock: Option<SharedRwLock>,
@@ -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<FragmentTreeRoot>,
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);
}

ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.