mirror of
https://github.com/servo/servo.git
synced 2025-08-07 06:25:32 +01:00
Auto merge of #25803 - ferjm:layout_debug, r=SimonSapin
Layout viewer for layout 2020 - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors This PR makes layout 2020 dump the box and fragment tree states into json files that can be visualized with the layout viewer tool. This tool has not much functionality other than displaying these trees and allowing to inspect each node additional data, so there is a lot of room for improvements. Some ideas for follow-ups: - Make the tool create and display diffs between tree states. - Actually allow creating new debug scopes during box tree and fragment tree construction. Right now there is a single scope created after constructing both trees, which is not ideal as it only allows looking at the reflow result. - Right now an independent JSON file is created per reflow. It would be nice to unify the data obtained on each reflow on a single JSON, so diffs between reflows can be displayed as well. - Dump and display the DOM tree. Link boxes to DOM nodes. - #23339
This commit is contained in:
commit
2d1ec68d31
19 changed files with 622 additions and 43 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -60,3 +60,6 @@ support/hololens/ServoApp/Release/
|
||||||
support/hololens/packages/
|
support/hololens/packages/
|
||||||
support/hololens/AppPackages/
|
support/hololens/AppPackages/
|
||||||
support/hololens/.vs/
|
support/hololens/.vs/
|
||||||
|
|
||||||
|
# Layout debugger trace files
|
||||||
|
layout_trace*
|
||||||
|
|
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2929,6 +2929,7 @@ dependencies = [
|
||||||
"script_layout_interface",
|
"script_layout_interface",
|
||||||
"script_traits",
|
"script_traits",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"servo_arc",
|
"servo_arc",
|
||||||
"servo_geometry",
|
"servo_geometry",
|
||||||
"servo_url",
|
"servo_url",
|
||||||
|
|
|
@ -33,6 +33,7 @@ rayon_croissant = "0.2.0"
|
||||||
script_layout_interface = {path = "../script_layout_interface"}
|
script_layout_interface = {path = "../script_layout_interface"}
|
||||||
script_traits = {path = "../script_traits"}
|
script_traits = {path = "../script_traits"}
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
|
serde_json = "1.0"
|
||||||
servo_arc = { path = "../servo_arc" }
|
servo_arc = { path = "../servo_arc" }
|
||||||
servo_geometry = {path = "../geometry"}
|
servo_geometry = {path = "../geometry"}
|
||||||
servo_url = {path = "../url"}
|
servo_url = {path = "../url"}
|
||||||
|
|
|
@ -10,7 +10,7 @@ use crate::style_ext::{ComputedValuesExt, DisplayInside};
|
||||||
use servo_arc::Arc;
|
use servo_arc::Arc;
|
||||||
use style::properties::ComputedValues;
|
use style::properties::ComputedValues;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Serialize)]
|
||||||
pub(crate) struct FloatBox {
|
pub(crate) struct FloatBox {
|
||||||
pub contents: IndependentFormattingContext,
|
pub contents: IndependentFormattingContext,
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ use crate::flow::float::FloatBox;
|
||||||
use crate::flow::FlowLayout;
|
use crate::flow::FlowLayout;
|
||||||
use crate::formatting_contexts::IndependentFormattingContext;
|
use crate::formatting_contexts::IndependentFormattingContext;
|
||||||
use crate::fragments::CollapsedBlockMargins;
|
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::geom::flow_relative::{Rect, Sides, Vec2};
|
||||||
use crate::positioned::{relative_adjustement, AbsolutelyPositionedBox, PositioningContext};
|
use crate::positioned::{relative_adjustement, AbsolutelyPositionedBox, PositioningContext};
|
||||||
use crate::sizing::ContentSizes;
|
use crate::sizing::ContentSizes;
|
||||||
|
@ -23,12 +23,12 @@ use style::values::specified::text::TextAlignKeyword;
|
||||||
use style::Zero;
|
use style::Zero;
|
||||||
use webrender_api::FontInstanceKey;
|
use webrender_api::FontInstanceKey;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default, Serialize)]
|
||||||
pub(crate) struct InlineFormattingContext {
|
pub(crate) struct InlineFormattingContext {
|
||||||
pub(super) inline_level_boxes: Vec<Arc<InlineLevelBox>>,
|
pub(super) inline_level_boxes: Vec<Arc<InlineLevelBox>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Serialize)]
|
||||||
pub(crate) enum InlineLevelBox {
|
pub(crate) enum InlineLevelBox {
|
||||||
InlineBox(InlineBox),
|
InlineBox(InlineBox),
|
||||||
TextRun(TextRun),
|
TextRun(TextRun),
|
||||||
|
@ -37,9 +37,10 @@ pub(crate) enum InlineLevelBox {
|
||||||
Atomic(IndependentFormattingContext),
|
Atomic(IndependentFormattingContext),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Serialize)]
|
||||||
pub(crate) struct InlineBox {
|
pub(crate) struct InlineBox {
|
||||||
pub tag: OpaqueNode,
|
pub tag: OpaqueNode,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
pub style: Arc<ComputedValues>,
|
pub style: Arc<ComputedValues>,
|
||||||
pub first_fragment: bool,
|
pub first_fragment: bool,
|
||||||
pub last_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
|
/// https://www.w3.org/TR/css-display-3/#css-text-run
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Serialize)]
|
||||||
pub(crate) struct TextRun {
|
pub(crate) struct TextRun {
|
||||||
pub tag: OpaqueNode,
|
pub tag: OpaqueNode,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
pub parent_style: Arc<ComputedValues>,
|
pub parent_style: Arc<ComputedValues>,
|
||||||
pub text: String,
|
pub text: String,
|
||||||
}
|
}
|
||||||
|
@ -713,6 +715,7 @@ impl TextRun {
|
||||||
.fragments_so_far
|
.fragments_so_far
|
||||||
.push(Fragment::Text(TextFragment {
|
.push(Fragment::Text(TextFragment {
|
||||||
tag: self.tag,
|
tag: self.tag,
|
||||||
|
debug_id: DebugId::new(),
|
||||||
parent_style: self.parent_style.clone(),
|
parent_style: self.parent_style.clone(),
|
||||||
rect,
|
rect,
|
||||||
ascent: font_ascent.into(),
|
ascent: font_ascent.into(),
|
||||||
|
|
|
@ -8,8 +8,8 @@ use crate::context::LayoutContext;
|
||||||
use crate::flow::float::{FloatBox, FloatContext};
|
use crate::flow::float::{FloatBox, FloatContext};
|
||||||
use crate::flow::inline::InlineFormattingContext;
|
use crate::flow::inline::InlineFormattingContext;
|
||||||
use crate::formatting_contexts::{IndependentFormattingContext, IndependentLayout, NonReplacedIFC};
|
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::{CollapsedBlockMargins, CollapsedMargin, Fragment};
|
||||||
use crate::geom::flow_relative::{Rect, Sides, Vec2};
|
use crate::geom::flow_relative::{Rect, Sides, Vec2};
|
||||||
use crate::positioned::{AbsolutelyPositionedBox, PositioningContext};
|
use crate::positioned::{AbsolutelyPositionedBox, PositioningContext};
|
||||||
use crate::replaced::ReplacedContent;
|
use crate::replaced::ReplacedContent;
|
||||||
|
@ -30,22 +30,23 @@ mod root;
|
||||||
|
|
||||||
pub use root::{BoxTreeRoot, FragmentTreeRoot};
|
pub use root::{BoxTreeRoot, FragmentTreeRoot};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Serialize)]
|
||||||
pub(crate) struct BlockFormattingContext {
|
pub(crate) struct BlockFormattingContext {
|
||||||
pub contents: BlockContainer,
|
pub contents: BlockContainer,
|
||||||
pub contains_floats: bool,
|
pub contains_floats: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Serialize)]
|
||||||
pub(crate) enum BlockContainer {
|
pub(crate) enum BlockContainer {
|
||||||
BlockLevelBoxes(Vec<Arc<BlockLevelBox>>),
|
BlockLevelBoxes(Vec<Arc<BlockLevelBox>>),
|
||||||
InlineFormattingContext(InlineFormattingContext),
|
InlineFormattingContext(InlineFormattingContext),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Serialize)]
|
||||||
pub(crate) enum BlockLevelBox {
|
pub(crate) enum BlockLevelBox {
|
||||||
SameFormattingContextBlock {
|
SameFormattingContextBlock {
|
||||||
tag: OpaqueNode,
|
tag: OpaqueNode,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
style: Arc<ComputedValues>,
|
style: Arc<ComputedValues>,
|
||||||
contents: BlockContainer,
|
contents: BlockContainer,
|
||||||
},
|
},
|
||||||
|
|
|
@ -28,8 +28,10 @@ use style::properties::ComputedValues;
|
||||||
use style::values::computed::Length;
|
use style::values::computed::Length;
|
||||||
use style_traits::CSSPixel;
|
use style_traits::CSSPixel;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
pub struct BoxTreeRoot(BlockFormattingContext);
|
pub struct BoxTreeRoot(BlockFormattingContext);
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
pub struct FragmentTreeRoot {
|
pub struct FragmentTreeRoot {
|
||||||
/// The children of the root of the fragment tree.
|
/// The children of the root of the fragment tree.
|
||||||
children: Vec<Fragment>,
|
children: Vec<Fragment>,
|
||||||
|
|
|
@ -18,9 +18,10 @@ use style::properties::ComputedValues;
|
||||||
use style::values::computed::Length;
|
use style::values::computed::Length;
|
||||||
|
|
||||||
/// https://drafts.csswg.org/css-display/#independent-formatting-context
|
/// https://drafts.csswg.org/css-display/#independent-formatting-context
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Serialize)]
|
||||||
pub(crate) struct IndependentFormattingContext {
|
pub(crate) struct IndependentFormattingContext {
|
||||||
pub tag: OpaqueNode,
|
pub tag: OpaqueNode,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
pub style: Arc<ComputedValues>,
|
pub style: Arc<ComputedValues>,
|
||||||
|
|
||||||
/// If it was requested during construction
|
/// 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.
|
// Private so that code outside of this module cannot match variants.
|
||||||
// It should got through methods instead.
|
// It should got through methods instead.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Serialize)]
|
||||||
enum IndependentFormattingContextContents {
|
enum IndependentFormattingContextContents {
|
||||||
Flow(BlockFormattingContext),
|
Flow(BlockFormattingContext),
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,12 @@
|
||||||
|
|
||||||
use crate::geom::flow_relative::{Rect, Sides, Vec2};
|
use crate::geom::flow_relative::{Rect, Sides, Vec2};
|
||||||
use crate::geom::{PhysicalPoint, PhysicalRect};
|
use crate::geom::{PhysicalPoint, PhysicalRect};
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
use crate::layout_debug;
|
||||||
use gfx::text::glyph::GlyphStore;
|
use gfx::text::glyph::GlyphStore;
|
||||||
use gfx_traits::print_tree::PrintTree;
|
use gfx_traits::print_tree::PrintTree;
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
use serde::ser::{Serialize, Serializer};
|
||||||
use servo_arc::Arc as ServoArc;
|
use servo_arc::Arc as ServoArc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use style::computed_values::overflow_x::T as ComputedOverflow;
|
use style::computed_values::overflow_x::T as ComputedOverflow;
|
||||||
|
@ -16,6 +20,7 @@ use style::values::computed::Length;
|
||||||
use style::Zero;
|
use style::Zero;
|
||||||
use webrender_api::{FontInstanceKey, ImageKey};
|
use webrender_api::{FontInstanceKey, ImageKey};
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
pub(crate) enum Fragment {
|
pub(crate) enum Fragment {
|
||||||
Box(BoxFragment),
|
Box(BoxFragment),
|
||||||
Anonymous(AnonymousFragment),
|
Anonymous(AnonymousFragment),
|
||||||
|
@ -23,8 +28,11 @@ pub(crate) enum Fragment {
|
||||||
Image(ImageFragment),
|
Image(ImageFragment),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
pub(crate) struct BoxFragment {
|
pub(crate) struct BoxFragment {
|
||||||
pub tag: OpaqueNode,
|
pub tag: OpaqueNode,
|
||||||
|
pub debug_id: DebugId,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
pub style: ServoArc<ComputedValues>,
|
pub style: ServoArc<ComputedValues>,
|
||||||
pub children: Vec<Fragment>,
|
pub children: Vec<Fragment>,
|
||||||
|
|
||||||
|
@ -43,20 +51,23 @@ pub(crate) struct BoxFragment {
|
||||||
pub scrollable_overflow_from_children: PhysicalRect<Length>,
|
pub scrollable_overflow_from_children: PhysicalRect<Length>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
pub(crate) struct CollapsedBlockMargins {
|
pub(crate) struct CollapsedBlockMargins {
|
||||||
pub collapsed_through: bool,
|
pub collapsed_through: bool,
|
||||||
pub start: CollapsedMargin,
|
pub start: CollapsedMargin,
|
||||||
pub end: CollapsedMargin,
|
pub end: CollapsedMargin,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, Serialize)]
|
||||||
pub(crate) struct CollapsedMargin {
|
pub(crate) struct CollapsedMargin {
|
||||||
max_positive: Length,
|
max_positive: Length,
|
||||||
min_negative: Length,
|
min_negative: Length,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Can contain child fragments with relative coordinates, but does not contribute to painting itself.
|
/// Can contain child fragments with relative coordinates, but does not contribute to painting itself.
|
||||||
|
#[derive(Serialize)]
|
||||||
pub(crate) struct AnonymousFragment {
|
pub(crate) struct AnonymousFragment {
|
||||||
|
pub debug_id: DebugId,
|
||||||
pub rect: Rect<Length>,
|
pub rect: Rect<Length>,
|
||||||
pub children: Vec<Fragment>,
|
pub children: Vec<Fragment>,
|
||||||
pub mode: WritingMode,
|
pub mode: WritingMode,
|
||||||
|
@ -65,18 +76,26 @@ pub(crate) struct AnonymousFragment {
|
||||||
pub scrollable_overflow: PhysicalRect<Length>,
|
pub scrollable_overflow: PhysicalRect<Length>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
pub(crate) struct TextFragment {
|
pub(crate) struct TextFragment {
|
||||||
|
pub debug_id: DebugId,
|
||||||
pub tag: OpaqueNode,
|
pub tag: OpaqueNode,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
pub parent_style: ServoArc<ComputedValues>,
|
pub parent_style: ServoArc<ComputedValues>,
|
||||||
pub rect: Rect<Length>,
|
pub rect: Rect<Length>,
|
||||||
pub ascent: Length,
|
pub ascent: Length,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
pub font_key: FontInstanceKey,
|
pub font_key: FontInstanceKey,
|
||||||
pub glyphs: Vec<Arc<GlyphStore>>,
|
pub glyphs: Vec<Arc<GlyphStore>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
pub(crate) struct ImageFragment {
|
pub(crate) struct ImageFragment {
|
||||||
|
pub debug_id: DebugId,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
pub style: ServoArc<ComputedValues>,
|
pub style: ServoArc<ComputedValues>,
|
||||||
pub rect: Rect<Length>,
|
pub rect: Rect<Length>,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
pub image_key: ImageKey,
|
pub image_key: ImageKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,6 +138,7 @@ impl Fragment {
|
||||||
impl AnonymousFragment {
|
impl AnonymousFragment {
|
||||||
pub fn no_op(mode: WritingMode) -> Self {
|
pub fn no_op(mode: WritingMode) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
debug_id: DebugId::new(),
|
||||||
children: vec![],
|
children: vec![],
|
||||||
rect: Rect::zero(),
|
rect: Rect::zero(),
|
||||||
mode,
|
mode,
|
||||||
|
@ -136,6 +156,7 @@ impl AnonymousFragment {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
AnonymousFragment {
|
AnonymousFragment {
|
||||||
|
debug_id: DebugId::new(),
|
||||||
rect,
|
rect,
|
||||||
children,
|
children,
|
||||||
mode,
|
mode,
|
||||||
|
@ -175,6 +196,7 @@ impl BoxFragment {
|
||||||
});
|
});
|
||||||
BoxFragment {
|
BoxFragment {
|
||||||
tag,
|
tag,
|
||||||
|
debug_id: DebugId::new(),
|
||||||
style,
|
style,
|
||||||
children,
|
children,
|
||||||
content_rect,
|
content_rect,
|
||||||
|
@ -342,3 +364,33 @@ impl CollapsedMargin {
|
||||||
self.max_positive + self.min_negative
|
self.max_positive + self.min_negative
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct DebugId;
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
#[derive(Clone, Serialize)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
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<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
|
serializer.serialize_str(&format!("{:p}", &self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -18,19 +18,19 @@ pub type PhysicalRect<U> = euclid::Rect<U, CSSPixel>;
|
||||||
pub type PhysicalSides<U> = euclid::SideOffsets2D<U, CSSPixel>;
|
pub type PhysicalSides<U> = euclid::SideOffsets2D<U, CSSPixel>;
|
||||||
|
|
||||||
pub(crate) mod flow_relative {
|
pub(crate) mod flow_relative {
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Serialize)]
|
||||||
pub(crate) struct Vec2<T> {
|
pub(crate) struct Vec2<T> {
|
||||||
pub inline: T,
|
pub inline: T,
|
||||||
pub block: T,
|
pub block: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Serialize)]
|
||||||
pub(crate) struct Rect<T> {
|
pub(crate) struct Rect<T> {
|
||||||
pub start_corner: Vec2<T>,
|
pub start_corner: Vec2<T>,
|
||||||
pub size: Vec2<T>,
|
pub size: Vec2<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Serialize)]
|
||||||
pub(crate) struct Sides<T> {
|
pub(crate) struct Sides<T> {
|
||||||
pub inline_start: T,
|
pub inline_start: T,
|
||||||
pub inline_end: T,
|
pub inline_end: T,
|
||||||
|
|
148
components/layout_2020/layout_debug.rs
Normal file
148
components/layout_2020/layout_debug.rs
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
/* 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::{BoxTreeRoot, FragmentTreeRoot};
|
||||||
|
use serde_json::{to_string, to_value, Value};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::fs;
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
thread_local!(static STATE_KEY: RefCell<Option<State>> = RefCell::new(None));
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
static DEBUG_ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
|
||||||
|
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 TreeValues {
|
||||||
|
pub box_tree: Value,
|
||||||
|
pub fragment_tree: Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct ScopeData {
|
||||||
|
name: String,
|
||||||
|
pre: TreeValues,
|
||||||
|
post: TreeValues,
|
||||||
|
children: Vec<Box<ScopeData>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScopeData {
|
||||||
|
fn new(name: String, box_tree: Value, fragment_tree: Value) -> ScopeData {
|
||||||
|
ScopeData {
|
||||||
|
name,
|
||||||
|
pre: TreeValues {
|
||||||
|
box_tree,
|
||||||
|
fragment_tree,
|
||||||
|
},
|
||||||
|
post: TreeValues {
|
||||||
|
box_tree: Value::Null,
|
||||||
|
fragment_tree: Value::Null,
|
||||||
|
},
|
||||||
|
children: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
fragment_tree: Arc<FragmentTreeRoot>,
|
||||||
|
box_tree: Arc<BoxTreeRoot>,
|
||||||
|
scope_stack: Vec<Box<ScopeData>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
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 = 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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(box_tree: Arc<BoxTreeRoot>, fragment_tree: Arc<FragmentTreeRoot>) {
|
||||||
|
assert!(STATE_KEY.with(|ref r| r.borrow().is_none()));
|
||||||
|
|
||||||
|
STATE_KEY.with(|ref r| {
|
||||||
|
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(),
|
||||||
|
box_tree_value,
|
||||||
|
fragment_tree_value,
|
||||||
|
))],
|
||||||
|
box_tree,
|
||||||
|
fragment_tree,
|
||||||
|
};
|
||||||
|
*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 = 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();
|
||||||
|
fs::write(
|
||||||
|
format!("layout_trace-{}.json", generation),
|
||||||
|
result.as_bytes(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
|
@ -5,6 +5,9 @@
|
||||||
#![deny(unsafe_code)]
|
#![deny(unsafe_code)]
|
||||||
#![feature(exact_size_is_empty)]
|
#![feature(exact_size_is_empty)]
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde;
|
||||||
|
|
||||||
pub mod context;
|
pub mod context;
|
||||||
pub mod data;
|
pub mod data;
|
||||||
pub mod display_list;
|
pub mod display_list;
|
||||||
|
@ -14,6 +17,8 @@ mod flow;
|
||||||
mod formatting_contexts;
|
mod formatting_contexts;
|
||||||
mod fragments;
|
mod fragments;
|
||||||
mod geom;
|
mod geom;
|
||||||
|
#[macro_use]
|
||||||
|
pub mod layout_debug;
|
||||||
mod opaque_node;
|
mod opaque_node;
|
||||||
mod positioned;
|
mod positioned;
|
||||||
pub mod query;
|
pub mod query;
|
||||||
|
|
|
@ -18,7 +18,7 @@ use style::properties::ComputedValues;
|
||||||
use style::values::computed::{Length, LengthOrAuto, LengthPercentage, LengthPercentageOrAuto};
|
use style::values::computed::{Length, LengthOrAuto, LengthPercentage, LengthPercentageOrAuto};
|
||||||
use style::Zero;
|
use style::Zero;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Serialize)]
|
||||||
pub(crate) struct AbsolutelyPositionedBox {
|
pub(crate) struct AbsolutelyPositionedBox {
|
||||||
pub contents: IndependentFormattingContext,
|
pub contents: IndependentFormattingContext,
|
||||||
}
|
}
|
||||||
|
|
|
@ -166,7 +166,7 @@ impl LayoutRPC for LayoutRPCImpl {
|
||||||
|
|
||||||
pub fn process_content_box_request(
|
pub fn process_content_box_request(
|
||||||
requested_node: OpaqueNode,
|
requested_node: OpaqueNode,
|
||||||
fragment_tree_root: Option<&FragmentTreeRoot>,
|
fragment_tree_root: Option<Arc<FragmentTreeRoot>>,
|
||||||
) -> Option<Rect<Au>> {
|
) -> Option<Rect<Au>> {
|
||||||
let fragment_tree_root = match fragment_tree_root {
|
let fragment_tree_root = match fragment_tree_root {
|
||||||
Some(fragment_tree_root) => fragment_tree_root,
|
Some(fragment_tree_root) => fragment_tree_root,
|
||||||
|
@ -182,7 +182,7 @@ pub fn process_content_boxes_request(_requested_node: OpaqueNode) -> Vec<Rect<Au
|
||||||
|
|
||||||
pub fn process_node_geometry_request(
|
pub fn process_node_geometry_request(
|
||||||
requested_node: OpaqueNode,
|
requested_node: OpaqueNode,
|
||||||
fragment_tree_root: Option<&FragmentTreeRoot>,
|
fragment_tree_root: Option<Arc<FragmentTreeRoot>>,
|
||||||
) -> Rect<i32> {
|
) -> Rect<i32> {
|
||||||
let fragment_tree_root = match fragment_tree_root {
|
let fragment_tree_root = match fragment_tree_root {
|
||||||
Some(fragment_tree_root) => fragment_tree_root,
|
Some(fragment_tree_root) => fragment_tree_root,
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use crate::dom_traversal::NodeExt;
|
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::flow_relative::{Rect, Vec2};
|
||||||
use crate::geom::PhysicalSize;
|
use crate::geom::PhysicalSize;
|
||||||
use crate::sizing::ContentSizes;
|
use crate::sizing::ContentSizes;
|
||||||
|
@ -17,7 +17,7 @@ use style::values::computed::{Length, LengthOrAuto};
|
||||||
use style::values::CSSFloat;
|
use style::values::CSSFloat;
|
||||||
use style::Zero;
|
use style::Zero;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Serialize)]
|
||||||
pub(crate) struct ReplacedContent {
|
pub(crate) struct ReplacedContent {
|
||||||
pub kind: ReplacedContentKind,
|
pub kind: ReplacedContentKind,
|
||||||
intrinsic: IntrinsicSizes,
|
intrinsic: IntrinsicSizes,
|
||||||
|
@ -34,14 +34,14 @@ pub(crate) struct ReplacedContent {
|
||||||
///
|
///
|
||||||
/// * For SVG, see https://svgwg.org/svg2-draft/coords.html#SizingSVGInCSS
|
/// * For SVG, see https://svgwg.org/svg2-draft/coords.html#SizingSVGInCSS
|
||||||
/// and again https://github.com/w3c/csswg-drafts/issues/4572.
|
/// and again https://github.com/w3c/csswg-drafts/issues/4572.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Serialize)]
|
||||||
pub(crate) struct IntrinsicSizes {
|
pub(crate) struct IntrinsicSizes {
|
||||||
pub width: Option<Length>,
|
pub width: Option<Length>,
|
||||||
pub height: Option<Length>,
|
pub height: Option<Length>,
|
||||||
pub ratio: Option<CSSFloat>,
|
pub ratio: Option<CSSFloat>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Serialize)]
|
||||||
pub(crate) enum ReplacedContentKind {
|
pub(crate) enum ReplacedContentKind {
|
||||||
Image(Option<Arc<Image>>),
|
Image(Option<Arc<Image>>),
|
||||||
}
|
}
|
||||||
|
@ -113,6 +113,7 @@ impl ReplacedContent {
|
||||||
.and_then(|image| image.id)
|
.and_then(|image| image.id)
|
||||||
.map(|image_key| {
|
.map(|image_key| {
|
||||||
Fragment::Image(ImageFragment {
|
Fragment::Image(ImageFragment {
|
||||||
|
debug_id: DebugId::new(),
|
||||||
style: style.clone(),
|
style: style.clone(),
|
||||||
rect: Rect {
|
rect: Rect {
|
||||||
start_corner: Vec2::zero(),
|
start_corner: Vec2::zero(),
|
||||||
|
|
|
@ -48,7 +48,7 @@ impl ContentSizesRequest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
pub(crate) struct ContentSizes {
|
pub(crate) struct ContentSizes {
|
||||||
pub min_content: Length,
|
pub min_content: Length,
|
||||||
pub max_content: Length,
|
pub max_content: Length,
|
||||||
|
@ -83,7 +83,7 @@ impl ContentSizes {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Optional min/max-content for storage in the box tree
|
/// Optional min/max-content for storage in the box tree
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Serialize)]
|
||||||
pub(crate) enum BoxContentSizes {
|
pub(crate) enum BoxContentSizes {
|
||||||
NoneWereRequested, // … during box construction
|
NoneWereRequested, // … during box construction
|
||||||
Inline(ContentSizes),
|
Inline(ContentSizes),
|
||||||
|
|
|
@ -36,6 +36,7 @@ use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
|
||||||
use ipc_channel::router::ROUTER;
|
use ipc_channel::router::ROUTER;
|
||||||
use layout::context::LayoutContext;
|
use layout::context::LayoutContext;
|
||||||
use layout::display_list::{DisplayListBuilder, WebRenderImageInfo};
|
use layout::display_list::{DisplayListBuilder, WebRenderImageInfo};
|
||||||
|
use layout::layout_debug;
|
||||||
use layout::query::{
|
use layout::query::{
|
||||||
process_content_box_request, process_content_boxes_request, LayoutRPCImpl, LayoutThreadData,
|
process_content_box_request, process_content_boxes_request, LayoutRPCImpl, LayoutThreadData,
|
||||||
};
|
};
|
||||||
|
@ -172,10 +173,10 @@ pub struct LayoutThread {
|
||||||
outstanding_web_fonts: Arc<AtomicUsize>,
|
outstanding_web_fonts: Arc<AtomicUsize>,
|
||||||
|
|
||||||
/// The root of the box tree.
|
/// 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.
|
/// 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
|
/// The document-specific shared lock used for author-origin stylesheets
|
||||||
document_shared_lock: Option<SharedRwLock>,
|
document_shared_lock: Option<SharedRwLock>,
|
||||||
|
@ -234,6 +235,10 @@ pub struct LayoutThread {
|
||||||
|
|
||||||
/// Emits notifications when there is a relayout.
|
/// Emits notifications when there is a relayout.
|
||||||
relayout_event: bool,
|
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 {
|
impl LayoutThreadFactory for LayoutThread {
|
||||||
|
@ -266,7 +271,7 @@ impl LayoutThreadFactory for LayoutThread {
|
||||||
dump_rule_tree: bool,
|
dump_rule_tree: bool,
|
||||||
relayout_event: bool,
|
relayout_event: bool,
|
||||||
_nonincremental_layout: bool,
|
_nonincremental_layout: bool,
|
||||||
_trace_layout: bool,
|
trace_layout: bool,
|
||||||
dump_flow_tree: bool,
|
dump_flow_tree: bool,
|
||||||
) {
|
) {
|
||||||
thread::Builder::new()
|
thread::Builder::new()
|
||||||
|
@ -315,6 +320,7 @@ impl LayoutThreadFactory for LayoutThread {
|
||||||
dump_style_tree,
|
dump_style_tree,
|
||||||
dump_rule_tree,
|
dump_rule_tree,
|
||||||
dump_flow_tree,
|
dump_flow_tree,
|
||||||
|
trace_layout,
|
||||||
);
|
);
|
||||||
|
|
||||||
let reporter_name = format!("layout-reporter-{}", id);
|
let reporter_name = format!("layout-reporter-{}", id);
|
||||||
|
@ -483,6 +489,7 @@ impl LayoutThread {
|
||||||
dump_style_tree: bool,
|
dump_style_tree: bool,
|
||||||
dump_rule_tree: bool,
|
dump_rule_tree: bool,
|
||||||
dump_flow_tree: bool,
|
dump_flow_tree: bool,
|
||||||
|
trace_layout: bool,
|
||||||
) -> LayoutThread {
|
) -> LayoutThread {
|
||||||
// Let webrender know about this pipeline by sending an empty display list.
|
// Let webrender know about this pipeline by sending an empty display list.
|
||||||
webrender_api_sender.send_initial_transaction(webrender_document, id.to_webrender());
|
webrender_api_sender.send_initial_transaction(webrender_document, id.to_webrender());
|
||||||
|
@ -568,6 +575,7 @@ impl LayoutThread {
|
||||||
dump_style_tree,
|
dump_style_tree,
|
||||||
dump_rule_tree,
|
dump_rule_tree,
|
||||||
dump_flow_tree,
|
dump_flow_tree,
|
||||||
|
trace_layout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -870,9 +878,9 @@ impl LayoutThread {
|
||||||
self.dump_style_tree,
|
self.dump_style_tree,
|
||||||
self.dump_rule_tree,
|
self.dump_rule_tree,
|
||||||
self.relayout_event,
|
self.relayout_event,
|
||||||
true, // nonincremental_layout
|
true, // nonincremental_layout
|
||||||
false, // trace_layout
|
self.trace_layout, // trace_layout
|
||||||
self.dump_flow_tree,
|
self.dump_flow_tree, // dump_flow_tree
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1152,7 +1160,8 @@ impl LayoutThread {
|
||||||
} else {
|
} else {
|
||||||
build_box_tree()
|
build_box_tree()
|
||||||
};
|
};
|
||||||
Some(box_tree)
|
|
||||||
|
Some(Arc::new(box_tree))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
@ -1165,11 +1174,11 @@ impl LayoutThread {
|
||||||
self.viewport_size.height.to_f32_px(),
|
self.viewport_size.height.to_f32_px(),
|
||||||
);
|
);
|
||||||
let run_layout = || box_tree.layout(&layout_context, viewport_size);
|
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)
|
pool.install(run_layout)
|
||||||
} else {
|
} else {
|
||||||
run_layout()
|
run_layout()
|
||||||
};
|
});
|
||||||
*self.box_tree_root.borrow_mut() = Some(box_tree);
|
*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(fragment_tree);
|
||||||
}
|
}
|
||||||
|
@ -1201,7 +1210,7 @@ impl LayoutThread {
|
||||||
// Perform post-style recalculation layout passes.
|
// Perform post-style recalculation layout passes.
|
||||||
if let Some(root) = &*self.fragment_tree_root.borrow() {
|
if let Some(root) = &*self.fragment_tree_root.borrow() {
|
||||||
self.perform_post_style_recalc_layout_passes(
|
self.perform_post_style_recalc_layout_passes(
|
||||||
root,
|
root.clone(),
|
||||||
&data.reflow_goal,
|
&data.reflow_goal,
|
||||||
Some(&document),
|
Some(&document),
|
||||||
&mut layout_context,
|
&mut layout_context,
|
||||||
|
@ -1232,10 +1241,8 @@ impl LayoutThread {
|
||||||
match *reflow_goal {
|
match *reflow_goal {
|
||||||
ReflowGoal::LayoutQuery(ref querymsg, _) => match querymsg {
|
ReflowGoal::LayoutQuery(ref querymsg, _) => match querymsg {
|
||||||
&QueryMsg::ContentBoxQuery(node) => {
|
&QueryMsg::ContentBoxQuery(node) => {
|
||||||
rw_data.content_box_response = process_content_box_request(
|
rw_data.content_box_response =
|
||||||
node,
|
process_content_box_request(node, self.fragment_tree_root.borrow().clone());
|
||||||
(&*self.fragment_tree_root.borrow()).as_ref(),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
&QueryMsg::ContentBoxesQuery(node) => {
|
&QueryMsg::ContentBoxesQuery(node) => {
|
||||||
rw_data.content_boxes_response = process_content_boxes_request(node);
|
rw_data.content_boxes_response = process_content_boxes_request(node);
|
||||||
|
@ -1250,7 +1257,7 @@ impl LayoutThread {
|
||||||
&QueryMsg::ClientRectQuery(node) => {
|
&QueryMsg::ClientRectQuery(node) => {
|
||||||
rw_data.client_rect_response = process_node_geometry_request(
|
rw_data.client_rect_response = process_node_geometry_request(
|
||||||
node,
|
node,
|
||||||
(&*self.fragment_tree_root.borrow()).as_ref(),
|
self.fragment_tree_root.borrow().clone(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
&QueryMsg::NodeScrollGeometryQuery(node) => {
|
&QueryMsg::NodeScrollGeometryQuery(node) => {
|
||||||
|
@ -1364,7 +1371,7 @@ impl LayoutThread {
|
||||||
let mut layout_context = self.build_layout_context(guards, false, &snapshots, origin);
|
let mut layout_context = self.build_layout_context(guards, false, &snapshots, origin);
|
||||||
|
|
||||||
self.perform_post_style_recalc_layout_passes(
|
self.perform_post_style_recalc_layout_passes(
|
||||||
root,
|
root.clone(),
|
||||||
&ReflowGoal::TickAnimations,
|
&ReflowGoal::TickAnimations,
|
||||||
None,
|
None,
|
||||||
&mut layout_context,
|
&mut layout_context,
|
||||||
|
@ -1375,11 +1382,17 @@ impl LayoutThread {
|
||||||
|
|
||||||
fn perform_post_style_recalc_layout_passes(
|
fn perform_post_style_recalc_layout_passes(
|
||||||
&self,
|
&self,
|
||||||
fragment_tree: &FragmentTreeRoot,
|
fragment_tree: Arc<FragmentTreeRoot>,
|
||||||
reflow_goal: &ReflowGoal,
|
reflow_goal: &ReflowGoal,
|
||||||
document: Option<&ServoLayoutDocument>,
|
document: Option<&ServoLayoutDocument>,
|
||||||
context: &mut LayoutContext,
|
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() {
|
if !reflow_goal.needs_display() {
|
||||||
// Defer the paint step until the next ForDisplay.
|
// Defer the paint step until the next ForDisplay.
|
||||||
//
|
//
|
||||||
|
@ -1390,6 +1403,7 @@ impl LayoutThread {
|
||||||
.needs_paint_from_layout();
|
.needs_paint_from_layout();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(document) = document {
|
if let Some(document) = document {
|
||||||
document.will_paint();
|
document.will_paint();
|
||||||
}
|
}
|
||||||
|
@ -1432,6 +1446,10 @@ impl LayoutThread {
|
||||||
display_list.wr.finalize(),
|
display_list.wr.finalize(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if self.trace_layout {
|
||||||
|
layout_debug::end_trace(self.generation.get());
|
||||||
|
}
|
||||||
|
|
||||||
self.generation.set(self.generation.get() + 1);
|
self.generation.set(self.generation.get() + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,3 +27,7 @@
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tree-collapse {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
339
etc/layout_viewer/viewer_2020.html
Normal file
339
etc/layout_viewer/viewer_2020.html
Normal file
|
@ -0,0 +1,339 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>Servo Layout Debugger</title>
|
||||||
|
|
||||||
|
<!-- Bootstrap -->
|
||||||
|
<link href="css/bootstrap.min.css" rel="stylesheet" />
|
||||||
|
|
||||||
|
<!-- Treeview -->
|
||||||
|
<link href="css/bootstrap-treeview.min.css" rel="stylesheet" />
|
||||||
|
|
||||||
|
<!-- JSDiffPatch -->
|
||||||
|
<link href="css/formatters/html.css" rel="stylesheet" />
|
||||||
|
|
||||||
|
<!-- Custom -->
|
||||||
|
<link href="css/main.css" rel="stylesheet" />
|
||||||
|
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
|
||||||
|
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container" role="main">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<h1>Servo Layout Viewer</h1>
|
||||||
|
<p>
|
||||||
|
Check the
|
||||||
|
<a
|
||||||
|
href="https://github.com/servo/servo/blob/master/etc/layout_viewer/README.md"
|
||||||
|
>README</a
|
||||||
|
>
|
||||||
|
for instructions.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="well">
|
||||||
|
<input type="file" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div id="trace-tree"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<ul id="trace-list" class="list-group"></ul>
|
||||||
|
</div>
|
||||||
|
</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
|
||||||
|
<a
|
||||||
|
id="box-tree-collapse"
|
||||||
|
class="tree-collapse"
|
||||||
|
data-collapsed="0"
|
||||||
|
></a>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body" id="box-tree"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div id="box-diffs"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
Fragment Tree
|
||||||
|
<a
|
||||||
|
id="fragment-tree-collapse"
|
||||||
|
class="tree-collapse"
|
||||||
|
data-collapsed="0"
|
||||||
|
></a>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body" id="fragment-tree"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div id="fragment-diffs"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- jQuery -->
|
||||||
|
<script src="js/jquery.min.js"></script>
|
||||||
|
<!-- Bootstrap -->
|
||||||
|
<script src="js/bootstrap.min.js"></script>
|
||||||
|
<!-- Treeview -->
|
||||||
|
<script src="js/bootstrap-treeview.min.js"></script>
|
||||||
|
<!-- JSDiffPatch -->
|
||||||
|
<script src="js/bundle.min.js"></script>
|
||||||
|
<script src="js/formatters.min.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
info
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function box_tree_from_bfc(bfc) {
|
||||||
|
const { contains_floats, contents } = bfc;
|
||||||
|
let block_container = Object.values(contents)[0];
|
||||||
|
return {
|
||||||
|
text: "BlockFormattingContext",
|
||||||
|
info: {
|
||||||
|
contains_floats
|
||||||
|
},
|
||||||
|
nodes: [box_tree_from_container(contents)]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function create_fragment_tree(root) {
|
||||||
|
let fragment = Object.values(root)[0];
|
||||||
|
let node = {
|
||||||
|
text: Object.keys(root)[0],
|
||||||
|
id: fragment.debug_id,
|
||||||
|
href: "#diff-" + fragment.debug_id
|
||||||
|
};
|
||||||
|
|
||||||
|
if (fragment.children) {
|
||||||
|
let children = [];
|
||||||
|
for (let i = 0; i < fragment.children.length; ++i) {
|
||||||
|
children.push(create_fragment_tree(fragment.children[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (children.length > 0) {
|
||||||
|
node.nodes = children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node.info = Object.assign({}, fragment);
|
||||||
|
delete node.info.children;
|
||||||
|
delete node.info.debug_id;
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
function flatten_trace(trace_node) {
|
||||||
|
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)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function create_trace_tree_node(trace_node) {
|
||||||
|
const trace = flatten_trace(trace_node.pre);
|
||||||
|
|
||||||
|
let tree_node = {
|
||||||
|
text: trace_node.name,
|
||||||
|
icon: "dummy",
|
||||||
|
box_tree: trace.box_tree,
|
||||||
|
fragment_tree: trace.fragment_tree
|
||||||
|
};
|
||||||
|
|
||||||
|
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) {
|
||||||
|
tree_node.nodes = children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tree_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
function new_data_loaded(data) {
|
||||||
|
jsondiffpatch.formatters.html.hideUnchanged();
|
||||||
|
|
||||||
|
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();
|
||||||
|
$("#trace-tree")
|
||||||
|
.treeview(true)
|
||||||
|
.revealNode(node);
|
||||||
|
|
||||||
|
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({}, data.info);
|
||||||
|
|
||||||
|
const json = jsondiffpatch.formatters.html.format(delta, data.info);
|
||||||
|
|
||||||
|
$(`#${tree}-diffs`).append(
|
||||||
|
"<div class='panel panel-default'><div class='panel-heading'>" +
|
||||||
|
data.text +
|
||||||
|
"</div><div class='panel-body'>" +
|
||||||
|
json +
|
||||||
|
"</div></div>"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const on_fragment_tree_node_selected = on_tree_node_selected(
|
||||||
|
"fragment"
|
||||||
|
);
|
||||||
|
const on_box_tree_node_selected = on_tree_node_selected("box");
|
||||||
|
|
||||||
|
$("#fragment-tree").treeview({
|
||||||
|
data: [node.fragment_tree],
|
||||||
|
levels: 100,
|
||||||
|
enableLinks: false,
|
||||||
|
emptyIcon: "glyphicon glyphicon-unchecked hidden-glyphicon",
|
||||||
|
onNodeSelected: on_fragment_tree_node_selected
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#box-tree").treeview({
|
||||||
|
data: [node.box_tree],
|
||||||
|
levels: 100,
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#trace-tree")
|
||||||
|
.treeview(true)
|
||||||
|
.selectNode(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
let upload = document.getElementsByTagName("input")[0];
|
||||||
|
upload.onchange = function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
let file = upload.files[0],
|
||||||
|
reader = new FileReader();
|
||||||
|
reader.onload = function(event) {
|
||||||
|
new_data_loaded(JSON.parse(event.target.result));
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsText(file);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Add table
Add a link
Reference in a new issue