Coverage Report

Created: 2025-03-04 07:22

/src/serenity/Userland/Libraries/LibWeb/Layout/TreeBuilder.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org>
3
 * Copyright (c) 2022-2023, Sam Atkins <atkinssj@serenityos.org>
4
 * Copyright (c) 2022, MacDue <macdue@dueutil.tech>
5
 *
6
 * SPDX-License-Identifier: BSD-2-Clause
7
 */
8
9
#include <AK/Error.h>
10
#include <AK/Optional.h>
11
#include <AK/TemporaryChange.h>
12
#include <LibWeb/CSS/StyleComputer.h>
13
#include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
14
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
15
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
16
#include <LibWeb/DOM/Document.h>
17
#include <LibWeb/DOM/Element.h>
18
#include <LibWeb/DOM/ParentNode.h>
19
#include <LibWeb/DOM/ShadowRoot.h>
20
#include <LibWeb/Dump.h>
21
#include <LibWeb/HTML/HTMLButtonElement.h>
22
#include <LibWeb/HTML/HTMLInputElement.h>
23
#include <LibWeb/HTML/HTMLLIElement.h>
24
#include <LibWeb/HTML/HTMLOListElement.h>
25
#include <LibWeb/HTML/HTMLSlotElement.h>
26
#include <LibWeb/Layout/ListItemBox.h>
27
#include <LibWeb/Layout/ListItemMarkerBox.h>
28
#include <LibWeb/Layout/Node.h>
29
#include <LibWeb/Layout/SVGClipBox.h>
30
#include <LibWeb/Layout/SVGMaskBox.h>
31
#include <LibWeb/Layout/TableGrid.h>
32
#include <LibWeb/Layout/TableWrapper.h>
33
#include <LibWeb/Layout/TextNode.h>
34
#include <LibWeb/Layout/TreeBuilder.h>
35
#include <LibWeb/Layout/Viewport.h>
36
#include <LibWeb/SVG/SVGForeignObjectElement.h>
37
38
namespace Web::Layout {
39
40
0
TreeBuilder::TreeBuilder() = default;
41
42
static bool has_inline_or_in_flow_block_children(Layout::Node const& layout_node)
43
0
{
44
0
    for (auto child = layout_node.first_child(); child; child = child->next_sibling()) {
45
0
        if (child->is_inline() || child->is_in_flow())
46
0
            return true;
47
0
    }
48
0
    return false;
49
0
}
50
51
static bool has_in_flow_block_children(Layout::Node const& layout_node)
52
0
{
53
0
    if (layout_node.children_are_inline())
54
0
        return false;
55
0
    for (auto child = layout_node.first_child(); child; child = child->next_sibling()) {
56
0
        if (child->is_inline())
57
0
            continue;
58
0
        if (child->is_in_flow())
59
0
            return true;
60
0
    }
61
0
    return false;
62
0
}
63
64
// The insertion_parent_for_*() functions maintain the invariant that the in-flow children of
65
// block-level boxes must be either all block-level or all inline-level.
66
67
static Layout::Node& insertion_parent_for_inline_node(Layout::NodeWithStyle& layout_parent)
68
0
{
69
0
    auto last_child_creating_anonymous_wrapper_if_needed = [](auto& layout_parent) -> Layout::Node& {
70
0
        if (!layout_parent.last_child()
71
0
            || !layout_parent.last_child()->is_anonymous()
72
0
            || !layout_parent.last_child()->children_are_inline()
73
0
            || layout_parent.last_child()->is_generated()) {
74
0
            layout_parent.append_child(layout_parent.create_anonymous_wrapper());
75
0
        }
76
0
        return *layout_parent.last_child();
77
0
    };
78
79
0
    if (layout_parent.display().is_inline_outside() && layout_parent.display().is_flow_inside())
80
0
        return layout_parent;
81
82
0
    if (layout_parent.display().is_flex_inside() || layout_parent.display().is_grid_inside())
83
0
        return last_child_creating_anonymous_wrapper_if_needed(layout_parent);
84
85
0
    if (!has_in_flow_block_children(layout_parent) || layout_parent.children_are_inline())
86
0
        return layout_parent;
87
88
    // Parent has block-level children, insert into an anonymous wrapper block (and create it first if needed)
89
0
    return last_child_creating_anonymous_wrapper_if_needed(layout_parent);
90
0
}
91
92
static Layout::Node& insertion_parent_for_block_node(Layout::NodeWithStyle& layout_parent, Layout::Node& layout_node)
93
0
{
94
0
    if (!has_inline_or_in_flow_block_children(layout_parent)) {
95
        // Parent block has no children, insert this block into parent.
96
0
        return layout_parent;
97
0
    }
98
99
0
    if (layout_node.is_out_of_flow()
100
0
        && !layout_parent.display().is_flex_inside()
101
0
        && !layout_parent.display().is_grid_inside()
102
0
        && !layout_parent.last_child()->is_generated()
103
0
        && layout_parent.last_child()->is_anonymous()
104
0
        && layout_parent.last_child()->children_are_inline()) {
105
        // Block is out-of-flow & previous sibling was wrapped in an anonymous block.
106
        // Join the previous sibling inside the anonymous block.
107
0
        return *layout_parent.last_child();
108
0
    }
109
110
0
    if (!layout_parent.children_are_inline()) {
111
        // Parent block has block-level children, insert this block into parent.
112
0
        return layout_parent;
113
0
    }
114
115
0
    if (layout_node.is_out_of_flow()) {
116
        // Block is out-of-flow, it can have inline siblings if necessary.
117
0
        return layout_parent;
118
0
    }
119
120
    // Parent block has inline-level children (our siblings).
121
    // First move these siblings into an anonymous wrapper block.
122
0
    Vector<JS::Handle<Layout::Node>> children;
123
0
    {
124
0
        JS::GCPtr<Layout::Node> next;
125
0
        for (JS::GCPtr<Layout::Node> child = layout_parent.first_child(); child; child = next) {
126
0
            next = child->next_sibling();
127
            // NOTE: We let out-of-flow children stay in the parent, to preserve tree structure.
128
0
            if (child->is_out_of_flow())
129
0
                continue;
130
0
            layout_parent.remove_child(*child);
131
0
            children.append(*child);
132
0
        }
133
0
    }
134
0
    layout_parent.append_child(layout_parent.create_anonymous_wrapper());
135
0
    layout_parent.set_children_are_inline(false);
136
0
    for (auto& child : children) {
137
0
        layout_parent.last_child()->append_child(*child);
138
0
    }
139
0
    layout_parent.last_child()->set_children_are_inline(true);
140
    // Then it's safe to insert this block into parent.
141
0
    return layout_parent;
142
0
}
143
144
void TreeBuilder::insert_node_into_inline_or_block_ancestor(Layout::Node& node, CSS::Display display, AppendOrPrepend mode)
145
0
{
146
0
    if (node.display().is_contents())
147
0
        return;
148
149
0
    if (display.is_inline_outside()) {
150
        // Inlines can be inserted into the nearest ancestor without "display: contents".
151
0
        auto& nearest_ancestor_without_display_contents = [&]() -> Layout::NodeWithStyle& {
152
0
            for (auto& ancestor : m_ancestor_stack.in_reverse()) {
153
0
                if (!ancestor->display().is_contents())
154
0
                    return ancestor;
155
0
            }
156
0
            VERIFY_NOT_REACHED();
157
0
        }();
158
0
        auto& insertion_point = insertion_parent_for_inline_node(nearest_ancestor_without_display_contents);
159
0
        if (mode == AppendOrPrepend::Prepend)
160
0
            insertion_point.prepend_child(node);
161
0
        else
162
0
            insertion_point.append_child(node);
163
0
        insertion_point.set_children_are_inline(true);
164
0
    } else {
165
        // Non-inlines can't be inserted into an inline parent, so find the nearest non-inline ancestor.
166
0
        auto& nearest_non_inline_ancestor = [&]() -> Layout::NodeWithStyle& {
167
0
            for (auto& ancestor : m_ancestor_stack.in_reverse()) {
168
0
                if (ancestor->display().is_contents())
169
0
                    continue;
170
0
                if (!ancestor->display().is_inline_outside())
171
0
                    return ancestor;
172
0
                if (!ancestor->display().is_flow_inside())
173
0
                    return ancestor;
174
0
                if (ancestor->dom_node() && is<SVG::SVGForeignObjectElement>(*ancestor->dom_node()))
175
0
                    return ancestor;
176
0
            }
177
0
            VERIFY_NOT_REACHED();
178
0
        }();
179
0
        auto& insertion_point = insertion_parent_for_block_node(nearest_non_inline_ancestor, node);
180
0
        if (mode == AppendOrPrepend::Prepend)
181
0
            insertion_point.prepend_child(node);
182
0
        else
183
0
            insertion_point.append_child(node);
184
185
        // After inserting an in-flow block-level box into a parent, mark the parent as having non-inline children.
186
0
        if (!node.is_floating() && !node.is_absolutely_positioned())
187
0
            insertion_point.set_children_are_inline(false);
188
0
    }
189
0
}
190
191
void TreeBuilder::create_pseudo_element_if_needed(DOM::Element& element, CSS::Selector::PseudoElement::Type pseudo_element, AppendOrPrepend mode)
192
0
{
193
0
    auto& document = element.document();
194
195
0
    auto pseudo_element_style = element.pseudo_element_computed_css_values(pseudo_element);
196
0
    if (!pseudo_element_style)
197
0
        return;
198
199
0
    auto initial_quote_nesting_level = m_quote_nesting_level;
200
0
    auto [pseudo_element_content, final_quote_nesting_level] = pseudo_element_style->content(element, initial_quote_nesting_level);
201
0
    m_quote_nesting_level = final_quote_nesting_level;
202
0
    auto pseudo_element_display = pseudo_element_style->display();
203
    // ::before and ::after only exist if they have content. `content: normal` computes to `none` for them.
204
    // We also don't create them if they are `display: none`.
205
0
    if (pseudo_element_display.is_none()
206
0
        || pseudo_element_content.type == CSS::ContentData::Type::Normal
207
0
        || pseudo_element_content.type == CSS::ContentData::Type::None)
208
0
        return;
209
210
0
    auto pseudo_element_node = DOM::Element::create_layout_node_for_display_type(document, pseudo_element_display, *pseudo_element_style, nullptr);
211
0
    if (!pseudo_element_node)
212
0
        return;
213
214
0
    auto& style_computer = document.style_computer();
215
216
    // FIXME: This code actually computes style for element::marker, and shouldn't for element::pseudo::marker
217
0
    if (is<ListItemBox>(*pseudo_element_node)) {
218
0
        auto marker_style = style_computer.compute_style(element, CSS::Selector::PseudoElement::Type::Marker);
219
0
        auto list_item_marker = document.heap().allocate_without_realm<ListItemMarkerBox>(
220
0
            document,
221
0
            pseudo_element_node->computed_values().list_style_type(),
222
0
            pseudo_element_node->computed_values().list_style_position(),
223
0
            0,
224
0
            *marker_style);
225
0
        static_cast<ListItemBox&>(*pseudo_element_node).set_marker(list_item_marker);
226
0
        element.set_pseudo_element_node({}, CSS::Selector::PseudoElement::Type::Marker, list_item_marker);
227
0
        pseudo_element_node->append_child(*list_item_marker);
228
0
    }
229
230
0
    auto generated_for = Node::GeneratedFor::NotGenerated;
231
0
    if (pseudo_element == CSS::Selector::PseudoElement::Type::Before) {
232
0
        generated_for = Node::GeneratedFor::PseudoBefore;
233
0
    } else if (pseudo_element == CSS::Selector::PseudoElement::Type::After) {
234
0
        generated_for = Node::GeneratedFor::PseudoAfter;
235
0
    } else {
236
0
        VERIFY_NOT_REACHED();
237
0
    }
238
239
0
    pseudo_element_node->set_generated_for(generated_for, element);
240
0
    pseudo_element_node->set_initial_quote_nesting_level(initial_quote_nesting_level);
241
242
    // FIXME: Handle images, and multiple values
243
0
    if (pseudo_element_content.type == CSS::ContentData::Type::String) {
244
0
        auto text = document.heap().allocate<DOM::Text>(document.realm(), document, pseudo_element_content.data);
245
0
        auto text_node = document.heap().allocate_without_realm<Layout::TextNode>(document, *text);
246
0
        text_node->set_generated_for(generated_for, element);
247
248
0
        push_parent(*pseudo_element_node);
249
0
        insert_node_into_inline_or_block_ancestor(*text_node, text_node->display(), AppendOrPrepend::Append);
250
0
        pop_parent();
251
0
    } else {
252
0
        TODO();
253
0
    }
254
255
0
    element.set_pseudo_element_node({}, pseudo_element, pseudo_element_node);
256
0
    insert_node_into_inline_or_block_ancestor(*pseudo_element_node, pseudo_element_display, mode);
257
0
}
258
259
static bool is_ignorable_whitespace(Layout::Node const& node)
260
0
{
261
0
    if (node.is_text_node() && static_cast<TextNode const&>(node).text_for_rendering().bytes_as_string_view().is_whitespace())
262
0
        return true;
263
264
0
    if (node.is_anonymous() && node.is_block_container() && static_cast<BlockContainer const&>(node).children_are_inline()) {
265
0
        bool contains_only_white_space = true;
266
0
        node.for_each_in_inclusive_subtree_of_type<TextNode>([&contains_only_white_space](auto& text_node) {
267
0
            if (!text_node.text_for_rendering().bytes_as_string_view().is_whitespace()) {
268
0
                contains_only_white_space = false;
269
0
                return TraversalDecision::Break;
270
0
            }
271
0
            return TraversalDecision::Continue;
272
0
        });
273
0
        if (contains_only_white_space)
274
0
            return true;
275
0
    }
276
277
0
    return false;
278
0
}
279
280
i32 TreeBuilder::calculate_list_item_index(DOM::Node& dom_node)
281
0
{
282
0
    if (is<HTML::HTMLLIElement>(dom_node)) {
283
0
        auto& li = static_cast<HTML::HTMLLIElement&>(dom_node);
284
0
        if (li.value() != 0)
285
0
            return li.value();
286
0
    }
287
288
0
    if (dom_node.previous_sibling() != nullptr) {
289
0
        DOM::Node* current = dom_node.previous_sibling();
290
0
        while (current != nullptr) {
291
0
            if (is<HTML::HTMLLIElement>(*current))
292
0
                return calculate_list_item_index(*current) + 1;
293
0
            current = current->previous_sibling();
294
0
        }
295
0
    }
296
297
0
    if (is<HTML::HTMLOListElement>(*dom_node.parent())) {
298
0
        auto& ol = static_cast<HTML::HTMLOListElement&>(*dom_node.parent());
299
0
        return ol.start();
300
0
    }
301
0
    return 1;
302
0
}
303
304
void TreeBuilder::create_layout_tree(DOM::Node& dom_node, TreeBuilder::Context& context)
305
0
{
306
0
    if (dom_node.is_element()) {
307
0
        auto& element = static_cast<DOM::Element&>(dom_node);
308
0
        if (element.in_top_layer() && !context.layout_top_layer)
309
0
            return;
310
0
    }
311
0
    if (dom_node.is_element())
312
0
        dom_node.document().style_computer().push_ancestor(static_cast<DOM::Element const&>(dom_node));
313
314
0
    ScopeGuard pop_ancestor_guard = [&] {
315
0
        if (dom_node.is_element())
316
0
            dom_node.document().style_computer().pop_ancestor(static_cast<DOM::Element const&>(dom_node));
317
0
    };
318
319
0
    JS::GCPtr<Layout::Node> layout_node;
320
0
    Optional<TemporaryChange<bool>> has_svg_root_change;
321
322
0
    ScopeGuard remove_stale_layout_node_guard = [&] {
323
        // If we didn't create a layout node for this DOM node,
324
        // go through the DOM tree and remove any old layout & paint nodes since they are now all stale.
325
0
        if (!layout_node) {
326
0
            dom_node.for_each_in_inclusive_subtree([&](auto& node) {
327
0
                node.detach_layout_node({});
328
0
                node.set_paintable(nullptr);
329
0
                if (is<DOM::Element>(node))
330
0
                    static_cast<DOM::Element&>(node).clear_pseudo_element_nodes({});
331
0
                return TraversalDecision::Continue;
332
0
            });
333
0
        }
334
0
    };
335
336
0
    if (dom_node.is_svg_container()) {
337
0
        has_svg_root_change.emplace(context.has_svg_root, true);
338
0
    } else if (dom_node.requires_svg_container() && !context.has_svg_root) {
339
0
        return;
340
0
    }
341
342
0
    auto& document = dom_node.document();
343
0
    auto& style_computer = document.style_computer();
344
0
    RefPtr<CSS::StyleProperties> style;
345
0
    CSS::Display display;
346
347
0
    if (is<DOM::Element>(dom_node)) {
348
0
        auto& element = static_cast<DOM::Element&>(dom_node);
349
0
        element.clear_pseudo_element_nodes({});
350
0
        VERIFY(!element.needs_style_update());
351
0
        style = element.computed_css_values();
352
0
        element.resolve_counters(*style);
353
0
        display = style->display();
354
0
        if (display.is_none())
355
0
            return;
356
        // TODO: Implement changing element contents with the `content` property.
357
0
        if (context.layout_svg_mask_or_clip_path) {
358
0
            if (is<SVG::SVGMaskElement>(dom_node))
359
0
                layout_node = document.heap().allocate_without_realm<Layout::SVGMaskBox>(document, static_cast<SVG::SVGMaskElement&>(dom_node), *style);
360
0
            else if (is<SVG::SVGClipPathElement>(dom_node))
361
0
                layout_node = document.heap().allocate_without_realm<Layout::SVGClipBox>(document, static_cast<SVG::SVGClipPathElement&>(dom_node), *style);
362
0
            else
363
0
                VERIFY_NOT_REACHED();
364
            // Only layout direct uses of SVG masks/clipPaths.
365
0
            context.layout_svg_mask_or_clip_path = false;
366
0
        } else {
367
0
            layout_node = element.create_layout_node(*style);
368
0
        }
369
0
    } else if (is<DOM::Document>(dom_node)) {
370
0
        style = style_computer.create_document_style();
371
0
        display = style->display();
372
0
        layout_node = document.heap().allocate_without_realm<Layout::Viewport>(static_cast<DOM::Document&>(dom_node), *style);
373
0
    } else if (is<DOM::Text>(dom_node)) {
374
0
        layout_node = document.heap().allocate_without_realm<Layout::TextNode>(document, static_cast<DOM::Text&>(dom_node));
375
0
        display = CSS::Display(CSS::DisplayOutside::Inline, CSS::DisplayInside::Flow);
376
0
    }
377
378
0
    if (!layout_node)
379
0
        return;
380
381
0
    if (!dom_node.parent_or_shadow_host()) {
382
0
        m_layout_root = layout_node;
383
0
    } else if (layout_node->is_svg_box()) {
384
0
        m_ancestor_stack.last()->append_child(*layout_node);
385
0
    } else {
386
0
        insert_node_into_inline_or_block_ancestor(*layout_node, display, AppendOrPrepend::Append);
387
0
    }
388
389
0
    auto shadow_root = is<DOM::Element>(dom_node) ? verify_cast<DOM::Element>(dom_node).shadow_root() : nullptr;
390
391
0
    auto element_has_content_visibility_hidden = [&dom_node]() {
392
0
        if (is<DOM::Element>(dom_node)) {
393
0
            auto& element = static_cast<DOM::Element&>(dom_node);
394
0
            return element.computed_css_values()->content_visibility() == CSS::ContentVisibility::Hidden;
395
0
        }
396
0
        return false;
397
0
    }();
398
399
    // Add node for the ::before pseudo-element.
400
0
    if (is<DOM::Element>(dom_node) && layout_node->can_have_children() && !element_has_content_visibility_hidden) {
401
0
        auto& element = static_cast<DOM::Element&>(dom_node);
402
0
        push_parent(verify_cast<NodeWithStyle>(*layout_node));
403
0
        create_pseudo_element_if_needed(element, CSS::Selector::PseudoElement::Type::Before, AppendOrPrepend::Prepend);
404
0
        pop_parent();
405
0
    }
406
407
0
    if ((dom_node.has_children() || shadow_root) && layout_node->can_have_children() && !element_has_content_visibility_hidden) {
408
0
        push_parent(verify_cast<NodeWithStyle>(*layout_node));
409
0
        if (shadow_root) {
410
0
            for (auto* node = shadow_root->first_child(); node; node = node->next_sibling()) {
411
0
                create_layout_tree(*node, context);
412
0
            }
413
0
        } else {
414
            // This is the same as verify_cast<DOM::ParentNode>(dom_node).for_each_child
415
0
            for (auto* node = verify_cast<DOM::ParentNode>(dom_node).first_child(); node; node = node->next_sibling())
416
0
                create_layout_tree(*node, context);
417
0
        }
418
419
0
        if (dom_node.is_document()) {
420
            // Elements in the top layer do not lay out normally based on their position in the document; instead they
421
            // generate boxes as if they were siblings of the root element.
422
0
            TemporaryChange<bool> layout_mask(context.layout_top_layer, true);
423
0
            for (auto const& top_layer_element : document.top_layer_elements())
424
0
                create_layout_tree(top_layer_element, context);
425
0
        }
426
0
        pop_parent();
427
0
    }
428
429
0
    if (is<ListItemBox>(*layout_node)) {
430
0
        auto& element = static_cast<DOM::Element&>(dom_node);
431
0
        auto marker_style = style_computer.compute_style(element, CSS::Selector::PseudoElement::Type::Marker);
432
0
        auto list_item_marker = document.heap().allocate_without_realm<ListItemMarkerBox>(document, layout_node->computed_values().list_style_type(), layout_node->computed_values().list_style_position(), calculate_list_item_index(dom_node), *marker_style);
433
0
        static_cast<ListItemBox&>(*layout_node).set_marker(list_item_marker);
434
0
        element.set_pseudo_element_node({}, CSS::Selector::PseudoElement::Type::Marker, list_item_marker);
435
0
        layout_node->append_child(*list_item_marker);
436
0
    }
437
438
0
    if (is<HTML::HTMLSlotElement>(dom_node)) {
439
0
        auto& slot_element = static_cast<HTML::HTMLSlotElement&>(dom_node);
440
441
0
        if (slot_element.computed_css_values()->content_visibility() == CSS::ContentVisibility::Hidden)
442
0
            return;
443
444
0
        auto slottables = slot_element.assigned_nodes_internal();
445
0
        push_parent(verify_cast<NodeWithStyle>(*layout_node));
446
447
0
        for (auto const& slottable : slottables)
448
0
            slottable.visit([&](auto& node) { create_layout_tree(node, context); });
Unexecuted instantiation: TreeBuilder.cpp:auto Web::Layout::TreeBuilder::create_layout_tree(Web::DOM::Node&, Web::Layout::TreeBuilder::Context&)::$_2::operator()<JS::NonnullGCPtr<Web::DOM::Element> const>(JS::NonnullGCPtr<Web::DOM::Element> const&) const
Unexecuted instantiation: TreeBuilder.cpp:auto Web::Layout::TreeBuilder::create_layout_tree(Web::DOM::Node&, Web::Layout::TreeBuilder::Context&)::$_2::operator()<JS::NonnullGCPtr<Web::DOM::Text> const>(JS::NonnullGCPtr<Web::DOM::Text> const&) const
Unexecuted instantiation: TreeBuilder.cpp:auto Web::Layout::TreeBuilder::create_layout_tree(Web::DOM::Node&, Web::Layout::TreeBuilder::Context&)::$_2::operator()<JS::NonnullGCPtr<Web::DOM::Element> >(JS::NonnullGCPtr<Web::DOM::Element>&) const
Unexecuted instantiation: TreeBuilder.cpp:auto Web::Layout::TreeBuilder::create_layout_tree(Web::DOM::Node&, Web::Layout::TreeBuilder::Context&)::$_2::operator()<JS::NonnullGCPtr<Web::DOM::Text> >(JS::NonnullGCPtr<Web::DOM::Text>&) const
449
450
0
        pop_parent();
451
0
    }
452
453
0
    if (is<SVG::SVGGraphicsElement>(dom_node)) {
454
0
        auto& graphics_element = static_cast<SVG::SVGGraphicsElement&>(dom_node);
455
        // Create the layout tree for the SVG mask/clip paths as a child of the masked element.
456
        // Note: This will create a new subtree for each use of the mask (so there's  not a 1-to-1 mapping
457
        // from DOM node to mask layout node). Each use of a mask may be laid out differently so this
458
        // duplication is necessary.
459
0
        auto layout_mask_or_clip_path = [&](JS::GCPtr<SVG::SVGElement const> mask_or_clip_path) {
460
0
            TemporaryChange<bool> layout_mask(context.layout_svg_mask_or_clip_path, true);
461
0
            push_parent(verify_cast<NodeWithStyle>(*layout_node));
462
0
            create_layout_tree(const_cast<SVG::SVGElement&>(*mask_or_clip_path), context);
463
0
            pop_parent();
464
0
        };
465
0
        if (auto mask = graphics_element.mask())
466
0
            layout_mask_or_clip_path(mask);
467
0
        if (auto clip_path = graphics_element.clip_path())
468
0
            layout_mask_or_clip_path(clip_path);
469
0
    }
470
471
0
    auto is_button_layout = [&] {
472
0
        if (dom_node.is_html_button_element())
473
0
            return true;
474
0
        if (!dom_node.is_html_input_element())
475
0
            return false;
476
        // https://html.spec.whatwg.org/multipage/rendering.html#the-input-element-as-a-button
477
        // An input element whose type attribute is in the Submit Button, Reset Button, or Button state, when it generates a CSS box, is expected to depict a button and use button layout
478
0
        auto const& input_element = static_cast<HTML::HTMLInputElement const&>(dom_node);
479
0
        if (input_element.is_button())
480
0
            return true;
481
0
        return false;
482
0
    }();
483
484
    // https://html.spec.whatwg.org/multipage/rendering.html#button-layout
485
    // If the computed value of 'inline-size' is 'auto', then the used value is the fit-content inline size.
486
0
    if (is_button_layout && dom_node.layout_node()->computed_values().width().is_auto()) {
487
0
        auto& computed_values = verify_cast<NodeWithStyle>(*dom_node.layout_node()).mutable_computed_values();
488
0
        computed_values.set_width(CSS::Size::make_fit_content());
489
0
    }
490
491
    // https://html.spec.whatwg.org/multipage/rendering.html#button-layout
492
    // If the element is an input element, or if it is a button element and its computed value for
493
    // 'display' is not 'inline-grid', 'grid', 'inline-flex', or 'flex', then the element's box has
494
    // a child anonymous button content box with the following behaviors:
495
0
    if (is_button_layout && !display.is_grid_inside() && !display.is_flex_inside()) {
496
0
        auto& parent = *dom_node.layout_node();
497
498
        // If the box does not overflow in the vertical axis, then it is centered vertically.
499
        // FIXME: Only apply alignment when box overflows
500
0
        auto flex_computed_values = parent.computed_values().clone_inherited_values();
501
0
        auto& mutable_flex_computed_values = static_cast<CSS::MutableComputedValues&>(*flex_computed_values);
502
0
        mutable_flex_computed_values.set_display(CSS::Display { CSS::DisplayOutside::Block, CSS::DisplayInside::Flex });
503
0
        mutable_flex_computed_values.set_justify_content(CSS::JustifyContent::Center);
504
0
        mutable_flex_computed_values.set_flex_direction(CSS::FlexDirection::Column);
505
0
        mutable_flex_computed_values.set_height(CSS::Size::make_percentage(CSS::Percentage(100)));
506
0
        mutable_flex_computed_values.set_min_height(parent.computed_values().min_height());
507
0
        auto flex_wrapper = parent.heap().template allocate_without_realm<BlockContainer>(parent.document(), nullptr, move(flex_computed_values));
508
509
0
        auto content_box_computed_values = parent.computed_values().clone_inherited_values();
510
0
        auto content_box_wrapper = parent.heap().template allocate_without_realm<BlockContainer>(parent.document(), nullptr, move(content_box_computed_values));
511
0
        content_box_wrapper->set_children_are_inline(parent.children_are_inline());
512
513
0
        Vector<JS::Handle<Node>> sequence;
514
0
        for (auto child = parent.first_child(); child; child = child->next_sibling()) {
515
0
            if (child->is_generated_for_before_pseudo_element())
516
0
                continue;
517
0
            sequence.append(*child);
518
0
        }
519
520
0
        for (auto& node : sequence) {
521
0
            parent.remove_child(*node);
522
0
            content_box_wrapper->append_child(*node);
523
0
        }
524
525
0
        flex_wrapper->append_child(*content_box_wrapper);
526
527
0
        parent.append_child(*flex_wrapper);
528
0
        parent.set_children_are_inline(false);
529
0
    }
530
531
    // Add nodes for the ::after pseudo-element.
532
0
    if (is<DOM::Element>(dom_node) && layout_node->can_have_children() && !element_has_content_visibility_hidden) {
533
0
        auto& element = static_cast<DOM::Element&>(dom_node);
534
0
        push_parent(verify_cast<NodeWithStyle>(*layout_node));
535
0
        create_pseudo_element_if_needed(element, CSS::Selector::PseudoElement::Type::After, AppendOrPrepend::Append);
536
0
        pop_parent();
537
0
    }
538
0
}
539
540
JS::GCPtr<Layout::Node> TreeBuilder::build(DOM::Node& dom_node)
541
0
{
542
0
    VERIFY(dom_node.is_document());
543
544
0
    dom_node.document().style_computer().reset_ancestor_filter();
545
546
0
    Context context;
547
0
    m_quote_nesting_level = 0;
548
0
    create_layout_tree(dom_node, context);
549
550
0
    if (auto* root = dom_node.document().layout_node())
551
0
        fixup_tables(*root);
552
553
0
    return move(m_layout_root);
554
0
}
555
556
template<CSS::DisplayInternal internal, typename Callback>
557
void TreeBuilder::for_each_in_tree_with_internal_display(NodeWithStyle& root, Callback callback)
558
0
{
559
0
    root.for_each_in_inclusive_subtree_of_type<Box>([&](auto& box) {
560
0
        auto const display = box.display();
561
0
        if (display.is_internal() && display.internal() == internal)
562
0
            callback(box);
563
0
        return TraversalDecision::Continue;
564
0
    });
Unexecuted instantiation: TreeBuilder.cpp:auto Web::Layout::TreeBuilder::for_each_in_tree_with_internal_display<(Web::CSS::DisplayInternal)6, Web::Layout::TreeBuilder::remove_irrelevant_boxes(Web::Layout::NodeWithStyle&)::$_0>(Web::Layout::NodeWithStyle&, Web::Layout::TreeBuilder::remove_irrelevant_boxes(Web::Layout::NodeWithStyle&)::$_0)::{lambda(auto:1&)#1}::operator()<Web::Layout::Box>(Web::Layout::Box&) const
Unexecuted instantiation: TreeBuilder.cpp:auto Web::Layout::TreeBuilder::for_each_in_tree_with_internal_display<(Web::CSS::DisplayInternal)5, Web::Layout::TreeBuilder::remove_irrelevant_boxes(Web::Layout::NodeWithStyle&)::$_1>(Web::Layout::NodeWithStyle&, Web::Layout::TreeBuilder::remove_irrelevant_boxes(Web::Layout::NodeWithStyle&)::$_1)::{lambda(auto:1&)#1}::operator()<Web::Layout::Box>(Web::Layout::Box&) const
Unexecuted instantiation: TreeBuilder.cpp:auto Web::Layout::TreeBuilder::for_each_in_tree_with_internal_display<(Web::CSS::DisplayInternal)0, Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_1>(Web::Layout::NodeWithStyle&, Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_1)::{lambda(auto:1&)#1}::operator()<Web::Layout::Box>(Web::Layout::Box&) const
Unexecuted instantiation: TreeBuilder.cpp:auto Web::Layout::TreeBuilder::for_each_in_tree_with_internal_display<(Web::CSS::DisplayInternal)1, Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_2>(Web::Layout::NodeWithStyle&, Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_2)::{lambda(auto:1&)#1}::operator()<Web::Layout::Box>(Web::Layout::Box&) const
Unexecuted instantiation: TreeBuilder.cpp:auto Web::Layout::TreeBuilder::for_each_in_tree_with_internal_display<(Web::CSS::DisplayInternal)2, Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_3>(Web::Layout::NodeWithStyle&, Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_3)::{lambda(auto:1&)#1}::operator()<Web::Layout::Box>(Web::Layout::Box&) const
Unexecuted instantiation: TreeBuilder.cpp:auto Web::Layout::TreeBuilder::for_each_in_tree_with_internal_display<(Web::CSS::DisplayInternal)3, Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_4>(Web::Layout::NodeWithStyle&, Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_4)::{lambda(auto:1&)#1}::operator()<Web::Layout::Box>(Web::Layout::Box&) const
565
0
}
Unexecuted instantiation: TreeBuilder.cpp:void Web::Layout::TreeBuilder::for_each_in_tree_with_internal_display<(Web::CSS::DisplayInternal)6, Web::Layout::TreeBuilder::remove_irrelevant_boxes(Web::Layout::NodeWithStyle&)::$_0>(Web::Layout::NodeWithStyle&, Web::Layout::TreeBuilder::remove_irrelevant_boxes(Web::Layout::NodeWithStyle&)::$_0)
Unexecuted instantiation: TreeBuilder.cpp:void Web::Layout::TreeBuilder::for_each_in_tree_with_internal_display<(Web::CSS::DisplayInternal)5, Web::Layout::TreeBuilder::remove_irrelevant_boxes(Web::Layout::NodeWithStyle&)::$_1>(Web::Layout::NodeWithStyle&, Web::Layout::TreeBuilder::remove_irrelevant_boxes(Web::Layout::NodeWithStyle&)::$_1)
Unexecuted instantiation: TreeBuilder.cpp:void Web::Layout::TreeBuilder::for_each_in_tree_with_internal_display<(Web::CSS::DisplayInternal)0, Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_1>(Web::Layout::NodeWithStyle&, Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_1)
Unexecuted instantiation: TreeBuilder.cpp:void Web::Layout::TreeBuilder::for_each_in_tree_with_internal_display<(Web::CSS::DisplayInternal)1, Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_2>(Web::Layout::NodeWithStyle&, Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_2)
Unexecuted instantiation: TreeBuilder.cpp:void Web::Layout::TreeBuilder::for_each_in_tree_with_internal_display<(Web::CSS::DisplayInternal)2, Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_3>(Web::Layout::NodeWithStyle&, Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_3)
Unexecuted instantiation: TreeBuilder.cpp:void Web::Layout::TreeBuilder::for_each_in_tree_with_internal_display<(Web::CSS::DisplayInternal)3, Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_4>(Web::Layout::NodeWithStyle&, Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_4)
566
567
template<CSS::DisplayInside inside, typename Callback>
568
void TreeBuilder::for_each_in_tree_with_inside_display(NodeWithStyle& root, Callback callback)
569
0
{
570
0
    root.for_each_in_inclusive_subtree_of_type<Box>([&](auto& box) {
571
0
        auto const display = box.display();
572
0
        if (display.is_outside_and_inside() && display.inside() == inside)
573
0
            callback(box);
574
0
        return TraversalDecision::Continue;
575
0
    });
576
0
}
577
578
void TreeBuilder::fixup_tables(NodeWithStyle& root)
579
0
{
580
0
    remove_irrelevant_boxes(root);
581
0
    generate_missing_child_wrappers(root);
582
0
    auto table_root_boxes = generate_missing_parents(root);
583
0
    missing_cells_fixup(table_root_boxes);
584
0
}
585
586
void TreeBuilder::remove_irrelevant_boxes(NodeWithStyle& root)
587
0
{
588
    // The following boxes are discarded as if they were display:none:
589
590
0
    Vector<JS::Handle<Node>> to_remove;
591
592
    // Children of a table-column.
593
0
    for_each_in_tree_with_internal_display<CSS::DisplayInternal::TableColumn>(root, [&](Box& table_column) {
594
0
        table_column.for_each_child([&](auto& child) {
595
0
            to_remove.append(child);
596
0
            return IterationDecision::Continue;
597
0
        });
598
0
    });
599
600
    // Children of a table-column-group which are not a table-column.
601
0
    for_each_in_tree_with_internal_display<CSS::DisplayInternal::TableColumnGroup>(root, [&](Box& table_column_group) {
602
0
        table_column_group.for_each_child([&](auto& child) {
603
0
            if (!child.display().is_table_column())
604
0
                to_remove.append(child);
605
0
            return IterationDecision::Continue;
606
0
        });
607
0
    });
608
609
    // FIXME:
610
    // Anonymous inline boxes which contain only white space and are between two immediate siblings each of which is a table-non-root box.
611
    // Anonymous inline boxes which meet all of the following criteria:
612
    // - they contain only white space
613
    // - they are the first and/or last child of a tabular container
614
    // - whose immediate sibling, if any, is a table-non-root box
615
616
0
    for (auto& box : to_remove)
617
0
        box->parent()->remove_child(*box);
618
0
}
619
620
static bool is_table_track(CSS::Display display)
621
0
{
622
0
    return display.is_table_row() || display.is_table_column();
623
0
}
624
625
static bool is_table_track_group(CSS::Display display)
626
0
{
627
    // Unless explicitly mentioned otherwise, mentions of table-row-groups in this spec also encompass the specialized
628
    // table-header-groups and table-footer-groups.
629
0
    return display.is_table_row_group()
630
0
        || display.is_table_header_group()
631
0
        || display.is_table_footer_group()
632
0
        || display.is_table_column_group();
633
0
}
634
635
static bool is_proper_table_child(Node const& node)
636
0
{
637
0
    auto const display = node.display();
638
0
    return is_table_track_group(display) || is_table_track(display) || display.is_table_caption();
639
0
}
640
641
static bool is_not_proper_table_child(Node const& node)
642
0
{
643
0
    if (!node.has_style())
644
0
        return true;
645
0
    return !is_proper_table_child(node);
646
0
}
647
648
static bool is_table_row(Node const& node)
649
0
{
650
0
    return node.display().is_table_row();
651
0
}
652
653
static bool is_not_table_row(Node const& node)
654
0
{
655
0
    if (!node.has_style())
656
0
        return true;
657
0
    return !is_table_row(node);
658
0
}
659
660
static bool is_table_cell(Node const& node)
661
0
{
662
0
    return node.display().is_table_cell();
663
0
}
664
665
static bool is_not_table_cell(Node const& node)
666
0
{
667
0
    if (!node.has_style())
668
0
        return true;
669
0
    return !is_table_cell(node);
670
0
}
671
672
template<typename Matcher, typename Callback>
673
static void for_each_sequence_of_consecutive_children_matching(NodeWithStyle& parent, Matcher matcher, Callback callback)
674
0
{
675
0
    Vector<JS::Handle<Node>> sequence;
676
677
0
    auto sequence_is_all_ignorable_whitespace = [&]() -> bool {
678
0
        for (auto& node : sequence) {
679
0
            if (!is_ignorable_whitespace(*node))
680
0
                return false;
681
0
        }
682
0
        return true;
683
0
    };
Unexecuted instantiation: TreeBuilder.cpp:Web::Layout::for_each_sequence_of_consecutive_children_matching<bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_0::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1, auto:2)#1}>(Web::Layout::NodeWithStyle&, bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_0::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1, auto:2)#1})::{lambda()#1}::operator()() const
Unexecuted instantiation: TreeBuilder.cpp:Web::Layout::for_each_sequence_of_consecutive_children_matching<bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_1::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1}>(Web::Layout::NodeWithStyle&, bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_1::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1})::{lambda()#1}::operator()() const
Unexecuted instantiation: TreeBuilder.cpp:Web::Layout::for_each_sequence_of_consecutive_children_matching<bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_2::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1}>(Web::Layout::NodeWithStyle&, bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_2::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1})::{lambda()#1}::operator()() const
Unexecuted instantiation: TreeBuilder.cpp:Web::Layout::for_each_sequence_of_consecutive_children_matching<bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_3::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1}>(Web::Layout::NodeWithStyle&, bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_3::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1})::{lambda()#1}::operator()() const
Unexecuted instantiation: TreeBuilder.cpp:Web::Layout::for_each_sequence_of_consecutive_children_matching<bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_4::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1}>(Web::Layout::NodeWithStyle&, bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_4::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1})::{lambda()#1}::operator()() const
Unexecuted instantiation: TreeBuilder.cpp:Web::Layout::for_each_sequence_of_consecutive_children_matching<bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_parents(Web::Layout::NodeWithStyle&)::$_0::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1}>(Web::Layout::NodeWithStyle&, bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_parents(Web::Layout::NodeWithStyle&)::$_0::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1})::{lambda()#1}::operator()() const
Unexecuted instantiation: TreeBuilder.cpp:Web::Layout::for_each_sequence_of_consecutive_children_matching<bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_parents(Web::Layout::NodeWithStyle&)::$_0::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#2}>(Web::Layout::NodeWithStyle&, bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_parents(Web::Layout::NodeWithStyle&)::$_0::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#2})::{lambda()#1}::operator()() const
Unexecuted instantiation: TreeBuilder.cpp:Web::Layout::for_each_sequence_of_consecutive_children_matching<bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_parents(Web::Layout::NodeWithStyle&)::$_0::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#3}>(Web::Layout::NodeWithStyle&, bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_parents(Web::Layout::NodeWithStyle&)::$_0::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#3})::{lambda()#1}::operator()() const
684
685
0
    for (auto child = parent.first_child(); child; child = child->next_sibling()) {
686
0
        if (matcher(*child) || (!sequence.is_empty() && is_ignorable_whitespace(*child))) {
687
0
            sequence.append(*child);
688
0
        } else {
689
0
            if (!sequence.is_empty()) {
690
0
                if (!sequence_is_all_ignorable_whitespace())
691
0
                    callback(sequence, child);
692
0
                sequence.clear();
693
0
            }
694
0
        }
695
0
    }
696
0
    if (!sequence.is_empty() && !sequence_is_all_ignorable_whitespace())
697
0
        callback(sequence, nullptr);
698
0
}
Unexecuted instantiation: TreeBuilder.cpp:void Web::Layout::for_each_sequence_of_consecutive_children_matching<bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_0::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1, auto:2)#1}>(Web::Layout::NodeWithStyle&, bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_0::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1, auto:2)#1})
Unexecuted instantiation: TreeBuilder.cpp:void Web::Layout::for_each_sequence_of_consecutive_children_matching<bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_1::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1}>(Web::Layout::NodeWithStyle&, bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_1::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1})
Unexecuted instantiation: TreeBuilder.cpp:void Web::Layout::for_each_sequence_of_consecutive_children_matching<bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_2::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1}>(Web::Layout::NodeWithStyle&, bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_2::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1})
Unexecuted instantiation: TreeBuilder.cpp:void Web::Layout::for_each_sequence_of_consecutive_children_matching<bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_3::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1}>(Web::Layout::NodeWithStyle&, bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_3::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1})
Unexecuted instantiation: TreeBuilder.cpp:void Web::Layout::for_each_sequence_of_consecutive_children_matching<bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_4::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1}>(Web::Layout::NodeWithStyle&, bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_4::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1})
Unexecuted instantiation: TreeBuilder.cpp:void Web::Layout::for_each_sequence_of_consecutive_children_matching<bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_parents(Web::Layout::NodeWithStyle&)::$_0::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1}>(Web::Layout::NodeWithStyle&, bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_parents(Web::Layout::NodeWithStyle&)::$_0::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1})
Unexecuted instantiation: TreeBuilder.cpp:void Web::Layout::for_each_sequence_of_consecutive_children_matching<bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_parents(Web::Layout::NodeWithStyle&)::$_0::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#2}>(Web::Layout::NodeWithStyle&, bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_parents(Web::Layout::NodeWithStyle&)::$_0::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#2})
Unexecuted instantiation: TreeBuilder.cpp:void Web::Layout::for_each_sequence_of_consecutive_children_matching<bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_parents(Web::Layout::NodeWithStyle&)::$_0::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#3}>(Web::Layout::NodeWithStyle&, bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::generate_missing_parents(Web::Layout::NodeWithStyle&)::$_0::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#3})
699
700
template<typename WrapperBoxType>
701
static void wrap_in_anonymous(Vector<JS::Handle<Node>>& sequence, Node* nearest_sibling, CSS::Display display)
702
0
{
703
0
    VERIFY(!sequence.is_empty());
704
0
    auto& parent = *sequence.first()->parent();
705
0
    auto computed_values = parent.computed_values().clone_inherited_values();
706
0
    static_cast<CSS::MutableComputedValues&>(*computed_values).set_display(display);
707
0
    auto wrapper = parent.heap().template allocate_without_realm<WrapperBoxType>(parent.document(), nullptr, move(computed_values));
708
0
    for (auto& child : sequence) {
709
0
        parent.remove_child(*child);
710
0
        wrapper->append_child(*child);
711
0
    }
712
0
    wrapper->set_children_are_inline(parent.children_are_inline());
713
0
    if (nearest_sibling)
714
0
        parent.insert_before(*wrapper, *nearest_sibling);
715
0
    else
716
0
        parent.append_child(*wrapper);
717
0
}
Unexecuted instantiation: TreeBuilder.cpp:void Web::Layout::wrap_in_anonymous<Web::Layout::Box>(AK::Vector<JS::Handle<Web::Layout::Node>, 0ul>&, Web::Layout::Node*, Web::CSS::Display)
Unexecuted instantiation: TreeBuilder.cpp:void Web::Layout::wrap_in_anonymous<Web::Layout::BlockContainer>(AK::Vector<JS::Handle<Web::Layout::Node>, 0ul>&, Web::Layout::Node*, Web::CSS::Display)
718
719
void TreeBuilder::generate_missing_child_wrappers(NodeWithStyle& root)
720
0
{
721
    // An anonymous table-row box must be generated around each sequence of consecutive children of a table-root box which are not proper table child boxes.
722
0
    for_each_in_tree_with_inside_display<CSS::DisplayInside::Table>(root, [&](auto& parent) {
723
0
        for_each_sequence_of_consecutive_children_matching(parent, is_not_proper_table_child, [&](auto sequence, auto nearest_sibling) {
724
0
            wrap_in_anonymous<Box>(sequence, nearest_sibling, CSS::Display { CSS::DisplayInternal::TableRow });
725
0
        });
Unexecuted instantiation: TreeBuilder.cpp:auto Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_0::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1, auto:2)#1}::operator()<AK::Vector<JS::Handle<Web::Layout::Node>, 0ul>, JS::Handle*>(AK::Vector<JS::Handle<Web::Layout::Node>, 0ul>, JS::Handle*) const
Unexecuted instantiation: TreeBuilder.cpp:auto Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_0::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1, auto:2)#1}::operator()<AK::Vector<JS::Handle<Web::Layout::Node>, 0ul>, decltype(nullptr)>(AK::Vector<JS::Handle<Web::Layout::Node>, 0ul>, decltype(nullptr)) const
726
0
    });
727
728
    // An anonymous table-row box must be generated around each sequence of consecutive children of a table-row-group box which are not table-row boxes.
729
0
    for_each_in_tree_with_internal_display<CSS::DisplayInternal::TableRowGroup>(root, [&](auto& parent) {
730
0
        for_each_sequence_of_consecutive_children_matching(parent, is_not_table_row, [&](auto& sequence, auto nearest_sibling) {
731
0
            wrap_in_anonymous<Box>(sequence, nearest_sibling, CSS::Display { CSS::DisplayInternal::TableRow });
732
0
        });
Unexecuted instantiation: TreeBuilder.cpp:auto Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_1::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1}::operator()<AK::Vector<JS::Handle<Web::Layout::Node>, 0ul>, JS::Handle*>(Web::Layout::Box&, JS::Handle*) const
Unexecuted instantiation: TreeBuilder.cpp:auto Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_1::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1}::operator()<AK::Vector<JS::Handle<Web::Layout::Node>, 0ul>, decltype(nullptr)>(Web::Layout::Box&, decltype(nullptr)) const
733
0
    });
734
    // Unless explicitly mentioned otherwise, mentions of table-row-groups in this spec also encompass the specialized
735
    // table-header-groups and table-footer-groups.
736
0
    for_each_in_tree_with_internal_display<CSS::DisplayInternal::TableHeaderGroup>(root, [&](auto& parent) {
737
0
        for_each_sequence_of_consecutive_children_matching(parent, is_not_table_row, [&](auto& sequence, auto nearest_sibling) {
738
0
            wrap_in_anonymous<Box>(sequence, nearest_sibling, CSS::Display { CSS::DisplayInternal::TableRow });
739
0
        });
Unexecuted instantiation: TreeBuilder.cpp:auto Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_2::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1}::operator()<AK::Vector<JS::Handle<Web::Layout::Node>, 0ul>, JS::Handle*>(Web::Layout::Box&, JS::Handle*) const
Unexecuted instantiation: TreeBuilder.cpp:auto Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_2::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1}::operator()<AK::Vector<JS::Handle<Web::Layout::Node>, 0ul>, decltype(nullptr)>(Web::Layout::Box&, decltype(nullptr)) const
740
0
    });
741
0
    for_each_in_tree_with_internal_display<CSS::DisplayInternal::TableFooterGroup>(root, [&](auto& parent) {
742
0
        for_each_sequence_of_consecutive_children_matching(parent, is_not_table_row, [&](auto& sequence, auto nearest_sibling) {
743
0
            wrap_in_anonymous<Box>(sequence, nearest_sibling, CSS::Display { CSS::DisplayInternal::TableRow });
744
0
        });
Unexecuted instantiation: TreeBuilder.cpp:auto Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_3::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1}::operator()<AK::Vector<JS::Handle<Web::Layout::Node>, 0ul>, JS::Handle*>(Web::Layout::Box&, JS::Handle*) const
Unexecuted instantiation: TreeBuilder.cpp:auto Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_3::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1}::operator()<AK::Vector<JS::Handle<Web::Layout::Node>, 0ul>, decltype(nullptr)>(Web::Layout::Box&, decltype(nullptr)) const
745
0
    });
746
747
    // An anonymous table-cell box must be generated around each sequence of consecutive children of a table-row box which are not table-cell boxes. !Testcase
748
0
    for_each_in_tree_with_internal_display<CSS::DisplayInternal::TableRow>(root, [&](auto& parent) {
749
0
        for_each_sequence_of_consecutive_children_matching(parent, is_not_table_cell, [&](auto& sequence, auto nearest_sibling) {
750
0
            wrap_in_anonymous<BlockContainer>(sequence, nearest_sibling, CSS::Display { CSS::DisplayInternal::TableCell });
751
0
        });
Unexecuted instantiation: TreeBuilder.cpp:auto Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_4::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1}::operator()<AK::Vector<JS::Handle<Web::Layout::Node>, 0ul>, JS::Handle*>(Web::Layout::Box&, JS::Handle*) const
Unexecuted instantiation: TreeBuilder.cpp:auto Web::Layout::TreeBuilder::generate_missing_child_wrappers(Web::Layout::NodeWithStyle&)::$_4::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1}::operator()<AK::Vector<JS::Handle<Web::Layout::Node>, 0ul>, decltype(nullptr)>(Web::Layout::Box&, decltype(nullptr)) const
752
0
    });
753
0
}
754
755
Vector<JS::Handle<Box>> TreeBuilder::generate_missing_parents(NodeWithStyle& root)
756
0
{
757
0
    Vector<JS::Handle<Box>> table_roots_to_wrap;
758
0
    root.for_each_in_inclusive_subtree_of_type<Box>([&](auto& parent) {
759
        // An anonymous table-row box must be generated around each sequence of consecutive table-cell boxes whose parent is not a table-row.
760
0
        if (is_not_table_row(parent)) {
761
0
            for_each_sequence_of_consecutive_children_matching(parent, is_table_cell, [&](auto& sequence, auto nearest_sibling) {
762
0
                wrap_in_anonymous<Box>(sequence, nearest_sibling, CSS::Display { CSS::DisplayInternal::TableRow });
763
0
            });
Unexecuted instantiation: TreeBuilder.cpp:auto Web::Layout::TreeBuilder::generate_missing_parents(Web::Layout::NodeWithStyle&)::$_0::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1}::operator()<AK::Vector<JS::Handle<Web::Layout::Node>, 0ul>, JS::Handle*>(Web::Layout::Box&, JS::Handle*) const
Unexecuted instantiation: TreeBuilder.cpp:auto Web::Layout::TreeBuilder::generate_missing_parents(Web::Layout::NodeWithStyle&)::$_0::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#1}::operator()<AK::Vector<JS::Handle<Web::Layout::Node>, 0ul>, decltype(nullptr)>(Web::Layout::Box&, decltype(nullptr)) const
764
0
        }
765
766
        // A table-row is misparented if its parent is neither a table-row-group nor a table-root box.
767
0
        if (!parent.display().is_table_inside() && !is_proper_table_child(parent)) {
768
0
            for_each_sequence_of_consecutive_children_matching(parent, is_table_row, [&](auto& sequence, auto nearest_sibling) {
769
0
                wrap_in_anonymous<Box>(sequence, nearest_sibling, CSS::Display::from_short(parent.display().is_inline_outside() ? CSS::Display::Short::InlineTable : CSS::Display::Short::Table));
770
0
            });
Unexecuted instantiation: TreeBuilder.cpp:auto Web::Layout::TreeBuilder::generate_missing_parents(Web::Layout::NodeWithStyle&)::$_0::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#2}::operator()<AK::Vector<JS::Handle<Web::Layout::Node>, 0ul>, JS::Handle*>(Web::Layout::Box&, JS::Handle*) const
Unexecuted instantiation: TreeBuilder.cpp:auto Web::Layout::TreeBuilder::generate_missing_parents(Web::Layout::NodeWithStyle&)::$_0::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#2}::operator()<AK::Vector<JS::Handle<Web::Layout::Node>, 0ul>, decltype(nullptr)>(Web::Layout::Box&, decltype(nullptr)) const
771
0
        }
772
773
        // A table-row-group, table-column-group, or table-caption box is misparented if its parent is not a table-root box.
774
0
        if (!parent.display().is_table_inside() && !is_proper_table_child(parent)) {
775
0
            for_each_sequence_of_consecutive_children_matching(parent, is_proper_table_child, [&](auto& sequence, auto nearest_sibling) {
776
0
                wrap_in_anonymous<Box>(sequence, nearest_sibling, CSS::Display::from_short(parent.display().is_inline_outside() ? CSS::Display::Short::InlineTable : CSS::Display::Short::Table));
777
0
            });
Unexecuted instantiation: TreeBuilder.cpp:auto Web::Layout::TreeBuilder::generate_missing_parents(Web::Layout::NodeWithStyle&)::$_0::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#3}::operator()<AK::Vector<JS::Handle<Web::Layout::Node>, 0ul>, JS::Handle*>(Web::Layout::Box&, JS::Handle*) const
Unexecuted instantiation: TreeBuilder.cpp:auto Web::Layout::TreeBuilder::generate_missing_parents(Web::Layout::NodeWithStyle&)::$_0::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&, auto:2)#3}::operator()<AK::Vector<JS::Handle<Web::Layout::Node>, 0ul>, decltype(nullptr)>(Web::Layout::Box&, decltype(nullptr)) const
778
0
        }
779
780
        // An anonymous table-wrapper box must be generated around each table-root.
781
0
        if (parent.display().is_table_inside()) {
782
0
            table_roots_to_wrap.append(parent);
783
0
        }
784
785
0
        return TraversalDecision::Continue;
786
0
    });
787
788
0
    for (auto& table_box : table_roots_to_wrap) {
789
0
        auto* nearest_sibling = table_box->next_sibling();
790
0
        auto& parent = *table_box->parent();
791
792
0
        auto wrapper_computed_values = table_box->computed_values().clone_inherited_values();
793
0
        table_box->transfer_table_box_computed_values_to_wrapper_computed_values(*wrapper_computed_values);
794
795
0
        auto wrapper = parent.heap().allocate_without_realm<TableWrapper>(parent.document(), nullptr, move(wrapper_computed_values));
796
797
0
        parent.remove_child(*table_box);
798
0
        wrapper->append_child(*table_box);
799
800
0
        if (nearest_sibling)
801
0
            parent.insert_before(*wrapper, *nearest_sibling);
802
0
        else
803
0
            parent.append_child(*wrapper);
804
0
    }
805
806
0
    return table_roots_to_wrap;
807
0
}
808
809
template<typename Matcher, typename Callback>
810
static void for_each_child_box_matching(Box& parent, Matcher matcher, Callback callback)
811
0
{
812
0
    parent.for_each_child_of_type<Box>([&](Box& child_box) {
813
0
        if (matcher(child_box))
814
0
            callback(child_box);
815
0
        return IterationDecision::Continue;
816
0
    });
Unexecuted instantiation: TreeBuilder.cpp:Web::Layout::for_each_child_box_matching<bool (*)(Web::Layout::Box const&), Web::Layout::TreeBuilder::missing_cells_fixup(AK::Vector<JS::Handle<Web::Layout::Box>, 0ul> const&)::$_0>(Web::Layout::Box&, bool (*)(Web::Layout::Box const&), Web::Layout::TreeBuilder::missing_cells_fixup(AK::Vector<JS::Handle<Web::Layout::Box>, 0ul> const&)::$_0)::{lambda(Web::Layout::Box&)#1}::operator()(Web::Layout::Box&) const
Unexecuted instantiation: TreeBuilder.cpp:Web::Layout::for_each_child_box_matching<bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::missing_cells_fixup(AK::Vector<JS::Handle<Web::Layout::Box>, 0ul> const&)::$_0::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&)#1}>(Web::Layout::Box&, bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::missing_cells_fixup(AK::Vector<JS::Handle<Web::Layout::Box>, 0ul> const&)::$_0::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&)#1})::{lambda(Web::Layout::TreeBuilder::missing_cells_fixup(AK::Vector<JS::Handle<Web::Layout::Box>, 0ul> const&)::$_0::operator()<Web::Layout::Box>(auto:1&) const::{lambda(auto:1&)#1})#1}::operator()(Web::Layout::TreeBuilder::missing_cells_fixup(AK::Vector<JS::Handle<Web::Layout::Box>, 0ul> const&)::$_0::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&)#1}) const
Unexecuted instantiation: TreeBuilder.cpp:Web::Layout::for_each_child_box_matching<bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::missing_cells_fixup(AK::Vector<JS::Handle<Web::Layout::Box>, 0ul> const&)::$_1>(Web::Layout::Box&, bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::missing_cells_fixup(AK::Vector<JS::Handle<Web::Layout::Box>, 0ul> const&)::$_1)::{lambda(Web::Layout::Box&)#1}::operator()(Web::Layout::Box&) const
817
0
}
Unexecuted instantiation: TreeBuilder.cpp:void Web::Layout::for_each_child_box_matching<bool (*)(Web::Layout::Box const&), Web::Layout::TreeBuilder::missing_cells_fixup(AK::Vector<JS::Handle<Web::Layout::Box>, 0ul> const&)::$_0>(Web::Layout::Box&, bool (*)(Web::Layout::Box const&), Web::Layout::TreeBuilder::missing_cells_fixup(AK::Vector<JS::Handle<Web::Layout::Box>, 0ul> const&)::$_0)
Unexecuted instantiation: TreeBuilder.cpp:void Web::Layout::for_each_child_box_matching<bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::missing_cells_fixup(AK::Vector<JS::Handle<Web::Layout::Box>, 0ul> const&)::$_0::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&)#1}>(Web::Layout::Box&, bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::missing_cells_fixup(AK::Vector<JS::Handle<Web::Layout::Box>, 0ul> const&)::$_0::operator()<Web::Layout::Box>(Web::Layout::Box&) const::{lambda(auto:1&)#1})
Unexecuted instantiation: TreeBuilder.cpp:void Web::Layout::for_each_child_box_matching<bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::missing_cells_fixup(AK::Vector<JS::Handle<Web::Layout::Box>, 0ul> const&)::$_1>(Web::Layout::Box&, bool (*)(Web::Layout::Node const&), Web::Layout::TreeBuilder::missing_cells_fixup(AK::Vector<JS::Handle<Web::Layout::Box>, 0ul> const&)::$_1)
818
819
static void fixup_row(Box& row_box, TableGrid const& table_grid, size_t row_index)
820
0
{
821
0
    for (size_t column_index = 0; column_index < table_grid.column_count(); ++column_index) {
822
0
        if (table_grid.occupancy_grid().contains({ column_index, row_index }))
823
0
            continue;
824
825
0
        auto computed_values = row_box.computed_values().clone_inherited_values();
826
0
        auto& mutable_computed_values = static_cast<CSS::MutableComputedValues&>(*computed_values);
827
0
        mutable_computed_values.set_display(Web::CSS::Display { CSS::DisplayInternal::TableCell });
828
        // Ensure that the cell (with zero content height) will have the same height as the row by setting vertical-align to middle.
829
0
        mutable_computed_values.set_vertical_align(CSS::VerticalAlign::Middle);
830
0
        auto cell_box = row_box.heap().template allocate_without_realm<BlockContainer>(row_box.document(), nullptr, move(computed_values));
831
0
        row_box.append_child(cell_box);
832
0
    }
833
0
}
834
835
void TreeBuilder::missing_cells_fixup(Vector<JS::Handle<Box>> const& table_root_boxes)
836
0
{
837
    // Implements https://www.w3.org/TR/css-tables-3/#missing-cells-fixup.
838
0
    for (auto& table_box : table_root_boxes) {
839
0
        auto table_grid = TableGrid::calculate_row_column_grid(*table_box);
840
0
        size_t row_index = 0;
841
0
        for_each_child_box_matching(*table_box, TableGrid::is_table_row_group, [&](auto& row_group_box) {
842
0
            for_each_child_box_matching(row_group_box, is_table_row, [&](auto& row_box) {
843
0
                fixup_row(row_box, table_grid, row_index);
844
0
                ++row_index;
845
0
                return IterationDecision::Continue;
846
0
            });
847
0
        });
848
849
0
        for_each_child_box_matching(*table_box, is_table_row, [&](auto& row_box) {
850
0
            fixup_row(row_box, table_grid, row_index);
851
0
            ++row_index;
852
0
            return IterationDecision::Continue;
853
0
        });
854
0
    }
855
0
}
856
}