2020-01-18 09:38:21 +01:00
/*
2022-02-21 01:43:37 +01:00
* Copyright ( c ) 2018 - 2022 , Andreas Kling < kling @ serenityos . org >
2023-03-24 15:04:24 +00:00
* Copyright ( c ) 2022 - 2023 , Sam Atkins < atkinssj @ serenityos . org >
2022-07-22 16:08:03 +01:00
* Copyright ( c ) 2022 , MacDue < macdue @ dueutil . tech >
2020-01-18 09:38:21 +01:00
*
2021-04-22 01:24:48 -07:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-01-18 09:38:21 +01:00
*/
2022-12-06 03:08:20 -03:00
# include <AK/Error.h>
2021-08-05 10:26:09 +02:00
# include <AK/Optional.h>
# include <AK/TemporaryChange.h>
2023-05-08 06:37:18 +02:00
# include <LibWeb/CSS/StyleComputer.h>
2023-04-26 21:05:38 +02:00
# include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
2023-03-24 15:04:24 +00:00
# include <LibWeb/CSS/StyleValues/IdentifierStyleValue.h>
2023-03-24 17:28:43 +00:00
# include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
2020-03-07 10:32:51 +01:00
# include <LibWeb/DOM/Document.h>
2021-01-06 14:10:53 +01:00
# include <LibWeb/DOM/Element.h>
2020-03-07 10:32:51 +01:00
# include <LibWeb/DOM/ParentNode.h>
2021-02-10 18:23:52 +01:00
# include <LibWeb/DOM/ShadowRoot.h>
2021-01-07 18:00:51 +01:00
# include <LibWeb/Dump.h>
2023-08-06 15:59:40 +02:00
# include <LibWeb/HTML/HTMLButtonElement.h>
2022-11-30 22:15:12 -05:00
# include <LibWeb/HTML/HTMLInputElement.h>
2023-10-11 18:42:09 +02:00
# include <LibWeb/HTML/HTMLLIElement.h>
# include <LibWeb/HTML/HTMLOListElement.h>
2023-09-05 15:17:10 -04:00
# include <LibWeb/HTML/HTMLSlotElement.h>
2022-02-21 01:43:37 +01:00
# include <LibWeb/Layout/ListItemBox.h>
# include <LibWeb/Layout/ListItemMarkerBox.h>
2020-11-22 15:53:01 +01:00
# include <LibWeb/Layout/Node.h>
2023-08-05 05:50:07 +00:00
# include <LibWeb/Layout/TableGrid.h>
2023-01-14 14:48:20 +01:00
# include <LibWeb/Layout/TableWrapper.h>
2020-11-22 15:53:01 +01:00
# include <LibWeb/Layout/TextNode.h>
2020-11-25 20:29:03 +01:00
# include <LibWeb/Layout/TreeBuilder.h>
2023-02-25 11:04:29 +01:00
# include <LibWeb/Layout/Viewport.h>
2022-11-10 13:54:57 +01:00
# include <LibWeb/SVG/SVGForeignObjectElement.h>
2019-10-15 12:22:41 +02:00
2020-11-22 15:53:01 +01:00
namespace Web : : Layout {
2020-03-07 10:27:02 +01:00
2022-03-14 13:21:51 -06:00
TreeBuilder : : TreeBuilder ( ) = default ;
2019-10-15 12:22:41 +02:00
2022-03-22 19:13:55 +01:00
static bool has_inline_or_in_flow_block_children ( Layout : : Node const & layout_node )
{
2022-10-17 14:41:50 +02:00
for ( auto child = layout_node . first_child ( ) ; child ; child = child - > next_sibling ( ) ) {
2022-03-22 19:13:55 +01:00
if ( child - > is_inline ( ) )
return true ;
if ( ! child - > is_floating ( ) & & ! child - > is_absolutely_positioned ( ) )
return true ;
}
return false ;
}
static bool has_in_flow_block_children ( Layout : : Node const & layout_node )
{
if ( layout_node . children_are_inline ( ) )
return false ;
2022-10-17 14:41:50 +02:00
for ( auto child = layout_node . first_child ( ) ; child ; child = child - > next_sibling ( ) ) {
2022-03-22 19:13:55 +01:00
if ( child - > is_inline ( ) )
continue ;
if ( ! child - > is_floating ( ) & & ! child - > is_absolutely_positioned ( ) )
return true ;
}
return false ;
}
// The insertion_parent_for_*() functions maintain the invariant that the in-flow children of
// block-level boxes must be either all block-level or all inline-level.
2021-01-02 03:30:04 +01:00
2021-01-06 14:10:53 +01:00
static Layout : : Node & insertion_parent_for_inline_node ( Layout : : NodeWithStyle & layout_parent )
2020-11-26 21:18:34 +01:00
{
2023-10-10 19:27:01 +01:00
auto last_child_creating_anonymous_wrapper_if_needed = [ ] ( auto & layout_parent ) - > Layout : : Node & {
if ( ! layout_parent . last_child ( )
| | ! layout_parent . last_child ( ) - > is_anonymous ( )
| | ! layout_parent . last_child ( ) - > children_are_inline ( )
| | layout_parent . last_child ( ) - > is_generated ( ) ) {
layout_parent . append_child ( layout_parent . create_anonymous_wrapper ( ) ) ;
}
return * layout_parent . last_child ( ) ;
} ;
2022-10-07 12:47:46 +02:00
if ( layout_parent . display ( ) . is_inline_outside ( ) & & layout_parent . display ( ) . is_flow_inside ( ) )
2020-11-26 21:18:34 +01:00
return layout_parent ;
2023-10-10 19:27:01 +01:00
if ( layout_parent . display ( ) . is_flex_inside ( ) | | layout_parent . display ( ) . is_grid_inside ( ) )
return last_child_creating_anonymous_wrapper_if_needed ( layout_parent ) ;
2021-09-29 17:18:49 +02:00
2022-03-22 19:13:55 +01:00
if ( ! has_in_flow_block_children ( layout_parent ) | | layout_parent . children_are_inline ( ) )
2020-11-26 21:18:34 +01:00
return layout_parent ;
2021-01-02 03:30:04 +01:00
// Parent has block-level children, insert into an anonymous wrapper block (and create it first if needed)
2023-10-10 19:27:01 +01:00
return last_child_creating_anonymous_wrapper_if_needed ( layout_parent ) ;
2020-11-26 21:18:34 +01:00
}
2022-03-24 18:03:58 +01:00
static Layout : : Node & insertion_parent_for_block_node ( Layout : : NodeWithStyle & layout_parent , Layout : : Node & layout_node )
2020-11-26 21:18:34 +01:00
{
2022-03-22 19:13:55 +01:00
if ( ! has_inline_or_in_flow_block_children ( layout_parent ) ) {
2021-01-02 03:30:04 +01:00
// Parent block has no children, insert this block into parent.
2020-11-26 21:18:34 +01:00
return layout_parent ;
2021-01-02 03:30:04 +01:00
}
2020-11-26 21:18:34 +01:00
2023-03-25 15:33:22 +01:00
bool is_out_of_flow = layout_node . is_absolutely_positioned ( ) | | layout_node . is_floating ( ) ;
if ( is_out_of_flow
2023-04-06 18:01:01 +02:00
& & ! layout_parent . display ( ) . is_flex_inside ( )
& & ! layout_parent . display ( ) . is_grid_inside ( )
2023-03-25 15:33:22 +01:00
& & layout_parent . last_child ( ) - > is_anonymous ( )
& & layout_parent . last_child ( ) - > children_are_inline ( ) ) {
// Block is out-of-flow & previous sibling was wrapped in an anonymous block.
// Join the previous sibling inside the anonymous block.
return * layout_parent . last_child ( ) ;
}
2022-11-15 22:17:27 +03:00
if ( ! layout_parent . children_are_inline ( ) ) {
// Parent block has block-level children, insert this block into parent.
2022-03-22 19:13:55 +01:00
return layout_parent ;
}
2023-03-25 15:33:22 +01:00
if ( is_out_of_flow ) {
2022-11-23 00:55:12 +03:00
// Block is out-of-flow, it can have inline siblings if necessary.
return layout_parent ;
}
2021-01-02 03:30:04 +01:00
// Parent block has inline-level children (our siblings).
// First move these siblings into an anonymous wrapper block.
2022-10-17 14:41:50 +02:00
Vector < JS : : Handle < Layout : : Node > > children ;
2023-12-10 21:33:03 +01:00
{
JS : : GCPtr < Layout : : Node > next ;
for ( JS : : GCPtr < Layout : : Node > child = layout_parent . first_child ( ) ; child ; child = next ) {
next = child - > next_sibling ( ) ;
// NOTE: We let out-of-flow children stay in the parent, to preserve tree structure.
if ( child - > is_floating ( ) | | child - > is_absolutely_positioned ( ) )
continue ;
layout_parent . remove_child ( * child ) ;
children . append ( * child ) ;
}
2020-11-26 21:18:34 +01:00
}
2022-03-24 18:03:58 +01:00
layout_parent . append_child ( layout_parent . create_anonymous_wrapper ( ) ) ;
2021-01-02 03:30:04 +01:00
layout_parent . set_children_are_inline ( false ) ;
for ( auto & child : children ) {
2022-10-17 14:41:50 +02:00
layout_parent . last_child ( ) - > append_child ( * child ) ;
2021-01-02 03:30:04 +01:00
}
layout_parent . last_child ( ) - > set_children_are_inline ( true ) ;
// Then it's safe to insert this block into parent.
return layout_parent ;
2020-11-26 21:18:34 +01:00
}
2022-10-06 14:14:16 +02:00
void TreeBuilder : : insert_node_into_inline_or_block_ancestor ( Layout : : Node & node , CSS : : Display display , AppendOrPrepend mode )
2022-10-06 13:10:03 +02:00
{
2023-07-27 23:31:42 +02:00
if ( node . display ( ) . is_contents ( ) )
return ;
2022-10-06 20:57:29 +02:00
if ( display . is_inline_outside ( ) ) {
2023-12-23 20:10:42 +01:00
// Inlines can be inserted into the nearest ancestor without "display: contents".
auto & nearest_ancestor_without_display_contents = [ & ] ( ) - > Layout : : NodeWithStyle & {
for ( auto & ancestor : m_ancestor_stack . in_reverse ( ) ) {
if ( ! ancestor - > display ( ) . is_contents ( ) )
return ancestor ;
}
VERIFY_NOT_REACHED ( ) ;
} ( ) ;
auto & insertion_point = insertion_parent_for_inline_node ( nearest_ancestor_without_display_contents ) ;
2022-10-06 13:10:03 +02:00
if ( mode = = AppendOrPrepend : : Prepend )
insertion_point . prepend_child ( node ) ;
else
insertion_point . append_child ( node ) ;
insertion_point . set_children_are_inline ( true ) ;
} else {
// Non-inlines can't be inserted into an inline parent, so find the nearest non-inline ancestor.
auto & nearest_non_inline_ancestor = [ & ] ( ) - > Layout : : NodeWithStyle & {
for ( auto & ancestor : m_ancestor_stack . in_reverse ( ) ) {
2023-07-27 23:31:42 +02:00
if ( ancestor - > display ( ) . is_contents ( ) )
continue ;
2023-02-26 16:09:02 -07:00
if ( ! ancestor - > display ( ) . is_inline_outside ( ) )
2022-10-06 21:03:30 +02:00
return ancestor ;
2023-02-26 16:09:02 -07:00
if ( ! ancestor - > display ( ) . is_flow_inside ( ) )
2022-10-06 13:10:03 +02:00
return ancestor ;
2023-02-26 16:09:02 -07:00
if ( ancestor - > dom_node ( ) & & is < SVG : : SVGForeignObjectElement > ( * ancestor - > dom_node ( ) ) )
2022-11-10 13:54:57 +01:00
return ancestor ;
2022-10-06 13:10:03 +02:00
}
VERIFY_NOT_REACHED ( ) ;
} ( ) ;
auto & insertion_point = insertion_parent_for_block_node ( nearest_non_inline_ancestor , node ) ;
if ( mode = = AppendOrPrepend : : Prepend )
insertion_point . prepend_child ( node ) ;
else
insertion_point . append_child ( node ) ;
// After inserting an in-flow block-level box into a parent, mark the parent as having non-inline children.
if ( ! node . is_floating ( ) & & ! node . is_absolutely_positioned ( ) )
insertion_point . set_children_are_inline ( false ) ;
}
}
2023-12-10 21:00:03 +13:00
ErrorOr < void > TreeBuilder : : create_pseudo_element_if_needed ( DOM : : Element & element , CSS : : Selector : : PseudoElement : : Type pseudo_element , AppendOrPrepend mode )
2022-10-06 13:10:03 +02:00
{
auto & document = element . document ( ) ;
auto & style_computer = document . style_computer ( ) ;
2023-03-14 16:36:20 +01:00
auto pseudo_element_style = TRY ( style_computer . compute_pseudo_element_style_if_needed ( element , pseudo_element ) ) ;
if ( ! pseudo_element_style )
return { } ;
2023-09-18 15:41:17 +01:00
auto initial_quote_nesting_level = m_quote_nesting_level ;
auto [ pseudo_element_content , final_quote_nesting_level ] = pseudo_element_style - > content ( initial_quote_nesting_level ) ;
m_quote_nesting_level = final_quote_nesting_level ;
2022-10-06 13:10:03 +02:00
auto pseudo_element_display = pseudo_element_style - > display ( ) ;
// ::before and ::after only exist if they have content. `content: normal` computes to `none` for them.
// We also don't create them if they are `display: none`.
if ( pseudo_element_display . is_none ( )
| | pseudo_element_content . type = = CSS : : ContentData : : Type : : Normal
| | pseudo_element_content . type = = CSS : : ContentData : : Type : : None )
2022-12-06 03:08:20 -03:00
return { } ;
2022-10-06 13:56:54 +02:00
2023-03-14 16:36:20 +01:00
auto pseudo_element_node = DOM : : Element : : create_layout_node_for_display_type ( document , pseudo_element_display , * pseudo_element_style , nullptr ) ;
2022-10-06 13:56:54 +02:00
if ( ! pseudo_element_node )
2022-12-06 03:08:20 -03:00
return { } ;
2022-10-06 13:56:54 +02:00
2023-08-06 01:29:55 +02:00
auto generated_for = Node : : GeneratedFor : : NotGenerated ;
2023-12-10 21:00:03 +13:00
if ( pseudo_element = = CSS : : Selector : : PseudoElement : : Type : : Before ) {
2023-08-06 01:29:55 +02:00
generated_for = Node : : GeneratedFor : : PseudoBefore ;
2023-12-10 21:00:03 +13:00
} else if ( pseudo_element = = CSS : : Selector : : PseudoElement : : Type : : After ) {
2023-08-06 01:29:55 +02:00
generated_for = Node : : GeneratedFor : : PseudoAfter ;
} else {
VERIFY_NOT_REACHED ( ) ;
}
pseudo_element_node - > set_generated_for ( generated_for , element ) ;
2023-09-18 15:41:17 +01:00
pseudo_element_node - > set_initial_quote_nesting_level ( initial_quote_nesting_level ) ;
2023-08-06 01:29:55 +02:00
2022-10-06 13:56:54 +02:00
// FIXME: Handle images, and multiple values
if ( pseudo_element_content . type = = CSS : : ContentData : : Type : : String ) {
2023-09-06 15:17:20 +12:00
auto text = document . heap ( ) . allocate < DOM : : Text > ( document . realm ( ) , document , pseudo_element_content . data ) ;
2022-10-17 14:41:50 +02:00
auto text_node = document . heap ( ) . allocate_without_realm < Layout : : TextNode > ( document , * text ) ;
2023-08-06 01:29:55 +02:00
text_node - > set_generated_for ( generated_for , element ) ;
2022-10-06 13:56:54 +02:00
push_parent ( verify_cast < NodeWithStyle > ( * pseudo_element_node ) ) ;
2022-10-17 14:41:50 +02:00
insert_node_into_inline_or_block_ancestor ( * text_node , text_node - > display ( ) , AppendOrPrepend : : Append ) ;
2022-10-06 13:56:54 +02:00
pop_parent ( ) ;
} else {
TODO ( ) ;
2022-10-06 13:10:03 +02:00
}
2022-10-06 20:42:22 +02:00
element . set_pseudo_element_node ( { } , pseudo_element , pseudo_element_node ) ;
2022-10-06 14:14:16 +02:00
insert_node_into_inline_or_block_ancestor ( * pseudo_element_node , pseudo_element_display , mode ) ;
2022-12-06 03:08:20 -03:00
return { } ;
2022-10-06 13:56:54 +02:00
}
2022-10-06 13:10:03 +02:00
2023-08-06 15:59:40 +02:00
static bool is_ignorable_whitespace ( Layout : : Node const & node )
{
2023-11-21 10:56:29 +13:00
if ( node . is_text_node ( ) & & static_cast < TextNode const & > ( node ) . text_for_rendering ( ) . bytes_as_string_view ( ) . is_whitespace ( ) )
2023-08-06 15:59:40 +02:00
return true ;
if ( node . is_anonymous ( ) & & node . is_block_container ( ) & & static_cast < BlockContainer const & > ( node ) . children_are_inline ( ) ) {
bool contains_only_white_space = true ;
node . for_each_in_inclusive_subtree_of_type < TextNode > ( [ & contains_only_white_space ] ( auto & text_node ) {
2023-11-21 10:56:29 +13:00
if ( ! text_node . text_for_rendering ( ) . bytes_as_string_view ( ) . is_whitespace ( ) ) {
2023-08-06 15:59:40 +02:00
contains_only_white_space = false ;
return IterationDecision : : Break ;
}
return IterationDecision : : Continue ;
} ) ;
if ( contains_only_white_space )
return true ;
}
return false ;
}
2023-10-11 18:42:09 +02:00
i32 TreeBuilder : : calculate_list_item_index ( DOM : : Node & dom_node )
{
if ( is < HTML : : HTMLLIElement > ( dom_node ) ) {
auto & li = static_cast < HTML : : HTMLLIElement & > ( dom_node ) ;
if ( li . value ( ) ! = 0 )
return li . value ( ) ;
}
if ( dom_node . previous_sibling ( ) ! = nullptr ) {
DOM : : Node * current = dom_node . previous_sibling ( ) ;
while ( current ! = nullptr ) {
if ( is < HTML : : HTMLLIElement > ( * current ) )
return calculate_list_item_index ( * current ) + 1 ;
current = current - > previous_sibling ( ) ;
}
}
if ( is < HTML : : HTMLOListElement > ( * dom_node . parent ( ) ) ) {
auto & ol = static_cast < HTML : : HTMLOListElement & > ( * dom_node . parent ( ) ) ;
return ol . start ( ) ;
}
return 1 ;
}
2022-12-06 03:08:20 -03:00
ErrorOr < void > TreeBuilder : : create_layout_tree ( DOM : : Node & dom_node , TreeBuilder : : Context & context )
2020-11-26 21:18:34 +01:00
{
2023-08-01 08:23:13 +02:00
JS : : GCPtr < Layout : : Node > layout_node ;
2021-08-05 10:26:09 +02:00
Optional < TemporaryChange < bool > > has_svg_root_change ;
2023-08-01 08:23:13 +02:00
ScopeGuard remove_stale_layout_node_guard = [ & ] {
// If we didn't create a layout node for this DOM node,
2024-01-13 12:34:53 +01:00
// go through the DOM tree and remove any old layout & paint nodes since they are now all stale.
2023-08-01 08:23:13 +02:00
if ( ! layout_node ) {
dom_node . for_each_in_inclusive_subtree ( [ & ] ( auto & node ) {
node . detach_layout_node ( { } ) ;
2024-01-13 12:34:53 +01:00
node . set_paintable ( nullptr ) ;
2023-08-01 08:23:13 +02:00
if ( is < DOM : : Element > ( node ) )
static_cast < DOM : : Element & > ( node ) . clear_pseudo_element_nodes ( { } ) ;
return IterationDecision : : Continue ;
} ) ;
}
} ;
2021-08-05 10:26:09 +02:00
if ( dom_node . is_svg_container ( ) ) {
has_svg_root_change . emplace ( context . has_svg_root , true ) ;
} else if ( dom_node . requires_svg_container ( ) & & ! context . has_svg_root ) {
2022-12-06 03:08:20 -03:00
return { } ;
2021-08-05 10:26:09 +02:00
}
2022-02-05 13:17:01 +01:00
auto & document = dom_node . document ( ) ;
auto & style_computer = document . style_computer ( ) ;
2022-03-12 13:24:58 +01:00
RefPtr < CSS : : StyleProperties > style ;
2022-10-06 14:14:16 +02:00
CSS : : Display display ;
2022-02-05 13:17:01 +01:00
if ( is < DOM : : Element > ( dom_node ) ) {
auto & element = static_cast < DOM : : Element & > ( dom_node ) ;
2023-12-17 21:03:28 +01:00
element . clear_pseudo_element_nodes ( { } ) ;
VERIFY ( ! element . needs_style_update ( ) ) ;
style = element . computed_css_values ( ) ;
display = style - > display ( ) ;
2022-10-06 14:14:16 +02:00
if ( display . is_none ( ) )
2022-12-06 03:08:20 -03:00
return { } ;
2022-03-12 13:24:58 +01:00
layout_node = element . create_layout_node ( * style ) ;
2022-02-05 13:17:01 +01:00
} else if ( is < DOM : : Document > ( dom_node ) ) {
2022-03-12 13:24:58 +01:00
style = style_computer . create_document_style ( ) ;
2022-10-06 14:14:16 +02:00
display = style - > display ( ) ;
2023-02-25 11:04:29 +01:00
layout_node = document . heap ( ) . allocate_without_realm < Layout : : Viewport > ( static_cast < DOM : : Document & > ( dom_node ) , * style ) ;
2022-02-05 13:17:01 +01:00
} else if ( is < DOM : : Text > ( dom_node ) ) {
2022-10-17 14:41:50 +02:00
layout_node = document . heap ( ) . allocate_without_realm < Layout : : TextNode > ( document , static_cast < DOM : : Text & > ( dom_node ) ) ;
2023-09-04 17:39:15 +01:00
display = CSS : : Display ( CSS : : DisplayOutside : : Inline , CSS : : DisplayInside : : Flow ) ;
2022-02-05 13:17:01 +01:00
}
2020-11-26 21:18:34 +01:00
if ( ! layout_node )
2022-12-06 03:08:20 -03:00
return { } ;
2019-10-17 23:32:08 +02:00
2022-02-25 11:59:56 +00:00
if ( ! dom_node . parent_or_shadow_host ( ) ) {
m_layout_root = layout_node ;
2022-03-18 22:13:26 +01:00
} else if ( layout_node - > is_svg_box ( ) ) {
2023-02-26 16:09:02 -07:00
m_ancestor_stack . last ( ) - > append_child ( * layout_node ) ;
2022-02-25 11:59:56 +00:00
} else {
2022-10-06 14:14:16 +02:00
insert_node_into_inline_or_block_ancestor ( * layout_node , display , AppendOrPrepend : : Append ) ;
2020-11-26 21:18:34 +01:00
}
2023-01-28 20:22:52 +01:00
auto * shadow_root = is < DOM : : Element > ( dom_node ) ? verify_cast < DOM : : Element > ( dom_node ) . shadow_root_internal ( ) : nullptr ;
2021-02-10 18:23:52 +01:00
2023-09-18 17:47:42 +01:00
// Add node for the ::before pseudo-element.
if ( is < DOM : : Element > ( dom_node ) & & layout_node - > can_have_children ( ) ) {
auto & element = static_cast < DOM : : Element & > ( dom_node ) ;
push_parent ( verify_cast < NodeWithStyle > ( * layout_node ) ) ;
2023-12-10 21:00:03 +13:00
TRY ( create_pseudo_element_if_needed ( element , CSS : : Selector : : PseudoElement : : Type : : Before , AppendOrPrepend : : Prepend ) ) ;
2023-09-18 17:47:42 +01:00
pop_parent ( ) ;
}
2021-02-10 18:23:52 +01:00
if ( ( dom_node . has_children ( ) | | shadow_root ) & & layout_node - > can_have_children ( ) ) {
2021-06-24 19:53:42 +02:00
push_parent ( verify_cast < NodeWithStyle > ( * layout_node ) ) ;
2023-05-25 12:34:54 +02:00
if ( shadow_root ) {
for ( auto * node = shadow_root - > first_child ( ) ; node ; node = node - > next_sibling ( ) ) {
TRY ( create_layout_tree ( * node , context ) ) ;
}
2023-08-01 22:34:49 +01:00
} else {
// This is the same as verify_cast<DOM::ParentNode>(dom_node).for_each_child
for ( auto * node = verify_cast < DOM : : ParentNode > ( dom_node ) . first_child ( ) ; node ; node = node - > next_sibling ( ) )
TRY ( create_layout_tree ( * node , context ) ) ;
2023-05-25 12:34:54 +02:00
}
2020-11-26 21:18:34 +01:00
pop_parent ( ) ;
}
2022-02-21 01:43:37 +01:00
if ( is < ListItemBox > ( * layout_node ) ) {
2022-03-03 17:50:12 +00:00
auto & element = static_cast < DOM : : Element & > ( dom_node ) ;
2023-12-10 21:00:03 +13:00
auto marker_style = TRY ( style_computer . compute_style ( element , CSS : : Selector : : PseudoElement : : Type : : Marker ) ) ;
2023-10-11 18:42:09 +02:00
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 ) ;
2022-02-21 01:43:37 +01:00
static_cast < ListItemBox & > ( * layout_node ) . set_marker ( list_item_marker ) ;
2023-12-10 21:00:03 +13:00
element . set_pseudo_element_node ( { } , CSS : : Selector : : PseudoElement : : Type : : Marker , list_item_marker ) ;
2022-10-17 14:41:50 +02:00
layout_node - > append_child ( * list_item_marker ) ;
2022-02-21 01:43:37 +01:00
}
2022-07-22 16:08:03 +01:00
2023-09-05 15:17:10 -04:00
if ( is < HTML : : HTMLSlotElement > ( dom_node ) ) {
auto slottables = static_cast < HTML : : HTMLSlotElement & > ( dom_node ) . assigned_nodes_internal ( ) ;
push_parent ( verify_cast < NodeWithStyle > ( * layout_node ) ) ;
for ( auto const & slottable : slottables ) {
TRY ( slottable . visit ( [ & ] ( auto & node ) - > ErrorOr < void > {
return create_layout_tree ( node , context ) ;
} ) ) ;
}
pop_parent ( ) ;
}
2023-09-09 17:13:41 +02:00
// https://html.spec.whatwg.org/multipage/rendering.html#button-layout
// If the computed value of 'inline-size' is 'auto', then the used value is the fit-content inline size.
if ( dom_node . is_html_button_element ( ) & & dom_node . layout_node ( ) - > computed_values ( ) . width ( ) . is_auto ( ) ) {
auto & computed_values = verify_cast < NodeWithStyle > ( * dom_node . layout_node ( ) ) . mutable_computed_values ( ) ;
computed_values . set_width ( CSS : : Size : : make_fit_content ( ) ) ;
}
2023-08-06 15:59:40 +02:00
// https://html.spec.whatwg.org/multipage/rendering.html#button-layout
// If the element is an input element, or if it is a button element and its computed value for
// 'display' is not 'inline-grid', 'grid', 'inline-flex', or 'flex', then the element's box has
// a child anonymous button content box with the following behaviors:
2023-09-09 17:13:41 +02:00
if ( dom_node . is_html_button_element ( ) & & ! display . is_grid_inside ( ) & & ! display . is_flex_inside ( ) ) {
2023-08-06 15:59:40 +02:00
auto & parent = * dom_node . layout_node ( ) ;
// If the box does not overflow in the vertical axis, then it is centered vertically.
2023-09-03 11:07:38 +02:00
// FIXME: Only apply alignment when box overflows
auto flex_computed_values = parent . computed_values ( ) . clone_inherited_values ( ) ;
auto & mutable_flex_computed_values = static_cast < CSS : : MutableComputedValues & > ( flex_computed_values ) ;
2023-09-04 17:39:15 +01:00
mutable_flex_computed_values . set_display ( CSS : : Display { CSS : : DisplayOutside : : Block , CSS : : DisplayInside : : Flex } ) ;
2023-09-03 11:07:38 +02:00
mutable_flex_computed_values . set_justify_content ( CSS : : JustifyContent : : Center ) ;
mutable_flex_computed_values . set_flex_direction ( CSS : : FlexDirection : : Column ) ;
mutable_flex_computed_values . set_height ( CSS : : Size : : make_percentage ( CSS : : Percentage ( 100 ) ) ) ;
auto flex_wrapper = parent . heap ( ) . template allocate_without_realm < BlockContainer > ( parent . document ( ) , nullptr , move ( flex_computed_values ) ) ;
auto content_box_computed_values = parent . computed_values ( ) . clone_inherited_values ( ) ;
auto content_box_wrapper = parent . heap ( ) . template allocate_without_realm < BlockContainer > ( parent . document ( ) , nullptr , move ( content_box_computed_values ) ) ;
content_box_wrapper - > set_children_are_inline ( parent . children_are_inline ( ) ) ;
2023-08-06 15:59:40 +02:00
Vector < JS : : Handle < Node > > sequence ;
for ( auto child = parent . first_child ( ) ; child ; child = child - > next_sibling ( ) ) {
2023-09-18 17:47:42 +01:00
if ( child - > is_generated_for_before_pseudo_element ( ) )
continue ;
2023-08-11 03:19:31 +00:00
sequence . append ( * child ) ;
2023-08-06 15:59:40 +02:00
}
for ( auto & node : sequence ) {
parent . remove_child ( * node ) ;
2023-09-03 11:07:38 +02:00
content_box_wrapper - > append_child ( * node ) ;
2023-08-06 15:59:40 +02:00
}
2023-09-03 11:07:38 +02:00
flex_wrapper - > append_child ( * content_box_wrapper ) ;
parent . append_child ( * flex_wrapper ) ;
2023-09-10 22:14:25 +02:00
parent . set_children_are_inline ( false ) ;
2023-08-06 15:59:40 +02:00
}
2023-09-18 17:47:42 +01:00
// Add nodes for the ::after pseudo-element.
2023-09-14 14:30:39 +02:00
if ( is < DOM : : Element > ( dom_node ) & & layout_node - > can_have_children ( ) ) {
2023-09-10 22:34:41 +02:00
auto & element = static_cast < DOM : : Element & > ( dom_node ) ;
push_parent ( verify_cast < NodeWithStyle > ( * layout_node ) ) ;
2023-12-10 21:00:03 +13:00
TRY ( create_pseudo_element_if_needed ( element , CSS : : Selector : : PseudoElement : : Type : : After , AppendOrPrepend : : Append ) ) ;
2023-09-10 22:34:41 +02:00
pop_parent ( ) ;
}
2022-12-06 03:08:20 -03:00
return { } ;
2019-10-15 12:22:41 +02:00
}
2022-10-17 14:41:50 +02:00
JS : : GCPtr < Layout : : Node > TreeBuilder : : build ( DOM : : Node & dom_node )
2019-10-15 12:22:41 +02:00
{
2022-03-14 20:25:58 +01:00
VERIFY ( dom_node . is_document ( ) ) ;
2020-11-26 21:18:34 +01:00
2021-08-05 10:26:09 +02:00
Context context ;
2023-09-18 15:41:17 +01:00
m_quote_nesting_level = 0 ;
2022-12-06 03:08:20 -03:00
MUST ( create_layout_tree ( dom_node , context ) ) ; // FIXME propagate errors
2021-01-07 18:00:51 +01:00
if ( auto * root = dom_node . document ( ) . layout_node ( ) )
fixup_tables ( * root ) ;
2020-11-26 21:18:34 +01:00
return move ( m_layout_root ) ;
2019-10-15 12:22:41 +02:00
}
2020-03-07 10:27:02 +01:00
2023-09-04 17:39:15 +01:00
template < CSS : : DisplayInternal internal , typename Callback >
2021-10-06 17:57:44 +02:00
void TreeBuilder : : for_each_in_tree_with_internal_display ( NodeWithStyle & root , Callback callback )
2021-01-07 18:00:51 +01:00
{
2021-04-06 18:38:10 +01:00
root . for_each_in_inclusive_subtree_of_type < Box > ( [ & ] ( auto & box ) {
2022-10-06 16:02:53 +02:00
auto const display = box . display ( ) ;
2021-10-06 17:57:44 +02:00
if ( display . is_internal ( ) & & display . internal ( ) = = internal )
callback ( box ) ;
return IterationDecision : : Continue ;
} ) ;
}
2023-09-04 17:39:15 +01:00
template < CSS : : DisplayInside inside , typename Callback >
2021-10-06 17:57:44 +02:00
void TreeBuilder : : for_each_in_tree_with_inside_display ( NodeWithStyle & root , Callback callback )
{
root . for_each_in_inclusive_subtree_of_type < Box > ( [ & ] ( auto & box ) {
2022-10-06 16:02:53 +02:00
auto const display = box . display ( ) ;
2022-04-13 01:03:40 +02:00
if ( display . is_outside_and_inside ( ) & & display . inside ( ) = = inside )
2021-01-07 18:00:51 +01:00
callback ( box ) ;
return IterationDecision : : Continue ;
} ) ;
}
void TreeBuilder : : fixup_tables ( NodeWithStyle & root )
{
remove_irrelevant_boxes ( root ) ;
generate_missing_child_wrappers ( root ) ;
2023-08-05 05:50:07 +00:00
auto table_root_boxes = generate_missing_parents ( root ) ;
missing_cells_fixup ( table_root_boxes ) ;
2021-01-07 18:00:51 +01:00
}
void TreeBuilder : : remove_irrelevant_boxes ( NodeWithStyle & root )
{
// The following boxes are discarded as if they were display:none:
2022-10-17 14:41:50 +02:00
Vector < JS : : Handle < Node > > to_remove ;
2021-01-07 18:00:51 +01:00
// Children of a table-column.
2023-09-04 17:39:15 +01:00
for_each_in_tree_with_internal_display < CSS : : DisplayInternal : : TableColumn > ( root , [ & ] ( Box & table_column ) {
2021-01-07 18:00:51 +01:00
table_column . for_each_child ( [ & ] ( auto & child ) {
to_remove . append ( child ) ;
} ) ;
} ) ;
// Children of a table-column-group which are not a table-column.
2023-09-04 17:39:15 +01:00
for_each_in_tree_with_internal_display < CSS : : DisplayInternal : : TableColumnGroup > ( root , [ & ] ( Box & table_column_group ) {
2021-01-07 18:00:51 +01:00
table_column_group . for_each_child ( [ & ] ( auto & child ) {
2023-06-04 14:18:57 +00:00
if ( ! child . display ( ) . is_table_column ( ) )
2021-01-07 18:00:51 +01:00
to_remove . append ( child ) ;
} ) ;
} ) ;
// FIXME:
// Anonymous inline boxes which contain only white space and are between two immediate siblings each of which is a table-non-root box.
// Anonymous inline boxes which meet all of the following criteria:
// - they contain only white space
// - they are the first and/or last child of a tabular container
// - whose immediate sibling, if any, is a table-non-root box
for ( auto & box : to_remove )
2022-10-17 14:41:50 +02:00
box - > parent ( ) - > remove_child ( * box ) ;
2021-01-07 18:00:51 +01:00
}
static bool is_table_track ( CSS : : Display display )
{
2021-10-06 17:57:44 +02:00
return display . is_table_row ( ) | | display . is_table_column ( ) ;
2021-01-07 18:00:51 +01:00
}
static bool is_table_track_group ( CSS : : Display display )
{
2021-04-18 18:21:26 +01:00
// Unless explicitly mentioned otherwise, mentions of table-row-groups in this spec also encompass the specialized
// table-header-groups and table-footer-groups.
2021-10-06 17:57:44 +02:00
return display . is_table_row_group ( )
| | display . is_table_header_group ( )
| | display . is_table_footer_group ( )
| | display . is_table_column_group ( ) ;
2021-01-07 18:00:51 +01:00
}
2023-01-04 16:38:42 +03:00
static bool is_proper_table_child ( Node const & node )
{
auto const display = node . display ( ) ;
return is_table_track_group ( display ) | | is_table_track ( display ) | | display . is_table_caption ( ) ;
}
2022-04-01 20:58:27 +03:00
static bool is_not_proper_table_child ( Node const & node )
2021-01-07 18:00:51 +01:00
{
if ( ! node . has_style ( ) )
return true ;
2023-01-04 16:38:42 +03:00
return ! is_proper_table_child ( node ) ;
}
static bool is_table_row ( Node const & node )
{
return node . display ( ) . is_table_row ( ) ;
2021-01-07 18:00:51 +01:00
}
2022-04-01 20:58:27 +03:00
static bool is_not_table_row ( Node const & node )
2021-01-07 18:00:51 +01:00
{
if ( ! node . has_style ( ) )
return true ;
2023-01-04 16:38:42 +03:00
return ! is_table_row ( node ) ;
}
static bool is_table_cell ( Node const & node )
{
return node . display ( ) . is_table_cell ( ) ;
2021-01-07 18:00:51 +01:00
}
2022-04-01 20:58:27 +03:00
static bool is_not_table_cell ( Node const & node )
2021-01-07 18:00:51 +01:00
{
if ( ! node . has_style ( ) )
return true ;
2023-01-04 16:38:42 +03:00
return ! is_table_cell ( node ) ;
2021-01-07 18:00:51 +01:00
}
template < typename Matcher , typename Callback >
static void for_each_sequence_of_consecutive_children_matching ( NodeWithStyle & parent , Matcher matcher , Callback callback )
{
2022-10-17 14:41:50 +02:00
Vector < JS : : Handle < Node > > sequence ;
2021-10-27 18:20:41 +02:00
auto sequence_is_all_ignorable_whitespace = [ & ] ( ) - > bool {
for ( auto & node : sequence ) {
2022-10-17 14:41:50 +02:00
if ( ! is_ignorable_whitespace ( * node ) )
2021-10-27 18:20:41 +02:00
return false ;
}
return true ;
} ;
2022-12-09 13:53:51 +03:00
for ( auto child = parent . first_child ( ) ; child ; child = child - > next_sibling ( ) ) {
2023-01-04 16:38:42 +03:00
if ( matcher ( * child ) | | ( ! sequence . is_empty ( ) & & is_ignorable_whitespace ( * child ) ) ) {
2021-01-07 18:00:51 +01:00
sequence . append ( * child ) ;
} else {
if ( ! sequence . is_empty ( ) ) {
2021-10-27 18:20:41 +02:00
if ( ! sequence_is_all_ignorable_whitespace ( ) )
2022-12-09 13:53:51 +03:00
callback ( sequence , child ) ;
2021-01-07 18:00:51 +01:00
sequence . clear ( ) ;
}
}
}
2022-12-09 13:53:51 +03:00
if ( ! sequence . is_empty ( ) & & ! sequence_is_all_ignorable_whitespace ( ) )
2021-01-07 18:00:51 +01:00
callback ( sequence , nullptr ) ;
}
template < typename WrapperBoxType >
2023-05-29 14:11:19 +03:00
static void wrap_in_anonymous ( Vector < JS : : Handle < Node > > & sequence , Node * nearest_sibling , CSS : : Display display )
2021-01-07 18:00:51 +01:00
{
2021-02-23 20:42:32 +01:00
VERIFY ( ! sequence . is_empty ( ) ) ;
2022-10-17 14:41:50 +02:00
auto & parent = * sequence . first ( ) - > parent ( ) ;
2021-01-07 18:00:51 +01:00
auto computed_values = parent . computed_values ( ) . clone_inherited_values ( ) ;
2023-05-29 14:11:19 +03:00
static_cast < CSS : : MutableComputedValues & > ( computed_values ) . set_display ( display ) ;
2022-10-17 14:41:50 +02:00
auto wrapper = parent . heap ( ) . template allocate_without_realm < WrapperBoxType > ( parent . document ( ) , nullptr , move ( computed_values ) ) ;
2021-01-07 18:00:51 +01:00
for ( auto & child : sequence ) {
2022-10-17 14:41:50 +02:00
parent . remove_child ( * child ) ;
wrapper - > append_child ( * child ) ;
2021-01-07 18:00:51 +01:00
}
2023-05-22 02:59:10 +00:00
wrapper - > set_children_are_inline ( parent . children_are_inline ( ) ) ;
2021-01-07 18:00:51 +01:00
if ( nearest_sibling )
2022-10-17 14:41:50 +02:00
parent . insert_before ( * wrapper , * nearest_sibling ) ;
2021-01-07 18:00:51 +01:00
else
2022-10-17 14:41:50 +02:00
parent . append_child ( * wrapper ) ;
2021-01-07 18:00:51 +01:00
}
void TreeBuilder : : generate_missing_child_wrappers ( NodeWithStyle & root )
{
// 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.
2023-09-04 17:39:15 +01:00
for_each_in_tree_with_inside_display < CSS : : DisplayInside : : Table > ( root , [ & ] ( auto & parent ) {
2021-01-07 18:00:51 +01:00
for_each_sequence_of_consecutive_children_matching ( parent , is_not_proper_table_child , [ & ] ( auto sequence , auto nearest_sibling ) {
2023-09-04 17:39:15 +01:00
wrap_in_anonymous < Box > ( sequence , nearest_sibling , CSS : : Display { CSS : : DisplayInternal : : TableRow } ) ;
2021-01-07 18:00:51 +01:00
} ) ;
} ) ;
// 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.
2023-09-04 17:39:15 +01:00
for_each_in_tree_with_internal_display < CSS : : DisplayInternal : : TableRowGroup > ( root , [ & ] ( auto & parent ) {
2021-01-07 18:00:51 +01:00
for_each_sequence_of_consecutive_children_matching ( parent , is_not_table_row , [ & ] ( auto & sequence , auto nearest_sibling ) {
2023-09-04 17:39:15 +01:00
wrap_in_anonymous < Box > ( sequence , nearest_sibling , CSS : : Display { CSS : : DisplayInternal : : TableRow } ) ;
2021-01-07 18:00:51 +01:00
} ) ;
} ) ;
2021-04-18 18:21:26 +01:00
// Unless explicitly mentioned otherwise, mentions of table-row-groups in this spec also encompass the specialized
// table-header-groups and table-footer-groups.
2023-09-04 17:39:15 +01:00
for_each_in_tree_with_internal_display < CSS : : DisplayInternal : : TableHeaderGroup > ( root , [ & ] ( auto & parent ) {
2021-04-18 18:21:26 +01:00
for_each_sequence_of_consecutive_children_matching ( parent , is_not_table_row , [ & ] ( auto & sequence , auto nearest_sibling ) {
2023-09-04 17:39:15 +01:00
wrap_in_anonymous < Box > ( sequence , nearest_sibling , CSS : : Display { CSS : : DisplayInternal : : TableRow } ) ;
2021-04-18 18:21:26 +01:00
} ) ;
} ) ;
2023-09-04 17:39:15 +01:00
for_each_in_tree_with_internal_display < CSS : : DisplayInternal : : TableFooterGroup > ( root , [ & ] ( auto & parent ) {
2021-04-18 18:21:26 +01:00
for_each_sequence_of_consecutive_children_matching ( parent , is_not_table_row , [ & ] ( auto & sequence , auto nearest_sibling ) {
2023-09-04 17:39:15 +01:00
wrap_in_anonymous < Box > ( sequence , nearest_sibling , CSS : : Display { CSS : : DisplayInternal : : TableRow } ) ;
2021-04-18 18:21:26 +01:00
} ) ;
} ) ;
2021-01-07 18:00:51 +01:00
// 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
2023-09-04 17:39:15 +01:00
for_each_in_tree_with_internal_display < CSS : : DisplayInternal : : TableRow > ( root , [ & ] ( auto & parent ) {
2021-01-07 18:00:51 +01:00
for_each_sequence_of_consecutive_children_matching ( parent , is_not_table_cell , [ & ] ( auto & sequence , auto nearest_sibling ) {
2023-09-04 17:39:15 +01:00
wrap_in_anonymous < BlockContainer > ( sequence , nearest_sibling , CSS : : Display { CSS : : DisplayInternal : : TableCell } ) ;
2021-01-07 18:00:51 +01:00
} ) ;
} ) ;
}
2023-08-05 05:50:07 +00:00
Vector < JS : : Handle < Box > > TreeBuilder : : generate_missing_parents ( NodeWithStyle & root )
2021-01-07 18:00:51 +01:00
{
2023-05-29 14:11:19 +03:00
Vector < JS : : Handle < Box > > table_roots_to_wrap ;
2023-01-04 16:38:42 +03:00
root . for_each_in_inclusive_subtree_of_type < Box > ( [ & ] ( auto & parent ) {
// An anonymous table-row box must be generated around each sequence of consecutive table-cell boxes whose parent is not a table-row.
if ( is_not_table_row ( parent ) ) {
for_each_sequence_of_consecutive_children_matching ( parent , is_table_cell , [ & ] ( auto & sequence , auto nearest_sibling ) {
2023-09-04 17:39:15 +01:00
wrap_in_anonymous < Box > ( sequence , nearest_sibling , CSS : : Display { CSS : : DisplayInternal : : TableRow } ) ;
2023-01-04 16:38:42 +03:00
} ) ;
}
// A table-row is misparented if its parent is neither a table-row-group nor a table-root box.
if ( ! parent . display ( ) . is_table_inside ( ) & & ! is_proper_table_child ( parent ) ) {
for_each_sequence_of_consecutive_children_matching ( parent , is_table_row , [ & ] ( auto & sequence , auto nearest_sibling ) {
2023-05-29 14:11:19 +03:00
wrap_in_anonymous < Box > ( sequence , nearest_sibling , CSS : : Display : : from_short ( parent . display ( ) . is_inline_outside ( ) ? CSS : : Display : : Short : : InlineTable : CSS : : Display : : Short : : Table ) ) ;
2023-01-04 16:38:42 +03:00
} ) ;
}
// A table-row-group, table-column-group, or table-caption box is misparented if its parent is not a table-root box.
if ( ! parent . display ( ) . is_table_inside ( ) & & ! is_proper_table_child ( parent ) ) {
for_each_sequence_of_consecutive_children_matching ( parent , is_proper_table_child , [ & ] ( auto & sequence , auto nearest_sibling ) {
2023-05-29 14:11:19 +03:00
wrap_in_anonymous < Box > ( sequence , nearest_sibling , CSS : : Display : : from_short ( parent . display ( ) . is_inline_outside ( ) ? CSS : : Display : : Short : : InlineTable : CSS : : Display : : Short : : Table ) ) ;
2023-01-04 16:38:42 +03:00
} ) ;
}
2023-01-09 08:27:39 +03:00
// An anonymous table-wrapper box must be generated around each table-root.
if ( parent . display ( ) . is_table_inside ( ) ) {
2023-05-29 14:11:19 +03:00
table_roots_to_wrap . append ( parent ) ;
2023-01-09 08:27:39 +03:00
}
2023-01-04 16:38:42 +03:00
return IterationDecision : : Continue ;
} ) ;
2023-01-09 08:27:39 +03:00
for ( auto & table_box : table_roots_to_wrap ) {
auto * nearest_sibling = table_box - > next_sibling ( ) ;
auto & parent = * table_box - > parent ( ) ;
2023-09-05 11:58:18 +02:00
CSS : : ComputedValues wrapper_computed_values = table_box - > computed_values ( ) . clone_inherited_values ( ) ;
2023-06-22 12:22:29 +00:00
table_box - > transfer_table_box_computed_values_to_wrapper_computed_values ( wrapper_computed_values ) ;
2023-01-09 08:27:39 +03:00
2023-01-14 14:48:20 +01:00
auto wrapper = parent . heap ( ) . allocate_without_realm < TableWrapper > ( parent . document ( ) , nullptr , move ( wrapper_computed_values ) ) ;
2023-01-09 08:27:39 +03:00
parent . remove_child ( * table_box ) ;
wrapper - > append_child ( * table_box ) ;
2023-02-07 01:33:10 +03:00
if ( nearest_sibling )
parent . insert_before ( * wrapper , * nearest_sibling ) ;
else
parent . append_child ( * wrapper ) ;
2023-01-09 08:27:39 +03:00
}
2023-08-05 05:50:07 +00:00
return table_roots_to_wrap ;
}
template < typename Matcher , typename Callback >
static void for_each_child_box_matching ( Box & parent , Matcher matcher , Callback callback )
{
parent . for_each_child_of_type < Box > ( [ & ] ( Box & child_box ) {
if ( matcher ( child_box ) )
callback ( child_box ) ;
} ) ;
2021-01-07 18:00:51 +01:00
}
2023-08-05 05:50:07 +00:00
static void fixup_row ( Box & row_box , TableGrid const & table_grid , size_t row_index )
{
bool missing_cells_run_has_started = false ;
for ( size_t column_index = 0 ; column_index < table_grid . column_count ( ) ; + + column_index ) {
if ( table_grid . occupancy_grid ( ) . contains ( { column_index , row_index } ) ) {
VERIFY ( ! missing_cells_run_has_started ) ;
continue ;
}
missing_cells_run_has_started = true ;
auto row_computed_values = row_box . computed_values ( ) . clone_inherited_values ( ) ;
auto & cell_computed_values = static_cast < CSS : : MutableComputedValues & > ( row_computed_values ) ;
2023-09-04 17:39:15 +01:00
cell_computed_values . set_display ( Web : : CSS : : Display { CSS : : DisplayInternal : : TableCell } ) ;
2023-08-05 05:50:07 +00:00
// Ensure that the cell (with zero content height) will have the same height as the row by setting vertical-align to middle.
cell_computed_values . set_vertical_align ( CSS : : VerticalAlign : : Middle ) ;
auto cell_box = row_box . heap ( ) . template allocate_without_realm < BlockContainer > ( row_box . document ( ) , nullptr , cell_computed_values ) ;
row_box . append_child ( cell_box ) ;
}
}
void TreeBuilder : : missing_cells_fixup ( Vector < JS : : Handle < Box > > const & table_root_boxes )
{
// Implements https://www.w3.org/TR/css-tables-3/#missing-cells-fixup.
for ( auto & table_box : table_root_boxes ) {
auto table_grid = TableGrid : : calculate_row_column_grid ( * table_box ) ;
size_t row_index = 0 ;
for_each_child_box_matching ( * table_box , TableGrid : : is_table_row_group , [ & ] ( auto & row_group_box ) {
for_each_child_box_matching ( row_group_box , is_table_row , [ & ] ( auto & row_box ) {
fixup_row ( row_box , table_grid , row_index ) ;
+ + row_index ;
return IterationDecision : : Continue ;
} ) ;
} ) ;
for_each_child_box_matching ( * table_box , is_table_row , [ & ] ( auto & row_box ) {
fixup_row ( row_box , table_grid , row_index ) ;
+ + row_index ;
return IterationDecision : : Continue ;
} ) ;
}
}
2020-03-07 10:27:02 +01:00
}