1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 14:57:35 +00:00

LibWeb: Express intrinsic size layout via size constraints

Previously, we had three layout modes:

- Normal:
    - Everything uses the computed values from CSS.

- MinContent:
    - Containing blocks act as if they have 0 width.
    - All line breaking opportunities are taken.

- MaxContent:
    - Containing blocks act as if they have infinite width.
    - Only forced line breaks are accepted.

The above was based on a set of misunderstandings of CSS sizing.
A major problem with the above was that *all* containing blocks
behaved differently during intrinsic size layout, not just the
relevant one.

With this patch there are only two layout modes:

- Normal:
    - Everything uses the computed values from CSS.

- IntrinsicSizeDetermination:
    - One or more boxes have size constraints applied.

There are two size constraints per layout box, set here:

- FormattingState::NodeState::width_constraint
- FormattingState::NodeState::height_constraint

They are of type SizeConstraint and can be one of None, MinContent,
or MaxContent. The default is None.

When performing an IntrinsicSizeDetermination layout, we now assign
a size constraint to the box we're trying to determine the intrinsic
size of, which is then honored by using two new helpers to query
the dimensions of containing blocks:

- FormattingContext::containing_block_width_for(Box)
- FormattingContext::containing_block_height_for(Box)

If there's a relevant constraint in effect on the Box, the size of
its containing block is adjusted accordingly.

This is essentially an implementation of the "available space"
constraints from CSS-SIZING-3. I'm sure some things will break from
this, and we'll have to deal with that separately.

Spec: https://drafts.csswg.org/css-sizing-3/#available
This commit is contained in:
Andreas Kling 2022-07-09 15:17:47 +02:00
parent 66d08d2417
commit 64959a8504
13 changed files with 213 additions and 174 deletions

View file

@ -64,7 +64,7 @@ void BlockFormattingContext::parent_context_did_dimension_child_root_box()
// Right-side floats: offset_from_edge is from right edge (float_containing_block_width) to the left content edge of floating_box. // Right-side floats: offset_from_edge is from right edge (float_containing_block_width) to the left content edge of floating_box.
for (auto& floating_box : m_right_floats.all_boxes) { for (auto& floating_box : m_right_floats.all_boxes) {
auto float_containing_block_width = m_state.get(*floating_box->box.containing_block()).content_width; auto float_containing_block_width = containing_block_width_for(floating_box->box);
auto& box_state = m_state.get_mutable(floating_box->box); auto& box_state = m_state.get_mutable(floating_box->box);
box_state.offset.set_x(float_containing_block_width - floating_box->offset_from_edge); box_state.offset.set_x(float_containing_block_width - floating_box->offset_from_edge);
} }
@ -96,19 +96,7 @@ void BlockFormattingContext::compute_width(Box const& box, LayoutMode layout_mod
} }
auto const& computed_values = box.computed_values(); auto const& computed_values = box.computed_values();
float width_of_containing_block; float width_of_containing_block = containing_block_width_for(box);
switch (layout_mode) {
case LayoutMode::Normal:
width_of_containing_block = m_state.get(*box.containing_block()).content_width;
break;
case LayoutMode::MinContent:
width_of_containing_block = 0;
break;
case LayoutMode::MaxContent:
width_of_containing_block = INFINITY;
break;
}
auto width_of_containing_block_as_length = CSS::Length::make_px(width_of_containing_block); auto width_of_containing_block_as_length = CSS::Length::make_px(width_of_containing_block);
auto zero_value = CSS::Length::make_px(0); auto zero_value = CSS::Length::make_px(0);
@ -244,26 +232,12 @@ void BlockFormattingContext::compute_width(Box const& box, LayoutMode layout_mod
box_state.padding_right = padding_right.to_px(box); box_state.padding_right = padding_right.to_px(box);
} }
void BlockFormattingContext::compute_width_for_floating_box(Box const& box, LayoutMode layout_mode) void BlockFormattingContext::compute_width_for_floating_box(Box const& box, LayoutMode)
{ {
// 10.3.5 Floating, non-replaced elements // 10.3.5 Floating, non-replaced elements
auto& computed_values = box.computed_values(); auto& computed_values = box.computed_values();
auto& containing_block = *box.containing_block();
float width_of_containing_block = 0;
switch (layout_mode) {
case LayoutMode::Normal:
width_of_containing_block = m_state.get(containing_block).content_width;
break;
case LayoutMode::MinContent:
width_of_containing_block = 0;
break;
case LayoutMode::MaxContent:
width_of_containing_block = INFINITY;
break;
}
float width_of_containing_block = containing_block_width_for(box);
auto width_of_containing_block_as_length = CSS::Length::make_px(width_of_containing_block); auto width_of_containing_block_as_length = CSS::Length::make_px(width_of_containing_block);
auto zero_value = CSS::Length::make_px(0); auto zero_value = CSS::Length::make_px(0);
@ -338,8 +312,7 @@ float BlockFormattingContext::compute_theoretical_height(FormattingState const&
{ {
auto const& computed_values = box.computed_values(); auto const& computed_values = box.computed_values();
auto const& containing_block = *box.containing_block(); auto const& containing_block = *box.containing_block();
auto const& containing_block_state = state.get(containing_block); auto containing_block_height = CSS::Length::make_px(containing_block_height_for(box, state));
auto containing_block_height = CSS::Length::make_px(containing_block_state.content_height);
auto is_absolute = [](Optional<CSS::LengthPercentage> const& length_percentage) { auto is_absolute = [](Optional<CSS::LengthPercentage> const& length_percentage) {
return length_percentage.has_value() && length_percentage->is_length() && length_percentage->length().is_absolute(); return length_percentage.has_value() && length_percentage->is_length() && length_percentage->length().is_absolute();
@ -372,8 +345,7 @@ float BlockFormattingContext::compute_theoretical_height(FormattingState const&
void BlockFormattingContext::compute_height(Box const& box, FormattingState& state) void BlockFormattingContext::compute_height(Box const& box, FormattingState& state)
{ {
auto const& computed_values = box.computed_values(); auto const& computed_values = box.computed_values();
auto const& containing_block = *box.containing_block(); auto width_of_containing_block_as_length = CSS::Length::make_px(containing_block_width_for(box, state));
auto width_of_containing_block_as_length = CSS::Length::make_px(state.get(containing_block).content_width);
// First, resolve the top/bottom parts of the surrounding box model. // First, resolve the top/bottom parts of the surrounding box model.
@ -394,23 +366,29 @@ void BlockFormattingContext::layout_inline_children(BlockContainer const& block_
{ {
VERIFY(block_container.children_are_inline()); VERIFY(block_container.children_are_inline());
auto& block_container_state = m_state.get_mutable(block_container);
if (layout_mode == LayoutMode::IntrinsicSizeDetermination) {
block_container_state.content_width = containing_block_width_for(block_container);
block_container_state.content_height = containing_block_height_for(block_container);
}
InlineFormattingContext context(m_state, block_container, *this); InlineFormattingContext context(m_state, block_container, *this);
context.run(block_container, layout_mode); context.run(block_container, layout_mode);
float max_line_width = 0; float max_line_width = 0;
float content_height = 0; float content_height = 0;
auto& block_container_state = m_state.get_mutable(block_container);
for (auto& line_box : block_container_state.line_boxes) { for (auto& line_box : block_container_state.line_boxes) {
max_line_width = max(max_line_width, line_box.width()); max_line_width = max(max_line_width, line_box.width());
content_height += line_box.height(); content_height += line_box.height();
} }
if (layout_mode != LayoutMode::Normal) { if (layout_mode == LayoutMode::IntrinsicSizeDetermination) {
block_container_state.content_width = max_line_width; block_container_state.content_width = max_line_width;
} }
// FIXME: This is weird. Figure out a way to make callers responsible for setting the content height.
block_container_state.content_height = content_height; block_container_state.content_height = content_height;
} }
@ -420,6 +398,16 @@ void BlockFormattingContext::layout_block_level_children(BlockContainer const& b
float content_height = 0; float content_height = 0;
float original_width = 0;
float original_height = 0;
if (layout_mode == LayoutMode::IntrinsicSizeDetermination) {
auto& block_container_state = m_state.get_mutable(block_container);
original_width = block_container_state.content_width;
original_height = block_container_state.content_height;
block_container_state.content_width = containing_block_width_for(block_container);
block_container_state.content_height = containing_block_height_for(block_container);
}
block_container.for_each_child_of_type<Box>([&](Box& child_box) { block_container.for_each_child_of_type<Box>([&](Box& child_box) {
auto& box_state = m_state.get_mutable(child_box); auto& box_state = m_state.get_mutable(child_box);
@ -478,12 +466,16 @@ void BlockFormattingContext::layout_block_level_children(BlockContainer const& b
return IterationDecision::Continue; return IterationDecision::Continue;
}); });
if (layout_mode != LayoutMode::Normal) { if (layout_mode == LayoutMode::IntrinsicSizeDetermination) {
auto& width = block_container.computed_values().width(); auto& block_container_state = m_state.get_mutable(block_container);
if (width.is_auto()) { if (block_container_state.width_constraint != SizeConstraint::None)
auto& block_container_state = m_state.get_mutable(block_container);
block_container_state.content_width = greatest_child_width(block_container); block_container_state.content_width = greatest_child_width(block_container);
} else
block_container_state.content_width = original_width;
if (block_container_state.height_constraint != SizeConstraint::None)
block_container_state.content_height = content_height;
else
block_container_state.content_height = original_height;
} }
} }
@ -491,7 +483,7 @@ void BlockFormattingContext::compute_vertical_box_model_metrics(Box const& box,
{ {
auto& box_state = m_state.get_mutable(box); auto& box_state = m_state.get_mutable(box);
auto const& computed_values = box.computed_values(); auto const& computed_values = box.computed_values();
auto width_of_containing_block = CSS::Length::make_px(m_state.get(containing_block).content_width); auto width_of_containing_block = CSS::Length::make_px(containing_block_width_for(box));
box_state.margin_top = computed_values.margin().top.resolved(box, width_of_containing_block).resolved(containing_block).to_px(box); box_state.margin_top = computed_values.margin().top.resolved(box, width_of_containing_block).resolved(containing_block).to_px(box);
box_state.margin_bottom = computed_values.margin().bottom.resolved(box, width_of_containing_block).resolved(containing_block).to_px(box); box_state.margin_bottom = computed_values.margin().bottom.resolved(box, width_of_containing_block).resolved(containing_block).to_px(box);
@ -584,10 +576,9 @@ void BlockFormattingContext::place_block_level_element_in_normal_flow_vertically
void BlockFormattingContext::place_block_level_element_in_normal_flow_horizontally(Box const& child_box, BlockContainer const& containing_block) void BlockFormattingContext::place_block_level_element_in_normal_flow_horizontally(Box const& child_box, BlockContainer const& containing_block)
{ {
auto& box_state = m_state.get_mutable(child_box); auto& box_state = m_state.get_mutable(child_box);
auto const& containing_block_state = m_state.get(containing_block);
float x = 0; float x = 0;
float available_width_within_containing_block = containing_block_state.content_width; float available_width_within_containing_block = containing_block_width_for(child_box);
if ((!m_left_floats.current_boxes.is_empty() || !m_right_floats.current_boxes.is_empty()) if ((!m_left_floats.current_boxes.is_empty() || !m_right_floats.current_boxes.is_empty())
&& creates_block_formatting_context(child_box)) { && creates_block_formatting_context(child_box)) {
@ -652,19 +643,7 @@ void BlockFormattingContext::layout_floating_box(Box const& box, BlockContainer
VERIFY(box.is_floating()); VERIFY(box.is_floating());
auto& box_state = m_state.get_mutable(box); auto& box_state = m_state.get_mutable(box);
float width_of_containing_block = 0; float width_of_containing_block = containing_block_width_for(box);
switch (layout_mode) {
case LayoutMode::Normal:
width_of_containing_block = m_state.get(containing_block).content_width;
break;
case LayoutMode::MinContent:
width_of_containing_block = 0;
break;
case LayoutMode::MaxContent:
width_of_containing_block = INFINITY;
break;
}
compute_width(box, layout_mode); compute_width(box, layout_mode);
(void)layout_inside(box, layout_mode); (void)layout_inside(box, layout_mode);
@ -674,7 +653,7 @@ void BlockFormattingContext::layout_floating_box(Box const& box, BlockContainer
// If we have a LineBuilder, we're in the middle of inline layout, otherwise this is block layout. // If we have a LineBuilder, we're in the middle of inline layout, otherwise this is block layout.
if (line_builder) { if (line_builder) {
float y_offset = box_state.margin_box_top(); float y_offset = box_state.margin_box_top();
line_builder->break_if_needed(layout_mode, box_state.margin_box_width()); line_builder->break_if_needed(box_state.margin_box_width());
box_state.offset.set_y(line_builder->current_y() + y_offset); box_state.offset.set_y(line_builder->current_y() + y_offset);
line_builder->adjust_last_line_after_inserting_floating_box({}, box.computed_values().float_(), box_state.margin_box_width()); line_builder->adjust_last_line_after_inserting_floating_box({}, box.computed_values().float_(), box_state.margin_box_width());
} else { } else {

View file

@ -78,7 +78,7 @@ void FlexFormattingContext::run(Box const& run_box, LayoutMode layout_mode)
determine_flex_base_size_and_hypothetical_main_size(flex_item); determine_flex_base_size_and_hypothetical_main_size(flex_item);
} }
if (layout_mode == LayoutMode::MinContent || layout_mode == LayoutMode::MaxContent) { if (m_flex_container_state.width_constraint != SizeConstraint::None || m_flex_container_state.height_constraint != SizeConstraint::None) {
// We're computing intrinsic size for the flex container. // We're computing intrinsic size for the flex container.
determine_intrinsic_size_of_flex_container(layout_mode); determine_intrinsic_size_of_flex_container(layout_mode);
@ -740,7 +740,7 @@ float FlexFormattingContext::content_based_minimum_size(FlexItem const& item) co
void FlexFormattingContext::determine_main_size_of_flex_container(bool const main_is_constrained, float const main_min_size, float const main_max_size) void FlexFormattingContext::determine_main_size_of_flex_container(bool const main_is_constrained, float const main_min_size, float const main_max_size)
{ {
// FIXME: This function should make use of our ability to calculate the flex container's // FIXME: This function should make use of our ability to calculate the flex container's
// intrinsic max-content sizes via LayoutMode::MaxContent. // intrinsic max-content sizes.
if (!main_is_constrained || !m_available_space->main.has_value()) { if (!main_is_constrained || !m_available_space->main.has_value()) {
// Uses https://www.w3.org/TR/css-flexbox-1/#intrinsic-main-sizes // Uses https://www.w3.org/TR/css-flexbox-1/#intrinsic-main-sizes
@ -1348,7 +1348,7 @@ float FlexFormattingContext::calculate_intrinsic_main_size_of_flex_container(Lay
// The min-content main size of a single-line flex container is calculated identically to the max-content main size, // The min-content main size of a single-line flex container is calculated identically to the max-content main size,
// except that the flex items min-content contributions are used instead of their max-content contributions. // except that the flex items min-content contributions are used instead of their max-content contributions.
// However, for a multi-line container, it is simply the largest min-content contribution of all the non-collapsed flex items in the flex container. // However, for a multi-line container, it is simply the largest min-content contribution of all the non-collapsed flex items in the flex container.
if (!is_single_line() && layout_mode == LayoutMode::MinContent) { if (!is_single_line() && (m_flex_container_state.width_constraint == SizeConstraint::MinContent || m_flex_container_state.height_constraint == SizeConstraint::MinContent)) {
float largest_contribution = 0; float largest_contribution = 0;
for (auto const& flex_item : m_flex_items) { for (auto const& flex_item : m_flex_items) {
// FIXME: Skip collapsed flex items. // FIXME: Skip collapsed flex items.
@ -1368,7 +1368,7 @@ float FlexFormattingContext::calculate_intrinsic_main_size_of_flex_container(Lay
// This is the items max-content flex fraction. // This is the items max-content flex fraction.
for (auto& flex_item : m_flex_items) { for (auto& flex_item : m_flex_items) {
float contribution; float contribution;
if (layout_mode == LayoutMode::MinContent) if (m_flex_container_state.width_constraint == SizeConstraint::MinContent || m_flex_container_state.height_constraint == SizeConstraint::MinContent)
contribution = calculate_main_min_content_contribution(flex_item); contribution = calculate_main_min_content_contribution(flex_item);
else else
contribution = calculate_main_max_content_contribution(flex_item); contribution = calculate_main_max_content_contribution(flex_item);
@ -1433,9 +1433,9 @@ float FlexFormattingContext::calculate_intrinsic_cross_size_of_flex_container(La
float largest_contribution = 0; float largest_contribution = 0;
for (auto& flex_item : m_flex_items) { for (auto& flex_item : m_flex_items) {
float contribution; float contribution;
if (layout_mode == LayoutMode::MinContent) if (m_flex_container_state.width_constraint == SizeConstraint::MinContent || m_flex_container_state.height_constraint == SizeConstraint::MinContent)
contribution = calculate_cross_min_content_contribution(flex_item); contribution = calculate_cross_min_content_contribution(flex_item);
else if (layout_mode == LayoutMode::MaxContent) else if (m_flex_container_state.width_constraint == SizeConstraint::MaxContent || m_flex_container_state.height_constraint == SizeConstraint::MaxContent)
contribution = calculate_cross_max_content_contribution(flex_item); contribution = calculate_cross_max_content_contribution(flex_item);
largest_contribution = max(largest_contribution, contribution); largest_contribution = max(largest_contribution, contribution);
} }
@ -1529,6 +1529,16 @@ float FlexFormattingContext::calculate_min_content_main_size(FlexItem const& ite
return is_row_layout() ? calculate_min_content_width(item.box) : calculate_min_content_height(item.box); return is_row_layout() ? calculate_min_content_width(item.box) : calculate_min_content_height(item.box);
} }
float FlexFormattingContext::calculate_fit_content_main_size(FlexItem const& item) const
{
return is_row_layout() ? calculate_fit_content_width(item.box, m_available_space->main) : calculate_fit_content_height(item.box, m_available_space->main);
}
float FlexFormattingContext::calculate_fit_content_cross_size(FlexItem const& item) const
{
return is_row_layout() ? calculate_fit_content_height(item.box, m_available_space->cross) : calculate_fit_content_width(item.box, m_available_space->cross);
}
float FlexFormattingContext::calculate_max_content_main_size(FlexItem const& item) const float FlexFormattingContext::calculate_max_content_main_size(FlexItem const& item) const
{ {
return is_row_layout() ? calculate_max_content_width(item.box) : calculate_max_content_height(item.box); return is_row_layout() ? calculate_max_content_width(item.box) : calculate_max_content_height(item.box);

View file

@ -145,6 +145,9 @@ private:
[[nodiscard]] float calculate_min_content_cross_size(FlexItem const&) const; [[nodiscard]] float calculate_min_content_cross_size(FlexItem const&) const;
[[nodiscard]] float calculate_max_content_cross_size(FlexItem const&) const; [[nodiscard]] float calculate_max_content_cross_size(FlexItem const&) const;
[[nodiscard]] float calculate_fit_content_main_size(FlexItem const&) const;
[[nodiscard]] float calculate_fit_content_cross_size(FlexItem const&) const;
CSS::FlexBasisData used_flex_basis_for_item(FlexItem const&) const; CSS::FlexBasisData used_flex_basis_for_item(FlexItem const&) const;
FormattingState::NodeState& m_flex_container_state; FormattingState::NodeState& m_flex_container_state;

View file

@ -385,11 +385,10 @@ float FormattingContext::compute_width_for_replaced_element(FormattingState cons
// 10.3.2 Inline, replaced elements // 10.3.2 Inline, replaced elements
auto zero_value = CSS::Length::make_px(0); auto zero_value = CSS::Length::make_px(0);
auto const& containing_block = *box.containing_block(); auto width_of_containing_block_as_length = CSS::Length::make_px(containing_block_width_for(box, state));
auto width_of_containing_block = CSS::Length::make_px(state.get(containing_block).content_width);
auto margin_left = box.computed_values().margin().left.resolved(box, width_of_containing_block).resolved(box); auto margin_left = box.computed_values().margin().left.resolved(box, width_of_containing_block_as_length).resolved(box);
auto margin_right = box.computed_values().margin().right.resolved(box, width_of_containing_block).resolved(box); auto margin_right = box.computed_values().margin().right.resolved(box, width_of_containing_block_as_length).resolved(box);
// A computed value of 'auto' for 'margin-left' or 'margin-right' becomes a used value of '0'. // A computed value of 'auto' for 'margin-left' or 'margin-right' becomes a used value of '0'.
if (margin_left.is_auto()) if (margin_left.is_auto())
@ -397,14 +396,14 @@ float FormattingContext::compute_width_for_replaced_element(FormattingState cons
if (margin_right.is_auto()) if (margin_right.is_auto())
margin_right = zero_value; margin_right = zero_value;
auto specified_width = box.computed_values().width().resolved(box, width_of_containing_block).resolved(box); auto specified_width = box.computed_values().width().resolved(box, width_of_containing_block_as_length).resolved(box);
// 1. The tentative used width is calculated (without 'min-width' and 'max-width') // 1. The tentative used width is calculated (without 'min-width' and 'max-width')
auto used_width = tentative_width_for_replaced_element(state, box, specified_width); auto used_width = tentative_width_for_replaced_element(state, box, specified_width);
// 2. The tentative used width is greater than 'max-width', the rules above are applied again, // 2. The tentative used width is greater than 'max-width', the rules above are applied again,
// but this time using the computed value of 'max-width' as the computed value for 'width'. // but this time using the computed value of 'max-width' as the computed value for 'width'.
auto specified_max_width = box.computed_values().max_width().resolved(box, width_of_containing_block).resolved(box); auto specified_max_width = box.computed_values().max_width().resolved(box, width_of_containing_block_as_length).resolved(box);
if (!specified_max_width.is_auto()) { if (!specified_max_width.is_auto()) {
if (used_width > specified_max_width.to_px(box)) { if (used_width > specified_max_width.to_px(box)) {
used_width = tentative_width_for_replaced_element(state, box, specified_max_width); used_width = tentative_width_for_replaced_element(state, box, specified_max_width);
@ -413,7 +412,7 @@ float FormattingContext::compute_width_for_replaced_element(FormattingState cons
// 3. If the resulting width is smaller than 'min-width', the rules above are applied again, // 3. If the resulting width is smaller than 'min-width', the rules above are applied again,
// but this time using the value of 'min-width' as the computed value for 'width'. // but this time using the value of 'min-width' as the computed value for 'width'.
auto specified_min_width = box.computed_values().min_width().resolved(box, width_of_containing_block).resolved(box); auto specified_min_width = box.computed_values().min_width().resolved(box, width_of_containing_block_as_length).resolved(box);
if (!specified_min_width.is_auto()) { if (!specified_min_width.is_auto()) {
if (used_width < specified_min_width.to_px(box)) { if (used_width < specified_min_width.to_px(box)) {
used_width = tentative_width_for_replaced_element(state, box, specified_min_width); used_width = tentative_width_for_replaced_element(state, box, specified_min_width);
@ -427,9 +426,8 @@ float FormattingContext::compute_width_for_replaced_element(FormattingState cons
// https://www.w3.org/TR/CSS22/visudet.html#inline-replaced-height // https://www.w3.org/TR/CSS22/visudet.html#inline-replaced-height
float FormattingContext::tentative_height_for_replaced_element(FormattingState const& state, ReplacedBox const& box, CSS::Length const& computed_height) float FormattingContext::tentative_height_for_replaced_element(FormattingState const& state, ReplacedBox const& box, CSS::Length const& computed_height)
{ {
auto const& containing_block = *box.containing_block(); auto width_of_containing_block_as_length = CSS::Length::make_px(containing_block_width_for(box, state));
auto width_of_containing_block = CSS::Length::make_px(state.get(containing_block).content_width); auto computed_width = box.computed_values().width().resolved(box, width_of_containing_block_as_length).resolved(box);
auto computed_width = box.computed_values().width().resolved(box, width_of_containing_block).resolved(box);
// If 'height' and 'width' both have computed values of 'auto' and the element also has // If 'height' and 'width' both have computed values of 'auto' and the element also has
// an intrinsic height, then that intrinsic height is the used value of 'height'. // an intrinsic height, then that intrinsic height is the used value of 'height'.
@ -460,12 +458,10 @@ float FormattingContext::compute_height_for_replaced_element(FormattingState con
// 10.6.2 Inline replaced elements, block-level replaced elements in normal flow, // 10.6.2 Inline replaced elements, block-level replaced elements in normal flow,
// 'inline-block' replaced elements in normal flow and floating replaced elements // 'inline-block' replaced elements in normal flow and floating replaced elements
auto const& containing_block = *box.containing_block(); auto width_of_containing_block_as_length = CSS::Length::make_px(containing_block_width_for(box, state));
auto const& containing_block_state = state.get(containing_block); auto height_of_containing_block_as_length = CSS::Length::make_px(containing_block_height_for(box, state));
auto width_of_containing_block = CSS::Length::make_px(containing_block_state.content_width); auto specified_width = box.computed_values().width().resolved(box, width_of_containing_block_as_length).resolved(box);
auto height_of_containing_block = CSS::Length::make_px(containing_block_state.content_height); auto specified_height = box.computed_values().height().resolved(box, height_of_containing_block_as_length).resolved(box);
auto specified_width = box.computed_values().width().resolved(box, width_of_containing_block).resolved(box);
auto specified_height = box.computed_values().height().resolved(box, height_of_containing_block).resolved(box);
float used_height = tentative_height_for_replaced_element(state, box, specified_height); float used_height = tentative_height_for_replaced_element(state, box, specified_height);
@ -480,10 +476,8 @@ float FormattingContext::compute_height_for_replaced_element(FormattingState con
void FormattingContext::compute_width_for_absolutely_positioned_non_replaced_element(Box const& box) void FormattingContext::compute_width_for_absolutely_positioned_non_replaced_element(Box const& box)
{ {
auto& containing_block_state = m_state.get(*box.containing_block()); auto width_of_containing_block = containing_block_width_for(box);
auto& box_state = m_state.get_mutable(box); auto width_of_containing_block_as_length = CSS::Length::make_px(width_of_containing_block);
auto width_of_containing_block = CSS::Length::make_px(containing_block_state.content_width);
auto& computed_values = box.computed_values(); auto& computed_values = box.computed_values();
auto zero_value = CSS::Length::make_px(0); auto zero_value = CSS::Length::make_px(0);
@ -491,27 +485,27 @@ void FormattingContext::compute_width_for_absolutely_positioned_non_replaced_ele
auto margin_right = CSS::Length::make_auto(); auto margin_right = CSS::Length::make_auto();
auto const border_left = computed_values.border_left().width; auto const border_left = computed_values.border_left().width;
auto const border_right = computed_values.border_right().width; auto const border_right = computed_values.border_right().width;
auto const padding_left = computed_values.padding().left.resolved(box, width_of_containing_block).to_px(box); auto const padding_left = computed_values.padding().left.resolved(box, width_of_containing_block_as_length).to_px(box);
auto const padding_right = computed_values.padding().right.resolved(box, width_of_containing_block).to_px(box); auto const padding_right = computed_values.padding().right.resolved(box, width_of_containing_block_as_length).to_px(box);
auto try_compute_width = [&](auto const& a_width) { auto try_compute_width = [&](auto const& a_width) {
margin_left = computed_values.margin().left.resolved(box, width_of_containing_block).resolved(box); margin_left = computed_values.margin().left.resolved(box, width_of_containing_block_as_length).resolved(box);
margin_right = computed_values.margin().right.resolved(box, width_of_containing_block).resolved(box); margin_right = computed_values.margin().right.resolved(box, width_of_containing_block_as_length).resolved(box);
auto left = computed_values.inset().left.resolved(box, width_of_containing_block).resolved(box); auto left = computed_values.inset().left.resolved(box, width_of_containing_block_as_length).resolved(box);
auto right = computed_values.inset().right.resolved(box, width_of_containing_block).resolved(box); auto right = computed_values.inset().right.resolved(box, width_of_containing_block_as_length).resolved(box);
auto width = a_width; auto width = a_width;
auto solve_for_left = [&] { auto solve_for_left = [&] {
return CSS::Length(containing_block_state.content_width - margin_left.to_px(box) - border_left - padding_left - width.to_px(box) - padding_right - border_right - margin_right.to_px(box) - right.to_px(box), CSS::Length::Type::Px); return CSS::Length(width_of_containing_block - margin_left.to_px(box) - border_left - padding_left - width.to_px(box) - padding_right - border_right - margin_right.to_px(box) - right.to_px(box), CSS::Length::Type::Px);
}; };
auto solve_for_width = [&] { auto solve_for_width = [&] {
return CSS::Length(containing_block_state.content_width - left.to_px(box) - margin_left.to_px(box) - border_left - padding_left - padding_right - border_right - margin_right.to_px(box) - right.to_px(box), CSS::Length::Type::Px); return CSS::Length(width_of_containing_block - left.to_px(box) - margin_left.to_px(box) - border_left - padding_left - padding_right - border_right - margin_right.to_px(box) - right.to_px(box), CSS::Length::Type::Px);
}; };
auto solve_for_right = [&] { auto solve_for_right = [&] {
return CSS::Length(containing_block_state.content_width - left.to_px(box) - margin_left.to_px(box) - border_left - padding_left - width.to_px(box) - padding_right - border_right - margin_right.to_px(box), CSS::Length::Type::Px); return CSS::Length(width_of_containing_block - left.to_px(box) - margin_left.to_px(box) - border_left - padding_left - width.to_px(box) - padding_right - border_right - margin_right.to_px(box), CSS::Length::Type::Px);
}; };
// If all three of 'left', 'width', and 'right' are 'auto': // If all three of 'left', 'width', and 'right' are 'auto':
@ -588,14 +582,14 @@ void FormattingContext::compute_width_for_absolutely_positioned_non_replaced_ele
return width; return width;
}; };
auto specified_width = computed_values.width().resolved(box, width_of_containing_block).resolved(box); auto specified_width = computed_values.width().resolved(box, width_of_containing_block_as_length).resolved(box);
// 1. The tentative used width is calculated (without 'min-width' and 'max-width') // 1. The tentative used width is calculated (without 'min-width' and 'max-width')
auto used_width = try_compute_width(specified_width); auto used_width = try_compute_width(specified_width);
// 2. The tentative used width is greater than 'max-width', the rules above are applied again, // 2. The tentative used width is greater than 'max-width', the rules above are applied again,
// but this time using the computed value of 'max-width' as the computed value for 'width'. // but this time using the computed value of 'max-width' as the computed value for 'width'.
auto specified_max_width = computed_values.max_width().resolved(box, width_of_containing_block).resolved(box); auto specified_max_width = computed_values.max_width().resolved(box, width_of_containing_block_as_length).resolved(box);
if (!specified_max_width.is_auto()) { if (!specified_max_width.is_auto()) {
if (used_width.to_px(box) > specified_max_width.to_px(box)) { if (used_width.to_px(box) > specified_max_width.to_px(box)) {
used_width = try_compute_width(specified_max_width); used_width = try_compute_width(specified_max_width);
@ -604,13 +598,14 @@ void FormattingContext::compute_width_for_absolutely_positioned_non_replaced_ele
// 3. If the resulting width is smaller than 'min-width', the rules above are applied again, // 3. If the resulting width is smaller than 'min-width', the rules above are applied again,
// but this time using the value of 'min-width' as the computed value for 'width'. // but this time using the value of 'min-width' as the computed value for 'width'.
auto specified_min_width = computed_values.min_width().resolved(box, width_of_containing_block).resolved(box); auto specified_min_width = computed_values.min_width().resolved(box, width_of_containing_block_as_length).resolved(box);
if (!specified_min_width.is_auto()) { if (!specified_min_width.is_auto()) {
if (used_width.to_px(box) < specified_min_width.to_px(box)) { if (used_width.to_px(box) < specified_min_width.to_px(box)) {
used_width = try_compute_width(specified_min_width); used_width = try_compute_width(specified_min_width);
} }
} }
auto& box_state = m_state.get_mutable(box);
box_state.content_width = used_width.to_px(box); box_state.content_width = used_width.to_px(box);
box_state.margin_left = margin_left.to_px(box); box_state.margin_left = margin_left.to_px(box);
@ -638,31 +633,32 @@ void FormattingContext::compute_height_for_absolutely_positioned_non_replaced_el
// FIXME: The section below is partly on-spec, partly ad-hoc. // FIXME: The section below is partly on-spec, partly ad-hoc.
auto& computed_values = box.computed_values(); auto& computed_values = box.computed_values();
auto const& containing_block = *box.containing_block(); auto const& containing_block = *box.containing_block();
auto const& containing_block_state = m_state.get(containing_block);
auto& box_state = m_state.get_mutable(box); auto& box_state = m_state.get_mutable(box);
auto width_of_containing_block = CSS::Length::make_px(containing_block_state.content_width); auto width_of_containing_block = containing_block_width_for(box);
auto height_of_containing_block = CSS::Length::make_px(containing_block_state.content_height); auto height_of_containing_block = containing_block_height_for(box);
auto width_of_containing_block_as_length = CSS::Length::make_px(width_of_containing_block);
auto height_of_containing_block_as_length = CSS::Length::make_px(height_of_containing_block);
CSS::Length specified_top = computed_values.inset().top.resolved(box, height_of_containing_block).resolved(box); CSS::Length specified_top = computed_values.inset().top.resolved(box, height_of_containing_block_as_length).resolved(box);
CSS::Length specified_bottom = computed_values.inset().bottom.resolved(box, height_of_containing_block).resolved(box); CSS::Length specified_bottom = computed_values.inset().bottom.resolved(box, height_of_containing_block_as_length).resolved(box);
CSS::Length specified_height = CSS::Length::make_auto(); CSS::Length specified_height = CSS::Length::make_auto();
if (computed_values.height().is_percentage() if (computed_values.height().is_percentage()
&& !(containing_block.computed_values().height().is_length() && containing_block.computed_values().height().length().is_absolute())) { && !(containing_block.computed_values().height().is_length() && containing_block.computed_values().height().length().is_absolute())) {
// specified_height is already auto // specified_height is already auto
} else { } else {
specified_height = computed_values.height().resolved(box, height_of_containing_block).resolved(box); specified_height = computed_values.height().resolved(box, height_of_containing_block_as_length).resolved(box);
} }
auto specified_max_height = computed_values.max_height().resolved(box, height_of_containing_block).resolved(box); auto specified_max_height = computed_values.max_height().resolved(box, height_of_containing_block_as_length).resolved(box);
auto specified_min_height = computed_values.min_height().resolved(box, height_of_containing_block).resolved(box); auto specified_min_height = computed_values.min_height().resolved(box, height_of_containing_block_as_length).resolved(box);
box_state.margin_top = computed_values.margin().top.resolved(box, width_of_containing_block).to_px(box); box_state.margin_top = computed_values.margin().top.resolved(box, width_of_containing_block_as_length).to_px(box);
box_state.margin_bottom = computed_values.margin().bottom.resolved(box, width_of_containing_block).to_px(box); box_state.margin_bottom = computed_values.margin().bottom.resolved(box, width_of_containing_block_as_length).to_px(box);
box_state.border_top = computed_values.border_top().width; box_state.border_top = computed_values.border_top().width;
box_state.border_bottom = computed_values.border_bottom().width; box_state.border_bottom = computed_values.border_bottom().width;
box_state.padding_top = computed_values.padding().top.resolved(box, width_of_containing_block).to_px(box); box_state.padding_top = computed_values.padding().top.resolved(box, width_of_containing_block_as_length).to_px(box);
box_state.padding_bottom = computed_values.padding().bottom.resolved(box, width_of_containing_block).to_px(box); box_state.padding_bottom = computed_values.padding().bottom.resolved(box, width_of_containing_block_as_length).to_px(box);
if (specified_height.is_auto() && specified_top.is_auto() && specified_bottom.is_auto()) { if (specified_height.is_auto() && specified_top.is_auto() && specified_bottom.is_auto()) {
specified_height = CSS::Length(compute_auto_height_for_block_level_element(m_state, box), CSS::Length::Type::Px); specified_height = CSS::Length(compute_auto_height_for_block_level_element(m_state, box), CSS::Length::Type::Px);
@ -670,11 +666,11 @@ void FormattingContext::compute_height_for_absolutely_positioned_non_replaced_el
else if (specified_height.is_auto() && !specified_top.is_auto() && specified_bottom.is_auto()) { else if (specified_height.is_auto() && !specified_top.is_auto() && specified_bottom.is_auto()) {
specified_height = CSS::Length(compute_auto_height_for_block_level_element(m_state, box), CSS::Length::Type::Px); specified_height = CSS::Length(compute_auto_height_for_block_level_element(m_state, box), CSS::Length::Type::Px);
box_state.inset_bottom = containing_block_state.content_height - specified_height.to_px(box) - specified_top.to_px(box) - box_state.margin_top - box_state.padding_top - box_state.border_top - box_state.margin_bottom - box_state.padding_bottom - box_state.border_bottom; box_state.inset_bottom = height_of_containing_block - specified_height.to_px(box) - specified_top.to_px(box) - box_state.margin_top - box_state.padding_top - box_state.border_top - box_state.margin_bottom - box_state.padding_bottom - box_state.border_bottom;
} }
else if (specified_height.is_auto() && !specified_top.is_auto() && !specified_bottom.is_auto()) { else if (specified_height.is_auto() && !specified_top.is_auto() && !specified_bottom.is_auto()) {
specified_height = CSS::Length(containing_block_state.content_height - specified_top.to_px(box) - box_state.margin_top - box_state.padding_top - box_state.border_top - specified_bottom.to_px(box) - box_state.margin_bottom - box_state.padding_bottom - box_state.border_bottom, CSS::Length::Type::Px); specified_height = CSS::Length(height_of_containing_block - specified_top.to_px(box) - box_state.margin_top - box_state.padding_top - box_state.border_top - specified_bottom.to_px(box) - box_state.margin_bottom - box_state.padding_bottom - box_state.border_bottom, CSS::Length::Type::Px);
} }
if (!specified_height.is_auto()) { if (!specified_height.is_auto()) {
@ -689,31 +685,32 @@ void FormattingContext::compute_height_for_absolutely_positioned_non_replaced_el
void FormattingContext::layout_absolutely_positioned_element(Box const& box) void FormattingContext::layout_absolutely_positioned_element(Box const& box)
{ {
auto const& containing_block_state = m_state.get(*box.containing_block()); auto width_of_containing_block = containing_block_width_for(box);
auto width_of_containing_block = CSS::Length::make_px(containing_block_state.content_width); auto height_of_containing_block = containing_block_height_for(box);
auto height_of_containing_block = CSS::Length::make_px(containing_block_state.content_height); auto width_of_containing_block_as_length = CSS::Length::make_px(width_of_containing_block);
auto& box_state = m_state.get_mutable(box); auto height_of_containing_block_as_length = CSS::Length::make_px(height_of_containing_block);
auto specified_width = box.computed_values().width().resolved(box, width_of_containing_block).resolved(box); auto specified_width = box.computed_values().width().resolved(box, width_of_containing_block_as_length).resolved(box);
compute_width_for_absolutely_positioned_element(box); compute_width_for_absolutely_positioned_element(box);
auto independent_formatting_context = layout_inside(box, LayoutMode::Normal); auto independent_formatting_context = layout_inside(box, LayoutMode::Normal);
compute_height_for_absolutely_positioned_element(box); compute_height_for_absolutely_positioned_element(box);
box_state.margin_left = box.computed_values().margin().left.resolved(box, width_of_containing_block).to_px(box); auto& box_state = m_state.get_mutable(box);
box_state.margin_top = box.computed_values().margin().top.resolved(box, height_of_containing_block).to_px(box); box_state.margin_left = box.computed_values().margin().left.resolved(box, width_of_containing_block_as_length).to_px(box);
box_state.margin_right = box.computed_values().margin().right.resolved(box, width_of_containing_block).to_px(box); box_state.margin_top = box.computed_values().margin().top.resolved(box, height_of_containing_block_as_length).to_px(box);
box_state.margin_bottom = box.computed_values().margin().bottom.resolved(box, height_of_containing_block).to_px(box); box_state.margin_right = box.computed_values().margin().right.resolved(box, width_of_containing_block_as_length).to_px(box);
box_state.margin_bottom = box.computed_values().margin().bottom.resolved(box, height_of_containing_block_as_length).to_px(box);
box_state.border_left = box.computed_values().border_left().width; box_state.border_left = box.computed_values().border_left().width;
box_state.border_right = box.computed_values().border_right().width; box_state.border_right = box.computed_values().border_right().width;
box_state.border_top = box.computed_values().border_top().width; box_state.border_top = box.computed_values().border_top().width;
box_state.border_bottom = box.computed_values().border_bottom().width; box_state.border_bottom = box.computed_values().border_bottom().width;
box_state.inset_left = box.computed_values().inset().left.resolved(box, width_of_containing_block).to_px(box); box_state.inset_left = box.computed_values().inset().left.resolved(box, width_of_containing_block_as_length).to_px(box);
box_state.inset_top = box.computed_values().inset().top.resolved(box, height_of_containing_block).to_px(box); box_state.inset_top = box.computed_values().inset().top.resolved(box, height_of_containing_block_as_length).to_px(box);
box_state.inset_right = box.computed_values().inset().right.resolved(box, width_of_containing_block).to_px(box); box_state.inset_right = box.computed_values().inset().right.resolved(box, width_of_containing_block_as_length).to_px(box);
box_state.inset_bottom = box.computed_values().inset().bottom.resolved(box, height_of_containing_block).to_px(box); box_state.inset_bottom = box.computed_values().inset().bottom.resolved(box, height_of_containing_block_as_length).to_px(box);
auto is_auto = [](auto const& length_percentage) { auto is_auto = [](auto const& length_percentage) {
return length_percentage.is_length() && length_percentage.length().is_auto(); return length_percentage.is_length() && length_percentage.length().is_auto();
@ -736,7 +733,7 @@ void FormattingContext::layout_absolutely_positioned_element(Box const& box)
float x_offset = 0 float x_offset = 0
- box_state.inset_right - box_state.inset_right
- box_state.border_box_right(); - box_state.border_box_right();
used_offset.set_x(containing_block_state.content_width + x_offset - box_state.content_width - box_state.margin_right); used_offset.set_x(width_of_containing_block + x_offset - box_state.content_width - box_state.margin_right);
} else { } else {
float x_offset = box_state.margin_box_left(); float x_offset = box_state.margin_box_left();
used_offset.set_x(x_offset); used_offset.set_x(x_offset);
@ -750,7 +747,7 @@ void FormattingContext::layout_absolutely_positioned_element(Box const& box)
float y_offset = 0 float y_offset = 0
- box_state.inset_bottom - box_state.inset_bottom
- box_state.border_box_bottom(); - box_state.border_box_bottom();
used_offset.set_y(containing_block_state.content_height + y_offset - box_state.content_height - box_state.margin_bottom); used_offset.set_y(height_of_containing_block + y_offset - box_state.content_height - box_state.margin_bottom);
} else { } else {
float y_offset = box_state.margin_box_top(); float y_offset = box_state.margin_box_top();
used_offset.set_y(y_offset); used_offset.set_y(y_offset);
@ -803,11 +800,10 @@ void FormattingContext::compute_inset(Box const& box)
auto& box_state = m_state.get_mutable(box); auto& box_state = m_state.get_mutable(box);
auto const& computed_values = box.computed_values(); auto const& computed_values = box.computed_values();
auto const& containing_block_state = m_state.get(*box.containing_block());
// FIXME: Respect the containing block's writing-mode. // FIXME: Respect the containing block's writing-mode.
resolve_two_opposing_insets(computed_values.inset().left, computed_values.inset().right, box_state.inset_left, box_state.inset_right, containing_block_state.content_width); resolve_two_opposing_insets(computed_values.inset().left, computed_values.inset().right, box_state.inset_left, box_state.inset_right, containing_block_width_for(box));
resolve_two_opposing_insets(computed_values.inset().top, computed_values.inset().bottom, box_state.inset_top, box_state.inset_bottom, containing_block_state.content_height); resolve_two_opposing_insets(computed_values.inset().top, computed_values.inset().bottom, box_state.inset_top, box_state.inset_bottom, containing_block_height_for(box));
} }
float FormattingContext::calculate_fit_content_size(float min_content_size, float max_content_size, Optional<float> available_space) const float FormattingContext::calculate_fit_content_size(float min_content_size, float max_content_size, Optional<float> available_space) const
@ -861,11 +857,14 @@ float FormattingContext::calculate_min_content_width(Layout::Box const& box) con
auto const& containing_block = *box.containing_block(); auto const& containing_block = *box.containing_block();
auto& containing_block_state = throwaway_state.get_mutable(containing_block); auto& containing_block_state = throwaway_state.get_mutable(containing_block);
containing_block_state.content_width = 0; containing_block_state.content_width = 0;
auto& box_state = throwaway_state.get_mutable(box);
box_state.width_constraint = SizeConstraint::MinContent;
auto context = const_cast<FormattingContext*>(this)->create_independent_formatting_context_if_needed(throwaway_state, box); auto context = const_cast<FormattingContext*>(this)->create_independent_formatting_context_if_needed(throwaway_state, box);
VERIFY(context); VERIFY(context);
context->run(box, LayoutMode::MinContent); context->run(box, LayoutMode::IntrinsicSizeDetermination);
if (context->type() == FormattingContext::Type::Flex) { if (context->type() == FormattingContext::Type::Flex) {
auto const& box_state = throwaway_state.get(box);
cache.min_content_width = box_state.content_width; cache.min_content_width = box_state.content_width;
} else { } else {
cache.min_content_width = context->greatest_child_width(box); cache.min_content_width = context->greatest_child_width(box);
@ -888,11 +887,14 @@ float FormattingContext::calculate_max_content_width(Layout::Box const& box) con
auto const& containing_block = *box.containing_block(); auto const& containing_block = *box.containing_block();
auto& containing_block_state = throwaway_state.get_mutable(containing_block); auto& containing_block_state = throwaway_state.get_mutable(containing_block);
containing_block_state.content_width = INFINITY; containing_block_state.content_width = INFINITY;
auto& box_state = throwaway_state.get_mutable(box);
box_state.width_constraint = SizeConstraint::MaxContent;
auto context = const_cast<FormattingContext*>(this)->create_independent_formatting_context_if_needed(throwaway_state, box); auto context = const_cast<FormattingContext*>(this)->create_independent_formatting_context_if_needed(throwaway_state, box);
VERIFY(context); VERIFY(context);
context->run(box, LayoutMode::MaxContent); context->run(box, LayoutMode::IntrinsicSizeDetermination);
if (context->type() == FormattingContext::Type::Flex) { if (context->type() == FormattingContext::Type::Flex) {
auto const& box_state = throwaway_state.get(box);
cache.max_content_width = box_state.content_width; cache.max_content_width = box_state.content_width;
} else { } else {
cache.max_content_width = context->greatest_child_width(box); cache.max_content_width = context->greatest_child_width(box);
@ -916,11 +918,14 @@ float FormattingContext::calculate_min_content_height(Layout::Box const& box) co
auto const& containing_block = *box.containing_block(); auto const& containing_block = *box.containing_block();
auto& containing_block_state = throwaway_state.get_mutable(containing_block); auto& containing_block_state = throwaway_state.get_mutable(containing_block);
containing_block_state.content_height = 0; containing_block_state.content_height = 0;
auto& box_state = throwaway_state.get_mutable(box);
box_state.height_constraint = SizeConstraint::MinContent;
auto context = const_cast<FormattingContext*>(this)->create_independent_formatting_context_if_needed(throwaway_state, box); auto context = const_cast<FormattingContext*>(this)->create_independent_formatting_context_if_needed(throwaway_state, box);
VERIFY(context); VERIFY(context);
context->run(box, LayoutMode::MinContent); context->run(box, LayoutMode::IntrinsicSizeDetermination);
if (context->type() == FormattingContext::Type::Flex) { if (context->type() == FormattingContext::Type::Flex) {
auto const& box_state = throwaway_state.get(box);
cache.min_content_height = box_state.content_height; cache.min_content_height = box_state.content_height;
} else { } else {
cache.min_content_height = calculate_auto_height(throwaway_state, box); cache.min_content_height = calculate_auto_height(throwaway_state, box);
@ -944,11 +949,14 @@ float FormattingContext::calculate_max_content_height(Layout::Box const& box) co
auto const& containing_block = *box.containing_block(); auto const& containing_block = *box.containing_block();
auto& containing_block_state = throwaway_state.get_mutable(containing_block); auto& containing_block_state = throwaway_state.get_mutable(containing_block);
containing_block_state.content_height = INFINITY; containing_block_state.content_height = INFINITY;
auto& box_state = throwaway_state.get_mutable(box);
box_state.height_constraint = SizeConstraint::MaxContent;
auto context = const_cast<FormattingContext*>(this)->create_independent_formatting_context_if_needed(throwaway_state, box); auto context = const_cast<FormattingContext*>(this)->create_independent_formatting_context_if_needed(throwaway_state, box);
VERIFY(context); VERIFY(context);
context->run(box, LayoutMode::MaxContent); context->run(box, LayoutMode::IntrinsicSizeDetermination);
if (context->type() == FormattingContext::Type::Flex) { if (context->type() == FormattingContext::Type::Flex) {
auto const& box_state = throwaway_state.get(box);
cache.max_content_height = box_state.content_height; cache.max_content_height = box_state.content_height;
} else { } else {
cache.max_content_height = calculate_auto_height(throwaway_state, box); cache.max_content_height = calculate_auto_height(throwaway_state, box);
@ -957,4 +965,36 @@ float FormattingContext::calculate_max_content_height(Layout::Box const& box) co
return *cache.max_content_height; return *cache.max_content_height;
} }
float FormattingContext::containing_block_width_for(Box const& box, FormattingState const& state)
{
auto& containing_block_state = state.get(*box.containing_block());
auto& box_state = state.get(box);
switch (box_state.width_constraint) {
case SizeConstraint::MinContent:
return 0;
case SizeConstraint::MaxContent:
return INFINITY;
case SizeConstraint::None:
return containing_block_state.content_width;
}
VERIFY_NOT_REACHED();
}
float FormattingContext::containing_block_height_for(Box const& box, FormattingState const& state)
{
auto& containing_block_state = state.get(*box.containing_block());
auto& box_state = state.get(box);
switch (box_state.height_constraint) {
case SizeConstraint::MinContent:
return 0;
case SizeConstraint::MaxContent:
return INFINITY;
case SizeConstraint::None:
return containing_block_state.content_height;
}
VERIFY_NOT_REACHED();
}
} }

View file

@ -55,6 +55,12 @@ public:
virtual float greatest_child_width(Box const&); virtual float greatest_child_width(Box const&);
float containing_block_width_for(Box const& box) const { return containing_block_width_for(box, m_state); }
float containing_block_height_for(Box const& box) const { return containing_block_height_for(box, m_state); }
static float containing_block_width_for(Box const&, FormattingState const&);
static float containing_block_height_for(Box const&, FormattingState const&);
protected: protected:
FormattingContext(Type, FormattingState&, Box const&, FormattingContext* parent = nullptr); FormattingContext(Type, FormattingState&, Box const&, FormattingContext* parent = nullptr);

View file

@ -14,6 +14,12 @@
namespace Web::Layout { namespace Web::Layout {
enum class SizeConstraint {
None,
MinContent,
MaxContent,
};
struct FormattingState { struct FormattingState {
FormattingState() FormattingState()
: m_root(*this) : m_root(*this)
@ -39,6 +45,9 @@ struct FormattingState {
float content_height { 0 }; float content_height { 0 };
Gfx::FloatPoint offset; Gfx::FloatPoint offset;
SizeConstraint width_constraint { SizeConstraint::None };
SizeConstraint height_constraint { SizeConstraint::None };
float margin_left { 0 }; float margin_left { 0 };
float margin_right { 0 }; float margin_right { 0 };
float margin_top { 0 }; float margin_top { 0 };

View file

@ -219,7 +219,7 @@ void InlineFormattingContext::generate_line_boxes(LayoutMode layout_mode)
break; break;
case InlineLevelIterator::Item::Type::Element: { case InlineLevelIterator::Item::Type::Element: {
auto& box = verify_cast<Layout::Box>(*item.node); auto& box = verify_cast<Layout::Box>(*item.node);
line_builder.break_if_needed(layout_mode, item.border_box_width()); line_builder.break_if_needed(item.border_box_width());
line_builder.append_box(box, item.border_start + item.padding_start, item.padding_end + item.border_end, item.margin_start, item.margin_end); line_builder.append_box(box, item.border_start + item.padding_start, item.padding_end + item.border_end, item.margin_start, item.margin_end);
break; break;
} }
@ -236,7 +236,7 @@ void InlineFormattingContext::generate_line_boxes(LayoutMode layout_mode)
case InlineLevelIterator::Item::Type::Text: { case InlineLevelIterator::Item::Type::Text: {
auto& text_node = verify_cast<Layout::TextNode>(*item.node); auto& text_node = verify_cast<Layout::TextNode>(*item.node);
if (text_node.computed_values().white_space() != CSS::WhiteSpace::Nowrap && line_builder.break_if_needed(layout_mode, item.border_box_width())) { if (text_node.computed_values().white_space() != CSS::WhiteSpace::Nowrap && line_builder.break_if_needed(item.border_box_width())) {
// If whitespace caused us to break, we swallow the whitespace instead of // If whitespace caused us to break, we swallow the whitespace instead of
// putting it on the next line. // putting it on the next line.

View file

@ -36,16 +36,16 @@ void LineBuilder::begin_new_line(bool increment_y)
if (increment_y) if (increment_y)
m_current_y += max(m_max_height_on_current_line, m_context.containing_block().line_height()); m_current_y += max(m_max_height_on_current_line, m_context.containing_block().line_height());
switch (m_layout_mode) { switch (m_containing_block_state.width_constraint) {
case LayoutMode::Normal: case SizeConstraint::MinContent:
m_available_width_for_current_line = m_context.available_space_for_line(m_current_y);
break;
case LayoutMode::MinContent:
m_available_width_for_current_line = 0; m_available_width_for_current_line = 0;
break; break;
case LayoutMode::MaxContent: case SizeConstraint::MaxContent:
m_available_width_for_current_line = INFINITY; m_available_width_for_current_line = INFINITY;
break; break;
default:
m_available_width_for_current_line = m_context.available_space_for_line(m_current_y);
break;
} }
m_max_height_on_current_line = 0; m_max_height_on_current_line = 0;
@ -79,12 +79,8 @@ void LineBuilder::append_text_chunk(TextNode const& text_node, size_t offset_in_
m_max_height_on_current_line = max(m_max_height_on_current_line, content_height); m_max_height_on_current_line = max(m_max_height_on_current_line, content_height);
} }
bool LineBuilder::should_break(LayoutMode layout_mode, float next_item_width) bool LineBuilder::should_break(float next_item_width)
{ {
if (layout_mode == LayoutMode::MinContent)
return true;
if (layout_mode == LayoutMode::MaxContent)
return false;
auto const& line_boxes = m_containing_block_state.line_boxes; auto const& line_boxes = m_containing_block_state.line_boxes;
if (line_boxes.is_empty() || line_boxes.last().is_empty()) if (line_boxes.is_empty() || line_boxes.last().is_empty())
return false; return false;

View file

@ -23,9 +23,9 @@ public:
void append_text_chunk(TextNode const&, size_t offset_in_node, size_t length_in_node, float leading_size, float trailing_size, float leading_margin, float trailing_margin, float content_width, float content_height); void append_text_chunk(TextNode const&, size_t offset_in_node, size_t length_in_node, float leading_size, float trailing_size, float leading_margin, float trailing_margin, float content_width, float content_height);
// Returns whether a line break occurred. // Returns whether a line break occurred.
bool break_if_needed(LayoutMode layout_mode, float next_item_width) bool break_if_needed(float next_item_width)
{ {
if (should_break(layout_mode, next_item_width)) { if (should_break(next_item_width)) {
break_line(); break_line();
return true; return true;
} }
@ -45,7 +45,7 @@ public:
private: private:
void begin_new_line(bool increment_y); void begin_new_line(bool increment_y);
bool should_break(LayoutMode, float next_item_width); bool should_break(float next_item_width);
LineBox& ensure_last_line_box(); LineBox& ensure_last_line_box();

View file

@ -21,20 +21,13 @@
namespace Web::Layout { namespace Web::Layout {
enum class LayoutMode { enum class LayoutMode {
// Normal layout. // Normal layout. No min-content or max-content constraints applied.
// - We use the containing block's used width.
// - Content flows into the available space, line breaks inserted where necessary.
Normal, Normal,
// MinContent layout is used for discovering the min-content intrinsic size of a box. // Intrinsic size determination.
// - We act as if the containing block has 0 used width. // Boxes honor min-content and max-content constraints (set via FormattingState::NodeState::{width,height}_constraint)
// - Every line-breaking opportunity is taken. // by considering their containing block to be 0-sized or infinitely large in the relevant axis.
MinContent, IntrinsicSizeDetermination,
// MaxContent layout is used for discovering the max-content intrinsic size of a box.
// - We act as if the containing block has infinite used width.
// - Only forced line-breaking opportunities are taken.
MaxContent,
}; };
class Node : public TreeNode<Node> { class Node : public TreeNode<Node> {

View file

@ -119,7 +119,13 @@ void TableFormattingContext::calculate_column_widths(Box const& row, CSS::Length
auto const& computed_values = cell.computed_values(); auto const& computed_values = cell.computed_values();
auto specified_width = computed_values.width().resolved(cell, table_width).resolved(cell); auto specified_width = computed_values.width().resolved(cell, table_width).resolved(cell);
compute_width(cell, specified_width.is_auto() ? LayoutMode::MinContent : LayoutMode::Normal); if (specified_width.is_auto()) {
auto width = calculate_max_content_width(cell);
cell_state.content_width = width;
} else {
compute_width(cell, LayoutMode::Normal);
}
(void)layout_inside(cell, LayoutMode::Normal); (void)layout_inside(cell, LayoutMode::Normal);
if (cell.colspan() == 1) { if (cell.colspan() == 1) {

View file

@ -111,12 +111,12 @@ Optional<TextNode::Chunk> TextNode::ChunkIterator::next()
// Otherwise, commit the newline! // Otherwise, commit the newline!
++m_iterator; ++m_iterator;
auto result = try_commit_chunk(start_of_chunk, m_iterator, true, true); auto result = try_commit_chunk(start_of_chunk, m_iterator, true);
VERIFY(result.has_value()); VERIFY(result.has_value());
return result.release_value(); return result.release_value();
} }
if (m_wrap_lines || m_layout_mode == LayoutMode::MinContent) { if (m_wrap_lines) {
if (is_ascii_space(*m_iterator)) { if (is_ascii_space(*m_iterator)) {
// Whitespace encountered, and we're allowed to break on whitespace. // Whitespace encountered, and we're allowed to break on whitespace.
// If we have accumulated some code points in the current chunk, commit them now and continue with the whitespace next time. // If we have accumulated some code points in the current chunk, commit them now and continue with the whitespace next time.
@ -136,18 +136,15 @@ Optional<TextNode::Chunk> TextNode::ChunkIterator::next()
if (start_of_chunk != m_utf8_view.end()) { if (start_of_chunk != m_utf8_view.end()) {
// Try to output whatever's left at the end of the text node. // Try to output whatever's left at the end of the text node.
if (auto result = try_commit_chunk(start_of_chunk, m_utf8_view.end(), false, true); result.has_value()) if (auto result = try_commit_chunk(start_of_chunk, m_utf8_view.end(), false); result.has_value())
return result.release_value(); return result.release_value();
} }
return {}; return {};
} }
Optional<TextNode::Chunk> TextNode::ChunkIterator::try_commit_chunk(Utf8View::Iterator const& start, Utf8View::Iterator const& end, bool has_breaking_newline, bool must_commit) const Optional<TextNode::Chunk> TextNode::ChunkIterator::try_commit_chunk(Utf8View::Iterator const& start, Utf8View::Iterator const& end, bool has_breaking_newline) const
{ {
if (m_layout_mode == LayoutMode::MaxContent && !must_commit)
return {};
auto byte_offset = m_utf8_view.byte_offset_of(start); auto byte_offset = m_utf8_view.byte_offset_of(start);
auto byte_length = m_utf8_view.byte_offset_of(end) - byte_offset; auto byte_length = m_utf8_view.byte_offset_of(end) - byte_offset;

View file

@ -37,7 +37,7 @@ public:
Optional<Chunk> next(); Optional<Chunk> next();
private: private:
Optional<Chunk> try_commit_chunk(Utf8View::Iterator const& start, Utf8View::Iterator const& end, bool has_breaking_newline, bool must_commit = false) const; Optional<Chunk> try_commit_chunk(Utf8View::Iterator const& start, Utf8View::Iterator const& end, bool has_breaking_newline) const;
const LayoutMode m_layout_mode; const LayoutMode m_layout_mode;
bool const m_wrap_lines; bool const m_wrap_lines;