LibWeb: Restrict throwaway layout states to their subtree

Throwaway LayoutState instances used for intrinsic sizing should not
access nodes outside the laid-out subtree. Make this explicit by setting
a subtree root on each throwaway state, pre-populating only the
immediate containing block, and using try_get() for any ancestor
lookups — treating unavailable ancestors as indefinite rather than
silently populating them with incorrectly-resolved values.
This commit is contained in:
Aliaksandr Kalenik 2026-02-27 08:59:19 +01:00 committed by Alexander Kalenik
parent 703bef336a
commit edf1ca8f19
9 changed files with 145 additions and 68 deletions

View file

@ -1358,8 +1358,7 @@ void Document::mark_svg_root_as_needing_relayout(Layout::SVGSVGBox& svg_root)
static void relayout_svg_root(Layout::SVGSVGBox& svg_root)
{
Layout::LayoutState layout_state;
layout_state.set_subtree_root(svg_root);
Layout::LayoutState layout_state(svg_root);
// Pre-populate the svg_root itself.
if (auto const* paintable = svg_root.paintable_box())

View file

@ -609,7 +609,9 @@ void BlockFormattingContext::layout_inline_children(BlockContainer const& block_
auto used_width_px = context.automatic_content_width();
// https://www.w3.org/TR/css-sizing-3/#sizing-values
// Percentages are resolved against the width/height, as appropriate, of the boxs containing block.
auto containing_block_width = m_state.get(*block_container.containing_block()).content_width();
CSSPixels containing_block_width = 0;
if (auto const* containing_block_used_values = m_state.try_get(*block_container.containing_block()))
containing_block_width = containing_block_used_values->content_width();
auto available_width = AvailableSize::make_definite(containing_block_width);
if (!should_treat_max_width_as_none(block_container, available_space.width)) {
auto max_width_px = calculate_inner_width(block_container, available_width, block_container.computed_values().max_width());
@ -714,13 +716,20 @@ CSSPixels BlockFormattingContext::compute_auto_height_for_block_level_element(Bo
static CSSPixels containing_block_height_to_resolve_percentage_in_quirks_mode(Box const& box, LayoutState const& state)
{
auto content_height_of = [&](NodeWithStyleAndBoxModelMetrics const& node) -> CSSPixels {
auto const* node_used_values = state.try_get(node);
if (!node_used_values)
return 0;
return node_used_values->content_height();
};
// https://quirks.spec.whatwg.org/#the-percentage-height-calculation-quirk
auto containing_block = box.containing_block();
while (containing_block) {
// 1. Let element be the nearest ancestor containing block of element, if there is one.
// Otherwise, return the initial containing block.
if (containing_block->is_viewport()) {
return state.get(*containing_block).content_height();
return content_height_of(*containing_block);
}
// 2. If element has a computed value of the display property that is table-cell, then return a
@ -732,13 +741,13 @@ static CSSPixels containing_block_height_to_resolve_percentage_in_quirks_mode(Bo
// 3. If element has a computed value of the height property that is not auto, then return element.
if (!containing_block->computed_values().height().is_auto()) {
return state.get(*containing_block).content_height();
return content_height_of(*containing_block);
}
// 4. If element has a computed value of the position property that is absolute, or if element is a
// not a block container or a table wrapper box, then return element.
if (containing_block->is_absolutely_positioned() || !is<BlockContainer>(*containing_block) || is<TableWrapper>(*containing_block)) {
return state.get(*containing_block).content_height();
return content_height_of(*containing_block);
}
// 5. Jump to the first step.
@ -879,7 +888,9 @@ void BlockFormattingContext::layout_block_level_box(Box const& box, BlockContain
// For boxes with auto height but non-auto min-height, we need to determine if the content height is less than
// min-height. If so, we run layout with min-height as the available height.
if (should_treat_height_as_auto(box, available_space) && !box.computed_values().min_height().is_auto()) {
LayoutState throwaway_state;
LayoutState throwaway_state(box);
throwaway_state.populate_node_from(m_state, *box.containing_block());
auto measuring_context = create_independent_formatting_context_if_needed(throwaway_state, LayoutMode::IntrinsicSizing, box);
measuring_context->run(inner_available_space);
auto content_height = measuring_context->automatic_content_height();

View file

@ -312,9 +312,12 @@ CSSPixelSize FormattingContext::solve_replaced_size_constraint(CSSPixels input_w
// https://www.w3.org/TR/CSS22/visudet.html#min-max-widths
auto const& containing_block = *box.non_anonymous_containing_block();
auto const& containing_block_state = m_state.get(containing_block);
auto width_of_containing_block = containing_block_state.content_width();
auto height_of_containing_block = containing_block_state.content_height();
CSSPixels width_of_containing_block = 0;
CSSPixels height_of_containing_block = 0;
if (auto const* containing_block_used_values = m_state.try_get(containing_block)) {
width_of_containing_block = containing_block_used_values->content_width();
height_of_containing_block = containing_block_used_values->content_height();
}
auto min_width = box.computed_values().min_width().is_auto() ? 0 : box.computed_values().min_width().to_px(box, width_of_containing_block);
auto specified_max_width = should_treat_max_width_as_none(box, available_space.width) ? input_width : box.computed_values().max_width().to_px(box, width_of_containing_block);
@ -458,7 +461,8 @@ CSSPixels FormattingContext::compute_table_box_width_inside_table_wrapper(Box co
});
VERIFY(table_box.has_value());
LayoutState throwaway_state;
LayoutState throwaway_state(box);
throwaway_state.populate_node_from(m_state, *box.containing_block());
auto& table_box_state = throwaway_state.get_mutable(*table_box);
auto const& table_box_computed_values = table_box->computed_values();
@ -467,13 +471,6 @@ CSSPixels FormattingContext::compute_table_box_width_inside_table_wrapper(Box co
table_box_state.padding_left = table_box_computed_values.padding().left().to_px_or_zero(*table_box, width_of_containing_block);
table_box_state.padding_right = table_box_computed_values.padding().right().to_px_or_zero(*table_box, width_of_containing_block);
// Propagate the containing block width so percentage table widths can resolve instead of being treated as "auto".
if (auto wrapper_containing_block = box.containing_block()) {
auto const& containing_block_state = m_state.get(*wrapper_containing_block);
if (containing_block_state.has_definite_width())
throwaway_state.get_mutable(*wrapper_containing_block).set_content_width(containing_block_state.content_width());
}
auto context = make<TableFormattingContext>(throwaway_state, LayoutMode::IntrinsicSizing, *table_box, this);
context->run_until_width_calculation(m_state.get(*table_box).available_inner_space_or_constraints_from(available_space));
@ -499,7 +496,8 @@ CSSPixels FormattingContext::compute_table_box_height_inside_table_wrapper(Box c
// table-wrapper can't have borders or paddings but it might have margin taken from table-root.
auto available_height = height_of_containing_block - margin_top - margin_bottom;
LayoutState throwaway_state;
LayoutState throwaway_state(box);
throwaway_state.populate_node_from(m_state, *box.containing_block());
auto context = create_independent_formatting_context_if_needed(throwaway_state, LayoutMode::IntrinsicSizing, box);
VERIFY(context);
@ -665,7 +663,9 @@ CSSPixels FormattingContext::compute_height_for_replaced_element(Box const& box,
// 10.6.6 Floating replaced elements
// 10.6.10 'inline-block' replaced elements in normal flow
auto height_of_containing_block = m_state.get(*box.non_anonymous_containing_block()).content_height();
CSSPixels height_of_containing_block = 0;
if (auto const* containing_block_used_values = m_state.try_get(*box.non_anonymous_containing_block()))
height_of_containing_block = containing_block_used_values->content_height();
auto computed_width = should_treat_width_as_auto(box, available_space) ? CSS::Size::make_auto() : box.computed_values().width();
auto computed_height = should_treat_height_as_auto(box, available_space) ? CSS::Size::make_auto() : box.computed_values().height();
@ -1687,7 +1687,8 @@ CSSPixels FormattingContext::calculate_min_content_width(Layout::Box const& box)
if (cache.has_value())
return cache.value();
LayoutState throwaway_state;
LayoutState throwaway_state(box);
throwaway_state.populate_node_from(m_state, *box.containing_block());
auto& box_state = throwaway_state.get_mutable(box);
box_state.width_constraint = SizeConstraint::MinContent;
@ -1721,7 +1722,8 @@ CSSPixels FormattingContext::calculate_max_content_width(Layout::Box const& box)
if (cache.has_value())
return cache.value();
LayoutState throwaway_state;
LayoutState throwaway_state(box);
throwaway_state.populate_node_from(m_state, *box.containing_block());
auto const& actual_box_state = m_state.get(box);
@ -1770,7 +1772,8 @@ CSSPixels FormattingContext::calculate_min_content_height(Layout::Box const& box
if (cache.has_value())
return cache.value();
LayoutState throwaway_state;
LayoutState throwaway_state(box);
throwaway_state.populate_node_from(m_state, *box.containing_block());
auto& box_state = throwaway_state.get_mutable(box);
box_state.height_constraint = SizeConstraint::MinContent;
@ -1802,7 +1805,8 @@ CSSPixels FormattingContext::calculate_max_content_height(Layout::Box const& box
if (cache_slot.has_value())
return cache_slot.value();
LayoutState throwaway_state;
LayoutState throwaway_state(box);
throwaway_state.populate_node_from(m_state, *box.containing_block());
auto& box_state = throwaway_state.get_mutable(box);
box_state.height_constraint = SizeConstraint::MaxContent;
@ -1892,8 +1896,10 @@ CSSPixels FormattingContext::calculate_inner_height(Box const& box, AvailableSpa
containing_block = containing_block->containing_block();
}
if (containing_block && m_state.get(*containing_block).has_definite_height())
height_of_containing_block = m_state.get(*containing_block).content_height();
if (auto const* containing_block_used_values = containing_block ? m_state.try_get(*containing_block) : nullptr) {
if (containing_block_used_values->has_definite_height())
height_of_containing_block = containing_block_used_values->content_height();
}
}
auto& computed_values = box.computed_values();
@ -2034,7 +2040,12 @@ bool FormattingContext::should_treat_height_as_auto(Box const& box, AvailableSpa
auto containing_block = box.containing_block();
while (containing_block && containing_block->is_anonymous())
containing_block = containing_block->containing_block();
if (containing_block && !m_state.get(*containing_block).has_definite_height())
if (!containing_block)
return true;
auto const* containing_block_used_values = m_state.try_get(*containing_block);
if (!containing_block_used_values)
return true;
if (!containing_block_used_values->has_definite_height())
return true;
}
}
@ -2261,7 +2272,10 @@ bool FormattingContext::should_treat_max_width_as_none(Box const& box, Available
return true;
return false;
}
if (!m_state.get(*box.non_anonymous_containing_block()).has_definite_width())
auto const* containing_block_used_values = m_state.try_get(*box.non_anonymous_containing_block());
if (!containing_block_used_values)
return true;
if (!containing_block_used_values->has_definite_width())
return true;
}
if (max_width.is_fit_content() && available_width.is_intrinsic_sizing_constraint())
@ -2285,7 +2299,10 @@ bool FormattingContext::should_treat_max_height_as_none(Box const& box, Availabl
if (max_height.contains_percentage()) {
if (available_height.is_min_content())
return false;
if (!m_state.get(*box.non_anonymous_containing_block()).has_definite_height())
auto const* containing_block_used_values = m_state.try_get(*box.non_anonymous_containing_block());
if (!containing_block_used_values)
return true;
if (!containing_block_used_values->has_definite_height())
return true;
}
if (max_height.is_fit_content() && available_height.is_intrinsic_sizing_constraint())

View file

@ -20,6 +20,11 @@
namespace Web::Layout {
LayoutState::LayoutState(NodeWithStyle const& subtree_root)
: m_subtree_root(&subtree_root)
{
}
LayoutState::~LayoutState()
{
}
@ -52,6 +57,18 @@ LayoutState::UsedValues& LayoutState::populate_from_paintable(NodeWithStyle cons
return used_values;
}
LayoutState::UsedValues& LayoutState::populate_node_from(LayoutState const& source, NodeWithStyle const& node)
{
VERIFY(m_subtree_root);
auto index = node.layout_index();
VERIFY(!m_used_values_store.get(index));
auto& values = m_used_values_store.allocate(index);
values = source.get(node);
values.m_containing_block_used_values = nullptr;
return values;
}
LayoutState::UsedValues& LayoutState::ensure_used_values_for(NodeWithStyle const& node)
{
auto index = node.layout_index();
@ -59,10 +76,16 @@ LayoutState::UsedValues& LayoutState::ensure_used_values_for(NodeWithStyle const
if (auto* used_values = m_used_values_store.get(index))
return *used_values;
// During subtree layout, all nodes outside the subtree must be pre-populated before running the formatting context
// During subtree layout, only the subtree root and nodes inside the subtree are allowed.
VERIFY(!m_subtree_root || m_subtree_root == &node || m_subtree_root->is_inclusive_ancestor_of(node));
auto const* containing_block_used_values = (node.is_viewport() || m_subtree_root == &node) ? nullptr : &get(*node.containing_block());
UsedValues const* containing_block_used_values = nullptr;
if (m_subtree_root == &node) {
// For the subtree root, ancestor values are not available in the throwaway state.
containing_block_used_values = try_get(*node.containing_block());
} else if (!node.is_viewport()) {
containing_block_used_values = &get(*node.containing_block());
}
auto& used_values = m_used_values_store.allocate(index);
used_values.set_node(node, containing_block_used_values);

View file

@ -278,19 +278,20 @@ struct LayoutState {
Optional<StaticPositionRect> m_static_position_rect;
};
LayoutState() = default;
explicit LayoutState(NodeWithStyle const& subtree_root);
~LayoutState();
// Commits the used values produced by layout and builds a paintable tree.
void commit(Box& root);
void set_subtree_root(NodeWithStyle const& node) { m_subtree_root = &node; }
void ensure_capacity(u32 node_count);
UsedValues& get_mutable(NodeWithStyle const&);
UsedValues const& get(NodeWithStyle const&) const;
UsedValues& populate_from_paintable(NodeWithStyle const&, Painting::PaintableBox const&);
UsedValues& populate_node_from(LayoutState const& source, NodeWithStyle const& node);
UsedValues const* try_get(NodeWithStyle const&) const;
UsedValues* try_get_mutable(NodeWithStyle const&);

View file

@ -21,6 +21,22 @@ TableFormattingContext::TableFormattingContext(LayoutState& state, LayoutMode la
TableFormattingContext::~TableFormattingContext() = default;
CSSPixels TableFormattingContext::table_wrapper_containing_block_width() const
{
auto const* containing_block_used_values = m_state.try_get(*table_wrapper().containing_block());
if (!containing_block_used_values)
return 0;
return containing_block_used_values->content_width();
}
CSSPixels TableFormattingContext::table_wrapper_containing_block_height() const
{
auto const* containing_block_used_values = m_state.try_get(*table_wrapper().containing_block());
if (!containing_block_used_values)
return 0;
return containing_block_used_values->content_height();
}
static inline bool is_table_column_group(Box const& box)
{
return box.display().is_table_column_group();
@ -119,16 +135,17 @@ void TableFormattingContext::compute_constrainedness()
void TableFormattingContext::compute_cell_measures()
{
// Implements https://www.w3.org/TR/css-tables-3/#computing-cell-measures.
auto const& containing_block = m_state.get(*table_wrapper().containing_block());
auto containing_block_width = table_wrapper_containing_block_width();
auto containing_block_height = table_wrapper_containing_block_height();
compute_constrainedness();
for (auto& cell : m_cells) {
auto const& computed_values = cell.box->computed_values();
CSSPixels padding_top = computed_values.padding().top().to_px_or_zero(cell.box, containing_block.content_height());
CSSPixels padding_bottom = computed_values.padding().bottom().to_px_or_zero(cell.box, containing_block.content_height());
CSSPixels padding_left = computed_values.padding().left().to_px_or_zero(cell.box, containing_block.content_width());
CSSPixels padding_right = computed_values.padding().right().to_px_or_zero(cell.box, containing_block.content_width());
CSSPixels padding_top = computed_values.padding().top().to_px_or_zero(cell.box, containing_block_height);
CSSPixels padding_bottom = computed_values.padding().bottom().to_px_or_zero(cell.box, containing_block_height);
CSSPixels padding_left = computed_values.padding().left().to_px_or_zero(cell.box, containing_block_width);
CSSPixels padding_right = computed_values.padding().right().to_px_or_zero(cell.box, containing_block_width);
auto const& cell_state = m_state.get(cell.box);
auto use_collapsing_borders_model = cell_state.override_borders_data().has_value();
@ -144,11 +161,11 @@ void TableFormattingContext::compute_cell_measures()
auto max_content_height = calculate_max_content_height(cell.box, min_content_width);
// The outer min-content height of a table-cell is max(min-height, min-content height) adjusted by the cell intrinsic offsets.
auto min_height = computed_values.min_height().to_px(cell.box, containing_block.content_height());
auto min_height = computed_values.min_height().to_px(cell.box, containing_block_height);
auto cell_intrinsic_height_offsets = padding_top + padding_bottom + border_top + border_bottom;
cell.outer_min_height = max(min_height, min_content_height) + cell_intrinsic_height_offsets;
// The outer min-content width of a table-cell is max(min-width, min-content width) adjusted by the cell intrinsic offsets.
auto min_width = computed_values.min_width().to_px(cell.box, containing_block.content_width());
auto min_width = computed_values.min_width().to_px(cell.box, containing_block_width);
auto cell_intrinsic_width_offsets = padding_left + padding_right + border_left + border_right;
// For fixed mode, according to https://www.w3.org/TR/css-tables-3/#computing-column-measures:
// The min-content and max-content width of cells is considered zero unless they are directly specified as a length-percentage,
@ -161,8 +178,8 @@ void TableFormattingContext::compute_cell_measures()
// The tables specification isn't explicit on how to use the height and max-height CSS properties in the outer max-content formulas.
// However, during this early phase we don't have enough information to resolve percentage sizes yet and the formulas for outer sizes
// in the specification give enough clues to pick defaults in a way that makes sense.
auto height = computed_values.height().is_length() ? computed_values.height().to_px(cell.box, containing_block.content_height()) : 0;
auto max_height = computed_values.max_height().is_length() ? computed_values.max_height().to_px(cell.box, containing_block.content_height()) : CSSPixels::max();
auto height = computed_values.height().is_length() ? computed_values.height().to_px(cell.box, containing_block_height) : 0;
auto max_height = computed_values.max_height().is_length() ? computed_values.max_height().to_px(cell.box, containing_block_height) : CSSPixels::max();
if (m_rows[cell.row_index].is_constrained) {
// The outer max-content height of a table-cell in a constrained row is
// max(min-height, height, min-content height, min(max-height, height)) adjusted by the cell intrinsic offsets.
@ -175,8 +192,8 @@ void TableFormattingContext::compute_cell_measures()
}
// See the explanation for height and max_height above.
auto width = computed_values.width().is_length() ? computed_values.width().to_px(cell.box, containing_block.content_width()) : 0;
auto max_width = computed_values.max_width().is_length() ? computed_values.max_width().to_px(cell.box, containing_block.content_width()) : CSSPixels::max();
auto width = computed_values.width().is_length() ? computed_values.width().to_px(cell.box, containing_block_width) : 0;
auto max_width = computed_values.max_width().is_length() ? computed_values.max_width().to_px(cell.box, containing_block_width) : CSSPixels::max();
if (use_fixed_mode_layout() && !width_is_specified_length_or_percentage) {
continue;
}
@ -195,15 +212,16 @@ void TableFormattingContext::compute_cell_measures()
void TableFormattingContext::compute_outer_content_sizes()
{
auto const& containing_block = m_state.get(*table_wrapper().containing_block());
auto containing_block_width = table_wrapper_containing_block_width();
auto containing_block_height = table_wrapper_containing_block_height();
size_t column_index = 0;
TableGrid::for_each_child_box_matching(table_box(), is_table_column_group, [&](auto& column_group_box) {
TableGrid::for_each_child_box_matching(column_group_box, is_table_column, [&](auto& column_box) {
auto const& computed_values = column_box.computed_values();
auto min_width = computed_values.min_width().to_px(column_box, containing_block.content_width());
auto max_width = computed_values.max_width().is_length() ? computed_values.max_width().to_px(column_box, containing_block.content_width()) : CSSPixels::max();
auto width = computed_values.width().to_px(column_box, containing_block.content_width());
auto min_width = computed_values.min_width().to_px(column_box, containing_block_width);
auto max_width = computed_values.max_width().is_length() ? computed_values.max_width().to_px(column_box, containing_block_width) : CSSPixels::max();
auto width = computed_values.width().to_px(column_box, containing_block_width);
// The outer min-content width of a table-column or table-column-group is max(min-width, width).
m_columns[column_index].min_size = max(min_width, width);
// The outer max-content width of a table-column or table-column-group is max(min-width, min(max-width, width)).
@ -216,9 +234,9 @@ void TableFormattingContext::compute_outer_content_sizes()
for (auto& row : m_rows) {
auto const& computed_values = row.box->computed_values();
auto min_height = computed_values.min_height().to_px(row.box, containing_block.content_height());
auto max_height = computed_values.max_height().is_length() ? computed_values.max_height().to_px(row.box, containing_block.content_height()) : CSSPixels::max();
auto height = computed_values.height().to_px(row.box, containing_block.content_height());
auto min_height = computed_values.min_height().to_px(row.box, containing_block_height);
auto max_height = computed_values.max_height().is_length() ? computed_values.max_height().to_px(row.box, containing_block_height) : CSSPixels::max();
auto height = computed_values.height().to_px(row.box, containing_block_height);
// The outer min-content height of a table-row or table-row-group is max(min-height, height).
row.min_size = max(min_height, height);
// The outer max-content height of a table-row or table-row-group is max(min-height, min(max-height, height)).
@ -229,12 +247,12 @@ void TableFormattingContext::compute_outer_content_sizes()
template<>
void TableFormattingContext::initialize_table_measures<TableFormattingContext::Row>()
{
auto const& containing_block = m_state.get(*table_wrapper().containing_block());
auto containing_block_height = table_wrapper_containing_block_height();
for (auto& cell : m_cells) {
auto const& computed_values = cell.box->computed_values();
if (cell.row_span == 1) {
auto specified_height = computed_values.height().to_px(cell.box, containing_block.content_height());
auto specified_height = computed_values.height().to_px(cell.box, containing_block_height);
// https://www.w3.org/TR/css-tables-3/#row-layout makes specified cell height part of the initialization formula for row table measures:
// This is done by running the same algorithm as the column measurement, with the span=1 value being initialized (for min-content) with
// the largest of the resulting height of the previous row layout, the height specified on the corresponding table-row (if any), and
@ -464,9 +482,15 @@ CSSPixels TableFormattingContext::compute_capmin()
return capmin;
}
static bool width_is_auto_relative_to_state(CSS::Size const& width, LayoutState::UsedValues const& state)
static bool width_is_auto_or_indefinite_percentage(CSS::Size const& width, LayoutState::UsedValues const* containing_block_state)
{
return width.is_auto() || (width.contains_percentage() && !state.has_definite_width());
if (width.is_auto())
return true;
if (width.contains_percentage()) {
if (!containing_block_state || !containing_block_state->has_definite_width())
return true;
}
return false;
}
void TableFormattingContext::compute_table_width()
@ -481,8 +505,7 @@ void TableFormattingContext::compute_table_width()
// Percentages on 'width' and 'height' on the table are relative to the table wrapper box's containing block,
// not the table wrapper box itself.
auto const& containing_block_state = m_state.get(*table_wrapper().containing_block());
CSSPixels width_of_table_wrapper_containing_block = containing_block_state.content_width();
CSSPixels width_of_table_wrapper_containing_block = table_wrapper_containing_block_width();
// Compute undistributable space due to border spacing: https://www.w3.org/TR/css-tables-3/#computing-undistributable-space.
auto undistributable_space = (m_columns.size() + 1) * border_spacing_horizontal();
@ -514,7 +537,7 @@ void TableFormattingContext::compute_table_width()
used_width = grid_min;
} else if (m_available_space->width.is_max_content()) {
used_width = grid_max;
} else if (width_is_auto_relative_to_state(computed_values.width(), containing_block_state)) {
} else if (width_is_auto_or_indefinite_percentage(computed_values.width(), m_state.try_get(*table_wrapper().containing_block()))) {
// If the table-root has 'width: auto', the used width is the greater of
// min(GRIDMAX, the tables containing block width), the used min-width of the table.
if (width_of_table_containing_block.is_definite())
@ -948,7 +971,7 @@ void TableFormattingContext::compute_table_height()
// If the table has a height property with a value other than auto, it is treated as a minimum height for the
// table grid, and will eventually be distributed to the height of the rows if their collective minimum height
// ends up smaller than this number.
CSSPixels height_of_table_containing_block = m_state.get(*table_wrapper().containing_block()).content_height();
CSSPixels height_of_table_containing_block = table_wrapper_containing_block_height();
auto specified_table_height = table_box().computed_values().height().to_px(table_box(), height_of_table_containing_block);
if (table_box().computed_values().box_sizing() == CSS::BoxSizing::BorderBox) {
auto const& table_state = m_state.get(table_box());

View file

@ -78,6 +78,9 @@ private:
bool use_fixed_mode_layout() const;
CSSPixels table_wrapper_containing_block_width() const;
CSSPixels table_wrapper_containing_block_height() const;
CSSPixels m_table_height { 0 };
CSSPixels m_automatic_content_height { 0 };

View file

@ -4,20 +4,20 @@ Viewport <#document> at [0,0] [0+0+0 800 0+0+0] [0+0+0 600 0+0+0] [BFC] children
Box <div.grid-container> at [8,8] [0+0+0 784 0+0+0] [0+0+0 18 0+0+0] [GFC] children: not-inline
BlockContainer <(anonymous)> (not painted) [BFC] children: inline
TextNode <#text> (not painted)
BlockContainer <div.grid-item> at [8,8] [0+0+0 100 0+0+0] [0+0+0 18 0+0+0] [BFC] children: inline
BlockContainer <div.grid-item> at [8,8] [0+0+0 98 0+0+0] [0+0+0 18 0+0+0] [BFC] children: inline
frag 0 from TextNode start: 0, length: 11, rect: [8,8 93.765625x18] baseline: 13.796875
"min-content"
TextNode <#text> (not painted)
BlockContainer <(anonymous)> (not painted) [BFC] children: inline
TextNode <#text> (not painted)
BlockContainer <div.grid-item> at [208,8] [0+0+0 98.640625 0+0+0] [0+0+0 18 0+0+0] [BFC] children: inline
frag 0 from TextNode start: 0, length: 11, rect: [208,8 98.640625x18] baseline: 13.796875
BlockContainer <div.grid-item> at [204,8] [0+0+0 98.640625 0+0+0] [0+0+0 18 0+0+0] [BFC] children: inline
frag 0 from TextNode start: 0, length: 11, rect: [204,8 98.640625x18] baseline: 13.796875
"max-content"
TextNode <#text> (not painted)
BlockContainer <(anonymous)> (not painted) [BFC] children: inline
TextNode <#text> (not painted)
BlockContainer <div.grid-item> at [306.640625,8] [0+0+0 485.359375 0+0+0] [0+0+0 18 0+0+0] [BFC] children: inline
frag 0 from TextNode start: 0, length: 3, rect: [306.640625,8 21.609375x18] baseline: 13.796875
BlockContainer <div.grid-item> at [302.640625,8] [0+0+0 489.359375 0+0+0] [0+0+0 18 0+0+0] [BFC] children: inline
frag 0 from TextNode start: 0, length: 3, rect: [302.640625,8 21.609375x18] baseline: 13.796875
"1fr"
TextNode <#text> (not painted)
BlockContainer <(anonymous)> (not painted) [BFC] children: inline
@ -27,11 +27,11 @@ ViewportPaintable (Viewport<#document>) [0,0 800x600]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x34]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x18]
PaintableBox (Box<DIV>.grid-container) [8,8 784x18]
PaintableWithLines (BlockContainer<DIV>.grid-item) [8,8 100x18]
PaintableWithLines (BlockContainer<DIV>.grid-item) [8,8 98x18]
TextPaintable (TextNode<#text>)
PaintableWithLines (BlockContainer<DIV>.grid-item) [208,8 98.640625x18]
PaintableWithLines (BlockContainer<DIV>.grid-item) [204,8 98.640625x18]
TextPaintable (TextNode<#text>)
PaintableWithLines (BlockContainer<DIV>.grid-item) [306.640625,8 485.359375x18]
PaintableWithLines (BlockContainer<DIV>.grid-item) [302.640625,8 489.359375x18]
TextPaintable (TextNode<#text>)
SC for Viewport<#document> [0,0 800x600] [children: 1] (z-index: auto)

View file

@ -7,7 +7,7 @@ Viewport <#document> at [0,0] [0+0+0 800 0+0+0] [0+0+0 600 0+0+0] [BFC] children
TextNode <#text> (not painted)
TextNode <#text> (not painted)
TableWrapper <(anonymous)> at [125.59375,8] floating [0+0+0 478.234375 0+0+0] [0+0+0 24 0+0+0] [BFC] children: not-inline
Box <table.middle> at [125.59375,8] table-box [0+0+0 478.234375 0+0+190.328125] [0+0+0 24 0+0+0] [TFC] children: not-inline
Box <table.middle> at [125.59375,8] table-box [0+0+0 478.234375 0+0+186.515625] [0+0+0 24 0+0+0] [TFC] children: not-inline
BlockContainer <(anonymous)> (not painted) children: inline
TextNode <#text> (not painted)
Box <tbody> at [127.59375,10] table-row-group [0+0+0 474.234375 0+0+0] [0+0+0 20 0+0+0] children: not-inline