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

This commit is contained in:
Fernando Jiménez Moreno 2020-02-17 15:30:32 +01:00
parent aaa3cd9a59
commit a042f85083
11 changed files with 161 additions and 56 deletions

View file

@ -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,
}

View file

@ -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,9 +37,10 @@ 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,
@ -47,9 +48,10 @@ pub(crate) struct InlineBox {
}
/// 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,
}

View file

@ -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,
},

View file

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

View file

@ -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),

View file

@ -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,
}
#[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();

View file

@ -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,
}

View file

@ -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>>),
}

View file

@ -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),

View file

@ -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();
}

View file

@ -54,6 +54,14 @@
</div>
</div>
<div class="col-sm-8">
<div class="row">
<div class="col-sm-12">
<div class='panel panel-default'>
<div class='panel-heading'>Box Tree</div>
<div class='panel-body' id="box-tree"></div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class='panel panel-default'>
@ -90,10 +98,69 @@
<script src="js/formatters.min.js"></script>
<script>
function create_fragment_tree(trace_node) {
var fragment = Object.values(trace_node)[0];
function get_inner_boxes(box) {
const box_type = Object.keys(box)[0];
switch (box_type) {
case "BlockLevelBoxes":
return box.BlockLevelBoxes;
case "InlineFormattingContext":
return box.InlineFormattingContext.inline_level_boxes;
case "InlineBox":
return box.InlineBox.children;
case "SameFormattingContextBlock":
case "Independent":
case "Flow":
case "OutOfFlowAbsolutelyPositionedBox":
case "OutOfFlowFloatBox":
case "Atomic":
return box[box_type].contents;
}
return null;
}
function box_tree_from_container(container) {
const box_type = Object.keys(container)[0];
let inner_boxes = get_inner_boxes(container);
let nodes = [];
let text = box_type;
if (Array.isArray(inner_boxes)) {
if (!inner_boxes.length) {
nodes = null;
} else {
for (let box in inner_boxes) {
nodes.push(box_tree_from_container(inner_boxes[box]));
}
}
} else if (inner_boxes != null) {
nodes.push(box_tree_from_container(inner_boxes));
} else {
if (box_type == "TextRun") {
text += ` (${container.TextRun.text})`;
}
nodes = null;
}
return {
text,
nodes,
}
}
function box_tree_from_bfc(bfc) {
const { contains_floats, contents } = bfc;
var block_container = Object.values(contents)[0];
return {
text: "BlockFormattingContext",
info: {
contains_floats,
},
nodes: [box_tree_from_container(contents)]
};
}
function create_fragment_tree(root) {
var fragment = Object.values(root)[0];
var node = {
text: Object.keys(trace_node)[0],
text: Object.keys(root)[0],
id: fragment.debug_id,
icon: "dummy",
href: "#diff-" + fragment.debug_id
@ -132,12 +199,13 @@
}
function flatten_trace(trace_node) {
const node = Object.values(trace_node)[0];
const fragment_tree_root = Object.values(trace_node.fragment_tree)[0];
var fragments_info = {};
get_fragments_info(node, fragments_info);
get_fragments_info(fragment_tree_root, fragments_info);
return {
tree: create_fragment_tree(node),
fragments_info: fragments_info,
fragment_tree: create_fragment_tree(fragment_tree_root),
box_tree: box_tree_from_bfc(trace_node.box_tree),
fragments_info,
};
}
@ -148,9 +216,12 @@
var tree_node = {
text: trace_node.name,
icon: "dummy",
fragment_tree: pre_trace.tree, // assume pre/post trace always have same tree!
pre: pre_trace.fragments_info,
post: post_trace.fragments_info,
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,
};
var trace_node = Object.values(trace_node)[0];
@ -169,7 +240,7 @@
}
function update_fragment_tree_bgcolor(fragment_tree_node, node_color_hash) {
fragment_tree_node.backColor = node_color_hash[fragment_tree_node.id];
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)
@ -187,9 +258,9 @@
$("#fragment-diffs").empty();
$('#trace-tree').treeview(true).revealNode(node);
for (var key in node.pre) {
var fragment_info_left = node.pre[key];
var fragment_info_right = node.post[key];
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.create({
objectHash: function(obj) {
@ -224,6 +295,7 @@
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"});
$('#box-tree').treeview({data: [node.box_tree], levels: 100, enableLinks: true, emptyIcon: "glyphicon glyphicon-unchecked hidden-glyphicon"});
});
$('#trace-tree').treeview(true).selectNode(0);