Browse Source

LibWeb: Improve FFC step 7 (hypothetical cross size)

- Avoid performing inside layout on definite-size flex items (since
  their computed size can be used as-is.)

- Use FormattingState::clone() to generate a throwaway layout instead of
  mutating the tree in-place.

- Update spec link & comments based on current CSSWG draft. The latest
  version is quite a bit clearer on how this should work.
Andreas Kling 3 years ago
parent
commit
1ce1af5d8b

+ 29 - 26
Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2021-2022, Andreas Kling <kling@serenityos.org>
  * Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
  *
  * SPDX-License-Identifier: BSD-2-Clause
@@ -117,7 +117,7 @@ void FlexFormattingContext::run(Box const& run_box, LayoutMode)
     // Cross Size Determination
     // 7. Determine the hypothetical cross size of each item
     for (auto& flex_item : m_flex_items) {
-        flex_item.hypothetical_cross_size = determine_hypothetical_cross_size_of_item(flex_item.box);
+        determine_hypothetical_cross_size_of_item(flex_item);
     }
 
     // 8. Calculate the cross size of each flex line.
@@ -825,36 +825,39 @@ void FlexFormattingContext::resolve_flexible_lengths(float const main_available_
     }
 }
 
-// https://www.w3.org/TR/css-flexbox-1/#algo-cross-item
-float FlexFormattingContext::determine_hypothetical_cross_size_of_item(Box const& box)
+// https://drafts.csswg.org/css-flexbox-1/#algo-cross-item
+void FlexFormattingContext::determine_hypothetical_cross_size_of_item(FlexItem& item)
 {
-    bool cross_constrained = false;
-    if (is_row_layout()) {
-        if (!is_undefined_or_auto(box.computed_values().height()) || !is_undefined_or_auto(box.computed_values().min_height())) {
-            cross_constrained = true;
-        }
-    } else {
-        if (!is_undefined_or_auto(box.computed_values().width()) || !is_undefined_or_auto(box.computed_values().min_width())) {
-            cross_constrained = true;
-        }
+    // Determine the hypothetical cross size of each item by performing layout
+    // as if it were an in-flow block-level box with the used main size
+    // and the given available space, treating auto as fit-content.
+
+    // If we have a definite cross size, this is easy! No need to perform layout, we can just use it as-is.
+    if (has_definite_cross_size(item.box)) {
+        auto const& cross_value = is_row_layout() ? item.box.computed_values().height() : item.box.computed_values().width();
+        item.hypothetical_cross_size = cross_value->length().to_px(item.box);
+        return;
     }
 
-    if (!cross_constrained && box.children_are_inline()) {
-        auto& block_container = verify_cast<BlockContainer>(box);
-        BlockFormattingContext bfc(m_state, block_container, this);
-        bfc.run(box, LayoutMode::Default);
-        InlineFormattingContext ifc(m_state, block_container, bfc);
-        ifc.run(box, LayoutMode::OnlyRequiredLineBreaks);
+    // For indefinite cross sizes, we perform a throwaway layout and then measure it.
+    auto throwaway_state = m_state;
+    auto& tmp_container_state = throwaway_state.get_mutable(flex_container());
+    tmp_container_state.content_width = is_row_layout() ? m_available_space->main : m_available_space->cross;
 
-        auto const& box_state = m_state.get(box);
-        return is_row_layout() ? box_state.content_height : box_state.content_width;
+    VERIFY(item.box.containing_block() == &flex_container());
+    if (auto independent_formatting_context = create_independent_formatting_context_if_needed(throwaway_state, item.box)) {
+        independent_formatting_context->run(item.box, LayoutMode::Default);
+    } else {
+        // NOTE: Flex items should always create an independent formatting context!
+        VERIFY_NOT_REACHED();
     }
-    if (is_row_layout())
-        return BlockFormattingContext::compute_theoretical_height(m_state, box);
 
-    BlockFormattingContext context(m_state, verify_cast<BlockContainer>(box), this);
-    context.compute_width(box);
-    return m_state.get(box).content_width;
+    auto const& box_state = throwaway_state.get(item.box);
+    if (is_row_layout()) {
+        item.hypothetical_cross_size = BlockFormattingContext::compute_theoretical_height(throwaway_state, item.box);
+    } else {
+        item.hypothetical_cross_size = box_state.content_width;
+    }
 }
 
 // https://www.w3.org/TR/css-flexbox-1/#algo-cross-line

+ 1 - 1
Userland/Libraries/LibWeb/Layout/FlexFormattingContext.h

@@ -96,7 +96,7 @@ private:
 
     void resolve_flexible_lengths(float main_available_size);
 
-    float determine_hypothetical_cross_size_of_item(Box const&);
+    void determine_hypothetical_cross_size_of_item(FlexItem&);
 
     void calculate_cross_size_of_each_flex_line(float cross_min_size, float cross_max_size);