LibWeb: Add debug tracing for FormattingContext::run() calls

Add optional tracing that prints a tree visualization of formatting
context `run()` invocations. This is useful for debugging layout issues
where you need to understand the nesting and order of layout passes,
or why a box receives unexpected available space.

Example output:
```
├─ BFC <Viewport<#document>> run(definite(800) x definite(600))
│ ├─ BFC <BlockContainer<HTML>> run(definite(800) x indefinite)
│ │ ├─ IFC <BlockContainer(anonymous)> run(definite(800) x indefinite)
│ │ ├─ GFC <Box<DIV.grid>> run(definite(800) x indefinite)
│ │ │ ├─ BFC <BlockContainer<DIV.item>> run(definite(400) x indefinite)
```
This commit is contained in:
Aliaksandr Kalenik 2026-01-08 19:25:36 +01:00 committed by Andreas Kling
parent a6360a9792
commit ccffc42d6a
10 changed files with 66 additions and 9 deletions

View file

@ -82,6 +82,10 @@
# cmakedefine01 FLAC_ENCODER_DEBUG
#endif
#ifndef FORMATTING_CONTEXT_TRACE_DEBUG
# cmakedefine01 FORMATTING_CONTEXT_TRACE_DEBUG
#endif
#ifndef GENERATE_DEBUG
# cmakedefine01 GENERATE_DEBUG
#endif

View file

@ -313,19 +313,11 @@ void dump_tree(StringBuilder& builder, Layout::Node const& layout_node, bool sho
if (auto formatting_context_type = Layout::FormattingContext::formatting_context_type_created_by_box(box); formatting_context_type.has_value()) {
switch (formatting_context_type.value()) {
case Layout::FormattingContext::Type::Block:
builder.appendff(" [{}BFC{}]", formatting_context_color_on, color_off);
break;
case Layout::FormattingContext::Type::Flex:
builder.appendff(" [{}FFC{}]", formatting_context_color_on, color_off);
break;
case Layout::FormattingContext::Type::Grid:
builder.appendff(" [{}GFC{}]", formatting_context_color_on, color_off);
break;
case Layout::FormattingContext::Type::Table:
builder.appendff(" [{}TFC{}]", formatting_context_color_on, color_off);
break;
case Layout::FormattingContext::Type::SVG:
builder.appendff(" [{}SVG{}]", formatting_context_color_on, color_off);
builder.appendff(" [{}{}{}]", formatting_context_color_on, Layout::FormattingContext::type_name(formatting_context_type.value()), color_off);
break;
default:
break;

View file

@ -74,6 +74,7 @@ static bool margins_collapse_through(Box const& box, LayoutState& state)
void BlockFormattingContext::run(AvailableSpace const& available_space)
{
FORMATTING_CONTEXT_TRACE();
// https://drafts.csswg.org/css-multicol-2/#the-multi-column-model
auto root_state = m_state.get(root());
auto column_count = determine_used_value_for_column_count(root_state.content_width());

View file

@ -66,6 +66,7 @@ void FlexFormattingContext::run(AvailableSpace const& available_space)
return;
}
FORMATTING_CONTEXT_TRACE();
m_available_space = available_space;
// 1. Generate anonymous flex items

View file

@ -22,6 +22,10 @@ template<typename T>
}
class FormattingContext {
#if FORMATTING_CONTEXT_TRACE_DEBUG
friend class FormattingContextTracer;
#endif
public:
virtual ~FormattingContext();
@ -36,6 +40,29 @@ public:
InternalDummy, // Internal hack formatting context for unimplemented things. FIXME: Get rid of this.
};
static constexpr StringView type_name(Type type)
{
switch (type) {
case Type::Block:
return "BFC"sv;
case Type::Inline:
return "IFC"sv;
case Type::Flex:
return "FFC"sv;
case Type::Grid:
return "GFC"sv;
case Type::Table:
return "TFC"sv;
case Type::SVG:
return "SVG"sv;
case Type::InternalReplaced:
return "Replaced"sv;
case Type::InternalDummy:
return "Dummy"sv;
}
VERIFY_NOT_REACHED();
}
virtual void run(AvailableSpace const&) = 0;
// These functions return the automatic content dimensions of the context's root box.
@ -166,4 +193,31 @@ protected:
LayoutState& m_state;
};
#if FORMATTING_CONTEXT_TRACE_DEBUG
class FormattingContextTracer {
public:
FormattingContextTracer(FormattingContext const& fc, AvailableSpace const& available_space)
{
StringBuilder indent_builder;
for (int i = 0; i < s_depth; ++i)
indent_builder.append("| "sv);
auto intrinsic_marker = fc.m_layout_mode == LayoutMode::IntrinsicSizing ? " [intrinsic]"sv : ""sv;
dbgln("{}|- {} <{}> run({}){}", indent_builder.string_view(), FormattingContext::type_name(fc.m_type), fc.m_context_box->debug_description(), available_space, intrinsic_marker);
++s_depth;
}
~FormattingContextTracer()
{
--s_depth;
}
private:
inline static int s_depth = 0;
};
# define FORMATTING_CONTEXT_TRACE() FormattingContextTracer _formatting_context_tracer(*this, available_space)
#else
# define FORMATTING_CONTEXT_TRACE()
#endif
}

View file

@ -2044,6 +2044,7 @@ void GridFormattingContext::run(AvailableSpace const& available_space)
return;
}
FORMATTING_CONTEXT_TRACE();
m_available_space = available_space;
init_grid_lines(GridDimension::Column);

View file

@ -82,6 +82,7 @@ CSSPixels InlineFormattingContext::automatic_content_height() const
void InlineFormattingContext::run(AvailableSpace const& available_space)
{
FORMATTING_CONTEXT_TRACE();
VERIFY(containing_block().children_are_inline());
m_available_space = available_space;
generate_line_boxes();

View file

@ -175,6 +175,7 @@ static bool is_container_element(Node const& node)
void SVGFormattingContext::run(AvailableSpace const& available_space)
{
FORMATTING_CONTEXT_TRACE();
// NOTE: SVG doesn't have a "formatting context" in the spec, but this is the most
// obvious way to drive SVG layout in our engine at the moment.

View file

@ -1672,6 +1672,7 @@ void TableFormattingContext::parent_context_did_dimension_child_root_box()
void TableFormattingContext::run(AvailableSpace const& available_space)
{
FORMATTING_CONTEXT_TRACE();
m_available_space = available_space;
auto total_captions_height = run_caption_layout(CSS::CaptionSide::Top);

View file

@ -16,6 +16,7 @@ set(EDITOR_DEBUG ON)
set(EMOJI_DEBUG ON)
set(FILE_WATCHER_DEBUG ON)
set(FLAC_ENCODER_DEBUG ON)
set(FORMATTING_CONTEXT_TRACE_DEBUG ON)
set(GENERATE_DEBUG ON)
set(GHASH_PROCESS_DEBUG ON)
set(GIF_DEBUG ON)