2020-01-18 09:38:21 +01:00
/*
2021-01-07 18:00:51 +01:00
* Copyright ( c ) 2018 - 2021 , Andreas Kling < kling @ serenityos . org >
2020-01-18 09:38:21 +01:00
* All rights reserved .
*
* Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions are met :
*
* 1. Redistributions of source code must retain the above copyright notice , this
* list of conditions and the following disclaimer .
*
* 2. Redistributions in binary form must reproduce the above copyright notice ,
* this list of conditions and the following disclaimer in the documentation
* and / or other materials provided with the distribution .
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS " AS IS "
* AND ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT , INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL
* DAMAGES ( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY ,
* OR TORT ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
*/
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>
# include <LibWeb/Layout/InitialContainingBlockBox.h>
2020-11-22 15:53:01 +01:00
# include <LibWeb/Layout/Node.h>
# 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
2020-11-25 20:29:03 +01:00
TreeBuilder : : TreeBuilder ( )
2019-10-15 12:22:41 +02:00
{
}
2021-01-02 03:30:04 +01:00
// The insertion_parent_for_*() functions maintain the invariant that block-level boxes must have either
// only block-level children or only inline-level children.
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 ;
if ( ! layout_parent . has_children ( ) | | layout_parent . children_are_inline ( ) )
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 ( ) ;
}
static Layout : : Node & insertion_parent_for_block_node ( Layout : : Node & layout_parent , Layout : : Node & layout_node )
{
2021-01-02 03:30:04 +01:00
if ( ! layout_parent . has_children ( ) ) {
// 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
}
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
}
2021-01-06 14:10:53 +01:00
layout_parent . append_child ( adopt ( * new BlockBox ( layout_node . document ( ) , nullptr , layout_parent . computed_values ( ) . clone_inherited_values ( ) ) ) ) ;
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
}
void TreeBuilder : : create_layout_tree ( DOM : : Node & dom_node )
{
// 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-01-06 14:27:40 +01:00
auto layout_node = dom_node . create_layout_node ( ) ;
2020-11-26 21:18:34 +01:00
if ( ! layout_node )
return ;
2019-10-17 23:32:08 +02:00
2021-02-10 18:23:52 +01:00
if ( ! dom_node . parent_or_shadow_host ( ) ) {
2020-11-26 21:18:34 +01:00
m_layout_root = layout_node ;
} else {
if ( layout_node - > is_inline ( ) ) {
// Inlines can be inserted into the nearest ancestor.
2021-01-06 14:10:53 +01:00
auto & insertion_point = insertion_parent_for_inline_node ( * m_parent_stack . last ( ) ) ;
2020-11-26 21:18:34 +01:00
insertion_point . append_child ( * layout_node ) ;
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.
2020-11-29 12:39:10 +01:00
auto & nearest_non_inline_ancestor = [ & ] ( ) - > Layout : : Node & {
2020-11-26 21:18:34 +01:00
for ( ssize_t i = m_parent_stack . size ( ) - 1 ; i > = 0 ; - - i ) {
2020-11-29 22:24:53 +01:00
if ( ! m_parent_stack [ i ] - > is_inline ( ) | | m_parent_stack [ i ] - > is_inline_block ( ) )
2020-11-26 21:18:34 +01:00
return * m_parent_stack [ i ] ;
}
2021-02-23 20:42:32 +01:00
VERIFY_NOT_REACHED ( ) ;
2020-11-26 21:18:34 +01:00
} ( ) ;
2020-11-29 12:39:10 +01:00
auto & insertion_point = insertion_parent_for_block_node ( nearest_non_inline_ancestor , * layout_node ) ;
2020-11-26 21:18:34 +01:00
insertion_point . append_child ( * layout_node ) ;
insertion_point . set_children_are_inline ( false ) ;
}
}
2021-02-10 18:23:52 +01:00
auto * shadow_root = is < DOM : : Element > ( dom_node ) ? downcast < DOM : : Element > ( dom_node ) . shadow_root ( ) : nullptr ;
if ( ( dom_node . has_children ( ) | | shadow_root ) & & layout_node - > can_have_children ( ) ) {
2021-01-06 14:10:53 +01:00
push_parent ( downcast < NodeWithStyle > ( * layout_node ) ) ;
2021-02-10 18:23:52 +01:00
if ( shadow_root )
create_layout_tree ( * shadow_root ) ;
2020-11-26 21:18:34 +01:00
downcast < DOM : : ParentNode > ( dom_node ) . for_each_child ( [ & ] ( auto & dom_child ) {
create_layout_tree ( dom_child ) ;
} ) ;
pop_parent ( ) ;
}
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
{
2020-11-29 16:11:54 +01:00
if ( dom_node . parent ( ) ) {
// We're building a partial layout tree, so start by building up the stack of parent layout nodes.
for ( auto * ancestor = dom_node . parent ( ) - > layout_node ( ) ; ancestor ; ancestor = ancestor - > parent ( ) )
2021-01-06 14:10:53 +01:00
m_parent_stack . prepend ( downcast < NodeWithStyle > ( ancestor ) ) ;
2020-06-06 22:13:24 +02:00
}
2020-11-26 21:18:34 +01:00
create_layout_tree ( dom_node ) ;
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-01-07 18:00:51 +01:00
template < CSS : : Display display , typename Callback >
void TreeBuilder : : for_each_in_tree_with_display ( NodeWithStyle & root , Callback callback )
{
2021-04-06 18:38:10 +01:00
root . for_each_in_inclusive_subtree_of_type < Box > ( [ & ] ( auto & box ) {
2021-01-07 18:00:51 +01:00
if ( box . computed_values ( ) . display ( ) = = display )
callback ( box ) ;
return IterationDecision : : Continue ;
} ) ;
}
void TreeBuilder : : fixup_tables ( NodeWithStyle & root )
{
// NOTE: Even if we only do a partial build, we always do fixup from the 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:
NonnullRefPtrVector < Box > to_remove ;
// Children of a table-column.
for_each_in_tree_with_display < CSS : : Display : : TableColumn > ( root , [ & ] ( Box & table_column ) {
table_column . for_each_child ( [ & ] ( auto & child ) {
to_remove . append ( child ) ;
} ) ;
} ) ;
// Children of a table-column-group which are not a table-column.
for_each_in_tree_with_display < CSS : : Display : : TableColumnGroup > ( root , [ & ] ( Box & table_column_group ) {
table_column_group . for_each_child ( [ & ] ( auto & child ) {
if ( child . computed_values ( ) . display ( ) ! = CSS : : Display : : TableColumn )
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 )
{
return display = = CSS : : Display : : TableRow | | display = = CSS : : Display : : TableColumn ;
}
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.
return display = = CSS : : Display : : TableRowGroup | | display = = CSS : : Display : : TableHeaderGroup | | display = = CSS : : Display : : TableFooterGroup
| | display = = CSS : : Display : : TableColumnGroup ;
2021-01-07 18:00:51 +01:00
}
static bool is_not_proper_table_child ( const Node & node )
{
if ( ! node . has_style ( ) )
return true ;
auto display = node . computed_values ( ) . display ( ) ;
return ! is_table_track_group ( display ) & & ! is_table_track ( display ) & & display ! = CSS : : Display : : TableCaption ;
}
static bool is_not_table_row ( const Node & node )
{
if ( ! node . has_style ( ) )
return true ;
auto display = node . computed_values ( ) . display ( ) ;
return display ! = CSS : : Display : : TableRow ;
}
static bool is_not_table_cell ( const Node & node )
{
if ( ! node . has_style ( ) )
return true ;
auto display = node . computed_values ( ) . display ( ) ;
return display ! = CSS : : Display : : TableCell ;
}
template < typename Matcher , typename Callback >
static void for_each_sequence_of_consecutive_children_matching ( NodeWithStyle & parent , Matcher matcher , Callback callback )
{
NonnullRefPtrVector < Node > sequence ;
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 ( ) ) {
callback ( sequence , next_sibling ) ;
sequence . clear ( ) ;
}
}
}
if ( ! sequence . is_empty ( ) )
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 ( ) ) ;
auto wrapper = adopt ( * new WrapperBoxType ( parent . document ( ) , nullptr , move ( computed_values ) ) ) ;
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.
for_each_in_tree_with_display < CSS : : Display : : Table > ( root , [ & ] ( auto & parent ) {
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.
for_each_in_tree_with_display < CSS : : Display : : TableRowGroup > ( root , [ & ] ( auto & parent ) {
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.
for_each_in_tree_with_display < CSS : : Display : : TableHeaderGroup > ( root , [ & ] ( auto & parent ) {
for_each_sequence_of_consecutive_children_matching ( parent , is_not_table_row , [ & ] ( auto & sequence , auto nearest_sibling ) {
wrap_in_anonymous < TableRowBox > ( sequence , nearest_sibling ) ;
} ) ;
} ) ;
for_each_in_tree_with_display < CSS : : Display : : TableFooterGroup > ( root , [ & ] ( auto & parent ) {
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
for_each_in_tree_with_display < CSS : : Display : : TableRow > ( root , [ & ] ( auto & parent ) {
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
}