/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 | | } |