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

Dump box tree state into json files and display it on layout 2020 viewer

  • Loading branch information
ferjm committed Feb 21, 2020
commit a042f850830e20195866f435ed4beb1df6f3c90e
@@ -10,7 +10,7 @@ use crate::style_ext::{ComputedValuesExt, DisplayInside};
use servo_arc::Arc;
use style::properties::ComputedValues;

#[derive(Debug)]
#[derive(Debug, Serialize)]
pub(crate) struct FloatBox {
pub contents: IndependentFormattingContext,
}
@@ -23,12 +23,12 @@ use style::values::specified::text::TextAlignKeyword;
use style::Zero;
use webrender_api::FontInstanceKey;

#[derive(Debug, Default)]
#[derive(Debug, Default, Serialize)]
pub(crate) struct InlineFormattingContext {
pub(super) inline_level_boxes: Vec<Arc<InlineLevelBox>>,
}

#[derive(Debug)]
#[derive(Debug, Serialize)]
pub(crate) enum InlineLevelBox {
InlineBox(InlineBox),
TextRun(TextRun),
@@ -37,19 +37,21 @@ pub(crate) enum InlineLevelBox {
Atomic(IndependentFormattingContext),
}

#[derive(Debug)]
#[derive(Debug, Serialize)]
pub(crate) struct InlineBox {
pub tag: OpaqueNode,
#[serde(skip_serializing)]
pub style: Arc<ComputedValues>,
pub first_fragment: bool,
pub last_fragment: bool,
pub children: Vec<Arc<InlineLevelBox>>,
}

/// https://www.w3.org/TR/css-display-3/#css-text-run
#[derive(Debug)]
#[derive(Debug, Serialize)]
pub(crate) struct TextRun {
pub tag: OpaqueNode,
#[serde(skip_serializing)]
pub parent_style: Arc<ComputedValues>,
pub text: String,
}
@@ -31,22 +31,23 @@ mod root;

pub use root::{BoxTreeRoot, FragmentTreeRoot};

#[derive(Debug)]
#[derive(Debug, Serialize)]
pub(crate) struct BlockFormattingContext {
pub contents: BlockContainer,
pub contains_floats: bool,
}

#[derive(Debug)]
#[derive(Debug, Serialize)]
pub(crate) enum BlockContainer {
BlockLevelBoxes(Vec<Arc<BlockLevelBox>>),
InlineFormattingContext(InlineFormattingContext),
}

#[derive(Debug)]
#[derive(Debug, Serialize)]
pub(crate) enum BlockLevelBox {
SameFormattingContextBlock {
tag: OpaqueNode,
#[serde(skip_serializing)]
style: Arc<ComputedValues>,
contents: BlockContainer,
},
@@ -28,6 +28,7 @@ use style::properties::ComputedValues;
use style::values::computed::Length;
use style_traits::CSSPixel;

#[derive(Serialize)]
pub struct BoxTreeRoot(BlockFormattingContext);

#[derive(Serialize)]
@@ -18,9 +18,10 @@ use style::properties::ComputedValues;
use style::values::computed::Length;

/// https://drafts.csswg.org/css-display/#independent-formatting-context
#[derive(Debug)]
#[derive(Debug, Serialize)]
pub(crate) struct IndependentFormattingContext {
pub tag: OpaqueNode,
#[serde(skip_serializing)]
pub style: Arc<ComputedValues>,

/// If it was requested during construction
@@ -38,7 +39,7 @@ pub(crate) struct IndependentLayout {

// Private so that code outside of this module cannot match variants.
// It should got through methods instead.
#[derive(Debug)]
#[derive(Debug, Serialize)]
enum IndependentFormattingContextContents {
Flow(BlockFormattingContext),

@@ -5,7 +5,7 @@
//! 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 crate::flow::{BoxTreeRoot, FragmentTreeRoot};
use serde_json::{to_string, to_value, Value};
use std::cell::RefCell;
use std::fs::File;
@@ -32,38 +32,52 @@ macro_rules! layout_debug_scope(
)
);

#[derive(Serialize)]
struct TreeValues {
pub box_tree: Value,
pub fragment_tree: Value,
Comment on lines +37 to +38

This comment has been minimized.

Copy link
@SimonSapin

SimonSapin Feb 20, 2020

Member

What do you think of keeping already-serialized Strings here, instead of Values? I don’t really know which is better, it’s just another idea.

This comment has been minimized.

Copy link
@ferjm

ferjm Feb 21, 2020

Author Member

Since there's no clear benefit of keeping already-serialized Strings, I'd prefer to keep the Values to make the parsing on the viewer easier and to allow an easier visualization of the JSON files directly on the browser:

Strings:

Screenshot 2020-02-21 at 16 51 13

vs Objects:

Screenshot 2020-02-21 at 16 56 33

This comment has been minimized.

Copy link
@SimonSapin

SimonSapin Feb 21, 2020

Member

I meant Strings in JSON syntax that would be written directly to files. Your "Strings" screenshot involves double-serialization to JSON, which is not what I was suggesting. But anyway, Value is fine (and might be easier to combine if you want more than one in a given .json file?)

}

#[derive(Serialize)]
struct ScopeData {
name: String,
pre: Value,
post: Value,
pre: TreeValues,
post: TreeValues,
children: Vec<Box<ScopeData>>,
}

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

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

/// A layout debugging scope. The entire state of the fragment tree
/// A layout debugging scope. The entire state of the box and fragment trees
/// 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));
let box_tree = to_value(&state.box_tree).unwrap();
let fragment_tree = to_value(&state.fragment_tree).unwrap();
let data = Box::new(ScopeData::new(name.clone(), box_tree, fragment_tree));
state.scope_stack.push(data);
}
});
@@ -77,7 +91,10 @@ impl Drop for Scope {
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();
current_scope.post = TreeValues {
box_tree: to_value(&state.box_tree).unwrap(),
fragment_tree: to_value(&state.fragment_tree).unwrap(),
};
let previous_scope = state.scope_stack.last_mut().unwrap();
previous_scope.children.push(current_scope);
}
@@ -93,14 +110,20 @@ pub fn generate_unique_debug_id() -> u16 {

/// Begin a layout debug trace. If this has not been called,
/// creating debug scopes has no effect.
pub fn begin_trace(root: Arc<FragmentTreeRoot>) {
pub fn begin_trace(box_tree: Arc<BoxTreeRoot>, fragment_tree: 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 box_tree_value = to_value(&box_tree).unwrap();
let fragment_tree_value = to_value(&fragment_tree).unwrap();
let state = State {
scope_stack: vec![Box::new(ScopeData::new("root".to_owned(), root_trace))],
fragment: root.clone(),
scope_stack: vec![Box::new(ScopeData::new(
"root".to_owned(),
box_tree_value,
fragment_tree_value,
))],
box_tree,
fragment_tree,
};
*r.borrow_mut() = Some(state);
});
@@ -113,8 +136,10 @@ 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();

root_scope.post = TreeValues {
box_tree: to_value(&thread_state.box_tree).unwrap_or(Value::Null),
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();
@@ -18,7 +18,7 @@ use style::properties::ComputedValues;
use style::values::computed::{Length, LengthOrAuto, LengthPercentage, LengthPercentageOrAuto};
use style::Zero;

#[derive(Debug)]
#[derive(Debug, Serialize)]
pub(crate) struct AbsolutelyPositionedBox {
pub contents: IndependentFormattingContext,
}
@@ -17,7 +17,7 @@ use style::values::computed::{Length, LengthOrAuto};
use style::values::CSSFloat;
use style::Zero;

#[derive(Debug)]
#[derive(Debug, Serialize)]
pub(crate) struct ReplacedContent {
pub kind: ReplacedContentKind,
intrinsic: IntrinsicSizes,
@@ -41,7 +41,7 @@ pub(crate) struct IntrinsicSizes {
pub ratio: Option<CSSFloat>,
}

#[derive(Debug)]
#[derive(Debug, Serialize)]
pub(crate) enum ReplacedContentKind {
Image(Option<Arc<Image>>),
}
@@ -48,7 +48,7 @@ impl ContentSizesRequest {
}
}

#[derive(Clone, Debug)]
#[derive(Clone, Debug, Serialize)]
pub(crate) struct ContentSizes {
pub min_content: Length,
pub max_content: Length,
@@ -83,7 +83,7 @@ impl ContentSizes {
}

/// Optional min/max-content for storage in the box tree
#[derive(Debug)]
#[derive(Debug, Serialize)]
pub(crate) enum BoxContentSizes {
NoneWereRequested, // … during box construction
Inline(ContentSizes),
@@ -173,7 +173,7 @@ pub struct LayoutThread {
outstanding_web_fonts: Arc<AtomicUsize>,

/// The root of the box tree.
box_tree_root: RefCell<Option<BoxTreeRoot>>,
box_tree_root: RefCell<Option<Arc<BoxTreeRoot>>>,

/// The root of the fragment tree.
fragment_tree_root: RefCell<Option<Arc<FragmentTreeRoot>>>,
@@ -1154,7 +1154,8 @@ impl LayoutThread {
} else {
build_box_tree()
};
Some(box_tree)

Some(Arc::new(box_tree))
} else {
None
};
@@ -1167,13 +1168,13 @@ impl LayoutThread {
self.viewport_size.height.to_f32_px(),
);
let run_layout = || box_tree.layout(&layout_context, viewport_size);
let fragment_tree = if let Some(pool) = rayon_pool {
let fragment_tree = Arc::new(if let Some(pool) = rayon_pool {
pool.install(run_layout)
} else {
run_layout()
};
});
*self.box_tree_root.borrow_mut() = Some(box_tree);
*self.fragment_tree_root.borrow_mut() = Some(Arc::new(fragment_tree));
*self.fragment_tree_root.borrow_mut() = Some(fragment_tree);
}

for element in elements_with_snapshot {
@@ -1382,6 +1383,12 @@ impl LayoutThread {
document: Option<&ServoLayoutDocument>,
context: &mut LayoutContext,
) {
if self.trace_layout {
if let Some(box_tree) = &*self.box_tree_root.borrow() {
layout_debug::begin_trace(box_tree.clone(), fragment_tree.clone());
}
}

if !reflow_goal.needs_display() {
// Defer the paint step until the next ForDisplay.
//
@@ -1393,10 +1400,6 @@ impl LayoutThread {
return;
}

if self.trace_layout {
layout_debug::begin_trace(fragment_tree.clone());
}

if let Some(document) = document {
document.will_paint();
}
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.