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 >
2022-02-25 11:59:56 +00:00
* Copyright ( c ) 2022 , 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
*/
2021-08-05 10:26:09 +02:00
# include <AK/Optional.h>
# include <AK/TemporaryChange.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>
2022-07-22 16:08:03 +01:00
# include <LibWeb/HTML/HTMLProgressElement.h>
2021-09-08 11:27:46 +02:00
# include <LibWeb/Layout/InitialContainingBlock.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>
2022-07-22 16:08:03 +01:00
# include <LibWeb/Layout/Progress.h>
2020-11-22 15:53:01 +01:00
# include <LibWeb/Layout/TableBox.h>
2021-01-07 18:00:51 +01:00
# include <LibWeb/Layout/TableCellBox.h>
# include <LibWeb/Layout/TableRowBox.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>
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 )
{
for ( auto const * child = layout_node . first_child ( ) ; child ; child = child - > next_sibling ( ) ) {
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 ;
for ( auto const * child = layout_node . first_child ( ) ; child ; child = child - > next_sibling ( ) ) {
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
{
2021-01-04 22:32:34 +01:00
if ( layout_parent . is_inline ( ) & & ! layout_parent . is_inline_block ( ) )
2020-11-26 21:18:34 +01:00
return layout_parent ;
2021-10-06 17:57:44 +02:00
if ( layout_parent . computed_values ( ) . display ( ) . is_flex_inside ( ) ) {
2021-09-29 17:18:49 +02:00
layout_parent . append_child ( layout_parent . create_anonymous_wrapper ( ) ) ;
2022-07-13 01:18:59 +02:00
return * layout_parent . last_child ( ) ;
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)
2020-11-26 21:18:34 +01:00
if ( ! layout_parent . last_child ( ) - > is_anonymous ( ) | | ! layout_parent . last_child ( ) - > children_are_inline ( ) ) {
2021-01-06 14:10:53 +01:00
layout_parent . append_child ( layout_parent . create_anonymous_wrapper ( ) ) ;
2020-11-26 21:18:34 +01:00
}
return * layout_parent . last_child ( ) ;
}
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
2021-01-02 03:30:04 +01:00
if ( ! layout_parent . children_are_inline ( ) ) {
// Parent block has block-level children, insert this block into parent.
return layout_parent ;
2019-10-17 22:42:51 +02:00
}
2022-03-22 19:13:55 +01:00
if ( layout_node . is_floating ( ) | | layout_node . is_absolutely_positioned ( ) ) {
// 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.
NonnullRefPtrVector < Layout : : Node > children ;
while ( RefPtr < Layout : : Node > child = layout_parent . first_child ( ) ) {
layout_parent . remove_child ( * child ) ;
children . append ( child . release_nonnull ( ) ) ;
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 ) {
layout_parent . last_child ( ) - > append_child ( child ) ;
}
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
}
2021-08-05 10:26:09 +02:00
void TreeBuilder : : create_layout_tree ( DOM : : Node & dom_node , TreeBuilder : : Context & context )
2020-11-26 21:18:34 +01:00
{
// If the parent doesn't have a layout node, we don't need one either.
2021-02-10 18:23:52 +01:00
if ( dom_node . parent_or_shadow_host ( ) & & ! dom_node . parent_or_shadow_host ( ) - > layout_node ( ) )
2020-11-26 21:18:34 +01:00
return ;
2021-08-05 10:26:09 +02:00
Optional < TemporaryChange < bool > > has_svg_root_change ;
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 ) {
return ;
}
2022-02-05 13:17:01 +01:00
auto & document = dom_node . document ( ) ;
auto & style_computer = document . style_computer ( ) ;
RefPtr < Layout : : Node > layout_node ;
2022-03-12 13:24:58 +01:00
RefPtr < CSS : : StyleProperties > style ;
2022-02-05 13:17:01 +01:00
if ( is < DOM : : Element > ( dom_node ) ) {
auto & element = static_cast < DOM : : Element & > ( dom_node ) ;
2022-03-03 17:50:12 +00:00
element . clear_pseudo_element_nodes ( { } ) ;
2022-03-14 21:47:37 +01:00
VERIFY ( ! element . needs_style_update ( ) ) ;
style = element . computed_css_values ( ) ;
2022-02-05 13:17:01 +01:00
if ( style - > display ( ) . is_none ( ) )
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 ( ) ;
layout_node = adopt_ref ( * new Layout : : InitialContainingBlock ( static_cast < DOM : : Document & > ( dom_node ) , * style ) ) ;
2022-02-05 13:17:01 +01:00
} else if ( is < DOM : : Text > ( dom_node ) ) {
layout_node = adopt_ref ( * new Layout : : TextNode ( document , static_cast < DOM : : Text & > ( dom_node ) ) ) ;
} else if ( is < DOM : : ShadowRoot > ( dom_node ) ) {
layout_node = adopt_ref ( * new Layout : : BlockContainer ( document , & static_cast < DOM : : ShadowRoot & > ( dom_node ) , CSS : : ComputedValues { } ) ) ;
}
2020-11-26 21:18:34 +01:00
if ( ! layout_node )
return ;
2019-10-17 23:32:08 +02:00
2022-02-25 11:59:56 +00:00
auto insert_node_into_inline_or_block_ancestor = [ this ] ( auto & node , bool prepend = false ) {
2022-04-13 18:44:14 +02:00
if ( node - > is_inline ( ) & & ! ( node - > is_inline_block ( ) & & m_ancestor_stack . last ( ) . computed_values ( ) . display ( ) . is_flex_inside ( ) ) ) {
2020-11-26 21:18:34 +01:00
// Inlines can be inserted into the nearest ancestor.
2022-04-13 18:44:14 +02:00
auto & insertion_point = insertion_parent_for_inline_node ( m_ancestor_stack . last ( ) ) ;
2022-02-25 11:59:56 +00:00
if ( prepend )
insertion_point . prepend_child ( * node ) ;
else
insertion_point . append_child ( * node ) ;
2020-11-26 21:18:34 +01:00
insertion_point . set_children_are_inline ( true ) ;
} else {
2020-11-29 16:11:27 +01:00
// Non-inlines can't be inserted into an inline parent, so find the nearest non-inline ancestor.
2022-03-24 18:03:58 +01:00
auto & nearest_non_inline_ancestor = [ & ] ( ) - > Layout : : NodeWithStyle & {
2022-04-13 18:44:14 +02:00
for ( auto & ancestor : m_ancestor_stack . in_reverse ( ) ) {
if ( ! ancestor . is_inline ( ) | | ancestor . is_inline_block ( ) )
return ancestor ;
2020-11-26 21:18:34 +01:00
}
2021-02-23 20:42:32 +01:00
VERIFY_NOT_REACHED ( ) ;
2020-11-26 21:18:34 +01:00
} ( ) ;
2022-02-25 11:59:56 +00:00
auto & insertion_point = insertion_parent_for_block_node ( nearest_non_inline_ancestor , * node ) ;
if ( prepend )
insertion_point . prepend_child ( * node ) ;
else
insertion_point . append_child ( * node ) ;
2022-03-29 19:51:21 +02:00
// 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 ) ;
2020-11-26 21:18:34 +01: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 ( ) ) {
2022-04-13 18:44:14 +02:00
m_ancestor_stack . last ( ) . append_child ( * layout_node ) ;
2022-02-25 11:59:56 +00:00
} else {
insert_node_into_inline_or_block_ancestor ( layout_node ) ;
2020-11-26 21:18:34 +01:00
}
2021-06-24 19:53:42 +02:00
auto * shadow_root = is < DOM : : Element > ( dom_node ) ? verify_cast < DOM : : Element > ( dom_node ) . shadow_root ( ) : nullptr ;
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 ) ) ;
2021-02-10 18:23:52 +01:00
if ( shadow_root )
2021-08-05 10:26:09 +02:00
create_layout_tree ( * shadow_root , context ) ;
2021-06-24 19:53:42 +02:00
verify_cast < DOM : : ParentNode > ( dom_node ) . for_each_child ( [ & ] ( auto & dom_child ) {
2021-08-05 10:26:09 +02:00
create_layout_tree ( dom_child , context ) ;
2020-11-26 21:18:34 +01:00
} ) ;
pop_parent ( ) ;
}
2022-02-21 01:43:37 +01:00
2022-02-25 11:59:56 +00:00
// Add nodes for the ::before and ::after pseudo-elements.
if ( is < DOM : : Element > ( dom_node ) ) {
auto & element = static_cast < DOM : : Element & > ( dom_node ) ;
auto create_pseudo_element_if_needed = [ & ] ( CSS : : Selector : : PseudoElement pseudo_element ) - > RefPtr < Node > {
auto pseudo_element_style = style_computer . compute_style ( element , pseudo_element ) ;
auto pseudo_element_content = pseudo_element_style - > content ( ) ;
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 )
return nullptr ;
if ( auto pseudo_element_node = DOM : : Element : : create_layout_node_for_display_type ( document , pseudo_element_display , move ( pseudo_element_style ) , nullptr ) ) {
2022-09-28 17:11:23 +02:00
pseudo_element_node - > set_generated ( true ) ;
2022-02-25 11:59:56 +00:00
// FIXME: Handle images, and multiple values
if ( pseudo_element_content . type = = CSS : : ContentData : : Type : : String ) {
2022-08-28 13:42:07 +02:00
auto * text = document . heap ( ) . allocate < DOM : : Text > ( document . realm ( ) , document , pseudo_element_content . data ) ;
auto text_node = adopt_ref ( * new TextNode ( document , * text ) ) ;
2022-02-25 11:59:56 +00:00
push_parent ( verify_cast < NodeWithStyle > ( * pseudo_element_node ) ) ;
insert_node_into_inline_or_block_ancestor ( text_node ) ;
pop_parent ( ) ;
} else {
TODO ( ) ;
}
2022-08-28 13:42:07 +02:00
return pseudo_element_node . ptr ( ) ;
2022-02-25 11:59:56 +00:00
}
return nullptr ;
} ;
push_parent ( verify_cast < NodeWithStyle > ( * layout_node ) ) ;
2022-03-03 17:50:12 +00:00
if ( auto before_node = create_pseudo_element_if_needed ( CSS : : Selector : : PseudoElement : : Before ) ) {
2022-08-28 13:42:07 +02:00
element . set_pseudo_element_node ( { } , CSS : : Selector : : PseudoElement : : Before , before_node . ptr ( ) ) ;
2022-02-25 11:59:56 +00:00
insert_node_into_inline_or_block_ancestor ( before_node , true ) ;
2022-03-03 17:50:12 +00:00
}
if ( auto after_node = create_pseudo_element_if_needed ( CSS : : Selector : : PseudoElement : : After ) ) {
2022-08-28 13:42:07 +02:00
element . set_pseudo_element_node ( { } , CSS : : Selector : : PseudoElement : : After , after_node . ptr ( ) ) ;
2022-02-25 11:59:56 +00:00
insert_node_into_inline_or_block_ancestor ( after_node ) ;
2022-03-03 17:50:12 +00:00
}
2022-02-25 11:59:56 +00: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 ) ;
2022-02-21 01:43:37 +01:00
int child_index = layout_node - > parent ( ) - > index_of_child < ListItemBox > ( * layout_node ) . value ( ) ;
2022-03-03 17:50:12 +00:00
auto marker_style = style_computer . compute_style ( element , CSS : : Selector : : PseudoElement : : Marker ) ;
2022-02-21 01:43:37 +01:00
auto list_item_marker = adopt_ref ( * new ListItemMarkerBox ( document , layout_node - > computed_values ( ) . list_style_type ( ) , child_index + 1 , * marker_style ) ) ;
if ( layout_node - > first_child ( ) )
list_item_marker - > set_inline ( layout_node - > first_child ( ) - > is_inline ( ) ) ;
static_cast < ListItemBox & > ( * layout_node ) . set_marker ( list_item_marker ) ;
2022-03-03 17:50:12 +00:00
element . set_pseudo_element_node ( { } , CSS : : Selector : : PseudoElement : : Marker , list_item_marker ) ;
2022-02-21 01:43:37 +01:00
layout_node - > append_child ( move ( list_item_marker ) ) ;
}
2022-07-22 16:08:03 +01:00
if ( is < HTML : : HTMLProgressElement > ( dom_node ) ) {
auto & progress = static_cast < HTML : : HTMLProgressElement & > ( dom_node ) ;
if ( ! progress . using_system_appearance ( ) ) {
auto bar_style = style_computer . compute_style ( progress , CSS : : Selector : : PseudoElement : : ProgressBar ) ;
auto value_style = style_computer . compute_style ( progress , CSS : : Selector : : PseudoElement : : ProgressValue ) ;
auto position = progress . position ( ) ;
value_style - > set_property ( CSS : : PropertyID : : Width , CSS : : PercentageStyleValue : : create ( CSS : : Percentage ( position > = 0 ? round_to < int > ( 100 * position ) : 0 ) ) ) ;
auto progress_bar = adopt_ref ( * new Layout : : BlockContainer ( document , nullptr , bar_style ) ) ;
auto progress_value = adopt_ref ( * new Layout : : BlockContainer ( document , nullptr , value_style ) ) ;
progress_bar - > append_child ( * progress_value ) ;
layout_node - > append_child ( * progress_bar ) ;
progress . set_pseudo_element_node ( { } , CSS : : Selector : : PseudoElement : : ProgressBar , progress_bar ) ;
progress . set_pseudo_element_node ( { } , CSS : : Selector : : PseudoElement : : ProgressValue , progress_value ) ;
}
}
2019-10-15 12:22:41 +02:00
}
2020-11-26 21:18:34 +01:00
RefPtr < 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 ;
create_layout_tree ( dom_node , context ) ;
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
2021-10-06 17:57:44 +02:00
template < CSS : : Display : : Internal internal , typename Callback >
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 ) {
2021-10-06 17:57:44 +02:00
auto const & display = box . computed_values ( ) . display ( ) ;
if ( display . is_internal ( ) & & display . internal ( ) = = internal )
callback ( box ) ;
return IterationDecision : : Continue ;
} ) ;
}
template < CSS : : Display : : Inside inside , typename Callback >
void TreeBuilder : : for_each_in_tree_with_inside_display ( NodeWithStyle & root , Callback callback )
{
root . for_each_in_inclusive_subtree_of_type < Box > ( [ & ] ( auto & box ) {
auto const & display = box . computed_values ( ) . 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 ) ;
generate_missing_parents ( root ) ;
}
void TreeBuilder : : remove_irrelevant_boxes ( NodeWithStyle & root )
{
// The following boxes are discarded as if they were display:none:
2021-09-03 19:11:51 +02:00
NonnullRefPtrVector < Node > to_remove ;
2021-01-07 18:00:51 +01:00
// Children of a table-column.
2021-10-06 17:57:44 +02:00
for_each_in_tree_with_internal_display < CSS : : Display : : Internal : : 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.
2021-10-06 17:57:44 +02:00
for_each_in_tree_with_internal_display < CSS : : Display : : Internal : : TableColumnGroup > ( root , [ & ] ( Box & table_column_group ) {
2021-01-07 18:00:51 +01:00
table_column_group . for_each_child ( [ & ] ( auto & child ) {
2021-10-06 17:57:44 +02:00
if ( child . computed_values ( ) . 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 )
box . parent ( ) - > remove_child ( box ) ;
}
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
}
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 ;
auto display = node . computed_values ( ) . display ( ) ;
2021-10-06 17:57:44 +02:00
return ! is_table_track_group ( display ) & & ! is_table_track ( display ) & & ! display . is_table_caption ( ) ;
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 ;
auto display = node . computed_values ( ) . display ( ) ;
2021-10-06 17:57:44 +02:00
return ! 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_cell ( Node const & node )
2021-01-07 18:00:51 +01:00
{
if ( ! node . has_style ( ) )
return true ;
auto display = node . computed_values ( ) . display ( ) ;
2021-10-06 17:57:44 +02:00
return ! display . is_table_cell ( ) ;
2021-01-07 18:00:51 +01:00
}
2021-10-27 18:20:41 +02:00
static bool is_ignorable_whitespace ( Layout : : Node const & node )
{
if ( node . is_text_node ( ) & & static_cast < TextNode const & > ( node ) . text_for_rendering ( ) . is_whitespace ( ) )
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 ) {
if ( ! text_node . text_for_rendering ( ) . is_whitespace ( ) ) {
contains_only_white_space = false ;
return IterationDecision : : Break ;
}
return IterationDecision : : Continue ;
} ) ;
if ( contains_only_white_space )
return true ;
}
return false ;
}
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 )
{
NonnullRefPtrVector < Node > sequence ;
2021-10-27 18:20:41 +02:00
auto sequence_is_all_ignorable_whitespace = [ & ] ( ) - > bool {
for ( auto & node : sequence ) {
if ( ! is_ignorable_whitespace ( node ) )
return false ;
}
return true ;
} ;
2021-01-07 18:00:51 +01:00
Node * next_sibling = nullptr ;
for ( auto * child = parent . first_child ( ) ; child ; child = next_sibling ) {
next_sibling = child - > next_sibling ( ) ;
if ( matcher ( * child ) ) {
sequence . append ( * child ) ;
} else {
if ( ! sequence . is_empty ( ) ) {
2021-10-27 18:20:41 +02:00
if ( ! sequence_is_all_ignorable_whitespace ( ) )
callback ( sequence , next_sibling ) ;
2021-01-07 18:00:51 +01:00
sequence . clear ( ) ;
}
}
}
2021-10-27 18:20:41 +02:00
if ( sequence . is_empty ( ) & & ! sequence_is_all_ignorable_whitespace ( ) )
2021-01-07 18:00:51 +01:00
callback ( sequence , nullptr ) ;
}
template < typename WrapperBoxType >
static void wrap_in_anonymous ( NonnullRefPtrVector < Node > & sequence , Node * nearest_sibling )
{
2021-02-23 20:42:32 +01:00
VERIFY ( ! sequence . is_empty ( ) ) ;
2021-01-07 18:00:51 +01:00
auto & parent = * sequence . first ( ) . parent ( ) ;
auto computed_values = parent . computed_values ( ) . clone_inherited_values ( ) ;
static_cast < CSS : : MutableComputedValues & > ( computed_values ) . set_display ( WrapperBoxType : : static_display ( ) ) ;
2021-04-23 16:46:57 +02:00
auto wrapper = adopt_ref ( * new WrapperBoxType ( parent . document ( ) , nullptr , move ( computed_values ) ) ) ;
2021-01-07 18:00:51 +01:00
for ( auto & child : sequence ) {
parent . remove_child ( child ) ;
wrapper - > append_child ( child ) ;
}
if ( nearest_sibling )
parent . insert_before ( move ( wrapper ) , * nearest_sibling ) ;
else
parent . append_child ( move ( wrapper ) ) ;
}
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.
2021-10-06 17:57:44 +02:00
for_each_in_tree_with_inside_display < CSS : : Display : : Inside : : 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 ) {
wrap_in_anonymous < TableRowBox > ( sequence , nearest_sibling ) ;
} ) ;
} ) ;
// 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.
2021-10-06 17:57:44 +02:00
for_each_in_tree_with_internal_display < CSS : : Display : : Internal : : 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 ) {
wrap_in_anonymous < TableRowBox > ( sequence , nearest_sibling ) ;
} ) ;
} ) ;
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
for_each_in_tree_with_internal_display < CSS : : Display : : Internal : : 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 ) {
wrap_in_anonymous < TableRowBox > ( sequence , nearest_sibling ) ;
} ) ;
} ) ;
2021-10-06 17:57:44 +02:00
for_each_in_tree_with_internal_display < CSS : : Display : : Internal : : 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 ) {
wrap_in_anonymous < TableRowBox > ( sequence , nearest_sibling ) ;
} ) ;
} ) ;
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
2021-10-06 17:57:44 +02:00
for_each_in_tree_with_internal_display < CSS : : Display : : Internal : : 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 ) {
wrap_in_anonymous < TableCellBox > ( sequence , nearest_sibling ) ;
} ) ;
} ) ;
}
void TreeBuilder : : generate_missing_parents ( NodeWithStyle & )
{
// FIXME: Implement.
}
2020-03-07 10:27:02 +01:00
}