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
*/
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>
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-11-30 22:15:12 -05:00
# include <LibWeb/HTML/HTMLInputElement.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>
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
{
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 ;
2022-10-06 16:02:53 +02:00
if ( layout_parent . 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
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 ;
}
2022-11-23 00:55:12 +03:00
if ( layout_node . is_absolutely_positioned ( ) | | layout_node . is_floating ( ) ) {
// 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 ;
while ( JS : : GCPtr < Layout : : Node > child = layout_parent . first_child ( ) ) {
2021-01-02 03:30:04 +01:00
layout_parent . remove_child ( * child ) ;
2022-10-17 14:41:50 +02:00
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
{
2022-10-06 20:57:29 +02:00
if ( display . is_inline_outside ( ) ) {
2022-10-06 13:10:03 +02:00
// Inlines can be inserted into the nearest ancestor.
auto & insertion_point = insertion_parent_for_inline_node ( m_ancestor_stack . last ( ) ) ;
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 ( ) ) {
2022-10-06 21:03:30 +02:00
if ( ! ancestor . display ( ) . is_inline_outside ( ) )
return ancestor ;
if ( ! ancestor . display ( ) . is_flow_inside ( ) )
2022-10-06 13:10:03 +02:00
return ancestor ;
2022-11-10 13:54:57 +01:00
if ( ancestor . dom_node ( ) & & is < SVG : : SVGForeignObjectElement > ( * ancestor . dom_node ( ) ) )
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 ) ;
}
}
2022-12-06 03:08:20 -03:00
ErrorOr < void > TreeBuilder : : create_pseudo_element_if_needed ( DOM : : Element & element , CSS : : Selector : : PseudoElement pseudo_element , AppendOrPrepend mode )
2022-10-06 13:10:03 +02:00
{
auto & document = element . document ( ) ;
auto & style_computer = document . style_computer ( ) ;
2022-12-06 03:08:20 -03:00
auto pseudo_element_style = TRY ( style_computer . compute_style ( element , pseudo_element ) ) ;
2022-10-06 13:10:03 +02:00
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 )
2022-12-06 03:08:20 -03:00
return { } ;
2022-10-06 13:56:54 +02:00
auto pseudo_element_node = DOM : : Element : : create_layout_node_for_display_type ( document , pseudo_element_display , pseudo_element_style , nullptr ) ;
if ( ! pseudo_element_node )
2022-12-06 03:08:20 -03:00
return { } ;
2022-10-06 13:56:54 +02:00
pseudo_element_node - > set_generated ( true ) ;
// FIXME: Handle images, and multiple values
if ( pseudo_element_content . type = = CSS : : ContentData : : Type : : String ) {
2022-12-14 17:40:33 +00: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 ) ;
2022-10-14 12:29:57 +02:00
text_node - > set_generated ( true ) ;
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
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
{
// 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 ( ) )
2022-12-06 03:08:20 -03:00
return { } ;
2020-11-26 21:18:34 +01:00
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 ) {
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-10-17 14:41:50 +02:00
JS : : GCPtr < Layout : : Node > layout_node ;
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 ) ;
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-10-06 14:14:16 +02:00
display = style - > display ( ) ;
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 ( ) ;
2022-10-17 14:41:50 +02:00
layout_node = document . heap ( ) . allocate_without_realm < Layout : : InitialContainingBlock > ( 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 ) ) ;
2022-10-06 14:14:16 +02:00
display = CSS : : Display ( CSS : : Display : : Outside : : Inline , CSS : : Display : : Inside : : Flow ) ;
2022-02-05 13:17:01 +01:00
} else if ( is < DOM : : ShadowRoot > ( dom_node ) ) {
2022-10-17 14:41:50 +02:00
layout_node = document . heap ( ) . allocate_without_realm < Layout : : BlockContainer > ( document , & static_cast < DOM : : ShadowRoot & > ( dom_node ) , CSS : : ComputedValues { } ) ;
2022-10-06 14:14:16 +02:00
display = CSS : : Display ( CSS : : Display : : Outside : : Block , CSS : : Display : : Inside : : FlowRoot ) ;
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 ( ) ) {
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 {
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
}
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 )
2022-12-06 03:08:20 -03:00
TRY ( create_layout_tree ( * shadow_root , context ) ) ;
// 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 ) ) ;
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 ) ;
push_parent ( verify_cast < NodeWithStyle > ( * layout_node ) ) ;
2022-12-06 03:08:20 -03:00
TRY ( create_pseudo_element_if_needed ( element , CSS : : Selector : : PseudoElement : : Before , AppendOrPrepend : : Prepend ) ) ;
TRY ( create_pseudo_element_if_needed ( element , CSS : : Selector : : PseudoElement : : After , AppendOrPrepend : : Append ) ) ;
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-12-06 03:08:20 -03:00
auto marker_style = TRY ( style_computer . compute_style ( element , CSS : : Selector : : PseudoElement : : Marker ) ) ;
2022-10-17 14:41:50 +02:00
auto list_item_marker = document . heap ( ) . allocate_without_realm < ListItemMarkerBox > ( document , layout_node - > computed_values ( ) . list_style_type ( ) , child_index + 1 , * marker_style ) ;
2022-02-21 01:43:37 +01:00
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-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
if ( is < HTML : : HTMLProgressElement > ( dom_node ) ) {
auto & progress = static_cast < HTML : : HTMLProgressElement & > ( dom_node ) ;
if ( ! progress . using_system_appearance ( ) ) {
2022-12-06 03:08:20 -03:00
auto bar_style = TRY ( style_computer . compute_style ( progress , CSS : : Selector : : PseudoElement : : ProgressBar ) ) ;
2022-10-08 22:15:32 +01:00
bar_style - > set_property ( CSS : : PropertyID : : Display , CSS : : IdentifierStyleValue : : create ( CSS : : ValueID : : InlineBlock ) ) ;
2022-12-06 03:08:20 -03:00
auto value_style = TRY ( style_computer . compute_style ( progress , CSS : : Selector : : PseudoElement : : ProgressValue ) ) ;
2022-10-08 22:15:32 +01:00
value_style - > set_property ( CSS : : PropertyID : : Display , CSS : : IdentifierStyleValue : : create ( CSS : : ValueID : : Block ) ) ;
2022-07-22 16:08:03 +01:00
auto position = progress . position ( ) ;
value_style - > set_property ( CSS : : PropertyID : : Width , CSS : : PercentageStyleValue : : create ( CSS : : Percentage ( position > = 0 ? round_to < int > ( 100 * position ) : 0 ) ) ) ;
2022-10-08 22:15:32 +01:00
auto bar_display = bar_style - > display ( ) ;
auto value_display = value_style - > display ( ) ;
auto progress_bar = DOM : : Element : : create_layout_node_for_display_type ( document , bar_display , bar_style , nullptr ) ;
auto progress_value = DOM : : Element : : create_layout_node_for_display_type ( document , value_display , value_style , nullptr ) ;
push_parent ( verify_cast < NodeWithStyle > ( * layout_node ) ) ;
push_parent ( verify_cast < NodeWithStyle > ( * progress_bar ) ) ;
insert_node_into_inline_or_block_ancestor ( * progress_value , value_display , AppendOrPrepend : : Append ) ;
pop_parent ( ) ;
insert_node_into_inline_or_block_ancestor ( * progress_bar , bar_display , AppendOrPrepend : : Append ) ;
pop_parent ( ) ;
2022-07-22 16:08:03 +01:00
progress . set_pseudo_element_node ( { } , CSS : : Selector : : PseudoElement : : ProgressBar , progress_bar ) ;
progress . set_pseudo_element_node ( { } , CSS : : Selector : : PseudoElement : : ProgressValue , progress_value ) ;
}
}
2022-11-30 22:15:12 -05:00
if ( is < HTML : : HTMLInputElement > ( dom_node ) ) {
auto & input_element = static_cast < HTML : : HTMLInputElement & > ( dom_node ) ;
if ( auto placeholder_value = input_element . placeholder_value ( ) ; placeholder_value . has_value ( ) ) {
2022-12-06 03:08:20 -03:00
auto placeholder_style = TRY ( style_computer . compute_style ( input_element , CSS : : Selector : : PseudoElement : : Placeholder ) ) ;
2022-11-30 22:15:12 -05:00
auto placeholder = DOM : : Element : : create_layout_node_for_display_type ( document , placeholder_style - > display ( ) , placeholder_style , nullptr ) ;
2022-12-14 17:40:33 +00:00
auto text = document . heap ( ) . allocate < DOM : : Text > ( document . realm ( ) , document , * placeholder_value ) ;
auto text_node = document . heap ( ) . allocate_without_realm < Layout : : TextNode > ( document , * text ) ;
2022-11-30 22:15:12 -05:00
text_node - > set_generated ( true ) ;
push_parent ( verify_cast < NodeWithStyle > ( * layout_node ) ) ;
push_parent ( verify_cast < NodeWithStyle > ( * placeholder ) ) ;
insert_node_into_inline_or_block_ancestor ( * text_node , text_node - > display ( ) , AppendOrPrepend : : Append ) ;
pop_parent ( ) ;
insert_node_into_inline_or_block_ancestor ( * placeholder , placeholder - > display ( ) , AppendOrPrepend : : Append ) ;
pop_parent ( ) ;
input_element . set_pseudo_element_node ( { } , CSS : : Selector : : PseudoElement : : Placeholder , placeholder ) ;
}
}
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 ;
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
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 ) {
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 ;
} ) ;
}
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 ) {
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 ) ;
generate_missing_parents ( root ) ;
}
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.
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 ) {
2022-10-06 16:02:53 +02: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
}
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 )
{
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 >
2022-10-17 14:41:50 +02:00
static void wrap_in_anonymous ( Vector < JS : : Handle < Node > > & sequence , Node * nearest_sibling )
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 ( ) ;
static_cast < CSS : : MutableComputedValues & > ( computed_values ) . set_display ( WrapperBoxType : : static_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
}
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.
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 ) ;
} ) ;
} ) ;
}
2023-01-04 16:38:42 +03:00
void TreeBuilder : : generate_missing_parents ( NodeWithStyle & root )
2021-01-07 18:00:51 +01:00
{
2023-01-09 08:27:39 +03:00
Vector < JS : : Handle < TableBox > > 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 ) {
wrap_in_anonymous < TableRowBox > ( sequence , nearest_sibling ) ;
} ) ;
}
// 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 ) {
wrap_in_anonymous < TableBox > ( sequence , nearest_sibling ) ;
} ) ;
}
// 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 ) {
wrap_in_anonymous < TableBox > ( sequence , nearest_sibling ) ;
} ) ;
}
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 ( ) ) {
table_roots_to_wrap . append ( static_cast < TableBox & > ( parent ) ) ;
}
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 ( ) ;
CSS : : ComputedValues wrapper_computed_values ;
// 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;
// all other values of non-inheritable properties are used on the table box and not the table wrapper box.
// (Where the table element's values are not used on the table and table wrapper boxes, the initial values are used instead.)
auto & mutable_wrapper_computed_values = static_cast < CSS : : MutableComputedValues & > ( wrapper_computed_values ) ;
mutable_wrapper_computed_values . set_display ( CSS : : Display ( CSS : : Display : : Outside : : Block , CSS : : Display : : Inside : : FlowRoot ) ) ;
mutable_wrapper_computed_values . set_position ( table_box - > computed_values ( ) . position ( ) ) ;
mutable_wrapper_computed_values . set_inset ( table_box - > computed_values ( ) . inset ( ) ) ;
mutable_wrapper_computed_values . set_float ( table_box - > computed_values ( ) . float_ ( ) ) ;
2023-01-09 17:38:52 +03:00
mutable_wrapper_computed_values . set_clear ( table_box - > computed_values ( ) . clear ( ) ) ;
2023-01-09 08:27:39 +03:00
mutable_wrapper_computed_values . set_margin ( table_box - > computed_values ( ) . margin ( ) ) ;
table_box - > reset_table_box_computed_values_used_by_wrapper_to_init_values ( ) ;
auto wrapper = parent . heap ( ) . allocate_without_realm < BlockContainer > ( parent . document ( ) , nullptr , move ( wrapper_computed_values ) ) ;
parent . remove_child ( * table_box ) ;
wrapper - > append_child ( * table_box ) ;
parent . insert_before ( * wrapper , * nearest_sibling ) ;
}
2021-01-07 18:00:51 +01:00
}
2020-03-07 10:27:02 +01:00
}