Coverage Report

Created: 2025-03-04 07:22

/src/serenity/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2021-2024, Andreas Kling <kling@serenityos.org>
3
 * Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
4
 *
5
 * SPDX-License-Identifier: BSD-2-Clause
6
 */
7
8
#include "InlineFormattingContext.h"
9
#include <AK/QuickSort.h>
10
#include <AK/StdLibExtras.h>
11
#include <LibWeb/Layout/BlockContainer.h>
12
#include <LibWeb/Layout/BlockFormattingContext.h>
13
#include <LibWeb/Layout/Box.h>
14
#include <LibWeb/Layout/FlexFormattingContext.h>
15
#include <LibWeb/Layout/ReplacedBox.h>
16
#include <LibWeb/Layout/TextNode.h>
17
#include <LibWeb/Layout/Viewport.h>
18
19
namespace Web::Layout {
20
21
CSSPixels FlexFormattingContext::get_pixel_width(Box const& box, CSS::Size const& size) const
22
0
{
23
0
    return calculate_inner_width(box, containing_block_width_as_available_size(box), size);
24
0
}
25
26
CSSPixels FlexFormattingContext::get_pixel_height(Box const& box, CSS::Size const& size) const
27
0
{
28
0
    return calculate_inner_height(box, containing_block_height_as_available_size(box), size);
29
0
}
30
31
FlexFormattingContext::FlexFormattingContext(LayoutState& state, LayoutMode layout_mode, Box const& flex_container, FormattingContext* parent)
32
0
    : FormattingContext(Type::Flex, layout_mode, state, flex_container, parent)
33
0
    , m_flex_container_state(m_state.get_mutable(flex_container))
34
0
    , m_flex_direction(flex_container.computed_values().flex_direction())
35
0
{
36
0
}
37
38
0
FlexFormattingContext::~FlexFormattingContext() = default;
39
40
CSSPixels FlexFormattingContext::automatic_content_width() const
41
0
{
42
0
    return m_flex_container_state.content_width();
43
0
}
44
45
CSSPixels FlexFormattingContext::automatic_content_height() const
46
0
{
47
0
    return m_flex_container_state.content_height();
48
0
}
49
50
void FlexFormattingContext::run(AvailableSpace const& available_space)
51
0
{
52
    // This implements https://www.w3.org/TR/css-flexbox-1/#layout-algorithm
53
54
    // 1. Generate anonymous flex items
55
0
    generate_anonymous_flex_items();
56
57
    // 2. Determine the available main and cross space for the flex items
58
0
    determine_available_space_for_items(available_space);
59
60
0
    {
61
        // https://drafts.csswg.org/css-flexbox-1/#definite-sizes
62
        // 3. If a single-line flex container has a definite cross size,
63
        //    the automatic preferred outer cross size of any stretched flex items is the flex container’s inner cross size
64
        //    (clamped to the flex item’s min and max cross size) and is considered definite.
65
0
        if (is_single_line() && has_definite_cross_size(m_flex_container_state)) {
66
0
            auto flex_container_inner_cross_size = inner_cross_size(m_flex_container_state);
67
0
            for (auto& item : m_flex_items) {
68
0
                if (!flex_item_is_stretched(item))
69
0
                    continue;
70
0
                auto item_min_cross_size = has_cross_min_size(item.box) ? specified_cross_min_size(item.box) : 0;
71
0
                auto item_max_cross_size = has_cross_max_size(item.box) ? specified_cross_max_size(item.box) : CSSPixels::max();
72
0
                auto item_preferred_outer_cross_size = css_clamp(flex_container_inner_cross_size, item_min_cross_size, item_max_cross_size);
73
0
                auto item_inner_cross_size = item_preferred_outer_cross_size - item.margins.cross_before - item.margins.cross_after - item.padding.cross_before - item.padding.cross_after - item.borders.cross_before - item.borders.cross_after;
74
0
                set_cross_size(item.box, item_inner_cross_size);
75
0
                set_has_definite_cross_size(item);
76
0
            }
77
0
        }
78
0
    }
79
80
    // 3. Determine the flex base size and hypothetical main size of each item
81
0
    for (auto& item : m_flex_items) {
82
0
        if (item.box->is_replaced_box()) {
83
            // FIXME: Get rid of prepare_for_replaced_layout() and make replaced elements figure out their intrinsic size lazily.
84
0
            static_cast<ReplacedBox&>(*item.box).prepare_for_replaced_layout();
85
0
        }
86
0
        determine_flex_base_size_and_hypothetical_main_size(item);
87
0
    }
88
89
0
    if (available_space.width.is_intrinsic_sizing_constraint() || available_space.height.is_intrinsic_sizing_constraint()) {
90
        // We're computing intrinsic size for the flex container. This happens at the end of run().
91
0
    } else {
92
        // 4. Determine the main size of the flex container
93
        // Determine the main size of the flex container using the rules of the formatting context in which it participates.
94
        // NOTE: The automatic block size of a block-level flex container is its max-content size.
95
96
        // NOTE: We've already handled this in the parent formatting context.
97
        //       Specifically, all formatting contexts will have assigned width & height to the flex container
98
        //       before this formatting context runs.
99
0
    }
100
101
    // 5. Collect flex items into flex lines:
102
    // After this step no additional items are to be added to flex_lines or any of its items!
103
0
    collect_flex_items_into_flex_lines();
104
105
    // 6. Resolve the flexible lengths
106
0
    resolve_flexible_lengths();
107
108
    // Cross Size Determination
109
    // 7. Determine the hypothetical cross size of each item
110
0
    for (auto& item : m_flex_items) {
111
0
        determine_hypothetical_cross_size_of_item(item, false);
112
0
    }
113
114
    // 8. Calculate the cross size of each flex line.
115
0
    calculate_cross_size_of_each_flex_line();
116
117
    // 9. Handle 'align-content: stretch'.
118
0
    handle_align_content_stretch();
119
120
    // 10. Collapse visibility:collapse items.
121
    // FIXME: This
122
123
    // 11. Determine the used cross size of each flex item.
124
0
    determine_used_cross_size_of_each_flex_item();
125
126
    // 12. Distribute any remaining free space.
127
0
    distribute_any_remaining_free_space();
128
129
    // 13. Resolve cross-axis auto margins.
130
0
    resolve_cross_axis_auto_margins();
131
132
    // 14. Align all flex items along the cross-axis
133
0
    align_all_flex_items_along_the_cross_axis();
134
135
    // 15. Determine the flex container’s used cross size
136
    // NOTE: This is handled by the parent formatting context.
137
138
0
    {
139
        // https://drafts.csswg.org/css-flexbox-1/#definite-sizes
140
        // 4. Once the cross size of a flex line has been determined,
141
        //    the cross sizes of items in auto-sized flex containers are also considered definite for the purpose of layout.
142
0
        auto const& flex_container_computed_cross_size = is_row_layout() ? flex_container().computed_values().height() : flex_container().computed_values().width();
143
0
        if (flex_container_computed_cross_size.is_auto()) {
144
0
            for (auto& item : m_flex_items) {
145
0
                set_cross_size(item.box, item.cross_size.value());
146
0
                set_has_definite_cross_size(item);
147
0
            }
148
0
        }
149
0
    }
150
151
0
    {
152
        // NOTE: We re-resolve cross sizes here, now that we can resolve percentages.
153
154
        // 7. Determine the hypothetical cross size of each item
155
0
        for (auto& item : m_flex_items) {
156
0
            determine_hypothetical_cross_size_of_item(item, true);
157
0
        }
158
159
        // 11. Determine the used cross size of each flex item.
160
0
        determine_used_cross_size_of_each_flex_item();
161
0
    }
162
163
    // 16. Align all flex lines (per align-content)
164
0
    align_all_flex_lines();
165
166
0
    if (available_space.width.is_intrinsic_sizing_constraint() || available_space.height.is_intrinsic_sizing_constraint()) {
167
        // We're computing intrinsic size for the flex container.
168
0
        determine_intrinsic_size_of_flex_container();
169
0
    } else {
170
        // This is a normal layout (not intrinsic sizing).
171
        // AD-HOC: Finally, layout the inside of all flex items.
172
0
        copy_dimensions_from_flex_items_to_boxes();
173
0
        for (auto& item : m_flex_items) {
174
0
            if (auto independent_formatting_context = layout_inside(item.box, LayoutMode::Normal, item.used_values.available_inner_space_or_constraints_from(m_available_space_for_items->space)))
175
0
                independent_formatting_context->parent_context_did_dimension_child_root_box();
176
177
0
            compute_inset(item.box);
178
0
        }
179
0
    }
180
0
}
181
182
void FlexFormattingContext::parent_context_did_dimension_child_root_box()
183
0
{
184
0
    if (m_layout_mode != LayoutMode::Normal)
185
0
        return;
186
187
0
    flex_container().for_each_child_of_type<Box>([&](Layout::Box& box) {
188
0
        if (box.is_absolutely_positioned()) {
189
0
            auto& cb_state = m_state.get(*box.containing_block());
190
0
            auto available_width = AvailableSize::make_definite(cb_state.content_width() + cb_state.padding_left + cb_state.padding_right);
191
0
            auto available_height = AvailableSize::make_definite(cb_state.content_height() + cb_state.padding_top + cb_state.padding_bottom);
192
0
            layout_absolutely_positioned_element(box, AvailableSpace(available_width, available_height));
193
0
        }
194
0
        return IterationDecision::Continue;
195
0
    });
196
0
}
197
198
// https://www.w3.org/TR/css-flexbox-1/#flex-direction-property
199
bool FlexFormattingContext::is_direction_reverse() const
200
0
{
201
0
    switch (flex_container().computed_values().direction()) {
202
0
    case CSS::Direction::Ltr:
203
0
        return m_flex_direction == CSS::FlexDirection::ColumnReverse || m_flex_direction == CSS::FlexDirection::RowReverse;
204
0
    case CSS::Direction::Rtl:
205
0
        return m_flex_direction == CSS::FlexDirection::ColumnReverse || m_flex_direction == CSS::FlexDirection::Row;
206
0
    default:
207
0
        VERIFY_NOT_REACHED();
208
0
    }
209
0
}
210
211
void FlexFormattingContext::populate_specified_margins(FlexItem& item, CSS::FlexDirection flex_direction) const
212
0
{
213
0
    auto width_of_containing_block = m_flex_container_state.content_width();
214
    // FIXME: This should also take reverse-ness into account
215
0
    if (flex_direction == CSS::FlexDirection::Row || flex_direction == CSS::FlexDirection::RowReverse) {
216
0
        item.borders.main_before = item.box->computed_values().border_left().width;
217
0
        item.borders.main_after = item.box->computed_values().border_right().width;
218
0
        item.borders.cross_before = item.box->computed_values().border_top().width;
219
0
        item.borders.cross_after = item.box->computed_values().border_bottom().width;
220
221
0
        item.padding.main_before = item.box->computed_values().padding().left().to_px(item.box, width_of_containing_block);
222
0
        item.padding.main_after = item.box->computed_values().padding().right().to_px(item.box, width_of_containing_block);
223
0
        item.padding.cross_before = item.box->computed_values().padding().top().to_px(item.box, width_of_containing_block);
224
0
        item.padding.cross_after = item.box->computed_values().padding().bottom().to_px(item.box, width_of_containing_block);
225
226
0
        item.margins.main_before = item.box->computed_values().margin().left().to_px(item.box, width_of_containing_block);
227
0
        item.margins.main_after = item.box->computed_values().margin().right().to_px(item.box, width_of_containing_block);
228
0
        item.margins.cross_before = item.box->computed_values().margin().top().to_px(item.box, width_of_containing_block);
229
0
        item.margins.cross_after = item.box->computed_values().margin().bottom().to_px(item.box, width_of_containing_block);
230
231
0
        item.margins.main_before_is_auto = item.box->computed_values().margin().left().is_auto();
232
0
        item.margins.main_after_is_auto = item.box->computed_values().margin().right().is_auto();
233
0
        item.margins.cross_before_is_auto = item.box->computed_values().margin().top().is_auto();
234
0
        item.margins.cross_after_is_auto = item.box->computed_values().margin().bottom().is_auto();
235
0
    } else {
236
0
        item.borders.main_before = item.box->computed_values().border_top().width;
237
0
        item.borders.main_after = item.box->computed_values().border_bottom().width;
238
0
        item.borders.cross_before = item.box->computed_values().border_left().width;
239
0
        item.borders.cross_after = item.box->computed_values().border_right().width;
240
241
0
        item.padding.main_before = item.box->computed_values().padding().top().to_px(item.box, width_of_containing_block);
242
0
        item.padding.main_after = item.box->computed_values().padding().bottom().to_px(item.box, width_of_containing_block);
243
0
        item.padding.cross_before = item.box->computed_values().padding().left().to_px(item.box, width_of_containing_block);
244
0
        item.padding.cross_after = item.box->computed_values().padding().right().to_px(item.box, width_of_containing_block);
245
246
0
        item.margins.main_before = item.box->computed_values().margin().top().to_px(item.box, width_of_containing_block);
247
0
        item.margins.main_after = item.box->computed_values().margin().bottom().to_px(item.box, width_of_containing_block);
248
0
        item.margins.cross_before = item.box->computed_values().margin().left().to_px(item.box, width_of_containing_block);
249
0
        item.margins.cross_after = item.box->computed_values().margin().right().to_px(item.box, width_of_containing_block);
250
251
0
        item.margins.main_before_is_auto = item.box->computed_values().margin().top().is_auto();
252
0
        item.margins.main_after_is_auto = item.box->computed_values().margin().bottom().is_auto();
253
0
        item.margins.cross_before_is_auto = item.box->computed_values().margin().left().is_auto();
254
0
        item.margins.cross_after_is_auto = item.box->computed_values().margin().right().is_auto();
255
0
    }
256
0
}
257
258
// https://www.w3.org/TR/css-flexbox-1/#flex-items
259
void FlexFormattingContext::generate_anonymous_flex_items()
260
0
{
261
    // More like, sift through the already generated items.
262
    // After this step no items are to be added or removed from flex_items!
263
    // It holds every item we need to consider and there should be nothing in the following
264
    // calculations that could change that.
265
    // This is particularly important since we take references to the items stored in flex_items
266
    // later, whose addresses won't be stable if we added or removed any items.
267
0
    HashMap<int, Vector<FlexItem>> order_item_bucket;
268
269
0
    flex_container().for_each_child_of_type<Box>([&](Box& child_box) {
270
0
        if (can_skip_is_anonymous_text_run(child_box))
271
0
            return IterationDecision::Continue;
272
273
        // Skip any "out-of-flow" children
274
0
        if (child_box.is_out_of_flow(*this))
275
0
            return IterationDecision::Continue;
276
277
0
        child_box.set_flex_item(true);
278
0
        FlexItem item = { child_box, m_state.get_mutable(child_box) };
279
0
        populate_specified_margins(item, m_flex_direction);
280
281
0
        auto& order_bucket = order_item_bucket.ensure(child_box.computed_values().order());
282
0
        order_bucket.append(move(item));
283
284
0
        return IterationDecision::Continue;
285
0
    });
286
287
0
    auto keys = order_item_bucket.keys();
288
289
0
    if (is_direction_reverse()) {
290
0
        quick_sort(keys, [](auto& a, auto& b) { return a > b; });
291
0
    } else {
292
0
        quick_sort(keys, [](auto& a, auto& b) { return a < b; });
293
0
    }
294
295
0
    for (auto key : keys) {
296
0
        auto order_bucket = order_item_bucket.get(key);
297
0
        if (order_bucket.has_value()) {
298
0
            auto& items = order_bucket.value();
299
0
            for (auto item : items) {
300
0
                m_flex_items.append(move(item));
301
0
            }
302
0
        }
303
0
    }
304
0
}
305
306
bool FlexFormattingContext::has_definite_main_size(LayoutState::UsedValues const& used_values) const
307
0
{
308
0
    return is_row_layout() ? used_values.has_definite_width() : used_values.has_definite_height();
309
0
}
310
311
CSSPixels FlexFormattingContext::inner_main_size(LayoutState::UsedValues const& used_values) const
312
0
{
313
0
    return is_row_layout() ? used_values.content_width() : used_values.content_height();
314
0
}
315
316
CSSPixels FlexFormattingContext::inner_cross_size(LayoutState::UsedValues const& used_values) const
317
0
{
318
0
    return is_row_layout() ? used_values.content_height() : used_values.content_width();
319
0
}
320
321
bool FlexFormattingContext::has_main_min_size(Box const& box) const
322
0
{
323
0
    auto const& value = is_row_layout() ? box.computed_values().min_width() : box.computed_values().min_height();
324
0
    return !value.is_auto();
325
0
}
326
327
bool FlexFormattingContext::has_cross_min_size(Box const& box) const
328
0
{
329
0
    auto const& value = is_row_layout() ? box.computed_values().min_height() : box.computed_values().min_width();
330
0
    return !value.is_auto();
331
0
}
332
333
bool FlexFormattingContext::has_definite_cross_size(LayoutState::UsedValues const& used_values) const
334
0
{
335
0
    return is_row_layout() ? used_values.has_definite_height() : used_values.has_definite_width();
336
0
}
337
338
CSSPixels FlexFormattingContext::specified_main_min_size(Box const& box) const
339
0
{
340
0
    return is_row_layout()
341
0
        ? get_pixel_width(box, box.computed_values().min_width())
342
0
        : get_pixel_height(box, box.computed_values().min_height());
343
0
}
344
345
CSSPixels FlexFormattingContext::specified_cross_min_size(Box const& box) const
346
0
{
347
0
    return is_row_layout()
348
0
        ? get_pixel_height(box, box.computed_values().min_height())
349
0
        : get_pixel_width(box, box.computed_values().min_width());
350
0
}
351
352
bool FlexFormattingContext::has_main_max_size(Box const& box) const
353
0
{
354
0
    return !should_treat_main_max_size_as_none(box);
355
0
}
356
357
bool FlexFormattingContext::has_cross_max_size(Box const& box) const
358
0
{
359
0
    return !should_treat_cross_max_size_as_none(box);
360
0
}
361
362
CSSPixels FlexFormattingContext::specified_main_max_size(Box const& box) const
363
0
{
364
0
    return is_row_layout()
365
0
        ? get_pixel_width(box, box.computed_values().max_width())
366
0
        : get_pixel_height(box, box.computed_values().max_height());
367
0
}
368
369
CSSPixels FlexFormattingContext::specified_cross_max_size(Box const& box) const
370
0
{
371
0
    return is_row_layout()
372
0
        ? get_pixel_height(box, box.computed_values().max_height())
373
0
        : get_pixel_width(box, box.computed_values().max_width());
374
0
}
375
376
bool FlexFormattingContext::is_cross_auto(Box const& box) const
377
0
{
378
0
    auto& cross_length = is_row_layout() ? box.computed_values().height() : box.computed_values().width();
379
0
    return cross_length.is_auto();
380
0
}
381
382
void FlexFormattingContext::set_has_definite_main_size(FlexItem& item)
383
0
{
384
0
    if (is_row_layout())
385
0
        item.used_values.set_has_definite_width(true);
386
0
    else
387
0
        item.used_values.set_has_definite_height(true);
388
0
}
389
390
void FlexFormattingContext::set_has_definite_cross_size(FlexItem& item)
391
0
{
392
0
    if (is_row_layout())
393
0
        item.used_values.set_has_definite_height(true);
394
0
    else
395
0
        item.used_values.set_has_definite_width(true);
396
0
}
397
398
void FlexFormattingContext::set_main_size(Box const& box, CSSPixels size)
399
0
{
400
0
    if (is_row_layout())
401
0
        m_state.get_mutable(box).set_content_width(size);
402
0
    else
403
0
        m_state.get_mutable(box).set_content_height(size);
404
0
}
405
406
void FlexFormattingContext::set_cross_size(Box const& box, CSSPixels size)
407
0
{
408
0
    if (is_row_layout())
409
0
        m_state.get_mutable(box).set_content_height(size);
410
0
    else
411
0
        m_state.get_mutable(box).set_content_width(size);
412
0
}
413
414
void FlexFormattingContext::set_offset(Box const& box, CSSPixels main_offset, CSSPixels cross_offset)
415
0
{
416
0
    if (is_row_layout())
417
0
        m_state.get_mutable(box).offset = CSSPixelPoint { main_offset, cross_offset };
418
0
    else
419
0
        m_state.get_mutable(box).offset = CSSPixelPoint { cross_offset, main_offset };
420
0
}
421
422
void FlexFormattingContext::set_main_axis_first_margin(FlexItem& item, CSSPixels margin)
423
0
{
424
0
    item.margins.main_before = margin;
425
0
    if (is_row_layout())
426
0
        m_state.get_mutable(item.box).margin_left = margin;
427
0
    else
428
0
        m_state.get_mutable(item.box).margin_top = margin;
429
0
}
430
431
void FlexFormattingContext::set_main_axis_second_margin(FlexItem& item, CSSPixels margin)
432
0
{
433
0
    item.margins.main_after = margin;
434
0
    if (is_row_layout())
435
0
        m_state.get_mutable(item.box).margin_right = margin;
436
0
    else
437
0
        m_state.get_mutable(item.box).margin_bottom = margin;
438
0
}
439
440
// https://drafts.csswg.org/css-flexbox-1/#algo-available
441
void FlexFormattingContext::determine_available_space_for_items(AvailableSpace const& available_space)
442
0
{
443
0
    if (is_row_layout()) {
444
0
        m_available_space_for_items = AxisAgnosticAvailableSpace {
445
0
            .main = available_space.width,
446
0
            .cross = available_space.height,
447
0
            .space = { available_space.width, available_space.height },
448
0
        };
449
0
    } else {
450
0
        m_available_space_for_items = AxisAgnosticAvailableSpace {
451
0
            .main = available_space.height,
452
0
            .cross = available_space.width,
453
0
            .space = { available_space.width, available_space.height },
454
0
        };
455
0
    }
456
0
}
457
458
// https://drafts.csswg.org/css-flexbox-1/#propdef-flex-basis
459
CSS::FlexBasis FlexFormattingContext::used_flex_basis_for_item(FlexItem const& item) const
460
0
{
461
0
    auto flex_basis = item.box->computed_values().flex_basis();
462
463
0
    if (flex_basis.has<CSS::Size>() && flex_basis.get<CSS::Size>().is_auto()) {
464
        // https://drafts.csswg.org/css-flexbox-1/#valdef-flex-basis-auto
465
        // When specified on a flex item, the auto keyword retrieves the value of the main size property as the used flex-basis.
466
        // If that value is itself auto, then the used value is content.
467
0
        auto const& main_size = is_row_layout() ? item.box->computed_values().width() : item.box->computed_values().height();
468
469
0
        if (main_size.is_auto()) {
470
0
            flex_basis = CSS::FlexBasisContent {};
471
0
        } else {
472
0
            flex_basis = main_size;
473
0
        }
474
0
    }
475
476
    // For example, percentage values of flex-basis are resolved against the flex item’s containing block
477
    // (i.e. its flex container); and if that containing block’s size is indefinite,
478
    // the used value for flex-basis is content.
479
0
    if (flex_basis.has<CSS::Size>()
480
0
        && flex_basis.get<CSS::Size>().is_percentage()
481
0
        && !has_definite_main_size(m_flex_container_state)) {
482
0
        flex_basis = CSS::FlexBasisContent {};
483
0
    }
484
485
0
    return flex_basis;
486
0
}
487
488
CSSPixels FlexFormattingContext::calculate_main_size_from_cross_size_and_aspect_ratio(CSSPixels cross_size, CSSPixelFraction aspect_ratio) const
489
0
{
490
0
    if (is_row_layout())
491
0
        return cross_size * aspect_ratio;
492
0
    return cross_size / aspect_ratio;
493
0
}
494
495
CSSPixels FlexFormattingContext::calculate_cross_size_from_main_size_and_aspect_ratio(CSSPixels main_size, CSSPixelFraction aspect_ratio) const
496
0
{
497
0
    if (is_row_layout())
498
0
        return main_size / aspect_ratio;
499
0
    return main_size * aspect_ratio;
500
0
}
501
502
// This function takes a size in the main axis and adjusts it according to the aspect ratio of the box
503
// if the min/max constraints in the cross axis forces us to come up with a new main axis size.
504
CSSPixels FlexFormattingContext::adjust_main_size_through_aspect_ratio_for_cross_size_min_max_constraints(Box const& box, CSSPixels main_size, CSS::Size const& min_cross_size, CSS::Size const& max_cross_size) const
505
0
{
506
0
    if (!should_treat_cross_max_size_as_none(box)) {
507
0
        auto max_cross_size_px = max_cross_size.to_px(box, !is_row_layout() ? m_flex_container_state.content_width() : m_flex_container_state.content_height());
508
0
        main_size = min(main_size, calculate_main_size_from_cross_size_and_aspect_ratio(max_cross_size_px, box.preferred_aspect_ratio().value()));
509
0
    }
510
511
0
    if (!min_cross_size.is_auto()) {
512
0
        auto min_cross_size_px = min_cross_size.to_px(box, !is_row_layout() ? m_flex_container_state.content_width() : m_flex_container_state.content_height());
513
0
        main_size = max(main_size, calculate_main_size_from_cross_size_and_aspect_ratio(min_cross_size_px, box.preferred_aspect_ratio().value()));
514
0
    }
515
516
0
    return main_size;
517
0
}
518
519
CSSPixels FlexFormattingContext::adjust_cross_size_through_aspect_ratio_for_main_size_min_max_constraints(Box const& box, CSSPixels cross_size, CSS::Size const& min_main_size, CSS::Size const& max_main_size) const
520
0
{
521
0
    if (!should_treat_main_max_size_as_none(box)) {
522
0
        auto max_main_size_px = max_main_size.to_px(box, is_row_layout() ? m_flex_container_state.content_width() : m_flex_container_state.content_height());
523
0
        cross_size = min(cross_size, calculate_cross_size_from_main_size_and_aspect_ratio(max_main_size_px, box.preferred_aspect_ratio().value()));
524
0
    }
525
526
0
    if (!min_main_size.is_auto()) {
527
0
        auto min_main_size_px = min_main_size.to_px(box, is_row_layout() ? m_flex_container_state.content_width() : m_flex_container_state.content_height());
528
0
        cross_size = max(cross_size, calculate_cross_size_from_main_size_and_aspect_ratio(min_main_size_px, box.preferred_aspect_ratio().value()));
529
0
    }
530
531
0
    return cross_size;
532
0
}
533
534
// https://www.w3.org/TR/css-flexbox-1/#algo-main-item
535
void FlexFormattingContext::determine_flex_base_size_and_hypothetical_main_size(FlexItem& item)
536
0
{
537
0
    auto& child_box = item.box;
538
539
0
    item.flex_base_size = [&] {
540
0
        item.used_flex_basis = used_flex_basis_for_item(item);
541
542
0
        item.used_flex_basis_is_definite = [&](CSS::FlexBasis const& flex_basis) -> bool {
543
0
            if (!flex_basis.has<CSS::Size>())
544
0
                return false;
545
0
            auto const& size = flex_basis.get<CSS::Size>();
546
0
            if (size.is_auto() || size.is_min_content() || size.is_max_content() || size.is_fit_content())
547
0
                return false;
548
0
            if (size.is_length())
549
0
                return true;
550
551
0
            bool can_resolve_percentages = is_row_layout()
552
0
                ? m_flex_container_state.has_definite_width()
553
0
                : m_flex_container_state.has_definite_height();
554
555
0
            if (size.is_calculated()) {
556
0
                auto const& calc_value = size.calculated();
557
0
                if (calc_value.resolves_to_percentage())
558
0
                    return can_resolve_percentages;
559
0
                if (calc_value.resolves_to_length()) {
560
0
                    if (calc_value.contains_percentage())
561
0
                        return can_resolve_percentages;
562
0
                    return true;
563
0
                }
564
0
                return false;
565
0
            }
566
0
            VERIFY(size.is_percentage());
567
0
            return can_resolve_percentages;
568
0
        }(*item.used_flex_basis);
569
570
        // A. If the item has a definite used flex basis, that’s the flex base size.
571
0
        if (item.used_flex_basis_is_definite) {
572
0
            auto const& size = item.used_flex_basis->get<CSS::Size>();
573
0
            if (is_row_layout())
574
0
                return get_pixel_width(child_box, size);
575
0
            return get_pixel_height(child_box, size);
576
0
        }
577
578
        // AD-HOC: If we're sizing the flex container under a min-content constraint in the main axis,
579
        //         flex items resolve percentages in the main axis to 0.
580
0
        if (m_available_space_for_items->main.is_min_content()
581
0
            && computed_main_size(item.box).contains_percentage()) {
582
0
            return CSSPixels(0);
583
0
        }
584
585
        // B. If the flex item has ...
586
        //    - an intrinsic aspect ratio,
587
        //    - a used flex basis of content, and
588
        //    - a definite cross size,
589
0
        if (item.box->has_preferred_aspect_ratio()
590
0
            && item.used_flex_basis->has<CSS::FlexBasisContent>()
591
0
            && has_definite_cross_size(item)) {
592
            // flex_base_size is calculated from definite cross size and intrinsic aspect ratio
593
0
            return adjust_main_size_through_aspect_ratio_for_cross_size_min_max_constraints(
594
0
                item.box,
595
0
                calculate_main_size_from_cross_size_and_aspect_ratio(inner_cross_size(item), item.box->preferred_aspect_ratio().value()),
596
0
                computed_cross_min_size(item.box),
597
0
                computed_cross_max_size(item.box));
598
0
        }
599
600
        // C. If the used flex basis is content or depends on its available space,
601
        //    and the flex container is being sized under a min-content or max-content constraint
602
        //    (e.g. when performing automatic table layout [CSS21]), size the item under that constraint.
603
        //    The flex base size is the item’s resulting main size.
604
0
        if (item.used_flex_basis->has<CSS::FlexBasisContent>() && m_available_space_for_items->main.is_intrinsic_sizing_constraint()) {
605
0
            if (m_available_space_for_items->main.is_min_content())
606
0
                return calculate_min_content_main_size(item);
607
0
            return calculate_max_content_main_size(item);
608
0
        }
609
610
        // D. Otherwise, if the used flex basis is content or depends on its available space,
611
        //    the available main size is infinite, and the flex item’s inline axis is parallel to the main axis,
612
        //    lay the item out using the rules for a box in an orthogonal flow [CSS3-WRITING-MODES].
613
        //    The flex base size is the item’s max-content main size.
614
0
        if (item.used_flex_basis->has<CSS::FlexBasisContent>()
615
            // FIXME: && main_size is infinite && inline axis is parallel to the main axis
616
0
            && false && false) {
617
0
            TODO();
618
            // Use rules for a flex_container in orthogonal flow
619
0
        }
620
621
        // E. Otherwise, size the item into the available space using its used flex basis in place of its main size,
622
        //    treating a value of content as max-content. If a cross size is needed to determine the main size
623
        //    (e.g. when the flex item’s main size is in its block axis) and the flex item’s cross size is auto and not definite,
624
        //    in this calculation use fit-content as the flex item’s cross size.
625
        //    The flex base size is the item’s resulting main size.
626
627
0
        if (auto* size = item.used_flex_basis->get_pointer<CSS::Size>()) {
628
0
            if (size->is_fit_content())
629
0
                return calculate_fit_content_main_size(item);
630
0
            if (size->is_max_content())
631
0
                return calculate_max_content_main_size(item);
632
0
            if (size->is_min_content())
633
0
                return calculate_min_content_main_size(item);
634
0
        }
635
636
        // NOTE: If the flex item has a definite main size, just use that as the flex base size.
637
0
        if (has_definite_main_size(item))
638
0
            return inner_main_size(item);
639
640
        // NOTE: There's a fundamental problem with many CSS specifications in that they neglect to mention
641
        //       which width to provide when calculating the intrinsic height of a box in various situations.
642
        //       Spec bug: https://github.com/w3c/csswg-drafts/issues/2890
643
644
        // NOTE: This is one of many situations where that causes trouble: if this is a flex column layout,
645
        //       we may need to calculate the intrinsic height of a flex item. This requires a width, but a
646
        //       width won't be determined until later on in the flex layout algorithm.
647
        //       In the specific case above (E), the spec mentions using `fit-content` in place of `auto`
648
        //       if "a cross size is needed to determine the main size", so that's exactly what we do.
649
650
        // NOTE: Finding a suitable width for intrinsic height determination actually happens elsewhere,
651
        //       in the various helpers that calculate the intrinsic sizes of a flex item,
652
        //       e.g. calculate_min_content_main_size().
653
654
0
        if (item.used_flex_basis->has<CSS::FlexBasisContent>())
655
0
            return calculate_max_content_main_size(item);
656
657
0
        return calculate_fit_content_main_size(item);
658
0
    }();
659
660
    // AD-HOC: This is not mentioned in the spec, but if the item has an aspect ratio, we may need
661
    //         to adjust the main size in these ways:
662
    //         - using stretch-fit main size if the flex basis is indefinite, there is no
663
    //           intrinsic size and no cross size to resolve the ratio against.
664
    //         - in response to cross size min/max constraints.
665
0
    if (item.box->has_natural_aspect_ratio()) {
666
0
        if (!item.used_flex_basis_is_definite && !item.box->has_natural_width() && !item.box->has_natural_height() && !has_definite_cross_size(item)) {
667
0
            item.flex_base_size = inner_main_size(m_flex_container_state);
668
0
        }
669
0
        item.flex_base_size = adjust_main_size_through_aspect_ratio_for_cross_size_min_max_constraints(child_box, item.flex_base_size, computed_cross_min_size(child_box), computed_cross_max_size(child_box));
670
0
    }
671
672
    // The hypothetical main size is the item’s flex base size clamped according to its used min and max main sizes (and flooring the content box size at zero).
673
0
    auto clamp_min = has_main_min_size(child_box) ? specified_main_min_size(child_box) : automatic_minimum_size(item);
674
0
    auto clamp_max = has_main_max_size(child_box) ? specified_main_max_size(child_box) : CSSPixels::max();
675
0
    item.hypothetical_main_size = max(CSSPixels(0), css_clamp(item.flex_base_size, clamp_min, clamp_max));
676
677
    // NOTE: At this point, we set the hypothetical main size as the flex item's *temporary* main size.
678
    //       The size may change again when we resolve flexible lengths, but this is necessary in order for
679
    //       descendants of this flex item to resolve percentage sizes against something.
680
    //
681
    //       The spec just barely hand-waves about this, but it seems to *roughly* match what other engines do.
682
    //       See "Note" section here: https://drafts.csswg.org/css-flexbox-1/#definite-sizes
683
0
    if (is_row_layout())
684
0
        item.used_values.set_temporary_content_width(item.hypothetical_main_size);
685
0
    else
686
0
        item.used_values.set_temporary_content_height(item.hypothetical_main_size);
687
0
}
688
689
// https://drafts.csswg.org/css-flexbox-1/#min-size-auto
690
CSSPixels FlexFormattingContext::automatic_minimum_size(FlexItem const& item) const
691
0
{
692
    // To provide a more reasonable default minimum size for flex items,
693
    // the used value of a main axis automatic minimum size on a flex item that is not a scroll container is its content-based minimum size;
694
    // for scroll containers the automatic minimum size is zero, as usual.
695
0
    if (!item.box->is_scroll_container())
696
0
        return content_based_minimum_size(item);
697
0
    return 0;
698
0
}
699
700
// https://drafts.csswg.org/css-flexbox-1/#specified-size-suggestion
701
Optional<CSSPixels> FlexFormattingContext::specified_size_suggestion(FlexItem const& item) const
702
0
{
703
    // If the item’s preferred main size is definite and not automatic,
704
    // then the specified size suggestion is that size. It is otherwise undefined.
705
0
    if (has_definite_main_size(item) && !should_treat_main_size_as_auto(item.box)) {
706
        // NOTE: We use get_pixel_{width,height} to ensure that CSS box-sizing is respected.
707
0
        return is_row_layout() ? get_pixel_width(item.box, computed_main_size(item.box)) : get_pixel_height(item.box, computed_main_size(item.box));
708
0
    }
709
0
    return {};
710
0
}
711
712
// https://drafts.csswg.org/css-flexbox-1/#content-size-suggestion
713
CSSPixels FlexFormattingContext::content_size_suggestion(FlexItem const& item) const
714
0
{
715
0
    auto suggestion = calculate_min_content_main_size(item);
716
717
0
    if (item.box->has_preferred_aspect_ratio()) {
718
0
        suggestion = adjust_main_size_through_aspect_ratio_for_cross_size_min_max_constraints(item.box, suggestion, computed_cross_min_size(item.box), computed_cross_max_size(item.box));
719
0
    }
720
721
0
    return suggestion;
722
0
}
723
724
// https://drafts.csswg.org/css-flexbox-1/#transferred-size-suggestion
725
Optional<CSSPixels> FlexFormattingContext::transferred_size_suggestion(FlexItem const& item) const
726
0
{
727
    // If the item has a preferred aspect ratio and its preferred cross size is definite,
728
    // then the transferred size suggestion is that size
729
    // (clamped by its minimum and maximum cross sizes if they are definite), converted through the aspect ratio.
730
0
    if (item.box->has_preferred_aspect_ratio() && has_definite_cross_size(item)) {
731
0
        auto aspect_ratio = item.box->preferred_aspect_ratio().value();
732
0
        return adjust_main_size_through_aspect_ratio_for_cross_size_min_max_constraints(
733
0
            item.box,
734
0
            calculate_main_size_from_cross_size_and_aspect_ratio(inner_cross_size(item), aspect_ratio),
735
0
            computed_cross_min_size(item.box),
736
0
            computed_cross_max_size(item.box));
737
0
    }
738
739
    // It is otherwise undefined.
740
0
    return {};
741
0
}
742
743
// https://drafts.csswg.org/css-flexbox-1/#content-based-minimum-size
744
CSSPixels FlexFormattingContext::content_based_minimum_size(FlexItem const& item) const
745
0
{
746
0
    auto unclamped_size = [&] {
747
        // The content-based minimum size of a flex item is the smaller of its specified size suggestion
748
        // and its content size suggestion if its specified size suggestion exists;
749
0
        if (auto specified_size_suggestion = this->specified_size_suggestion(item); specified_size_suggestion.has_value()) {
750
0
            return min(specified_size_suggestion.value(), content_size_suggestion(item));
751
0
        }
752
753
        // otherwise, the smaller of its transferred size suggestion and its content size suggestion
754
        // if the element is replaced and its transferred size suggestion exists;
755
0
        if (item.box->is_replaced_box()) {
756
0
            if (auto transferred_size_suggestion = this->transferred_size_suggestion(item); transferred_size_suggestion.has_value()) {
757
0
                return min(transferred_size_suggestion.value(), content_size_suggestion(item));
758
0
            }
759
0
        }
760
761
        // otherwise its content size suggestion.
762
0
        return content_size_suggestion(item);
763
0
    }();
764
765
    // In all cases, the size is clamped by the maximum main size if it’s definite.
766
0
    if (has_main_max_size(item.box)) {
767
0
        return min(unclamped_size, specified_main_max_size(item.box));
768
0
    }
769
0
    return unclamped_size;
770
0
}
771
772
// https://www.w3.org/TR/css-flexbox-1/#algo-line-break
773
void FlexFormattingContext::collect_flex_items_into_flex_lines()
774
0
{
775
    // If the flex container is single-line, collect all the flex items into a single flex line.
776
0
    if (is_single_line()) {
777
0
        FlexLine line;
778
0
        for (auto& item : m_flex_items) {
779
0
            if (is_direction_reverse()) {
780
0
                line.items.prepend(item);
781
0
            } else {
782
0
                line.items.append(item);
783
0
            }
784
0
        }
785
0
        m_flex_lines.append(move(line));
786
0
        return;
787
0
    }
788
789
    // Otherwise, starting from the first uncollected item, collect consecutive items one by one
790
    // until the first time that the next collected item would not fit into the flex container’s inner main size
791
    // (or until a forced break is encountered, see §10 Fragmenting Flex Layout).
792
    // If the very first uncollected item wouldn't fit, collect just it into the line.
793
794
    // For this step, the size of a flex item is its outer hypothetical main size. (Note: This can be negative.)
795
796
    // Repeat until all flex items have been collected into flex lines.
797
798
0
    FlexLine line;
799
0
    CSSPixels line_main_size = 0;
800
0
    for (auto& item : m_flex_items) {
801
0
        auto const outer_hypothetical_main_size = item.outer_hypothetical_main_size();
802
0
        if (!line.items.is_empty() && (line_main_size + outer_hypothetical_main_size) > m_available_space_for_items->main) {
803
0
            m_flex_lines.append(move(line));
804
0
            line = {};
805
0
            line_main_size = 0;
806
0
        }
807
808
0
        if (is_direction_reverse()) {
809
0
            line.items.prepend(item);
810
0
        } else {
811
0
            line.items.append(item);
812
0
        }
813
814
0
        line_main_size += outer_hypothetical_main_size;
815
        // CSS-FLEXBOX-2: Account for gap between flex items.
816
0
        line_main_size += main_gap();
817
0
    }
818
0
    m_flex_lines.append(move(line));
819
820
0
    if (flex_container().computed_values().flex_wrap() == CSS::FlexWrap::WrapReverse)
821
0
        m_flex_lines.reverse();
822
0
}
823
824
// https://drafts.csswg.org/css-flexbox-1/#resolve-flexible-lengths
825
void FlexFormattingContext::resolve_flexible_lengths_for_line(FlexLine& line)
826
0
{
827
    // AD-HOC: The spec tells us to use the "flex container’s inner main size" in this algorithm,
828
    //         but that doesn't work when we're sizing under a max-content constraint.
829
    //         In that case, there is effectively infinite size available in the main axis,
830
    //         but the inner main size has not been assigned yet.
831
    //         We solve this by calculating our own "available main size" here, which is essentially
832
    //         infinity under max-content, 0 under min-content, and the inner main size otherwise.
833
0
    AvailableSize available_main_size { AvailableSize::make_indefinite() };
834
0
    if (m_available_space_for_items->main.is_intrinsic_sizing_constraint())
835
0
        available_main_size = m_available_space_for_items->main;
836
0
    else
837
0
        available_main_size = AvailableSize::make_definite(inner_main_size(m_flex_container_state));
838
839
    // 1. Determine the used flex factor.
840
841
    // Sum the outer hypothetical main sizes of all items on the line.
842
    // If the sum is less than the flex container’s inner main size,
843
    // use the flex grow factor for the rest of this algorithm; otherwise, use the flex shrink factor
844
0
    enum FlexFactor {
845
0
        FlexGrowFactor,
846
0
        FlexShrinkFactor
847
0
    };
848
0
    auto used_flex_factor = [&]() -> FlexFactor {
849
0
        CSSPixels sum = 0;
850
0
        for (auto const& item : line.items) {
851
0
            sum += item.outer_hypothetical_main_size();
852
0
        }
853
        // CSS-FLEXBOX-2: Account for gap between flex items.
854
0
        sum += main_gap() * (line.items.size() - 1);
855
        // AD-HOC: Note that we're using our own "available main size" explained above
856
        //         instead of the flex container’s inner main size.
857
0
        if (sum < available_main_size)
858
0
            return FlexFactor::FlexGrowFactor;
859
0
        return FlexFactor::FlexShrinkFactor;
860
0
    }();
861
862
    // 2. Each item in the flex line has a target main size, initially set to its flex base size.
863
    //    Each item is initially unfrozen and may become frozen.
864
0
    for (auto& item : line.items) {
865
0
        item.target_main_size = item.flex_base_size;
866
0
        item.frozen = false;
867
0
    }
868
869
    // 3. Size inflexible items.
870
871
0
    for (FlexItem& item : line.items) {
872
0
        if (used_flex_factor == FlexFactor::FlexGrowFactor) {
873
0
            item.flex_factor = item.box->computed_values().flex_grow();
874
0
        } else if (used_flex_factor == FlexFactor::FlexShrinkFactor) {
875
0
            item.flex_factor = item.box->computed_values().flex_shrink();
876
0
        }
877
        // Freeze, setting its target main size to its hypothetical main size…
878
        // - any item that has a flex factor of zero
879
        // - if using the flex grow factor: any item that has a flex base size greater than its hypothetical main size
880
        // - if using the flex shrink factor: any item that has a flex base size smaller than its hypothetical main size
881
0
        if (item.flex_factor.value() == 0
882
0
            || (used_flex_factor == FlexFactor::FlexGrowFactor && item.flex_base_size > item.hypothetical_main_size)
883
0
            || (used_flex_factor == FlexFactor::FlexShrinkFactor && item.flex_base_size < item.hypothetical_main_size)) {
884
0
            item.frozen = true;
885
0
            item.target_main_size = item.hypothetical_main_size;
886
0
        }
887
0
    }
888
889
    // 4. Calculate initial free space
890
891
    // Sum the outer sizes of all items on the line, and subtract this from the flex container’s inner main size.
892
    // For frozen items, use their outer target main size; for other items, use their outer flex base size.
893
0
    auto calculate_remaining_free_space = [&]() -> Optional<CSSPixels> {
894
        // AD-HOC: If the container is sized under max-content constraints, then remaining_free_space won't have
895
        //         a value to avoid leaking an infinite value into layout calculations.
896
0
        if (available_main_size.is_intrinsic_sizing_constraint())
897
0
            return {};
898
0
        CSSPixels sum = 0;
899
0
        for (auto const& item : line.items) {
900
0
            if (item.frozen)
901
0
                sum += item.outer_target_main_size();
902
0
            else
903
0
                sum += item.outer_flex_base_size();
904
0
        }
905
        // CSS-FLEXBOX-2: Account for gap between flex items.
906
0
        sum += main_gap() * (line.items.size() - 1);
907
908
        // AD-HOC: Note that we're using our own "available main size" explained above
909
        //         instead of the flex container’s inner main size.
910
0
        return available_main_size.to_px_or_zero() - sum;
911
0
    };
912
0
    auto const initial_free_space = calculate_remaining_free_space();
913
914
    // 5. Loop
915
0
    while (true) {
916
        // a. Check for flexible items.
917
        //    If all the flex items on the line are frozen, free space has been distributed; exit this loop.
918
0
        if (all_of(line.items, [](auto const& item) { return item.frozen; })) {
919
0
            break;
920
0
        }
921
922
        // b. Calculate the remaining free space as for initial free space, above.
923
0
        line.remaining_free_space = calculate_remaining_free_space();
924
925
        // If the sum of the unfrozen flex items’ flex factors is less than one, multiply the initial free space by this sum.
926
0
        if (auto sum_of_flex_factor_of_unfrozen_items = line.sum_of_flex_factor_of_unfrozen_items(); sum_of_flex_factor_of_unfrozen_items < 1 && initial_free_space.has_value()) {
927
0
            auto value = CSSPixels::nearest_value_for(initial_free_space.value() * sum_of_flex_factor_of_unfrozen_items);
928
            // If the magnitude of this value is less than the magnitude of the remaining free space, use this as the remaining free space.
929
0
            if (abs(value) < abs(line.remaining_free_space.value()))
930
0
                line.remaining_free_space = value;
931
0
        }
932
933
        // AD-HOC: We allow the remaining free space to be infinite, but we can't let infinity
934
        //         leak into the layout geometry, so we treat infinity as zero when used in arithmetic.
935
0
        auto remaining_free_space_or_zero_if_infinite = line.remaining_free_space.has_value() ? line.remaining_free_space.value() : 0;
936
937
        // c. If the remaining free space is non-zero, distribute it proportional to the flex factors:
938
0
        if (line.remaining_free_space != 0) {
939
            // If using the flex grow factor
940
0
            if (used_flex_factor == FlexFactor::FlexGrowFactor) {
941
                // For every unfrozen item on the line,
942
                // find the ratio of the item’s flex grow factor to the sum of the flex grow factors of all unfrozen items on the line.
943
0
                auto sum_of_flex_factor_of_unfrozen_items = line.sum_of_flex_factor_of_unfrozen_items();
944
0
                for (auto& item : line.items) {
945
0
                    if (item.frozen)
946
0
                        continue;
947
0
                    double ratio = item.flex_factor.value() / sum_of_flex_factor_of_unfrozen_items;
948
                    // Set the item’s target main size to its flex base size plus a fraction of the remaining free space proportional to the ratio.
949
0
                    item.target_main_size = item.flex_base_size + remaining_free_space_or_zero_if_infinite.scaled(ratio);
950
0
                }
951
0
            }
952
            // If using the flex shrink factor
953
0
            else if (used_flex_factor == FlexFactor::FlexShrinkFactor) {
954
                // For every unfrozen item on the line, multiply its flex shrink factor by its inner flex base size, and note this as its scaled flex shrink factor.
955
0
                for (auto& item : line.items) {
956
0
                    if (item.frozen)
957
0
                        continue;
958
0
                    item.scaled_flex_shrink_factor = item.flex_factor.value() * item.flex_base_size.to_double();
959
0
                }
960
0
                auto sum_of_scaled_flex_shrink_factors_of_all_unfrozen_items_on_line = line.sum_of_scaled_flex_shrink_factor_of_unfrozen_items();
961
0
                for (auto& item : line.items) {
962
0
                    if (item.frozen)
963
0
                        continue;
964
                    // Find the ratio of the item’s scaled flex shrink factor to the sum of the scaled flex shrink factors of all unfrozen items on the line.
965
0
                    double ratio = 1.0;
966
0
                    if (sum_of_scaled_flex_shrink_factors_of_all_unfrozen_items_on_line != 0)
967
0
                        ratio = item.scaled_flex_shrink_factor / sum_of_scaled_flex_shrink_factors_of_all_unfrozen_items_on_line;
968
969
                    // Set the item’s target main size to its flex base size minus a fraction of the absolute value of the remaining free space proportional to the ratio.
970
                    // (Note this may result in a negative inner main size; it will be corrected in the next step.)
971
0
                    item.target_main_size = item.flex_base_size - abs(remaining_free_space_or_zero_if_infinite).scaled(ratio);
972
0
                }
973
0
            }
974
0
        }
975
976
        // d. Fix min/max violations.
977
0
        CSSPixels total_violation = 0;
978
979
        // Clamp each non-frozen item’s target main size by its used min and max main sizes and floor its content-box size at zero.
980
0
        for (auto& item : line.items) {
981
0
            if (item.frozen)
982
0
                continue;
983
0
            auto used_min_main_size = has_main_min_size(item.box)
984
0
                ? specified_main_min_size(item.box)
985
0
                : automatic_minimum_size(item);
986
987
0
            auto used_max_main_size = has_main_max_size(item.box)
988
0
                ? specified_main_max_size(item.box)
989
0
                : CSSPixels::max();
990
991
0
            auto original_target_main_size = item.target_main_size;
992
0
            item.target_main_size = css_clamp(item.target_main_size, used_min_main_size, used_max_main_size);
993
0
            item.target_main_size = max(item.target_main_size, CSSPixels(0));
994
995
            // If the item’s target main size was made smaller by this, it’s a max violation.
996
0
            if (item.target_main_size < original_target_main_size)
997
0
                item.is_max_violation = true;
998
999
            // If the item’s target main size was made larger by this, it’s a min violation.
1000
0
            if (item.target_main_size > original_target_main_size)
1001
0
                item.is_min_violation = true;
1002
1003
0
            total_violation += item.target_main_size - original_target_main_size;
1004
0
        }
1005
1006
        // e. Freeze over-flexed items.
1007
        //    The total violation is the sum of the adjustments from the previous step ∑(clamped size - unclamped size).
1008
1009
        // If the total violation is:
1010
        // Zero
1011
        //   Freeze all items.
1012
0
        if (total_violation == 0) {
1013
0
            for (auto& item : line.items) {
1014
0
                if (!item.frozen)
1015
0
                    item.frozen = true;
1016
0
            }
1017
0
        }
1018
        // Positive
1019
        //   Freeze all the items with min violations.
1020
0
        else if (total_violation > 0) {
1021
0
            for (auto& item : line.items) {
1022
0
                if (!item.frozen && item.is_min_violation)
1023
0
                    item.frozen = true;
1024
0
            }
1025
0
        }
1026
        // Negative
1027
        //   Freeze all the items with max violations.
1028
0
        else {
1029
0
            for (auto& item : line.items) {
1030
0
                if (!item.frozen && item.is_max_violation)
1031
0
                    item.frozen = true;
1032
0
            }
1033
0
        }
1034
        // NOTE: This freezes at least one item, ensuring that the loop makes progress and eventually terminates.
1035
1036
        // f. Return to the start of this loop.
1037
0
    }
1038
1039
    // NOTE: Calculate the remaining free space once again here, since it's needed later when aligning items.
1040
0
    line.remaining_free_space = calculate_remaining_free_space();
1041
1042
    // 6. Set each item’s used main size to its target main size.
1043
0
    for (auto& item : line.items) {
1044
0
        item.main_size = item.target_main_size;
1045
0
        set_main_size(item.box, item.target_main_size);
1046
1047
        // https://drafts.csswg.org/css-flexbox-1/#definite-sizes
1048
        // 1. If the flex container has a definite main size, then the post-flexing main sizes of its flex items are treated as definite.
1049
        // 2. If a flex item’s flex basis is definite, then its post-flexing main size is also definite.
1050
0
        if (has_definite_main_size(m_flex_container_state) || item.used_flex_basis_is_definite)
1051
0
            set_has_definite_main_size(item);
1052
0
    }
1053
0
}
1054
1055
// https://drafts.csswg.org/css-flexbox-1/#resolve-flexible-lengths
1056
void FlexFormattingContext::resolve_flexible_lengths()
1057
0
{
1058
0
    for (auto& line : m_flex_lines) {
1059
0
        resolve_flexible_lengths_for_line(line);
1060
0
    }
1061
0
}
1062
1063
// https://www.w3.org/TR/css-flexbox-1/#hypothetical-cross-size
1064
void FlexFormattingContext::determine_hypothetical_cross_size_of_item(FlexItem& item, bool resolve_percentage_min_max_sizes)
1065
0
{
1066
    // Determine the hypothetical cross size of each item by performing layout
1067
    // as if it were an in-flow block-level box with the used main size
1068
    // and the given available space, treating auto as fit-content.
1069
1070
0
    auto const& computed_min_size = this->computed_cross_min_size(item.box);
1071
0
    auto const& computed_max_size = this->computed_cross_max_size(item.box);
1072
1073
0
    auto clamp_min = (!computed_min_size.is_auto() && (resolve_percentage_min_max_sizes || !computed_min_size.contains_percentage())) ? specified_cross_min_size(item.box) : 0;
1074
0
    auto clamp_max = (!should_treat_cross_max_size_as_none(item.box) && (resolve_percentage_min_max_sizes || !computed_max_size.contains_percentage())) ? specified_cross_max_size(item.box) : CSSPixels::max();
1075
1076
    // If we have a definite cross size, this is easy! No need to perform layout, we can just use it as-is.
1077
0
    if (has_definite_cross_size(item)) {
1078
        // To avoid subtracting padding and border twice for `box-sizing: border-box` only min and max clamp should happen on a second pass
1079
0
        if (resolve_percentage_min_max_sizes) {
1080
0
            item.hypothetical_cross_size = css_clamp(item.hypothetical_cross_size, clamp_min, clamp_max);
1081
0
            return;
1082
0
        }
1083
1084
0
        item.hypothetical_cross_size = css_clamp(inner_cross_size(item), clamp_min, clamp_max);
1085
0
        return;
1086
0
    }
1087
1088
0
    if (item.box->has_preferred_aspect_ratio()) {
1089
0
        if (item.used_flex_basis_is_definite) {
1090
0
            item.hypothetical_cross_size = calculate_cross_size_from_main_size_and_aspect_ratio(item.main_size.value(), item.box->preferred_aspect_ratio().value());
1091
0
            return;
1092
0
        }
1093
0
        item.hypothetical_cross_size = inner_cross_size(m_flex_container_state);
1094
0
        return;
1095
0
    }
1096
1097
0
    auto computed_cross_size = [&]() -> CSS::Size {
1098
        // "... treating auto as fit-content"
1099
0
        if (should_treat_cross_size_as_auto(item.box))
1100
0
            return CSS::Size::make_fit_content();
1101
0
        return this->computed_cross_size(item.box);
1102
0
    }();
1103
1104
0
    if (computed_cross_size.is_min_content()) {
1105
0
        item.hypothetical_cross_size = css_clamp(calculate_min_content_cross_size(item), clamp_min, clamp_max);
1106
0
        return;
1107
0
    }
1108
1109
0
    if (computed_cross_size.is_max_content()) {
1110
0
        item.hypothetical_cross_size = css_clamp(calculate_max_content_cross_size(item), clamp_min, clamp_max);
1111
0
        return;
1112
0
    }
1113
1114
0
    if (computed_cross_size.is_fit_content()) {
1115
0
        CSSPixels fit_content_cross_size = 0;
1116
0
        if (is_row_layout()) {
1117
0
            auto available_width = item.main_size.has_value() ? AvailableSize::make_definite(item.main_size.value()) : AvailableSize::make_indefinite();
1118
0
            auto available_height = AvailableSize::make_indefinite();
1119
0
            fit_content_cross_size = calculate_fit_content_height(item.box, AvailableSpace(available_width, available_height));
1120
0
        } else {
1121
0
            fit_content_cross_size = calculate_fit_content_width(item.box, m_available_space_for_items->space);
1122
0
        }
1123
1124
0
        item.hypothetical_cross_size = css_clamp(fit_content_cross_size, clamp_min, clamp_max);
1125
0
        return;
1126
0
    }
1127
1128
    // For indefinite cross sizes, we perform a throwaway layout and then measure it.
1129
0
    LayoutState throwaway_state(&m_state);
1130
1131
0
    auto& box_state = throwaway_state.get_mutable(item.box);
1132
0
    if (is_row_layout()) {
1133
0
        box_state.set_content_width(item.main_size.value());
1134
0
    } else {
1135
0
        box_state.set_content_height(item.main_size.value());
1136
0
    }
1137
1138
    // Item has definite main size, layout with that as the used main size.
1139
0
    auto independent_formatting_context = create_independent_formatting_context_if_needed(throwaway_state, LayoutMode::Normal, item.box);
1140
    // NOTE: Flex items should always create an independent formatting context!
1141
0
    VERIFY(independent_formatting_context);
1142
1143
0
    auto available_width = is_row_layout() ? AvailableSize::make_definite(item.main_size.value()) : AvailableSize::make_indefinite();
1144
0
    auto available_height = is_row_layout() ? AvailableSize::make_indefinite() : AvailableSize::make_definite(item.main_size.value());
1145
1146
0
    independent_formatting_context->run(AvailableSpace(available_width, available_height));
1147
1148
0
    auto automatic_cross_size = is_row_layout() ? independent_formatting_context->automatic_content_height()
1149
0
                                                : independent_formatting_context->automatic_content_width();
1150
1151
0
    item.hypothetical_cross_size = css_clamp(automatic_cross_size, clamp_min, clamp_max);
1152
0
}
1153
1154
// https://www.w3.org/TR/css-flexbox-1/#algo-cross-line
1155
void FlexFormattingContext::calculate_cross_size_of_each_flex_line()
1156
0
{
1157
    // If the flex container is single-line and has a definite cross size, the cross size of the flex line is the flex container’s inner cross size.
1158
0
    if (is_single_line() && has_definite_cross_size(m_flex_container_state)) {
1159
0
        m_flex_lines[0].cross_size = inner_cross_size(m_flex_container_state);
1160
0
        return;
1161
0
    }
1162
1163
    // Otherwise, for each flex line:
1164
0
    for (auto& flex_line : m_flex_lines) {
1165
        // FIXME: 1. Collect all the flex items whose inline-axis is parallel to the main-axis, whose align-self is baseline,
1166
        //           and whose cross-axis margins are both non-auto. Find the largest of the distances between each item’s baseline
1167
        //           and its hypothetical outer cross-start edge, and the largest of the distances between each item’s baseline
1168
        //           and its hypothetical outer cross-end edge, and sum these two values.
1169
1170
        // 2. Among all the items not collected by the previous step, find the largest outer hypothetical cross size.
1171
0
        CSSPixels largest_hypothetical_cross_size = 0;
1172
0
        for (auto& item : flex_line.items) {
1173
0
            if (largest_hypothetical_cross_size < item.hypothetical_cross_size_with_margins())
1174
0
                largest_hypothetical_cross_size = item.hypothetical_cross_size_with_margins();
1175
0
        }
1176
1177
        // 3. The used cross-size of the flex line is the largest of the numbers found in the previous two steps and zero.
1178
0
        flex_line.cross_size = max(CSSPixels(0), largest_hypothetical_cross_size);
1179
0
    }
1180
1181
    // If the flex container is single-line, then clamp the line’s cross-size to be within the container’s computed min and max cross sizes.
1182
    // Note that if CSS 2.1’s definition of min/max-width/height applied more generally, this behavior would fall out automatically.
1183
    // AD-HOC: We don't do this when the flex container is being sized under a min-content or max-content constraint.
1184
0
    if (is_single_line() && !m_available_space_for_items->cross.is_intrinsic_sizing_constraint()) {
1185
0
        auto const& computed_min_size = this->computed_cross_min_size(flex_container());
1186
0
        auto const& computed_max_size = this->computed_cross_max_size(flex_container());
1187
0
        auto cross_min_size = (!computed_min_size.is_auto() && !computed_min_size.contains_percentage()) ? specified_cross_min_size(flex_container()) : 0;
1188
0
        auto cross_max_size = (!computed_max_size.is_none() && !computed_max_size.contains_percentage()) ? specified_cross_max_size(flex_container()) : CSSPixels::max();
1189
0
        m_flex_lines[0].cross_size = css_clamp(m_flex_lines[0].cross_size, cross_min_size, cross_max_size);
1190
0
    }
1191
0
}
1192
1193
// https://www.w3.org/TR/css-flexbox-1/#algo-stretch
1194
void FlexFormattingContext::determine_used_cross_size_of_each_flex_item()
1195
0
{
1196
0
    for (auto& flex_line : m_flex_lines) {
1197
0
        for (auto& item : flex_line.items) {
1198
            //  If a flex item has align-self: stretch, its computed cross size property is auto,
1199
            //  and neither of its cross-axis margins are auto, the used outer cross size is the used cross size of its flex line,
1200
            //  clamped according to the item’s used min and max cross sizes.
1201
0
            auto flex_item_alignment = alignment_for_item(item.box);
1202
0
            if ((flex_item_alignment == CSS::AlignItems::Stretch || flex_item_alignment == CSS::AlignItems::Normal)
1203
0
                && is_cross_auto(item.box)
1204
0
                && !item.margins.cross_before_is_auto
1205
0
                && !item.margins.cross_after_is_auto) {
1206
0
                auto unclamped_cross_size = flex_line.cross_size
1207
0
                    - item.margins.cross_before - item.margins.cross_after
1208
0
                    - item.padding.cross_before - item.padding.cross_after
1209
0
                    - item.borders.cross_before - item.borders.cross_after;
1210
1211
0
                auto const& computed_min_size = computed_cross_min_size(item.box);
1212
0
                auto const& computed_max_size = computed_cross_max_size(item.box);
1213
0
                auto cross_min_size = (!computed_min_size.is_auto() && !computed_min_size.contains_percentage()) ? specified_cross_min_size(item.box) : 0;
1214
0
                auto cross_max_size = (!should_treat_cross_max_size_as_none(item.box) && !computed_max_size.contains_percentage()) ? specified_cross_max_size(item.box) : CSSPixels::max();
1215
1216
0
                item.cross_size = css_clamp(unclamped_cross_size, cross_min_size, cross_max_size);
1217
0
            } else {
1218
                // Otherwise, the used cross size is the item’s hypothetical cross size.
1219
0
                item.cross_size = item.hypothetical_cross_size;
1220
0
            }
1221
0
        }
1222
0
    }
1223
0
}
1224
1225
// https://www.w3.org/TR/css-flexbox-1/#algo-main-align
1226
void FlexFormattingContext::distribute_any_remaining_free_space()
1227
0
{
1228
0
    for (auto& flex_line : m_flex_lines) {
1229
        // 12.1.
1230
0
        CSSPixels used_main_space = 0;
1231
0
        size_t auto_margins = 0;
1232
0
        for (auto& item : flex_line.items) {
1233
0
            used_main_space += item.main_size.value();
1234
0
            if (item.margins.main_before_is_auto)
1235
0
                ++auto_margins;
1236
1237
0
            if (item.margins.main_after_is_auto)
1238
0
                ++auto_margins;
1239
1240
0
            used_main_space += item.margins.main_before + item.margins.main_after
1241
0
                + item.borders.main_before + item.borders.main_after
1242
0
                + item.padding.main_before + item.padding.main_after;
1243
0
        }
1244
1245
        // CSS-FLEXBOX-2: Account for gap between flex items.
1246
0
        used_main_space += main_gap() * (flex_line.items.size() - 1);
1247
1248
0
        if (flex_line.remaining_free_space.has_value() && flex_line.remaining_free_space.value() > 0 && auto_margins > 0) {
1249
0
            CSSPixels size_per_auto_margin = flex_line.remaining_free_space.value() / auto_margins;
1250
0
            for (auto& item : flex_line.items) {
1251
0
                if (item.margins.main_before_is_auto)
1252
0
                    set_main_axis_first_margin(item, size_per_auto_margin);
1253
0
                if (item.margins.main_after_is_auto)
1254
0
                    set_main_axis_second_margin(item, size_per_auto_margin);
1255
0
            }
1256
0
        } else {
1257
0
            for (auto& item : flex_line.items) {
1258
0
                if (item.margins.main_before_is_auto)
1259
0
                    set_main_axis_first_margin(item, 0);
1260
0
                if (item.margins.main_after_is_auto)
1261
0
                    set_main_axis_second_margin(item, 0);
1262
0
            }
1263
0
        }
1264
1265
        // 12.2.
1266
0
        CSSPixels space_between_items = 0;
1267
0
        CSSPixels initial_offset = 0;
1268
0
        auto number_of_items = flex_line.items.size();
1269
1270
0
        if (auto_margins == 0 && number_of_items > 0) {
1271
0
            switch (flex_container().computed_values().justify_content()) {
1272
0
            case CSS::JustifyContent::Start:
1273
0
            case CSS::JustifyContent::Left:
1274
0
                initial_offset = 0;
1275
0
                break;
1276
0
            case CSS::JustifyContent::Stretch:
1277
0
            case CSS::JustifyContent::Normal:
1278
0
            case CSS::JustifyContent::FlexStart:
1279
0
                if (is_direction_reverse()) {
1280
0
                    initial_offset = inner_main_size(m_flex_container_state);
1281
0
                } else {
1282
0
                    initial_offset = 0;
1283
0
                }
1284
0
                break;
1285
0
            case CSS::JustifyContent::End:
1286
0
                initial_offset = inner_main_size(m_flex_container_state);
1287
0
                break;
1288
0
            case CSS::JustifyContent::Right:
1289
0
                if (is_row_layout()) {
1290
0
                    initial_offset = inner_main_size(m_flex_container_state);
1291
0
                } else {
1292
0
                    initial_offset = 0;
1293
0
                }
1294
0
                break;
1295
0
            case CSS::JustifyContent::FlexEnd:
1296
0
                if (is_direction_reverse()) {
1297
0
                    initial_offset = 0;
1298
0
                } else {
1299
0
                    initial_offset = inner_main_size(m_flex_container_state);
1300
0
                }
1301
0
                break;
1302
0
            case CSS::JustifyContent::Center:
1303
0
                initial_offset = (inner_main_size(m_flex_container_state) - used_main_space) / 2;
1304
0
                if (is_direction_reverse()) {
1305
0
                    initial_offset = inner_main_size(m_flex_container_state) - initial_offset;
1306
0
                }
1307
0
                break;
1308
0
            case CSS::JustifyContent::SpaceBetween:
1309
0
                if (is_direction_reverse()) {
1310
0
                    initial_offset = inner_main_size(m_flex_container_state);
1311
0
                } else {
1312
0
                    initial_offset = 0;
1313
0
                }
1314
0
                if (flex_line.remaining_free_space.has_value() && number_of_items > 1)
1315
0
                    space_between_items = max(CSSPixels(0), flex_line.remaining_free_space.value() / (number_of_items - 1));
1316
0
                break;
1317
0
            case CSS::JustifyContent::SpaceAround:
1318
0
                if (flex_line.remaining_free_space.has_value())
1319
0
                    space_between_items = max(CSSPixels(0), flex_line.remaining_free_space.value() / number_of_items);
1320
0
                if (is_direction_reverse()) {
1321
0
                    initial_offset = inner_main_size(m_flex_container_state) - space_between_items / 2;
1322
0
                } else {
1323
0
                    initial_offset = space_between_items / 2;
1324
0
                }
1325
0
                break;
1326
0
            case CSS::JustifyContent::SpaceEvenly:
1327
0
                if (flex_line.remaining_free_space.has_value())
1328
0
                    space_between_items = max(CSSPixels(0), flex_line.remaining_free_space.value() / (number_of_items + 1));
1329
0
                if (is_direction_reverse()) {
1330
0
                    initial_offset = inner_main_size(m_flex_container_state) - space_between_items;
1331
0
                } else {
1332
0
                    initial_offset = space_between_items;
1333
0
                }
1334
0
                break;
1335
0
            }
1336
0
        }
1337
1338
        // For reverse, we use FlexRegionRenderCursor::Right
1339
        // to indicate the cursor offset is the end and render backwards
1340
        // Otherwise the cursor offset is the 'start' of the region or initial offset
1341
0
        enum class FlexRegionRenderCursor {
1342
0
            Left,
1343
0
            Right
1344
0
        };
1345
0
        auto flex_region_render_cursor = FlexRegionRenderCursor::Left;
1346
1347
0
        if (auto_margins == 0) {
1348
0
            switch (flex_container().computed_values().justify_content()) {
1349
0
            case CSS::JustifyContent::Start:
1350
0
            case CSS::JustifyContent::Left:
1351
0
                flex_region_render_cursor = FlexRegionRenderCursor::Left;
1352
0
                break;
1353
0
            case CSS::JustifyContent::Normal:
1354
0
            case CSS::JustifyContent::FlexStart:
1355
0
            case CSS::JustifyContent::Center:
1356
0
            case CSS::JustifyContent::SpaceAround:
1357
0
            case CSS::JustifyContent::SpaceBetween:
1358
0
            case CSS::JustifyContent::SpaceEvenly:
1359
0
            case CSS::JustifyContent::Stretch:
1360
0
                if (is_direction_reverse()) {
1361
0
                    flex_region_render_cursor = FlexRegionRenderCursor::Right;
1362
0
                }
1363
0
                break;
1364
0
            case CSS::JustifyContent::End:
1365
0
                flex_region_render_cursor = FlexRegionRenderCursor::Right;
1366
0
                break;
1367
0
            case CSS::JustifyContent::Right:
1368
0
                if (is_row_layout()) {
1369
0
                    flex_region_render_cursor = FlexRegionRenderCursor::Right;
1370
0
                } else {
1371
0
                    flex_region_render_cursor = FlexRegionRenderCursor::Left;
1372
0
                }
1373
0
                break;
1374
0
            case CSS::JustifyContent::FlexEnd:
1375
0
                if (!is_direction_reverse()) {
1376
0
                    flex_region_render_cursor = FlexRegionRenderCursor::Right;
1377
0
                }
1378
0
                break;
1379
0
            default:
1380
0
                break;
1381
0
            }
1382
0
        }
1383
1384
0
        CSSPixels cursor_offset = initial_offset;
1385
1386
0
        auto place_item = [&](FlexItem& item) {
1387
            // CSS-FLEXBOX-2: Account for gap between items.
1388
0
            auto amount_of_main_size_used = item.main_size.value()
1389
0
                + item.margins.main_before
1390
0
                + item.borders.main_before
1391
0
                + item.padding.main_before
1392
0
                + item.margins.main_after
1393
0
                + item.borders.main_after
1394
0
                + item.padding.main_after
1395
0
                + space_between_items
1396
0
                + main_gap();
1397
1398
0
            if (is_direction_reverse() && flex_region_render_cursor == FlexRegionRenderCursor::Right) {
1399
0
                item.main_offset = cursor_offset - item.main_size.value() - item.margins.main_after - item.borders.main_after - item.padding.main_after;
1400
0
                cursor_offset -= amount_of_main_size_used;
1401
0
            } else if (flex_region_render_cursor == FlexRegionRenderCursor::Right) {
1402
0
                cursor_offset -= amount_of_main_size_used;
1403
0
                item.main_offset = cursor_offset + item.margins.main_before + item.borders.main_before + item.padding.main_before;
1404
0
            } else {
1405
0
                item.main_offset = cursor_offset + item.margins.main_before + item.borders.main_before + item.padding.main_before;
1406
0
                cursor_offset += amount_of_main_size_used;
1407
0
            }
1408
0
        };
1409
1410
0
        if (flex_region_render_cursor == FlexRegionRenderCursor::Right) {
1411
0
            for (ssize_t i = flex_line.items.size() - 1; i >= 0; --i) {
1412
0
                auto& item = flex_line.items[i];
1413
0
                place_item(item);
1414
0
            }
1415
0
        } else {
1416
0
            for (size_t i = 0; i < flex_line.items.size(); ++i) {
1417
0
                auto& item = flex_line.items[i];
1418
0
                place_item(item);
1419
0
            }
1420
0
        }
1421
0
    }
1422
0
}
1423
1424
void FlexFormattingContext::dump_items() const
1425
0
{
1426
0
    dbgln("\033[34;1mflex-container\033[0m {}, direction: {}, current-size: {}x{}", flex_container().debug_description(), is_row_layout() ? "row" : "column", m_flex_container_state.content_width(), m_flex_container_state.content_height());
1427
0
    for (size_t i = 0; i < m_flex_lines.size(); ++i) {
1428
0
        dbgln("{} flex-line #{}:", flex_container().debug_description(), i);
1429
0
        for (size_t j = 0; j < m_flex_lines[i].items.size(); ++j) {
1430
0
            auto& item = m_flex_lines[i].items[j];
1431
0
            dbgln("{}   flex-item #{}: {} (main:{}, cross:{})", flex_container().debug_description(), j, item.box->debug_description(), item.main_size.value_or(-1), item.cross_size.value_or(-1));
1432
0
        }
1433
0
    }
1434
0
}
1435
1436
CSS::AlignItems FlexFormattingContext::alignment_for_item(Box const& box) const
1437
0
{
1438
0
    switch (box.computed_values().align_self()) {
1439
0
    case CSS::AlignSelf::Auto:
1440
0
        return flex_container().computed_values().align_items();
1441
0
    case CSS::AlignSelf::End:
1442
0
        return CSS::AlignItems::End;
1443
0
    case CSS::AlignSelf::Normal:
1444
0
        return CSS::AlignItems::Normal;
1445
0
    case CSS::AlignSelf::SelfStart:
1446
0
        return CSS::AlignItems::SelfStart;
1447
0
    case CSS::AlignSelf::SelfEnd:
1448
0
        return CSS::AlignItems::SelfEnd;
1449
0
    case CSS::AlignSelf::FlexStart:
1450
0
        return CSS::AlignItems::FlexStart;
1451
0
    case CSS::AlignSelf::FlexEnd:
1452
0
        return CSS::AlignItems::FlexEnd;
1453
0
    case CSS::AlignSelf::Center:
1454
0
        return CSS::AlignItems::Center;
1455
0
    case CSS::AlignSelf::Baseline:
1456
0
        return CSS::AlignItems::Baseline;
1457
0
    case CSS::AlignSelf::Start:
1458
0
        return CSS::AlignItems::Start;
1459
0
    case CSS::AlignSelf::Stretch:
1460
0
        return CSS::AlignItems::Stretch;
1461
0
    case CSS::AlignSelf::Safe:
1462
0
        return CSS::AlignItems::Safe;
1463
0
    case CSS::AlignSelf::Unsafe:
1464
0
        return CSS::AlignItems::Unsafe;
1465
0
    default:
1466
0
        VERIFY_NOT_REACHED();
1467
0
    }
1468
0
}
1469
1470
void FlexFormattingContext::align_all_flex_items_along_the_cross_axis()
1471
0
{
1472
    // FIXME: Take better care of margins
1473
0
    for (auto& flex_line : m_flex_lines) {
1474
0
        for (auto& item : flex_line.items) {
1475
0
            CSSPixels half_line_size = flex_line.cross_size / 2;
1476
0
            switch (alignment_for_item(item.box)) {
1477
0
            case CSS::AlignItems::Normal:
1478
                // https://drafts.csswg.org/css-flexbox/#flex-wrap-property
1479
                // When flex-wrap is wrap-reverse, the cross-start and cross-end directions are swapped.
1480
0
                if (flex_container().computed_values().flex_wrap() == CSS::FlexWrap::WrapReverse) {
1481
0
                    item.cross_offset = half_line_size - item.cross_size.value() - item.margins.cross_after - item.borders.cross_after - item.padding.cross_after;
1482
0
                } else {
1483
0
                    item.cross_offset = -half_line_size + item.margins.cross_before + item.borders.cross_before + item.padding.cross_before;
1484
0
                }
1485
0
                break;
1486
0
            case CSS::AlignItems::Baseline:
1487
                // FIXME: Implement this
1488
                //  Fallthrough
1489
0
            case CSS::AlignItems::Start:
1490
0
            case CSS::AlignItems::FlexStart:
1491
0
            case CSS::AlignItems::SelfStart:
1492
0
            case CSS::AlignItems::Stretch:
1493
                // FIXME: 'start', 'flex-start' and 'self-start' have subtly different behavior.
1494
                //        The same goes for the end values.
1495
0
                item.cross_offset = -half_line_size + item.margins.cross_before + item.borders.cross_before + item.padding.cross_before;
1496
0
                break;
1497
0
            case CSS::AlignItems::End:
1498
0
            case CSS::AlignItems::FlexEnd:
1499
0
            case CSS::AlignItems::SelfEnd:
1500
0
                item.cross_offset = half_line_size - item.cross_size.value() - item.margins.cross_after - item.borders.cross_after - item.padding.cross_after;
1501
0
                break;
1502
0
            case CSS::AlignItems::Center:
1503
0
                item.cross_offset = -(item.cross_size.value() / 2);
1504
0
                break;
1505
0
            default:
1506
0
                break;
1507
0
            }
1508
0
        }
1509
0
    }
1510
0
}
1511
1512
// https://www.w3.org/TR/css-flexbox-1/#algo-line-align
1513
void FlexFormattingContext::align_all_flex_lines()
1514
0
{
1515
0
    if (m_flex_lines.is_empty())
1516
0
        return;
1517
1518
    // FIXME: Support reverse
1519
1520
0
    CSSPixels cross_size_of_flex_container = inner_cross_size(m_flex_container_state);
1521
1522
0
    if (is_single_line()) {
1523
        // For single-line flex containers, we only need to center the line along the cross axis.
1524
0
        auto& flex_line = m_flex_lines[0];
1525
0
        CSSPixels center_of_line = cross_size_of_flex_container / 2;
1526
0
        for (auto& item : flex_line.items) {
1527
0
            item.cross_offset += center_of_line;
1528
0
        }
1529
0
    } else {
1530
1531
0
        CSSPixels sum_of_flex_line_cross_sizes = 0;
1532
0
        for (auto& line : m_flex_lines)
1533
0
            sum_of_flex_line_cross_sizes += line.cross_size;
1534
1535
        // CSS-FLEXBOX-2: Account for gap between flex lines.
1536
0
        sum_of_flex_line_cross_sizes += cross_gap() * (m_flex_lines.size() - 1);
1537
1538
0
        CSSPixels start_of_current_line = 0;
1539
0
        CSSPixels gap_size = 0;
1540
0
        switch (flex_container().computed_values().align_content()) {
1541
0
        case CSS::AlignContent::FlexStart:
1542
0
        case CSS::AlignContent::Start:
1543
0
            start_of_current_line = 0;
1544
0
            break;
1545
0
        case CSS::AlignContent::FlexEnd:
1546
0
        case CSS::AlignContent::End:
1547
0
            start_of_current_line = cross_size_of_flex_container - sum_of_flex_line_cross_sizes;
1548
0
            break;
1549
0
        case CSS::AlignContent::Center:
1550
0
            start_of_current_line = (cross_size_of_flex_container / 2) - (sum_of_flex_line_cross_sizes / 2);
1551
0
            break;
1552
0
        case CSS::AlignContent::SpaceBetween: {
1553
0
            start_of_current_line = 0;
1554
1555
0
            auto leftover_free_space = cross_size_of_flex_container - sum_of_flex_line_cross_sizes;
1556
0
            auto leftover_flex_lines_size = m_flex_lines.size();
1557
0
            if (leftover_free_space >= 0 && leftover_flex_lines_size > 1) {
1558
0
                int gap_count = leftover_flex_lines_size - 1;
1559
0
                gap_size = leftover_free_space / gap_count;
1560
0
            }
1561
0
            break;
1562
0
        }
1563
0
        case CSS::AlignContent::SpaceAround: {
1564
0
            auto leftover_free_space = cross_size_of_flex_container - sum_of_flex_line_cross_sizes;
1565
0
            if (leftover_free_space < 0) {
1566
                // If the leftover free-space is negative this value is identical to center.
1567
0
                start_of_current_line = (cross_size_of_flex_container / 2) - (sum_of_flex_line_cross_sizes / 2);
1568
0
                break;
1569
0
            }
1570
1571
0
            gap_size = leftover_free_space / m_flex_lines.size();
1572
1573
            // The spacing between the first/last lines and the flex container edges is half the size of the spacing between flex lines.
1574
0
            start_of_current_line = gap_size / 2;
1575
0
            break;
1576
0
        }
1577
0
        case CSS::AlignContent::SpaceEvenly: {
1578
0
            auto leftover_free_space = cross_size_of_flex_container - sum_of_flex_line_cross_sizes;
1579
0
            if (leftover_free_space < 0) {
1580
                // If the leftover free-space is negative this value is identical to center.
1581
0
                start_of_current_line = (cross_size_of_flex_container / 2) - (sum_of_flex_line_cross_sizes / 2);
1582
0
                break;
1583
0
            }
1584
1585
0
            gap_size = leftover_free_space / (m_flex_lines.size() + 1);
1586
1587
            // The spacing between the first/last lines and the flex container edges is the size of the spacing between flex lines.
1588
0
            start_of_current_line = gap_size;
1589
0
            break;
1590
0
        }
1591
1592
0
        case CSS::AlignContent::Normal:
1593
0
        case CSS::AlignContent::Stretch:
1594
0
            start_of_current_line = 0;
1595
0
            break;
1596
0
        }
1597
1598
0
        for (auto& flex_line : m_flex_lines) {
1599
0
            CSSPixels center_of_current_line = start_of_current_line + (flex_line.cross_size / 2);
1600
0
            for (auto& item : flex_line.items) {
1601
0
                item.cross_offset += center_of_current_line;
1602
0
            }
1603
0
            start_of_current_line += flex_line.cross_size + gap_size;
1604
            // CSS-FLEXBOX-2: Account for gap between flex lines.
1605
0
            start_of_current_line += cross_gap();
1606
0
        }
1607
0
    }
1608
0
}
1609
1610
void FlexFormattingContext::copy_dimensions_from_flex_items_to_boxes()
1611
0
{
1612
0
    for (auto& item : m_flex_items) {
1613
0
        auto const& box = item.box;
1614
1615
0
        item.used_values.padding_left = box->computed_values().padding().left().to_px(box, m_flex_container_state.content_width());
1616
0
        item.used_values.padding_right = box->computed_values().padding().right().to_px(box, m_flex_container_state.content_width());
1617
0
        item.used_values.padding_top = box->computed_values().padding().top().to_px(box, m_flex_container_state.content_width());
1618
0
        item.used_values.padding_bottom = box->computed_values().padding().bottom().to_px(box, m_flex_container_state.content_width());
1619
1620
0
        item.used_values.margin_left = box->computed_values().margin().left().to_px(box, m_flex_container_state.content_width());
1621
0
        item.used_values.margin_right = box->computed_values().margin().right().to_px(box, m_flex_container_state.content_width());
1622
0
        item.used_values.margin_top = box->computed_values().margin().top().to_px(box, m_flex_container_state.content_width());
1623
0
        item.used_values.margin_bottom = box->computed_values().margin().bottom().to_px(box, m_flex_container_state.content_width());
1624
1625
0
        item.used_values.border_left = box->computed_values().border_left().width;
1626
0
        item.used_values.border_right = box->computed_values().border_right().width;
1627
0
        item.used_values.border_top = box->computed_values().border_top().width;
1628
0
        item.used_values.border_bottom = box->computed_values().border_bottom().width;
1629
1630
0
        set_main_size(box, item.main_size.value());
1631
0
        set_cross_size(box, item.cross_size.value());
1632
0
        set_offset(box, item.main_offset, item.cross_offset);
1633
0
    }
1634
0
}
1635
1636
// https://drafts.csswg.org/css-flexbox-1/#intrinsic-sizes
1637
void FlexFormattingContext::determine_intrinsic_size_of_flex_container()
1638
0
{
1639
0
    if (m_available_space_for_items->main.is_intrinsic_sizing_constraint()) {
1640
0
        CSSPixels main_size = calculate_intrinsic_main_size_of_flex_container();
1641
0
        set_main_size(flex_container(), main_size);
1642
0
    }
1643
0
    if (m_available_space_for_items->cross.is_intrinsic_sizing_constraint()) {
1644
0
        CSSPixels cross_size = calculate_intrinsic_cross_size_of_flex_container();
1645
0
        set_cross_size(flex_container(), cross_size);
1646
0
    }
1647
0
}
1648
1649
// https://drafts.csswg.org/css-flexbox-1/#intrinsic-main-sizes
1650
CSSPixels FlexFormattingContext::calculate_intrinsic_main_size_of_flex_container()
1651
0
{
1652
    // The min-content main size of a single-line flex container is calculated identically to the max-content main size,
1653
    // except that the flex items’ min-content contributions are used instead of their max-content contributions.
1654
    // 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.
1655
0
    if (!is_single_line() && m_available_space_for_items->main.is_min_content()) {
1656
0
        CSSPixels largest_contribution = 0;
1657
0
        for (auto const& item : m_flex_items) {
1658
            // FIXME: Skip collapsed flex items.
1659
0
            largest_contribution = max(largest_contribution, calculate_main_min_content_contribution(item));
1660
0
        }
1661
0
        return largest_contribution;
1662
0
    }
1663
1664
    // The max-content main size of a flex container is, fundamentally, the smallest size the flex container
1665
    // can take such that when flex layout is run with that container size, each flex item ends up at least
1666
    // as large as its max-content contribution, to the extent allowed by the items’ flexibility.
1667
    // It is calculated, considering only non-collapsed flex items, by:
1668
1669
    // 1. For each flex item, subtract its outer flex base size from its max-content contribution size.
1670
    //    If that result is positive, divide it by the item’s flex grow factor if the flex grow factor is ≥ 1,
1671
    //    or multiply it by the flex grow factor if the flex grow factor is < 1; if the result is negative,
1672
    //    divide it by the item’s scaled flex shrink factor (if dividing by zero, treat the result as negative infinity).
1673
    //    This is the item’s desired flex fraction.
1674
1675
0
    for (auto& item : m_flex_items) {
1676
0
        CSSPixels contribution = 0;
1677
0
        if (m_available_space_for_items->main.is_min_content())
1678
0
            contribution = calculate_main_min_content_contribution(item);
1679
0
        else if (m_available_space_for_items->main.is_max_content())
1680
0
            contribution = calculate_main_max_content_contribution(item);
1681
1682
0
        CSSPixels outer_flex_base_size = item.flex_base_size + item.margins.main_before + item.margins.main_after + item.borders.main_before + item.borders.main_after + item.padding.main_before + item.padding.main_after;
1683
1684
0
        CSSPixels result = contribution - outer_flex_base_size;
1685
0
        if (result > 0) {
1686
0
            if (item.box->computed_values().flex_grow() >= 1) {
1687
0
                result.scale_by(1 / item.box->computed_values().flex_grow());
1688
0
            } else {
1689
0
                result.scale_by(item.box->computed_values().flex_grow());
1690
0
            }
1691
0
        } else if (result < 0) {
1692
0
            if (item.scaled_flex_shrink_factor == 0)
1693
0
                result = CSSPixels::min();
1694
0
            else
1695
0
                result.scale_by(1 / item.scaled_flex_shrink_factor);
1696
0
        }
1697
1698
0
        item.desired_flex_fraction = result.to_double();
1699
0
    }
1700
1701
    // 2. Place all flex items into lines of infinite length.
1702
0
    m_flex_lines.clear();
1703
0
    if (!m_flex_items.is_empty())
1704
0
        m_flex_lines.append(FlexLine {});
1705
0
    for (auto& item : m_flex_items) {
1706
        // FIXME: Honor breaking requests.
1707
0
        m_flex_lines.last().items.append(item);
1708
0
    }
1709
1710
    //    Within each line, find the greatest (most positive) desired flex fraction among all the flex items.
1711
    //    This is the line’s chosen flex fraction.
1712
0
    for (auto& flex_line : m_flex_lines) {
1713
0
        float greatest_desired_flex_fraction = 0;
1714
0
        float sum_of_flex_grow_factors = 0;
1715
0
        float sum_of_flex_shrink_factors = 0;
1716
0
        for (auto& item : flex_line.items) {
1717
0
            greatest_desired_flex_fraction = max(greatest_desired_flex_fraction, item.desired_flex_fraction);
1718
0
            sum_of_flex_grow_factors += item.box->computed_values().flex_grow();
1719
0
            sum_of_flex_shrink_factors += item.box->computed_values().flex_shrink();
1720
0
        }
1721
0
        float chosen_flex_fraction = greatest_desired_flex_fraction;
1722
1723
        // 3. If the chosen flex fraction is positive, and the sum of the line’s flex grow factors is less than 1,
1724
        //    divide the chosen flex fraction by that sum.
1725
0
        if (chosen_flex_fraction > 0 && sum_of_flex_grow_factors < 1)
1726
0
            chosen_flex_fraction /= sum_of_flex_grow_factors;
1727
1728
        // If the chosen flex fraction is negative, and the sum of the line’s flex shrink factors is less than 1,
1729
        // multiply the chosen flex fraction by that sum.
1730
0
        if (chosen_flex_fraction < 0 && sum_of_flex_shrink_factors < 1)
1731
0
            chosen_flex_fraction *= sum_of_flex_shrink_factors;
1732
1733
0
        flex_line.chosen_flex_fraction = chosen_flex_fraction;
1734
0
    }
1735
1736
0
    auto determine_main_size = [&]() -> CSSPixels {
1737
0
        CSSPixels largest_sum = 0;
1738
0
        for (auto& flex_line : m_flex_lines) {
1739
            // 4. Add each item’s flex base size to the product of its flex grow factor (scaled flex shrink factor, if shrinking)
1740
            //    and the chosen flex fraction, then clamp that result by the max main size floored by the min main size.
1741
0
            CSSPixels sum = 0;
1742
0
            for (auto& item : flex_line.items) {
1743
0
                double product = 0;
1744
0
                if (item.desired_flex_fraction > 0)
1745
0
                    product = flex_line.chosen_flex_fraction * static_cast<double>(item.box->computed_values().flex_grow());
1746
0
                else if (item.desired_flex_fraction < 0)
1747
0
                    product = flex_line.chosen_flex_fraction * item.scaled_flex_shrink_factor;
1748
0
                auto result = item.flex_base_size + CSSPixels::nearest_value_for(product);
1749
1750
0
                auto const& computed_min_size = this->computed_main_min_size(item.box);
1751
0
                auto const& computed_max_size = this->computed_main_max_size(item.box);
1752
1753
0
                auto clamp_min = (!computed_min_size.is_auto() && !computed_min_size.contains_percentage()) ? specified_main_min_size(item.box) : automatic_minimum_size(item);
1754
0
                auto clamp_max = (!should_treat_main_max_size_as_none(item.box) && !computed_max_size.contains_percentage()) ? specified_main_max_size(item.box) : CSSPixels::max();
1755
1756
0
                result = css_clamp(result, clamp_min, clamp_max);
1757
1758
                // NOTE: The spec doesn't mention anything about the *outer* size here, but if we don't add the margin box,
1759
                //       flex items with non-zero padding/border/margin in the main axis end up overflowing the container.
1760
0
                result = item.add_main_margin_box_sizes(result);
1761
1762
0
                sum += result;
1763
0
            }
1764
            // CSS-FLEXBOX-2: Account for gap between flex items.
1765
0
            sum += main_gap() * (flex_line.items.size() - 1);
1766
0
            largest_sum = max(largest_sum, sum);
1767
0
        }
1768
        // 5. The flex container’s max-content size is the largest sum (among all the lines) of the afore-calculated sizes of all items within a single line.
1769
0
        return largest_sum;
1770
0
    };
1771
1772
0
    auto main_size = determine_main_size();
1773
0
    set_main_size(flex_container(), main_size);
1774
0
    return main_size;
1775
0
}
1776
1777
// https://drafts.csswg.org/css-flexbox-1/#intrinsic-cross-sizes
1778
CSSPixels FlexFormattingContext::calculate_intrinsic_cross_size_of_flex_container()
1779
0
{
1780
    // The min-content/max-content cross size of a single-line flex container
1781
    // is the largest min-content contribution/max-content contribution (respectively) of its flex items.
1782
0
    if (is_single_line()) {
1783
0
        auto calculate_largest_contribution = [&](bool resolve_percentage_min_max_sizes) {
1784
0
            CSSPixels largest_contribution = 0;
1785
0
            for (auto& item : m_flex_items) {
1786
0
                CSSPixels contribution = 0;
1787
0
                if (m_available_space_for_items->cross.is_min_content())
1788
0
                    contribution = calculate_cross_min_content_contribution(item, resolve_percentage_min_max_sizes);
1789
0
                else if (m_available_space_for_items->cross.is_max_content())
1790
0
                    contribution = calculate_cross_max_content_contribution(item, resolve_percentage_min_max_sizes);
1791
0
                largest_contribution = max(largest_contribution, contribution);
1792
0
            }
1793
0
            return largest_contribution;
1794
0
        };
1795
1796
0
        auto first_pass_largest_contribution = calculate_largest_contribution(false);
1797
0
        set_cross_size(flex_container(), first_pass_largest_contribution);
1798
0
        auto second_pass_largest_contribution = calculate_largest_contribution(true);
1799
0
        return second_pass_largest_contribution;
1800
0
    }
1801
1802
0
    if (is_row_layout()) {
1803
        // row multi-line flex container cross-size
1804
1805
        // The min-content/max-content cross size is the sum of the flex line cross sizes resulting from
1806
        // sizing the flex container under a cross-axis min-content constraint/max-content constraint (respectively).
1807
1808
        // NOTE: We fall through to the ad-hoc section below.
1809
0
    } else {
1810
        // column multi-line flex container cross-size
1811
1812
        // The min-content cross size is the largest min-content contribution among all of its flex items.
1813
0
        if (m_available_space_for_items->cross.is_min_content()) {
1814
0
            auto calculate_largest_contribution = [&](bool resolve_percentage_min_max_sizes) {
1815
0
                CSSPixels largest_contribution = 0;
1816
0
                for (auto& item : m_flex_items) {
1817
0
                    CSSPixels contribution = calculate_cross_min_content_contribution(item, resolve_percentage_min_max_sizes);
1818
0
                    largest_contribution = max(largest_contribution, contribution);
1819
0
                }
1820
0
                return largest_contribution;
1821
0
            };
1822
1823
0
            auto first_pass_largest_contribution = calculate_largest_contribution(false);
1824
0
            set_cross_size(flex_container(), first_pass_largest_contribution);
1825
0
            auto second_pass_largest_contribution = calculate_largest_contribution(true);
1826
0
            return second_pass_largest_contribution;
1827
0
        }
1828
1829
        // The max-content cross size is the sum of the flex line cross sizes resulting from
1830
        // sizing the flex container under a cross-axis max-content constraint,
1831
        // using the largest max-content cross-size contribution among the flex items
1832
        // as the available space in the cross axis for each of the flex items during layout.
1833
1834
        // NOTE: We fall through to the ad-hoc section below.
1835
0
    }
1836
1837
0
    CSSPixels sum_of_flex_line_cross_sizes = 0;
1838
0
    for (auto& flex_line : m_flex_lines) {
1839
0
        sum_of_flex_line_cross_sizes += flex_line.cross_size;
1840
0
    }
1841
    // CSS-FLEXBOX-2: Account for gap between flex lines.
1842
0
    sum_of_flex_line_cross_sizes += cross_gap() * (m_flex_lines.size() - 1);
1843
0
    return sum_of_flex_line_cross_sizes;
1844
0
}
1845
1846
// https://drafts.csswg.org/css-flexbox-1/#intrinsic-item-contributions
1847
CSSPixels FlexFormattingContext::calculate_main_min_content_contribution(FlexItem const& item) const
1848
0
{
1849
    // The main-size min-content contribution of a flex item is
1850
    // the larger of its outer min-content size and outer preferred size if that is not auto,
1851
    // clamped by its min/max main size.
1852
0
    auto larger_size = [&] {
1853
0
        auto inner_min_content_size = calculate_min_content_main_size(item);
1854
0
        if (computed_main_size(item.box).is_auto())
1855
0
            return inner_min_content_size;
1856
0
        auto inner_preferred_size = is_row_layout() ? get_pixel_width(item.box, computed_main_size(item.box)) : get_pixel_height(item.box, computed_main_size(item.box));
1857
0
        return max(inner_min_content_size, inner_preferred_size);
1858
0
    }();
1859
1860
0
    auto clamp_min = has_main_min_size(item.box) ? specified_main_min_size(item.box) : automatic_minimum_size(item);
1861
0
    auto clamp_max = has_main_max_size(item.box) ? specified_main_max_size(item.box) : CSSPixels::max();
1862
0
    auto clamped_inner_size = css_clamp(larger_size, clamp_min, clamp_max);
1863
1864
0
    return item.add_main_margin_box_sizes(clamped_inner_size);
1865
0
}
1866
1867
// https://drafts.csswg.org/css-flexbox-1/#intrinsic-item-contributions
1868
CSSPixels FlexFormattingContext::calculate_main_max_content_contribution(FlexItem const& item) const
1869
0
{
1870
    // The main-size max-content contribution of a flex item is
1871
    // the larger of its outer max-content size and outer preferred size if that is not auto,
1872
    // clamped by its min/max main size.
1873
0
    auto larger_size = [&] {
1874
0
        auto inner_max_content_size = calculate_max_content_main_size(item);
1875
0
        if (computed_main_size(item.box).is_auto())
1876
0
            return inner_max_content_size;
1877
0
        auto inner_preferred_size = is_row_layout() ? get_pixel_width(item.box, computed_main_size(item.box)) : get_pixel_height(item.box, computed_main_size(item.box));
1878
0
        return max(inner_max_content_size, inner_preferred_size);
1879
0
    }();
1880
1881
0
    auto clamp_min = has_main_min_size(item.box) ? specified_main_min_size(item.box) : automatic_minimum_size(item);
1882
0
    auto clamp_max = has_main_max_size(item.box) ? specified_main_max_size(item.box) : CSSPixels::max();
1883
0
    auto clamped_inner_size = css_clamp(larger_size, clamp_min, clamp_max);
1884
1885
0
    return item.add_main_margin_box_sizes(clamped_inner_size);
1886
0
}
1887
1888
bool FlexFormattingContext::should_treat_main_size_as_auto(Box const& box) const
1889
0
{
1890
0
    if (is_row_layout())
1891
0
        return should_treat_width_as_auto(box, m_available_space_for_items->space);
1892
0
    return should_treat_height_as_auto(box, m_available_space_for_items->space);
1893
0
}
1894
1895
bool FlexFormattingContext::should_treat_cross_size_as_auto(Box const& box) const
1896
0
{
1897
0
    if (is_row_layout())
1898
0
        return should_treat_height_as_auto(box, m_available_space_for_items->space);
1899
0
    return should_treat_width_as_auto(box, m_available_space_for_items->space);
1900
0
}
1901
1902
bool FlexFormattingContext::should_treat_main_max_size_as_none(Box const& box) const
1903
0
{
1904
0
    if (is_row_layout())
1905
0
        return should_treat_max_width_as_none(box, m_available_space_for_items->space.width);
1906
0
    return should_treat_max_height_as_none(box, m_available_space_for_items->space.height);
1907
0
}
1908
1909
bool FlexFormattingContext::should_treat_cross_max_size_as_none(Box const& box) const
1910
0
{
1911
0
    if (is_row_layout())
1912
0
        return should_treat_max_height_as_none(box, m_available_space_for_items->space.height);
1913
0
    return should_treat_max_width_as_none(box, m_available_space_for_items->space.width);
1914
0
}
1915
1916
CSSPixels FlexFormattingContext::calculate_cross_min_content_contribution(FlexItem const& item, bool resolve_percentage_min_max_sizes) const
1917
0
{
1918
0
    auto size = [&] {
1919
0
        if (should_treat_cross_size_as_auto(item.box))
1920
0
            return calculate_min_content_cross_size(item);
1921
0
        return !is_row_layout() ? get_pixel_width(item.box, computed_cross_size(item.box)) : get_pixel_height(item.box, computed_cross_size(item.box));
1922
0
    }();
1923
1924
0
    if (item.box->has_preferred_aspect_ratio())
1925
0
        size = adjust_cross_size_through_aspect_ratio_for_main_size_min_max_constraints(item.box, size, computed_main_min_size(item.box), computed_main_max_size(item.box));
1926
1927
0
    auto const& computed_min_size = this->computed_cross_min_size(item.box);
1928
0
    auto const& computed_max_size = this->computed_cross_max_size(item.box);
1929
1930
0
    auto clamp_min = (!computed_min_size.is_auto() && (resolve_percentage_min_max_sizes || !computed_min_size.contains_percentage())) ? specified_cross_min_size(item.box) : 0;
1931
0
    auto clamp_max = (!should_treat_cross_max_size_as_none(item.box) && (resolve_percentage_min_max_sizes || !computed_max_size.contains_percentage())) ? specified_cross_max_size(item.box) : CSSPixels::max();
1932
1933
0
    auto clamped_inner_size = css_clamp(size, clamp_min, clamp_max);
1934
1935
0
    return item.add_cross_margin_box_sizes(clamped_inner_size);
1936
0
}
1937
1938
CSSPixels FlexFormattingContext::calculate_cross_max_content_contribution(FlexItem const& item, bool resolve_percentage_min_max_sizes) const
1939
0
{
1940
0
    auto size = [&] {
1941
0
        if (should_treat_cross_size_as_auto(item.box))
1942
0
            return calculate_max_content_cross_size(item);
1943
0
        return !is_row_layout() ? get_pixel_width(item.box, computed_cross_size(item.box)) : get_pixel_height(item.box, computed_cross_size(item.box));
1944
0
    }();
1945
1946
0
    if (item.box->has_preferred_aspect_ratio())
1947
0
        size = adjust_cross_size_through_aspect_ratio_for_main_size_min_max_constraints(item.box, size, computed_main_min_size(item.box), computed_main_max_size(item.box));
1948
1949
0
    auto const& computed_min_size = this->computed_cross_min_size(item.box);
1950
0
    auto const& computed_max_size = this->computed_cross_max_size(item.box);
1951
1952
0
    auto clamp_min = (!computed_min_size.is_auto() && (resolve_percentage_min_max_sizes || !computed_min_size.contains_percentage())) ? specified_cross_min_size(item.box) : 0;
1953
0
    auto clamp_max = (!should_treat_cross_max_size_as_none(item.box) && (resolve_percentage_min_max_sizes || !computed_max_size.contains_percentage())) ? specified_cross_max_size(item.box) : CSSPixels::max();
1954
1955
0
    auto clamped_inner_size = css_clamp(size, clamp_min, clamp_max);
1956
1957
0
    return item.add_cross_margin_box_sizes(clamped_inner_size);
1958
0
}
1959
1960
CSSPixels FlexFormattingContext::calculate_width_to_use_when_determining_intrinsic_height_of_item(FlexItem const& item) const
1961
0
{
1962
0
    auto const& box = *item.box;
1963
0
    auto computed_width = box.computed_values().width();
1964
0
    auto const& computed_min_width = box.computed_values().min_width();
1965
0
    auto const& computed_max_width = box.computed_values().max_width();
1966
0
    auto clamp_min = (!computed_min_width.is_auto() && (!computed_min_width.contains_percentage())) ? get_pixel_width(box, computed_min_width) : 0;
1967
0
    auto clamp_max = (!should_treat_max_width_as_none(box, m_available_space_for_items->space.width) && (!computed_max_width.contains_percentage())) ? get_pixel_width(box, computed_max_width) : CSSPixels::max();
1968
1969
0
    CSSPixels width;
1970
0
    if (should_treat_width_as_auto(box, m_available_space_for_items->space) || computed_width.is_fit_content())
1971
0
        width = calculate_fit_content_width(box, m_available_space_for_items->space);
1972
0
    else if (computed_width.is_min_content())
1973
0
        width = calculate_min_content_width(box);
1974
0
    else if (computed_width.is_max_content())
1975
0
        width = calculate_max_content_width(box);
1976
1977
0
    return css_clamp(width, clamp_min, clamp_max);
1978
0
}
1979
1980
CSSPixels FlexFormattingContext::calculate_min_content_main_size(FlexItem const& item) const
1981
0
{
1982
0
    if (is_row_layout()) {
1983
0
        return calculate_min_content_width(item.box);
1984
0
    }
1985
0
    auto available_space = item.used_values.available_inner_space_or_constraints_from(m_available_space_for_items->space);
1986
0
    if (available_space.width.is_indefinite()) {
1987
0
        available_space.width = AvailableSize::make_definite(calculate_width_to_use_when_determining_intrinsic_height_of_item(item));
1988
0
    }
1989
0
    return calculate_min_content_height(item.box, available_space.width.to_px_or_zero());
1990
0
}
1991
1992
CSSPixels FlexFormattingContext::calculate_max_content_main_size(FlexItem const& item) const
1993
0
{
1994
0
    if (is_row_layout()) {
1995
0
        return calculate_max_content_width(item.box);
1996
0
    }
1997
0
    auto available_space = item.used_values.available_inner_space_or_constraints_from(m_available_space_for_items->space);
1998
0
    if (available_space.width.is_indefinite()) {
1999
0
        available_space.width = AvailableSize::make_definite(calculate_width_to_use_when_determining_intrinsic_height_of_item(item));
2000
0
    }
2001
0
    return calculate_max_content_height(item.box, available_space.width.to_px_or_zero());
2002
0
}
2003
2004
CSSPixels FlexFormattingContext::calculate_fit_content_main_size(FlexItem const& item) const
2005
0
{
2006
0
    if (is_row_layout())
2007
0
        return calculate_fit_content_width(item.box, m_available_space_for_items->space);
2008
0
    return calculate_fit_content_height(item.box, m_available_space_for_items->space);
2009
0
}
2010
2011
CSSPixels FlexFormattingContext::calculate_fit_content_cross_size(FlexItem const& item) const
2012
0
{
2013
0
    if (!is_row_layout())
2014
0
        return calculate_fit_content_width(item.box, m_available_space_for_items->space);
2015
0
    return calculate_fit_content_height(item.box, m_available_space_for_items->space);
2016
0
}
2017
2018
CSSPixels FlexFormattingContext::calculate_min_content_cross_size(FlexItem const& item) const
2019
0
{
2020
0
    if (is_row_layout()) {
2021
0
        auto available_space = item.used_values.available_inner_space_or_constraints_from(m_available_space_for_items->space);
2022
0
        if (available_space.width.is_indefinite()) {
2023
0
            available_space.width = AvailableSize::make_definite(calculate_width_to_use_when_determining_intrinsic_height_of_item(item));
2024
0
        }
2025
0
        return calculate_min_content_height(item.box, available_space.width.to_px_or_zero());
2026
0
    }
2027
0
    return calculate_min_content_width(item.box);
2028
0
}
2029
2030
CSSPixels FlexFormattingContext::calculate_max_content_cross_size(FlexItem const& item) const
2031
0
{
2032
0
    if (is_row_layout()) {
2033
0
        auto available_space = item.used_values.available_inner_space_or_constraints_from(m_available_space_for_items->space);
2034
0
        if (available_space.width.is_indefinite()) {
2035
0
            available_space.width = AvailableSize::make_definite(calculate_width_to_use_when_determining_intrinsic_height_of_item(item));
2036
0
        }
2037
0
        return calculate_max_content_height(item.box, available_space.width.to_px_or_zero());
2038
0
    }
2039
0
    return calculate_max_content_width(item.box);
2040
0
}
2041
2042
// https://drafts.csswg.org/css-flexbox-1/#stretched
2043
bool FlexFormattingContext::flex_item_is_stretched(FlexItem const& item) const
2044
0
{
2045
0
    auto alignment = alignment_for_item(item.box);
2046
0
    if (alignment != CSS::AlignItems::Stretch && alignment != CSS::AlignItems::Normal)
2047
0
        return false;
2048
    // If the cross size property of the flex item computes to auto, and neither of the cross-axis margins are auto, the flex item is stretched.
2049
0
    auto const& computed_cross_size = is_row_layout() ? item.box->computed_values().height() : item.box->computed_values().width();
2050
0
    return computed_cross_size.is_auto() && !item.margins.cross_before_is_auto && !item.margins.cross_after_is_auto;
2051
0
}
2052
2053
CSS::Size const& FlexFormattingContext::computed_main_size(Box const& box) const
2054
0
{
2055
0
    return is_row_layout() ? box.computed_values().width() : box.computed_values().height();
2056
0
}
2057
2058
CSS::Size const& FlexFormattingContext::computed_main_min_size(Box const& box) const
2059
0
{
2060
0
    return is_row_layout() ? box.computed_values().min_width() : box.computed_values().min_height();
2061
0
}
2062
2063
CSS::Size const& FlexFormattingContext::computed_main_max_size(Box const& box) const
2064
0
{
2065
0
    return is_row_layout() ? box.computed_values().max_width() : box.computed_values().max_height();
2066
0
}
2067
2068
CSS::Size const& FlexFormattingContext::computed_cross_size(Box const& box) const
2069
0
{
2070
0
    return !is_row_layout() ? box.computed_values().width() : box.computed_values().height();
2071
0
}
2072
2073
CSS::Size const& FlexFormattingContext::computed_cross_min_size(Box const& box) const
2074
0
{
2075
0
    return !is_row_layout() ? box.computed_values().min_width() : box.computed_values().min_height();
2076
0
}
2077
2078
CSS::Size const& FlexFormattingContext::computed_cross_max_size(Box const& box) const
2079
0
{
2080
0
    return !is_row_layout() ? box.computed_values().max_width() : box.computed_values().max_height();
2081
0
}
2082
2083
// https://drafts.csswg.org/css-flexbox-1/#algo-cross-margins
2084
void FlexFormattingContext::resolve_cross_axis_auto_margins()
2085
0
{
2086
0
    for (auto& line : m_flex_lines) {
2087
0
        for (auto& item : line.items) {
2088
            //  If a flex item has auto cross-axis margins:
2089
0
            if (!item.margins.cross_before_is_auto && !item.margins.cross_after_is_auto)
2090
0
                continue;
2091
2092
            // If its outer cross size (treating those auto margins as zero) is less than the cross size of its flex line,
2093
            // distribute the difference in those sizes equally to the auto margins.
2094
0
            auto outer_cross_size = item.cross_size.value() + item.padding.cross_before + item.padding.cross_after + item.borders.cross_before + item.borders.cross_after;
2095
0
            if (outer_cross_size < line.cross_size) {
2096
0
                CSSPixels remainder = line.cross_size - outer_cross_size;
2097
0
                if (item.margins.cross_before_is_auto && item.margins.cross_after_is_auto) {
2098
0
                    item.margins.cross_before = remainder / 2;
2099
0
                    item.margins.cross_after = remainder / 2;
2100
0
                } else if (item.margins.cross_before_is_auto) {
2101
0
                    item.margins.cross_before = remainder;
2102
0
                } else {
2103
0
                    item.margins.cross_after = remainder;
2104
0
                }
2105
0
            } else {
2106
                // FIXME: Otherwise, if the block-start or inline-start margin (whichever is in the cross axis) is auto, set it to zero.
2107
                //        Set the opposite margin so that the outer cross size of the item equals the cross size of its flex line.
2108
0
            }
2109
0
        }
2110
0
    }
2111
0
}
2112
2113
// https://drafts.csswg.org/css-flexbox-1/#algo-line-stretch
2114
void FlexFormattingContext::handle_align_content_stretch()
2115
0
{
2116
    // If the flex container has a definite cross size,
2117
0
    if (!has_definite_cross_size(m_flex_container_state))
2118
0
        return;
2119
2120
    // align-content is stretch,
2121
0
    if (flex_container().computed_values().align_content() != CSS::AlignContent::Stretch && flex_container().computed_values().align_content() != CSS::AlignContent::Normal)
2122
0
        return;
2123
2124
    // and the sum of the flex lines' cross sizes is less than the flex container’s inner cross size,
2125
0
    CSSPixels sum_of_flex_line_cross_sizes = 0;
2126
0
    for (auto& line : m_flex_lines)
2127
0
        sum_of_flex_line_cross_sizes += line.cross_size;
2128
2129
    // CSS-FLEXBOX-2: Account for gap between flex lines.
2130
0
    sum_of_flex_line_cross_sizes += cross_gap() * (m_flex_lines.size() - 1);
2131
2132
0
    if (sum_of_flex_line_cross_sizes >= inner_cross_size(m_flex_container_state))
2133
0
        return;
2134
2135
    // increase the cross size of each flex line by equal amounts
2136
    // such that the sum of their cross sizes exactly equals the flex container’s inner cross size.
2137
0
    CSSPixels remainder = inner_cross_size(m_flex_container_state) - sum_of_flex_line_cross_sizes;
2138
0
    CSSPixels extra_per_line = remainder / m_flex_lines.size();
2139
2140
0
    for (auto& line : m_flex_lines)
2141
0
        line.cross_size += extra_per_line;
2142
0
}
2143
2144
// https://drafts.csswg.org/css-flexbox-1/#abspos-items
2145
CSSPixelPoint FlexFormattingContext::calculate_static_position(Box const& box) const
2146
0
{
2147
    // The cross-axis edges of the static-position rectangle of an absolutely-positioned child
2148
    // of a flex container are the content edges of the flex container.
2149
0
    CSSPixels cross_offset = 0;
2150
0
    CSSPixels half_line_size = inner_cross_size(m_flex_container_state) / 2;
2151
2152
0
    auto cross_to_px = [&](CSS::LengthPercentage const& length_percentage) -> CSSPixels {
2153
0
        return length_percentage.to_px(box, m_flex_container_state.content_width());
2154
0
    };
2155
2156
0
    auto main_to_px = [&](CSS::LengthPercentage const& length_percentage) -> CSSPixels {
2157
0
        return length_percentage.to_px(box, m_flex_container_state.content_width());
2158
0
    };
2159
2160
0
    auto const& box_state = m_state.get(box);
2161
0
    CSSPixels cross_margin_before = is_row_layout() ? cross_to_px(box.computed_values().margin().top()) : cross_to_px(box.computed_values().margin().left());
2162
0
    CSSPixels cross_margin_after = is_row_layout() ? cross_to_px(box.computed_values().margin().bottom()) : cross_to_px(box.computed_values().margin().right());
2163
0
    CSSPixels cross_border_before = is_row_layout() ? box.computed_values().border_top().width : box.computed_values().border_left().width;
2164
0
    CSSPixels cross_border_after = is_row_layout() ? box.computed_values().border_bottom().width : box.computed_values().border_right().width;
2165
0
    CSSPixels cross_padding_before = is_row_layout() ? cross_to_px(box.computed_values().padding().top()) : cross_to_px(box.computed_values().padding().left());
2166
0
    CSSPixels cross_padding_after = is_row_layout() ? cross_to_px(box.computed_values().padding().bottom()) : cross_to_px(box.computed_values().padding().right());
2167
0
    CSSPixels main_margin_before = is_row_layout() ? main_to_px(box.computed_values().margin().left()) : main_to_px(box.computed_values().margin().top());
2168
0
    CSSPixels main_margin_after = is_row_layout() ? main_to_px(box.computed_values().margin().right()) : main_to_px(box.computed_values().margin().bottom());
2169
0
    CSSPixels main_border_before = is_row_layout() ? box.computed_values().border_left().width : box.computed_values().border_top().width;
2170
0
    CSSPixels main_border_after = is_row_layout() ? box.computed_values().border_right().width : box.computed_values().border_bottom().width;
2171
0
    CSSPixels main_padding_before = is_row_layout() ? main_to_px(box.computed_values().padding().left()) : main_to_px(box.computed_values().padding().top());
2172
0
    CSSPixels main_padding_after = is_row_layout() ? main_to_px(box.computed_values().padding().right()) : main_to_px(box.computed_values().padding().bottom());
2173
2174
0
    switch (alignment_for_item(box)) {
2175
0
    case CSS::AlignItems::Baseline:
2176
        // FIXME: Implement this
2177
        //  Fallthrough
2178
0
    case CSS::AlignItems::Start:
2179
0
    case CSS::AlignItems::FlexStart:
2180
0
    case CSS::AlignItems::SelfStart:
2181
0
    case CSS::AlignItems::Stretch:
2182
0
    case CSS::AlignItems::Normal:
2183
0
        cross_offset = -half_line_size;
2184
0
        break;
2185
0
    case CSS::AlignItems::End:
2186
0
    case CSS::AlignItems::SelfEnd:
2187
0
    case CSS::AlignItems::FlexEnd:
2188
0
        cross_offset = half_line_size - inner_cross_size(box_state) - (cross_margin_before + cross_margin_after) - (cross_border_before + cross_border_after) - (cross_padding_before + cross_padding_after);
2189
0
        break;
2190
0
    case CSS::AlignItems::Center:
2191
0
        cross_offset = -((inner_cross_size(box_state) + cross_margin_after + cross_margin_before + cross_border_before + cross_border_after + cross_padding_before + cross_padding_after) / 2);
2192
0
        break;
2193
0
    default:
2194
0
        break;
2195
0
    }
2196
2197
0
    cross_offset += inner_cross_size(m_flex_container_state) / 2;
2198
2199
    // The main-axis edges of the static-position rectangle are where the margin edges of the child
2200
    // would be positioned if it were the sole flex item in the flex container,
2201
    // assuming both the child and the flex container were fixed-size boxes of their used size.
2202
    // (For this purpose, auto margins are treated as zero.
2203
2204
0
    bool pack_from_end = true;
2205
0
    CSSPixels main_offset = 0;
2206
0
    switch (flex_container().computed_values().justify_content()) {
2207
0
    case CSS::JustifyContent::Start:
2208
0
    case CSS::JustifyContent::Left:
2209
0
        pack_from_end = false;
2210
0
        break;
2211
0
    case CSS::JustifyContent::Stretch:
2212
0
    case CSS::JustifyContent::Normal:
2213
0
    case CSS::JustifyContent::FlexStart:
2214
0
    case CSS::JustifyContent::SpaceBetween:
2215
0
        pack_from_end = is_direction_reverse();
2216
0
        break;
2217
0
    case CSS::JustifyContent::End:
2218
0
        pack_from_end = true;
2219
0
        break;
2220
0
    case CSS::JustifyContent::Right:
2221
0
        pack_from_end = is_row_layout();
2222
0
        break;
2223
0
    case CSS::JustifyContent::FlexEnd:
2224
0
        pack_from_end = !is_direction_reverse();
2225
0
        break;
2226
0
    case CSS::JustifyContent::Center:
2227
0
    case CSS::JustifyContent::SpaceAround:
2228
0
    case CSS::JustifyContent::SpaceEvenly:
2229
0
        pack_from_end = false;
2230
0
        main_offset = (inner_main_size(m_flex_container_state) - inner_main_size(box_state) - main_margin_before - main_margin_after - main_border_before - main_border_after - main_padding_before - main_padding_after) / 2;
2231
0
        break;
2232
0
    }
2233
2234
0
    if (pack_from_end)
2235
0
        main_offset += inner_main_size(m_flex_container_state) - inner_main_size(box_state) - main_margin_before - main_margin_after - main_border_before - main_border_after - main_padding_before - main_padding_after;
2236
2237
0
    auto static_position_offset = is_row_layout() ? CSSPixelPoint { main_offset, cross_offset } : CSSPixelPoint { cross_offset, main_offset };
2238
2239
0
    auto absolute_position_of_flex_container = absolute_content_rect(flex_container()).location();
2240
0
    auto absolute_position_of_abspos_containing_block = absolute_content_rect(*box.containing_block()).location();
2241
0
    auto diff = absolute_position_of_flex_container - absolute_position_of_abspos_containing_block;
2242
2243
0
    return static_position_offset + diff;
2244
0
}
2245
2246
double FlexFormattingContext::FlexLine::sum_of_flex_factor_of_unfrozen_items() const
2247
0
{
2248
0
    double sum = 0;
2249
0
    for (auto const& item : items) {
2250
0
        if (!item.frozen)
2251
0
            sum += item.flex_factor.value();
2252
0
    }
2253
0
    return sum;
2254
0
}
2255
2256
double FlexFormattingContext::FlexLine::sum_of_scaled_flex_shrink_factor_of_unfrozen_items() const
2257
0
{
2258
0
    double sum = 0;
2259
0
    for (auto const& item : items) {
2260
0
        if (!item.frozen)
2261
0
            sum += item.scaled_flex_shrink_factor;
2262
0
    }
2263
0
    return sum;
2264
0
}
2265
2266
CSSPixels FlexFormattingContext::main_gap() const
2267
0
{
2268
0
    auto const& computed_values = flex_container().computed_values();
2269
0
    auto gap = is_row_layout() ? computed_values.column_gap() : computed_values.row_gap();
2270
0
    return gap.to_px(flex_container(), inner_main_size(m_flex_container_state));
2271
0
}
2272
2273
CSSPixels FlexFormattingContext::cross_gap() const
2274
0
{
2275
0
    auto const& computed_values = flex_container().computed_values();
2276
0
    auto gap = is_row_layout() ? computed_values.row_gap() : computed_values.column_gap();
2277
0
    return gap.to_px(flex_container(), inner_cross_size(m_flex_container_state));
2278
0
}
2279
2280
}