1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 14:28:12 +00:00

LibWeb: Let parent formatting context determine size of flex containers

Until now, we had implemented flex container sizing by awkwardly doing
exactly what the spec said (basically having FFC size the container)
despite that not really making sense in the big picture. (Parent
formatting contexts should be responsible for sizing and placing their
children)

This patch moves us away from the Flexbox spec text a little bit, by
removing the logic for sizing the flex container in FFC, and instead
making sure that all formatting contexts can set both width and height
of flex container children.

This required changes in BFC and IFC, but it's actually quite simple!
Width was already not a problem, and it turns out height isn't either,
since the automatic height of a flex container is max-content.
With this in mind, we can simply determine the height of flex containers
before transferring control to FFC, and everything flows nicely.

With this change, we can remove all the virtuals and FFC logic for
negotiating container size with the parent formatting context.
We also don't need the "available space for flex container" stuff
anymore either, so that's gone as well.

There are some minor diffs in layout test results from this, but the
new results actually match other browsers more closely, so that's fine.

This should make flex layout, and indeed layout in general, easier to
understand, since this was the main weird special case outside of
BFC/IFC where a formatting context delegates work to its parent instead
of the other way around. :^)
This commit is contained in:
Andreas Kling 2024-01-10 14:52:21 +01:00
parent 5f0230a57e
commit 5af02a914c
9 changed files with 40 additions and 196 deletions

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021-2023, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021-2024, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
@ -56,35 +56,17 @@ CSSPixels FlexFormattingContext::automatic_content_height() const
return m_flex_container_state.content_height();
}
void FlexFormattingContext::run(Box const& run_box, LayoutMode, AvailableSpace const& available_content_space)
void FlexFormattingContext::run(Box const& run_box, LayoutMode, AvailableSpace const& available_space)
{
VERIFY(&run_box == &flex_container());
// NOTE: The available space provided by the parent context is basically our *content box*.
// FFC is currently written in a way that expects that to include padding and border as well,
// so we pad out the available space here to accommodate that.
// FIXME: Refactor the necessary parts of FFC so we don't need this hack!
auto available_width = available_content_space.width;
if (available_width.is_definite())
available_width = AvailableSize::make_definite(available_width.to_px_or_zero() + m_flex_container_state.border_box_left() + m_flex_container_state.border_box_right());
auto available_height = available_content_space.height;
if (available_height.is_definite())
available_height = AvailableSize::make_definite(available_height.to_px_or_zero() + m_flex_container_state.border_box_top() + m_flex_container_state.border_box_bottom());
m_available_space_for_flex_container = AxisAgnosticAvailableSpace {
.main = is_row_layout() ? available_width : available_height,
.cross = !is_row_layout() ? available_width : available_height,
.space = { available_width, available_height },
};
// This implements https://www.w3.org/TR/css-flexbox-1/#layout-algorithm
// 1. Generate anonymous flex items
generate_anonymous_flex_items();
// 2. Determine the available main and cross space for the flex items
determine_available_space_for_items(AvailableSpace(available_width, available_height));
determine_available_space_for_items(available_space);
{
// https://drafts.csswg.org/css-flexbox-1/#definite-sizes
@ -114,12 +96,16 @@ void FlexFormattingContext::run(Box const& run_box, LayoutMode, AvailableSpace c
determine_flex_base_size_and_hypothetical_main_size(item);
}
if (available_width.is_intrinsic_sizing_constraint() || available_height.is_intrinsic_sizing_constraint()) {
if (available_space.width.is_intrinsic_sizing_constraint() || available_space.height.is_intrinsic_sizing_constraint()) {
// We're computing intrinsic size for the flex container. This happens at the end of run().
} else {
// 4. Determine the main size of the flex container
determine_main_size_of_flex_container();
// Determine the main size of the flex container using the rules of the formatting context in which it participates.
// NOTE: The automatic block size of a block-level flex container is its max-content size.
// NOTE: We've already handled this in the parent formatting context.
// Specifically, all formatting contexts will have assigned width & height to the flex container
// before this formatting context runs.
}
// 5. Collect flex items into flex lines:
@ -186,7 +172,7 @@ void FlexFormattingContext::run(Box const& run_box, LayoutMode, AvailableSpace c
// 16. Align all flex lines (per align-content)
align_all_flex_lines();
if (available_width.is_intrinsic_sizing_constraint() || available_height.is_intrinsic_sizing_constraint()) {
if (available_space.width.is_intrinsic_sizing_constraint() || available_space.height.is_intrinsic_sizing_constraint()) {
// We're computing intrinsic size for the flex container.
determine_intrinsic_size_of_flex_container();
} else {
@ -195,7 +181,7 @@ void FlexFormattingContext::run(Box const& run_box, LayoutMode, AvailableSpace c
copy_dimensions_from_flex_items_to_boxes();
for (auto& item : m_flex_items) {
auto& box_state = m_state.get(item.box);
if (auto independent_formatting_context = layout_inside(item.box, LayoutMode::Normal, box_state.available_inner_space_or_constraints_from(m_available_space_for_flex_container->space)))
if (auto independent_formatting_context = layout_inside(item.box, LayoutMode::Normal, box_state.available_inner_space_or_constraints_from(m_available_space_for_items->space)))
independent_formatting_context->parent_context_did_dimension_child_root_box();
compute_inset(item.box);
@ -443,66 +429,17 @@ void FlexFormattingContext::set_main_axis_second_margin(FlexItem& item, CSSPixel
// https://drafts.csswg.org/css-flexbox-1/#algo-available
void FlexFormattingContext::determine_available_space_for_items(AvailableSpace const& available_space)
{
// For each dimension, if that dimension of the flex containers content box is a definite size, use that;
// if that dimension of the flex container is being sized under a min or max-content constraint, the available space in that dimension is that constraint;
// otherwise, subtract the flex containers margin, border, and padding from the space available to the flex container in that dimension and use that value.
// This might result in an infinite value.
Optional<AvailableSize> available_width_for_items;
if (m_flex_container_state.has_definite_width()) {
available_width_for_items = AvailableSize::make_definite(m_flex_container_state.content_width());
} else {
if (available_space.width.is_intrinsic_sizing_constraint()) {
available_width_for_items = available_space.width;
} else {
if (available_space.width.is_definite()) {
auto remaining = available_space.width.to_px_or_zero()
- m_flex_container_state.margin_left
- m_flex_container_state.margin_right
- m_flex_container_state.border_left
- m_flex_container_state.padding_right
- m_flex_container_state.padding_left
- m_flex_container_state.padding_right;
available_width_for_items = AvailableSize::make_definite(remaining);
} else {
available_width_for_items = AvailableSize::make_indefinite();
}
}
}
Optional<AvailableSize> available_height_for_items;
if (m_flex_container_state.has_definite_height()) {
available_height_for_items = AvailableSize::make_definite(m_flex_container_state.content_height());
} else {
if (available_space.height.is_intrinsic_sizing_constraint()) {
available_height_for_items = available_space.height;
} else {
if (available_space.height.is_definite()) {
auto remaining = available_space.height.to_px_or_zero()
- m_flex_container_state.margin_top
- m_flex_container_state.margin_bottom
- m_flex_container_state.border_top
- m_flex_container_state.padding_bottom
- m_flex_container_state.padding_top
- m_flex_container_state.padding_bottom;
available_height_for_items = AvailableSize::make_definite(remaining);
} else {
available_height_for_items = AvailableSize::make_indefinite();
}
}
}
if (is_row_layout()) {
m_available_space_for_items = AxisAgnosticAvailableSpace {
.main = *available_width_for_items,
.cross = *available_height_for_items,
.space = { *available_width_for_items, *available_height_for_items },
.main = available_space.width,
.cross = available_space.height,
.space = { available_space.width, available_space.height },
};
} else {
m_available_space_for_items = AxisAgnosticAvailableSpace {
.main = *available_height_for_items,
.cross = *available_width_for_items,
.space = { *available_width_for_items, *available_height_for_items },
.main = available_space.height,
.cross = available_space.width,
.space = { available_space.width, available_space.height },
};
}
}
@ -785,59 +722,6 @@ CSSPixels FlexFormattingContext::content_based_minimum_size(FlexItem const& item
return unclamped_size;
}
bool FlexFormattingContext::can_determine_size_of_child() const
{
return true;
}
void FlexFormattingContext::determine_width_of_child(Box const&, AvailableSpace const&)
{
// NOTE: For now, we simply do nothing here. If a child context is calling up to us
// and asking us to determine its width, we've already done so as part of the
// flex layout algorithm.
}
void FlexFormattingContext::determine_height_of_child(Box const&, AvailableSpace const&)
{
// NOTE: For now, we simply do nothing here. If a child context is calling up to us
// and asking us to determine its height, we've already done so as part of the
// flex layout algorithm.
}
// https://drafts.csswg.org/css-flexbox-1/#algo-main-container
void FlexFormattingContext::determine_main_size_of_flex_container()
{
// Determine the main size of the flex container using the rules of the formatting context in which it participates.
// NOTE: The automatic block size of a block-level flex container is its max-content size.
// FIXME: The code below doesn't know how to size absolutely positioned flex containers at all.
// We just leave it alone for now and let the parent context deal with it.
if (flex_container().is_absolutely_positioned())
return;
// FIXME: Once all parent contexts now how to size a given child, we can remove
// `can_determine_size_of_child()`.
if (parent()->can_determine_size_of_child()) {
if (is_row_layout()) {
parent()->determine_width_of_child(flex_container(), m_available_space_for_flex_container->space);
} else {
parent()->determine_height_of_child(flex_container(), m_available_space_for_flex_container->space);
}
return;
}
if (is_row_layout()) {
if (!flex_container().is_out_of_flow(*parent()) && m_state.get(*flex_container().containing_block()).has_definite_width()) {
set_main_size(flex_container(), calculate_stretch_fit_width(flex_container(), m_available_space_for_flex_container->space.width));
} else {
set_main_size(flex_container(), calculate_max_content_width(flex_container()));
}
} else {
if (!has_definite_main_size(flex_container()))
set_main_size(flex_container(), calculate_max_content_height(flex_container(), m_available_space_for_flex_container->space.width.to_px_or_zero()));
}
}
// https://www.w3.org/TR/css-flexbox-1/#algo-line-break
void FlexFormattingContext::collect_flex_items_into_flex_lines()
{
@ -1229,7 +1113,7 @@ void FlexFormattingContext::calculate_cross_size_of_each_flex_line()
// If the flex container is single-line, then clamp the lines cross-size to be within the containers computed min and max cross sizes.
// Note that if CSS 2.1s definition of min/max-width/height applied more generally, this behavior would fall out automatically.
// AD-HOC: We don't do this when the flex container is being sized under a min-content or max-content constraint.
if (is_single_line() && !m_available_space_for_flex_container->cross.is_intrinsic_sizing_constraint()) {
if (is_single_line() && !m_available_space_for_items->cross.is_intrinsic_sizing_constraint()) {
auto const& computed_min_size = this->computed_cross_min_size(flex_container());
auto const& computed_max_size = this->computed_cross_max_size(flex_container());
auto cross_min_size = (!computed_min_size.is_auto() && !computed_min_size.contains_percentage()) ? specified_cross_min_size(flex_container()) : 0;
@ -1553,7 +1437,7 @@ void FlexFormattingContext::determine_flex_container_used_cross_size()
}
// AD-HOC: We don't apply min/max cross size constraints when sizing the flex container under an intrinsic sizing constraint.
if (!m_available_space_for_flex_container->cross.is_intrinsic_sizing_constraint()) {
if (!m_available_space_for_items->cross.is_intrinsic_sizing_constraint()) {
auto const& computed_min_size = this->computed_cross_min_size(flex_container());
auto const& computed_max_size = this->computed_cross_max_size(flex_container());
auto cross_min_size = (!computed_min_size.is_auto() && !computed_min_size.contains_percentage()) ? specified_cross_min_size(flex_container()) : 0;
@ -1689,7 +1573,7 @@ void FlexFormattingContext::copy_dimensions_from_flex_items_to_boxes()
// https://drafts.csswg.org/css-flexbox-1/#intrinsic-sizes
void FlexFormattingContext::determine_intrinsic_size_of_flex_container()
{
if (m_available_space_for_flex_container->main.is_intrinsic_sizing_constraint()) {
if (m_available_space_for_items->main.is_intrinsic_sizing_constraint()) {
CSSPixels main_size = calculate_intrinsic_main_size_of_flex_container();
set_main_size(flex_container(), main_size);
}
@ -2051,7 +1935,7 @@ CSSPixels FlexFormattingContext::calculate_fit_content_cross_size(FlexItem const
CSSPixels FlexFormattingContext::calculate_min_content_cross_size(FlexItem const& item) const
{
if (is_row_layout()) {
auto available_space = m_state.get(item.box).available_inner_space_or_constraints_from(m_available_space_for_flex_container->space);
auto available_space = m_state.get(item.box).available_inner_space_or_constraints_from(m_available_space_for_items->space);
if (available_space.width.is_indefinite()) {
available_space.width = AvailableSize::make_definite(calculate_width_to_use_when_determining_intrinsic_height_of_item(item));
}
@ -2063,7 +1947,7 @@ CSSPixels FlexFormattingContext::calculate_min_content_cross_size(FlexItem const
CSSPixels FlexFormattingContext::calculate_max_content_cross_size(FlexItem const& item) const
{
if (is_row_layout()) {
auto available_space = m_state.get(item.box).available_inner_space_or_constraints_from(m_available_space_for_flex_container->space);
auto available_space = m_state.get(item.box).available_inner_space_or_constraints_from(m_available_space_for_items->space);
if (available_space.width.is_indefinite()) {
available_space.width = AvailableSize::make_definite(calculate_width_to_use_when_determining_intrinsic_height_of_item(item));
}