Serenity Operating System
at master 617 lines 28 kB view raw
1/* 2 * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2022, 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/DOM/Document.h> 13#include <LibWeb/DOM/Element.h> 14#include <LibWeb/DOM/ParentNode.h> 15#include <LibWeb/DOM/ShadowRoot.h> 16#include <LibWeb/Dump.h> 17#include <LibWeb/HTML/HTMLInputElement.h> 18#include <LibWeb/HTML/HTMLProgressElement.h> 19#include <LibWeb/Layout/ListItemBox.h> 20#include <LibWeb/Layout/ListItemMarkerBox.h> 21#include <LibWeb/Layout/Node.h> 22#include <LibWeb/Layout/Progress.h> 23#include <LibWeb/Layout/TableBox.h> 24#include <LibWeb/Layout/TableCellBox.h> 25#include <LibWeb/Layout/TableRowBox.h> 26#include <LibWeb/Layout/TableWrapper.h> 27#include <LibWeb/Layout/TextNode.h> 28#include <LibWeb/Layout/TreeBuilder.h> 29#include <LibWeb/Layout/Viewport.h> 30#include <LibWeb/SVG/SVGForeignObjectElement.h> 31 32namespace Web::Layout { 33 34TreeBuilder::TreeBuilder() = default; 35 36static bool has_inline_or_in_flow_block_children(Layout::Node const& layout_node) 37{ 38 for (auto child = layout_node.first_child(); child; child = child->next_sibling()) { 39 if (child->is_inline()) 40 return true; 41 if (!child->is_floating() && !child->is_absolutely_positioned()) 42 return true; 43 } 44 return false; 45} 46 47static bool has_in_flow_block_children(Layout::Node const& layout_node) 48{ 49 if (layout_node.children_are_inline()) 50 return false; 51 for (auto child = layout_node.first_child(); child; child = child->next_sibling()) { 52 if (child->is_inline()) 53 continue; 54 if (!child->is_floating() && !child->is_absolutely_positioned()) 55 return true; 56 } 57 return false; 58} 59 60// The insertion_parent_for_*() functions maintain the invariant that the in-flow children of 61// block-level boxes must be either all block-level or all inline-level. 62 63static Layout::Node& insertion_parent_for_inline_node(Layout::NodeWithStyle& layout_parent) 64{ 65 if (layout_parent.display().is_inline_outside() && layout_parent.display().is_flow_inside()) 66 return layout_parent; 67 68 if (layout_parent.display().is_flex_inside()) { 69 layout_parent.append_child(layout_parent.create_anonymous_wrapper()); 70 return *layout_parent.last_child(); 71 } 72 73 if (!has_in_flow_block_children(layout_parent) || layout_parent.children_are_inline()) 74 return layout_parent; 75 76 // Parent has block-level children, insert into an anonymous wrapper block (and create it first if needed) 77 if (!layout_parent.last_child()->is_anonymous() || !layout_parent.last_child()->children_are_inline()) { 78 layout_parent.append_child(layout_parent.create_anonymous_wrapper()); 79 } 80 return *layout_parent.last_child(); 81} 82 83static Layout::Node& insertion_parent_for_block_node(Layout::NodeWithStyle& layout_parent, Layout::Node& layout_node) 84{ 85 if (!has_inline_or_in_flow_block_children(layout_parent)) { 86 // Parent block has no children, insert this block into parent. 87 return layout_parent; 88 } 89 90 if (!layout_parent.children_are_inline()) { 91 // Parent block has block-level children, insert this block into parent. 92 return layout_parent; 93 } 94 95 if (layout_node.is_absolutely_positioned() || layout_node.is_floating()) { 96 // Block is out-of-flow, it can have inline siblings if necessary. 97 return layout_parent; 98 } 99 100 // Parent block has inline-level children (our siblings). 101 // First move these siblings into an anonymous wrapper block. 102 Vector<JS::Handle<Layout::Node>> children; 103 while (JS::GCPtr<Layout::Node> child = layout_parent.first_child()) { 104 layout_parent.remove_child(*child); 105 children.append(*child); 106 } 107 layout_parent.append_child(layout_parent.create_anonymous_wrapper()); 108 layout_parent.set_children_are_inline(false); 109 for (auto& child : children) { 110 layout_parent.last_child()->append_child(*child); 111 } 112 layout_parent.last_child()->set_children_are_inline(true); 113 // Then it's safe to insert this block into parent. 114 return layout_parent; 115} 116 117void TreeBuilder::insert_node_into_inline_or_block_ancestor(Layout::Node& node, CSS::Display display, AppendOrPrepend mode) 118{ 119 if (display.is_inline_outside()) { 120 // Inlines can be inserted into the nearest ancestor. 121 auto& insertion_point = insertion_parent_for_inline_node(m_ancestor_stack.last()); 122 if (mode == AppendOrPrepend::Prepend) 123 insertion_point.prepend_child(node); 124 else 125 insertion_point.append_child(node); 126 insertion_point.set_children_are_inline(true); 127 } else { 128 // Non-inlines can't be inserted into an inline parent, so find the nearest non-inline ancestor. 129 auto& nearest_non_inline_ancestor = [&]() -> Layout::NodeWithStyle& { 130 for (auto& ancestor : m_ancestor_stack.in_reverse()) { 131 if (!ancestor.display().is_inline_outside()) 132 return ancestor; 133 if (!ancestor.display().is_flow_inside()) 134 return ancestor; 135 if (ancestor.dom_node() && is<SVG::SVGForeignObjectElement>(*ancestor.dom_node())) 136 return ancestor; 137 } 138 VERIFY_NOT_REACHED(); 139 }(); 140 auto& insertion_point = insertion_parent_for_block_node(nearest_non_inline_ancestor, node); 141 if (mode == AppendOrPrepend::Prepend) 142 insertion_point.prepend_child(node); 143 else 144 insertion_point.append_child(node); 145 146 // After inserting an in-flow block-level box into a parent, mark the parent as having non-inline children. 147 if (!node.is_floating() && !node.is_absolutely_positioned()) 148 insertion_point.set_children_are_inline(false); 149 } 150} 151 152ErrorOr<void> TreeBuilder::create_pseudo_element_if_needed(DOM::Element& element, CSS::Selector::PseudoElement pseudo_element, AppendOrPrepend mode) 153{ 154 auto& document = element.document(); 155 auto& style_computer = document.style_computer(); 156 157 auto pseudo_element_style = TRY(style_computer.compute_style(element, pseudo_element)); 158 auto pseudo_element_content = pseudo_element_style->content(); 159 auto pseudo_element_display = pseudo_element_style->display(); 160 // ::before and ::after only exist if they have content. `content: normal` computes to `none` for them. 161 // We also don't create them if they are `display: none`. 162 if (pseudo_element_display.is_none() 163 || pseudo_element_content.type == CSS::ContentData::Type::Normal 164 || pseudo_element_content.type == CSS::ContentData::Type::None) 165 return {}; 166 167 auto pseudo_element_node = DOM::Element::create_layout_node_for_display_type(document, pseudo_element_display, pseudo_element_style, nullptr); 168 if (!pseudo_element_node) 169 return {}; 170 171 pseudo_element_node->set_generated(true); 172 // FIXME: Handle images, and multiple values 173 if (pseudo_element_content.type == CSS::ContentData::Type::String) { 174 auto text = document.heap().allocate<DOM::Text>(document.realm(), document, pseudo_element_content.data.to_deprecated_string()).release_allocated_value_but_fixme_should_propagate_errors(); 175 auto text_node = document.heap().allocate_without_realm<Layout::TextNode>(document, *text); 176 text_node->set_generated(true); 177 push_parent(verify_cast<NodeWithStyle>(*pseudo_element_node)); 178 insert_node_into_inline_or_block_ancestor(*text_node, text_node->display(), AppendOrPrepend::Append); 179 pop_parent(); 180 } else { 181 TODO(); 182 } 183 184 element.set_pseudo_element_node({}, pseudo_element, pseudo_element_node); 185 insert_node_into_inline_or_block_ancestor(*pseudo_element_node, pseudo_element_display, mode); 186 187 return {}; 188} 189 190ErrorOr<void> TreeBuilder::create_layout_tree(DOM::Node& dom_node, TreeBuilder::Context& context) 191{ 192 // If the parent doesn't have a layout node, we don't need one either. 193 if (dom_node.parent_or_shadow_host() && !dom_node.parent_or_shadow_host()->layout_node()) 194 return {}; 195 196 Optional<TemporaryChange<bool>> has_svg_root_change; 197 198 if (dom_node.is_svg_container()) { 199 has_svg_root_change.emplace(context.has_svg_root, true); 200 } else if (dom_node.requires_svg_container() && !context.has_svg_root) { 201 return {}; 202 } 203 204 auto& document = dom_node.document(); 205 auto& style_computer = document.style_computer(); 206 JS::GCPtr<Layout::Node> layout_node; 207 RefPtr<CSS::StyleProperties> style; 208 CSS::Display display; 209 210 if (is<DOM::Element>(dom_node)) { 211 auto& element = static_cast<DOM::Element&>(dom_node); 212 element.clear_pseudo_element_nodes({}); 213 VERIFY(!element.needs_style_update()); 214 style = element.computed_css_values(); 215 display = style->display(); 216 if (display.is_none()) 217 return {}; 218 layout_node = element.create_layout_node(*style); 219 } else if (is<DOM::Document>(dom_node)) { 220 style = style_computer.create_document_style(); 221 display = style->display(); 222 layout_node = document.heap().allocate_without_realm<Layout::Viewport>(static_cast<DOM::Document&>(dom_node), *style); 223 } else if (is<DOM::Text>(dom_node)) { 224 layout_node = document.heap().allocate_without_realm<Layout::TextNode>(document, static_cast<DOM::Text&>(dom_node)); 225 display = CSS::Display(CSS::Display::Outside::Inline, CSS::Display::Inside::Flow); 226 } else if (is<DOM::ShadowRoot>(dom_node)) { 227 layout_node = document.heap().allocate_without_realm<Layout::BlockContainer>(document, &static_cast<DOM::ShadowRoot&>(dom_node), CSS::ComputedValues {}); 228 display = CSS::Display(CSS::Display::Outside::Block, CSS::Display::Inside::FlowRoot); 229 } 230 231 if (!layout_node) 232 return {}; 233 234 if (!dom_node.parent_or_shadow_host()) { 235 m_layout_root = layout_node; 236 } else if (layout_node->is_svg_box()) { 237 m_ancestor_stack.last().append_child(*layout_node); 238 } else { 239 insert_node_into_inline_or_block_ancestor(*layout_node, display, AppendOrPrepend::Append); 240 } 241 242 auto* shadow_root = is<DOM::Element>(dom_node) ? verify_cast<DOM::Element>(dom_node).shadow_root_internal() : nullptr; 243 244 if ((dom_node.has_children() || shadow_root) && layout_node->can_have_children()) { 245 push_parent(verify_cast<NodeWithStyle>(*layout_node)); 246 if (shadow_root) 247 TRY(create_layout_tree(*shadow_root, context)); 248 249 // This is the same as verify_cast<DOM::ParentNode>(dom_node).for_each_child 250 for (auto* node = verify_cast<DOM::ParentNode>(dom_node).first_child(); node; node = node->next_sibling()) 251 TRY(create_layout_tree(*node, context)); 252 pop_parent(); 253 } 254 255 // Add nodes for the ::before and ::after pseudo-elements. 256 if (is<DOM::Element>(dom_node)) { 257 auto& element = static_cast<DOM::Element&>(dom_node); 258 push_parent(verify_cast<NodeWithStyle>(*layout_node)); 259 TRY(create_pseudo_element_if_needed(element, CSS::Selector::PseudoElement::Before, AppendOrPrepend::Prepend)); 260 TRY(create_pseudo_element_if_needed(element, CSS::Selector::PseudoElement::After, AppendOrPrepend::Append)); 261 pop_parent(); 262 } 263 264 if (is<ListItemBox>(*layout_node)) { 265 auto& element = static_cast<DOM::Element&>(dom_node); 266 int child_index = layout_node->parent()->index_of_child<ListItemBox>(*layout_node).value(); 267 auto marker_style = TRY(style_computer.compute_style(element, CSS::Selector::PseudoElement::Marker)); 268 auto list_item_marker = document.heap().allocate_without_realm<ListItemMarkerBox>(document, layout_node->computed_values().list_style_type(), child_index + 1, *marker_style); 269 static_cast<ListItemBox&>(*layout_node).set_marker(list_item_marker); 270 element.set_pseudo_element_node({}, CSS::Selector::PseudoElement::Marker, list_item_marker); 271 layout_node->append_child(*list_item_marker); 272 } 273 274 if (is<HTML::HTMLProgressElement>(dom_node)) { 275 auto& progress = static_cast<HTML::HTMLProgressElement&>(dom_node); 276 if (!progress.using_system_appearance()) { 277 auto bar_style = TRY(style_computer.compute_style(progress, CSS::Selector::PseudoElement::ProgressBar)); 278 bar_style->set_property(CSS::PropertyID::Display, CSS::IdentifierStyleValue::create(CSS::ValueID::InlineBlock)); 279 auto value_style = TRY(style_computer.compute_style(progress, CSS::Selector::PseudoElement::ProgressValue)); 280 value_style->set_property(CSS::PropertyID::Display, CSS::IdentifierStyleValue::create(CSS::ValueID::Block)); 281 auto position = progress.position(); 282 value_style->set_property(CSS::PropertyID::Width, CSS::PercentageStyleValue::create(CSS::Percentage(position >= 0 ? round_to<int>(100 * position) : 0))); 283 auto bar_display = bar_style->display(); 284 auto value_display = value_style->display(); 285 auto progress_bar = DOM::Element::create_layout_node_for_display_type(document, bar_display, bar_style, nullptr); 286 auto progress_value = DOM::Element::create_layout_node_for_display_type(document, value_display, value_style, nullptr); 287 push_parent(verify_cast<NodeWithStyle>(*layout_node)); 288 push_parent(verify_cast<NodeWithStyle>(*progress_bar)); 289 insert_node_into_inline_or_block_ancestor(*progress_value, value_display, AppendOrPrepend::Append); 290 pop_parent(); 291 insert_node_into_inline_or_block_ancestor(*progress_bar, bar_display, AppendOrPrepend::Append); 292 pop_parent(); 293 progress.set_pseudo_element_node({}, CSS::Selector::PseudoElement::ProgressBar, progress_bar); 294 progress.set_pseudo_element_node({}, CSS::Selector::PseudoElement::ProgressValue, progress_value); 295 } 296 } 297 298 if (is<HTML::HTMLInputElement>(dom_node)) { 299 auto& input_element = static_cast<HTML::HTMLInputElement&>(dom_node); 300 301 if (auto placeholder_value = input_element.placeholder_value(); placeholder_value.has_value()) { 302 auto placeholder_style = TRY(style_computer.compute_style(input_element, CSS::Selector::PseudoElement::Placeholder)); 303 auto placeholder = DOM::Element::create_layout_node_for_display_type(document, placeholder_style->display(), placeholder_style, nullptr); 304 305 auto text = document.heap().allocate<DOM::Text>(document.realm(), document, *placeholder_value).release_allocated_value_but_fixme_should_propagate_errors(); 306 auto text_node = document.heap().allocate_without_realm<Layout::TextNode>(document, *text); 307 text_node->set_generated(true); 308 309 push_parent(verify_cast<NodeWithStyle>(*layout_node)); 310 push_parent(verify_cast<NodeWithStyle>(*placeholder)); 311 insert_node_into_inline_or_block_ancestor(*text_node, text_node->display(), AppendOrPrepend::Append); 312 pop_parent(); 313 insert_node_into_inline_or_block_ancestor(*placeholder, placeholder->display(), AppendOrPrepend::Append); 314 pop_parent(); 315 316 input_element.set_pseudo_element_node({}, CSS::Selector::PseudoElement::Placeholder, placeholder); 317 } 318 } 319 320 return {}; 321} 322 323JS::GCPtr<Layout::Node> TreeBuilder::build(DOM::Node& dom_node) 324{ 325 VERIFY(dom_node.is_document()); 326 327 Context context; 328 MUST(create_layout_tree(dom_node, context)); // FIXME propagate errors 329 330 if (auto* root = dom_node.document().layout_node()) 331 fixup_tables(*root); 332 333 return move(m_layout_root); 334} 335 336template<CSS::Display::Internal internal, typename Callback> 337void TreeBuilder::for_each_in_tree_with_internal_display(NodeWithStyle& root, Callback callback) 338{ 339 root.for_each_in_inclusive_subtree_of_type<Box>([&](auto& box) { 340 auto const display = box.display(); 341 if (display.is_internal() && display.internal() == internal) 342 callback(box); 343 return IterationDecision::Continue; 344 }); 345} 346 347template<CSS::Display::Inside inside, typename Callback> 348void TreeBuilder::for_each_in_tree_with_inside_display(NodeWithStyle& root, Callback callback) 349{ 350 root.for_each_in_inclusive_subtree_of_type<Box>([&](auto& box) { 351 auto const display = box.display(); 352 if (display.is_outside_and_inside() && display.inside() == inside) 353 callback(box); 354 return IterationDecision::Continue; 355 }); 356} 357 358void TreeBuilder::fixup_tables(NodeWithStyle& root) 359{ 360 remove_irrelevant_boxes(root); 361 generate_missing_child_wrappers(root); 362 generate_missing_parents(root); 363} 364 365void TreeBuilder::remove_irrelevant_boxes(NodeWithStyle& root) 366{ 367 // The following boxes are discarded as if they were display:none: 368 369 Vector<JS::Handle<Node>> to_remove; 370 371 // Children of a table-column. 372 for_each_in_tree_with_internal_display<CSS::Display::Internal::TableColumn>(root, [&](Box& table_column) { 373 table_column.for_each_child([&](auto& child) { 374 to_remove.append(child); 375 }); 376 }); 377 378 // Children of a table-column-group which are not a table-column. 379 for_each_in_tree_with_internal_display<CSS::Display::Internal::TableColumnGroup>(root, [&](Box& table_column_group) { 380 table_column_group.for_each_child([&](auto& child) { 381 if (child.display().is_table_column()) 382 to_remove.append(child); 383 }); 384 }); 385 386 // FIXME: 387 // Anonymous inline boxes which contain only white space and are between two immediate siblings each of which is a table-non-root box. 388 // Anonymous inline boxes which meet all of the following criteria: 389 // - they contain only white space 390 // - they are the first and/or last child of a tabular container 391 // - whose immediate sibling, if any, is a table-non-root box 392 393 for (auto& box : to_remove) 394 box->parent()->remove_child(*box); 395} 396 397static bool is_table_track(CSS::Display display) 398{ 399 return display.is_table_row() || display.is_table_column(); 400} 401 402static bool is_table_track_group(CSS::Display display) 403{ 404 // Unless explicitly mentioned otherwise, mentions of table-row-groups in this spec also encompass the specialized 405 // table-header-groups and table-footer-groups. 406 return display.is_table_row_group() 407 || display.is_table_header_group() 408 || display.is_table_footer_group() 409 || display.is_table_column_group(); 410} 411 412static bool is_proper_table_child(Node const& node) 413{ 414 auto const display = node.display(); 415 return is_table_track_group(display) || is_table_track(display) || display.is_table_caption(); 416} 417 418static bool is_not_proper_table_child(Node const& node) 419{ 420 if (!node.has_style()) 421 return true; 422 return !is_proper_table_child(node); 423} 424 425static bool is_table_row(Node const& node) 426{ 427 return node.display().is_table_row(); 428} 429 430static bool is_not_table_row(Node const& node) 431{ 432 if (!node.has_style()) 433 return true; 434 return !is_table_row(node); 435} 436 437static bool is_table_cell(Node const& node) 438{ 439 return node.display().is_table_cell(); 440} 441 442static bool is_not_table_cell(Node const& node) 443{ 444 if (!node.has_style()) 445 return true; 446 return !is_table_cell(node); 447} 448 449static bool is_ignorable_whitespace(Layout::Node const& node) 450{ 451 if (node.is_text_node() && static_cast<TextNode const&>(node).text_for_rendering().is_whitespace()) 452 return true; 453 454 if (node.is_anonymous() && node.is_block_container() && static_cast<BlockContainer const&>(node).children_are_inline()) { 455 bool contains_only_white_space = true; 456 node.for_each_in_inclusive_subtree_of_type<TextNode>([&contains_only_white_space](auto& text_node) { 457 if (!text_node.text_for_rendering().is_whitespace()) { 458 contains_only_white_space = false; 459 return IterationDecision::Break; 460 } 461 return IterationDecision::Continue; 462 }); 463 if (contains_only_white_space) 464 return true; 465 } 466 467 return false; 468} 469 470template<typename Matcher, typename Callback> 471static void for_each_sequence_of_consecutive_children_matching(NodeWithStyle& parent, Matcher matcher, Callback callback) 472{ 473 Vector<JS::Handle<Node>> sequence; 474 475 auto sequence_is_all_ignorable_whitespace = [&]() -> bool { 476 for (auto& node : sequence) { 477 if (!is_ignorable_whitespace(*node)) 478 return false; 479 } 480 return true; 481 }; 482 483 for (auto child = parent.first_child(); child; child = child->next_sibling()) { 484 if (matcher(*child) || (!sequence.is_empty() && is_ignorable_whitespace(*child))) { 485 sequence.append(*child); 486 } else { 487 if (!sequence.is_empty()) { 488 if (!sequence_is_all_ignorable_whitespace()) 489 callback(sequence, child); 490 sequence.clear(); 491 } 492 } 493 } 494 if (!sequence.is_empty() && !sequence_is_all_ignorable_whitespace()) 495 callback(sequence, nullptr); 496} 497 498template<typename WrapperBoxType> 499static void wrap_in_anonymous(Vector<JS::Handle<Node>>& sequence, Node* nearest_sibling) 500{ 501 VERIFY(!sequence.is_empty()); 502 auto& parent = *sequence.first()->parent(); 503 auto computed_values = parent.computed_values().clone_inherited_values(); 504 static_cast<CSS::MutableComputedValues&>(computed_values).set_display(WrapperBoxType::static_display(parent.display().is_inline_outside())); 505 auto wrapper = parent.heap().template allocate_without_realm<WrapperBoxType>(parent.document(), nullptr, move(computed_values)); 506 for (auto& child : sequence) { 507 parent.remove_child(*child); 508 wrapper->append_child(*child); 509 } 510 if (nearest_sibling) 511 parent.insert_before(*wrapper, *nearest_sibling); 512 else 513 parent.append_child(*wrapper); 514} 515 516void TreeBuilder::generate_missing_child_wrappers(NodeWithStyle& root) 517{ 518 // 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. 519 for_each_in_tree_with_inside_display<CSS::Display::Inside::Table>(root, [&](auto& parent) { 520 for_each_sequence_of_consecutive_children_matching(parent, is_not_proper_table_child, [&](auto sequence, auto nearest_sibling) { 521 wrap_in_anonymous<TableRowBox>(sequence, nearest_sibling); 522 }); 523 }); 524 525 // 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. 526 for_each_in_tree_with_internal_display<CSS::Display::Internal::TableRowGroup>(root, [&](auto& parent) { 527 for_each_sequence_of_consecutive_children_matching(parent, is_not_table_row, [&](auto& sequence, auto nearest_sibling) { 528 wrap_in_anonymous<TableRowBox>(sequence, nearest_sibling); 529 }); 530 }); 531 // Unless explicitly mentioned otherwise, mentions of table-row-groups in this spec also encompass the specialized 532 // table-header-groups and table-footer-groups. 533 for_each_in_tree_with_internal_display<CSS::Display::Internal::TableHeaderGroup>(root, [&](auto& parent) { 534 for_each_sequence_of_consecutive_children_matching(parent, is_not_table_row, [&](auto& sequence, auto nearest_sibling) { 535 wrap_in_anonymous<TableRowBox>(sequence, nearest_sibling); 536 }); 537 }); 538 for_each_in_tree_with_internal_display<CSS::Display::Internal::TableFooterGroup>(root, [&](auto& parent) { 539 for_each_sequence_of_consecutive_children_matching(parent, is_not_table_row, [&](auto& sequence, auto nearest_sibling) { 540 wrap_in_anonymous<TableRowBox>(sequence, nearest_sibling); 541 }); 542 }); 543 544 // 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 545 for_each_in_tree_with_internal_display<CSS::Display::Internal::TableRow>(root, [&](auto& parent) { 546 for_each_sequence_of_consecutive_children_matching(parent, is_not_table_cell, [&](auto& sequence, auto nearest_sibling) { 547 wrap_in_anonymous<TableCellBox>(sequence, nearest_sibling); 548 }); 549 }); 550} 551 552void TreeBuilder::generate_missing_parents(NodeWithStyle& root) 553{ 554 Vector<JS::Handle<TableBox>> table_roots_to_wrap; 555 root.for_each_in_inclusive_subtree_of_type<Box>([&](auto& parent) { 556 // An anonymous table-row box must be generated around each sequence of consecutive table-cell boxes whose parent is not a table-row. 557 if (is_not_table_row(parent)) { 558 for_each_sequence_of_consecutive_children_matching(parent, is_table_cell, [&](auto& sequence, auto nearest_sibling) { 559 wrap_in_anonymous<TableRowBox>(sequence, nearest_sibling); 560 }); 561 } 562 563 // A table-row is misparented if its parent is neither a table-row-group nor a table-root box. 564 if (!parent.display().is_table_inside() && !is_proper_table_child(parent)) { 565 for_each_sequence_of_consecutive_children_matching(parent, is_table_row, [&](auto& sequence, auto nearest_sibling) { 566 wrap_in_anonymous<TableBox>(sequence, nearest_sibling); 567 }); 568 } 569 570 // A table-row-group, table-column-group, or table-caption box is misparented if its parent is not a table-root box. 571 if (!parent.display().is_table_inside() && !is_proper_table_child(parent)) { 572 for_each_sequence_of_consecutive_children_matching(parent, is_proper_table_child, [&](auto& sequence, auto nearest_sibling) { 573 wrap_in_anonymous<TableBox>(sequence, nearest_sibling); 574 }); 575 } 576 577 // An anonymous table-wrapper box must be generated around each table-root. 578 if (parent.display().is_table_inside()) { 579 table_roots_to_wrap.append(static_cast<TableBox&>(parent)); 580 } 581 582 return IterationDecision::Continue; 583 }); 584 585 for (auto& table_box : table_roots_to_wrap) { 586 auto* nearest_sibling = table_box->next_sibling(); 587 auto& parent = *table_box->parent(); 588 589 CSS::ComputedValues wrapper_computed_values; 590 // The computed values of properties 'position', 'float', 'margin-*', 'top', 'right', 'bottom', and 'left' on the table element are used on the table wrapper box and not the table box; 591 // all other values of non-inheritable properties are used on the table box and not the table wrapper box. 592 // (Where the table element's values are not used on the table and table wrapper boxes, the initial values are used instead.) 593 auto& mutable_wrapper_computed_values = static_cast<CSS::MutableComputedValues&>(wrapper_computed_values); 594 if (table_box->display().is_inline_outside()) 595 mutable_wrapper_computed_values.set_display(CSS::Display::from_short(CSS::Display::Short::InlineBlock)); 596 else 597 mutable_wrapper_computed_values.set_display(CSS::Display::from_short(CSS::Display::Short::FlowRoot)); 598 mutable_wrapper_computed_values.set_position(table_box->computed_values().position()); 599 mutable_wrapper_computed_values.set_inset(table_box->computed_values().inset()); 600 mutable_wrapper_computed_values.set_float(table_box->computed_values().float_()); 601 mutable_wrapper_computed_values.set_clear(table_box->computed_values().clear()); 602 mutable_wrapper_computed_values.set_margin(table_box->computed_values().margin()); 603 table_box->reset_table_box_computed_values_used_by_wrapper_to_init_values(); 604 605 auto wrapper = parent.heap().allocate_without_realm<TableWrapper>(parent.document(), nullptr, move(wrapper_computed_values)); 606 607 parent.remove_child(*table_box); 608 wrapper->append_child(*table_box); 609 610 if (nearest_sibling) 611 parent.insert_before(*wrapper, *nearest_sibling); 612 else 613 parent.append_child(*wrapper); 614 } 615} 616 617}