2020-11-22 13:38:18 +01:00
/*
* Copyright ( c ) 2020 , Andreas Kling < kling @ serenityos . org >
*
2021-04-22 01:24:48 -07:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-11-22 13:38:18 +01:00
*/
# include <LibWeb/CSS/Length.h>
# include <LibWeb/DOM/Node.h>
2021-10-06 20:02:41 +02:00
# include <LibWeb/Layout/BlockContainer.h>
2020-11-22 13:38:18 +01:00
# include <LibWeb/Layout/BlockFormattingContext.h>
2020-11-22 15:53:01 +01:00
# include <LibWeb/Layout/Box.h>
2021-09-08 11:27:46 +02:00
# include <LibWeb/Layout/InitialContainingBlock.h>
2020-11-22 13:38:18 +01:00
# include <LibWeb/Layout/InlineFormattingContext.h>
2020-11-22 15:53:01 +01:00
# include <LibWeb/Layout/ListItemBox.h>
2021-02-10 08:58:26 +01:00
# include <LibWeb/Layout/ReplacedBox.h>
2021-05-30 12:36:53 +02:00
# include <LibWeb/Page/BrowsingContext.h>
2020-11-22 13:38:18 +01:00
namespace Web : : Layout {
2021-10-06 19:47:10 +02:00
BlockFormattingContext : : BlockFormattingContext ( Box & root , FormattingContext * parent )
: FormattingContext ( root , parent )
2020-11-22 13:38:18 +01:00
{
}
BlockFormattingContext : : ~ BlockFormattingContext ( )
{
}
bool BlockFormattingContext : : is_initial ( ) const
{
2021-10-06 19:47:10 +02:00
return is < InitialContainingBlock > ( root ( ) ) ;
2020-11-22 13:38:18 +01:00
}
2020-12-06 19:48:02 +01:00
void BlockFormattingContext : : run ( Box & box , LayoutMode layout_mode )
2020-11-22 13:38:18 +01:00
{
if ( is_initial ( ) ) {
layout_initial_containing_block ( layout_mode ) ;
return ;
}
2020-12-06 19:48:02 +01:00
// FIXME: BFC currently computes the width+height of the target box.
2020-11-22 13:38:18 +01:00
// This is necessary to be able to place absolutely positioned descendants.
// The same work is also done by the parent BFC for each of its blocks..
if ( layout_mode = = LayoutMode : : Default )
2020-12-06 19:48:02 +01:00
compute_width ( box ) ;
2020-11-22 13:38:18 +01:00
2020-12-06 19:48:02 +01:00
if ( box . children_are_inline ( ) ) {
layout_inline_children ( box , layout_mode ) ;
2020-11-22 13:38:18 +01:00
} else {
2020-12-06 19:48:02 +01:00
layout_block_level_children ( box , layout_mode ) ;
2020-11-22 13:38:18 +01:00
}
2020-12-14 11:33:11 +01:00
if ( layout_mode = = LayoutMode : : Default ) {
2020-12-06 19:48:02 +01:00
compute_height ( box ) ;
2020-12-14 11:33:11 +01:00
box . for_each_child_of_type < Box > ( [ & ] ( auto & child_box ) {
if ( child_box . is_absolutely_positioned ( ) ) {
2021-01-06 18:18:46 +01:00
layout_absolutely_positioned_element ( child_box ) ;
2020-12-14 11:33:11 +01:00
}
return IterationDecision : : Continue ;
} ) ;
}
2021-09-18 18:42:27 +02:00
apply_transformations_to_children ( box ) ;
}
void BlockFormattingContext : : apply_transformations_to_children ( Box & box )
{
box . for_each_child_of_type < Box > ( [ & ] ( auto & child_box ) {
float transform_y_offset = 0.0f ;
if ( ! child_box . computed_values ( ) . transformations ( ) . is_empty ( ) ) {
// FIXME: All transformations can be interpreted as successive 3D-matrix operations on the box, we don't do that yet.
// https://drafts.csswg.org/css-transforms/#serialization-of-the-computed-value
for ( auto transformation : child_box . computed_values ( ) . transformations ( ) ) {
switch ( transformation . function ) {
case CSS : : TransformFunction : : TranslateY :
if ( transformation . values . size ( ) ! = 1 )
continue ;
transformation . values . first ( ) . visit (
[ & ] ( CSS : : Length & value ) {
transform_y_offset + = value . resolved_or_zero ( child_box , child_box . width ( ) ) . to_px ( child_box ) ;
} ,
[ & ] ( float value ) {
transform_y_offset + = value ;
} ,
[ & ] ( auto & ) {
dbgln ( " FIXME: Implement unsupported transformation function value type! " ) ;
} ) ;
break ;
default :
dbgln ( " FIXME: Implement missing transform function! " ) ;
}
}
}
auto untransformed_offset = child_box . effective_offset ( ) ;
child_box . set_offset ( untransformed_offset . x ( ) , untransformed_offset . y ( ) + transform_y_offset ) ;
} ) ;
2020-11-22 13:38:18 +01:00
}
2020-11-22 15:53:01 +01:00
void BlockFormattingContext : : compute_width ( Box & box )
2020-11-22 13:38:18 +01:00
{
2021-01-06 18:18:46 +01:00
if ( box . is_absolutely_positioned ( ) ) {
compute_width_for_absolutely_positioned_element ( box ) ;
return ;
}
2021-01-01 18:55:47 +01:00
if ( is < ReplacedBox > ( box ) ) {
2020-11-22 15:53:01 +01:00
// FIXME: This should not be done *by* ReplacedBox
2021-06-24 19:53:42 +02:00
auto & replaced = verify_cast < ReplacedBox > ( box ) ;
2020-11-22 13:38:18 +01:00
replaced . prepare_for_replaced_layout ( ) ;
2020-12-11 22:27:09 +01:00
compute_width_for_block_level_replaced_element_in_normal_flow ( replaced ) ;
2020-11-22 13:38:18 +01:00
return ;
}
2020-12-06 01:08:14 +01:00
if ( box . is_floating ( ) ) {
compute_width_for_floating_box ( box ) ;
return ;
}
2021-01-06 10:34:31 +01:00
auto & computed_values = box . computed_values ( ) ;
2020-11-22 13:38:18 +01:00
float width_of_containing_block = box . width_of_logical_containing_block ( ) ;
auto zero_value = CSS : : Length : : make_px ( 0 ) ;
auto margin_left = CSS : : Length : : make_auto ( ) ;
auto margin_right = CSS : : Length : : make_auto ( ) ;
2021-01-06 10:34:31 +01:00
const auto padding_left = computed_values . padding ( ) . left . resolved_or_zero ( box , width_of_containing_block ) ;
const auto padding_right = computed_values . padding ( ) . right . resolved_or_zero ( box , width_of_containing_block ) ;
2020-11-22 13:38:18 +01:00
auto try_compute_width = [ & ] ( const auto & a_width ) {
CSS : : Length width = a_width ;
2021-01-06 10:34:31 +01:00
margin_left = computed_values . margin ( ) . left . resolved_or_zero ( box , width_of_containing_block ) ;
margin_right = computed_values . margin ( ) . right . resolved_or_zero ( box , width_of_containing_block ) ;
2020-11-22 13:38:18 +01:00
2021-01-06 10:34:31 +01:00
float total_px = computed_values . border_left ( ) . width + computed_values . border_right ( ) . width ;
2020-11-22 13:38:18 +01:00
for ( auto & value : { margin_left , padding_left , width , padding_right , margin_right } ) {
total_px + = value . to_px ( box ) ;
}
2020-12-11 22:31:29 +01:00
if ( ! box . is_inline ( ) ) {
2020-11-22 13:38:18 +01:00
// 10.3.3 Block-level, non-replaced elements in normal flow
// If 'width' is not 'auto' and 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' (plus any of 'margin-left' or 'margin-right' that are not 'auto') is larger than the width of the containing block, then any 'auto' values for 'margin-left' or 'margin-right' are, for the following rules, treated as zero.
if ( width . is_auto ( ) & & total_px > width_of_containing_block ) {
if ( margin_left . is_auto ( ) )
margin_left = zero_value ;
if ( margin_right . is_auto ( ) )
margin_right = zero_value ;
}
// 10.3.3 cont'd.
auto underflow_px = width_of_containing_block - total_px ;
if ( width . is_auto ( ) ) {
if ( margin_left . is_auto ( ) )
margin_left = zero_value ;
if ( margin_right . is_auto ( ) )
margin_right = zero_value ;
if ( underflow_px > = 0 ) {
width = CSS : : Length ( underflow_px , CSS : : Length : : Type : : Px ) ;
} else {
width = zero_value ;
margin_right = CSS : : Length ( margin_right . to_px ( box ) + underflow_px , CSS : : Length : : Type : : Px ) ;
}
} else {
if ( ! margin_left . is_auto ( ) & & ! margin_right . is_auto ( ) ) {
margin_right = CSS : : Length ( margin_right . to_px ( box ) + underflow_px , CSS : : Length : : Type : : Px ) ;
} else if ( ! margin_left . is_auto ( ) & & margin_right . is_auto ( ) ) {
margin_right = CSS : : Length ( underflow_px , CSS : : Length : : Type : : Px ) ;
} else if ( margin_left . is_auto ( ) & & ! margin_right . is_auto ( ) ) {
margin_left = CSS : : Length ( underflow_px , CSS : : Length : : Type : : Px ) ;
} else { // margin_left.is_auto() && margin_right.is_auto()
auto half_of_the_underflow = CSS : : Length ( underflow_px / 2 , CSS : : Length : : Type : : Px ) ;
margin_left = half_of_the_underflow ;
margin_right = half_of_the_underflow ;
}
}
2020-12-11 22:31:29 +01:00
} else if ( box . is_inline_block ( ) ) {
2020-11-22 13:38:18 +01:00
// 10.3.9 'Inline-block', non-replaced elements in normal flow
// A computed value of 'auto' for 'margin-left' or 'margin-right' becomes a used value of '0'.
if ( margin_left . is_auto ( ) )
margin_left = zero_value ;
if ( margin_right . is_auto ( ) )
margin_right = zero_value ;
// If 'width' is 'auto', the used value is the shrink-to-fit width as for floating elements.
if ( width . is_auto ( ) ) {
// Find the available width: in this case, this is the width of the containing
// block minus the used values of 'margin-left', 'border-left-width', 'padding-left',
// 'padding-right', 'border-right-width', 'margin-right', and the widths of any relevant scroll bars.
float available_width = width_of_containing_block
2021-01-06 10:34:31 +01:00
- margin_left . to_px ( box ) - computed_values . border_left ( ) . width - padding_left . to_px ( box )
- padding_right . to_px ( box ) - computed_values . border_right ( ) . width - margin_right . to_px ( box ) ;
2020-11-22 13:38:18 +01:00
auto result = calculate_shrink_to_fit_widths ( box ) ;
// Then the shrink-to-fit width is: min(max(preferred minimum width, available width), preferred width).
width = CSS : : Length ( min ( max ( result . preferred_minimum_width , available_width ) , result . preferred_width ) , CSS : : Length : : Type : : Px ) ;
}
}
return width ;
} ;
2021-01-06 10:34:31 +01:00
auto specified_width = computed_values . width ( ) . resolved_or_auto ( box , width_of_containing_block ) ;
2020-11-22 13:38:18 +01:00
// 1. The tentative used width is calculated (without 'min-width' and 'max-width')
auto used_width = try_compute_width ( specified_width ) ;
// 2. The tentative used width is greater than 'max-width', the rules above are applied again,
// but this time using the computed value of 'max-width' as the computed value for 'width'.
2021-01-06 10:34:31 +01:00
auto specified_max_width = computed_values . max_width ( ) . resolved_or_auto ( box , width_of_containing_block ) ;
2020-11-22 13:38:18 +01:00
if ( ! specified_max_width . is_auto ( ) ) {
if ( used_width . to_px ( box ) > specified_max_width . to_px ( box ) ) {
used_width = try_compute_width ( specified_max_width ) ;
}
}
// 3. If the resulting width is smaller than 'min-width', the rules above are applied again,
// but this time using the value of 'min-width' as the computed value for 'width'.
2021-01-06 10:34:31 +01:00
auto specified_min_width = computed_values . min_width ( ) . resolved_or_auto ( box , width_of_containing_block ) ;
2020-11-22 13:38:18 +01:00
if ( ! specified_min_width . is_auto ( ) ) {
if ( used_width . to_px ( box ) < specified_min_width . to_px ( box ) ) {
used_width = try_compute_width ( specified_min_width ) ;
}
}
box . set_width ( used_width . to_px ( box ) ) ;
2020-12-12 21:02:06 +01:00
box . box_model ( ) . margin . left = margin_left . to_px ( box ) ;
box . box_model ( ) . margin . right = margin_right . to_px ( box ) ;
2021-01-06 10:34:31 +01:00
box . box_model ( ) . border . left = computed_values . border_left ( ) . width ;
box . box_model ( ) . border . right = computed_values . border_right ( ) . width ;
2020-12-12 21:02:06 +01:00
box . box_model ( ) . padding . left = padding_left . to_px ( box ) ;
box . box_model ( ) . padding . right = padding_right . to_px ( box ) ;
2020-11-22 13:38:18 +01:00
}
2020-12-06 01:08:14 +01:00
void BlockFormattingContext : : compute_width_for_floating_box ( Box & box )
{
// 10.3.5 Floating, non-replaced elements
2021-01-06 10:34:31 +01:00
auto & computed_values = box . computed_values ( ) ;
2020-12-06 01:08:14 +01:00
float width_of_containing_block = box . width_of_logical_containing_block ( ) ;
auto zero_value = CSS : : Length : : make_px ( 0 ) ;
2021-04-29 18:18:53 +03:00
auto margin_left = computed_values . margin ( ) . left . resolved_or_zero ( box , width_of_containing_block ) ;
auto margin_right = computed_values . margin ( ) . right . resolved_or_zero ( box , width_of_containing_block ) ;
2021-01-06 10:34:31 +01:00
const auto padding_left = computed_values . padding ( ) . left . resolved_or_zero ( box , width_of_containing_block ) ;
const auto padding_right = computed_values . padding ( ) . right . resolved_or_zero ( box , width_of_containing_block ) ;
2020-12-06 01:08:14 +01:00
// If 'margin-left', or 'margin-right' are computed as 'auto', their used value is '0'.
if ( margin_left . is_auto ( ) )
margin_left = zero_value ;
if ( margin_right . is_auto ( ) )
margin_right = zero_value ;
2021-01-06 10:34:31 +01:00
auto width = computed_values . width ( ) . resolved_or_auto ( box , width_of_containing_block ) ;
2020-12-06 01:08:14 +01:00
// If 'width' is computed as 'auto', the used value is the "shrink-to-fit" width.
if ( width . is_auto ( ) ) {
// Find the available width: in this case, this is the width of the containing
// block minus the used values of 'margin-left', 'border-left-width', 'padding-left',
// 'padding-right', 'border-right-width', 'margin-right', and the widths of any relevant scroll bars.
float available_width = width_of_containing_block
2021-01-06 10:34:31 +01:00
- margin_left . to_px ( box ) - computed_values . border_left ( ) . width - padding_left . to_px ( box )
- padding_right . to_px ( box ) - computed_values . border_right ( ) . width - margin_right . to_px ( box ) ;
2020-12-06 01:08:14 +01:00
auto result = calculate_shrink_to_fit_widths ( box ) ;
// Then the shrink-to-fit width is: min(max(preferred minimum width, available width), preferred width).
width = CSS : : Length ( min ( max ( result . preferred_minimum_width , available_width ) , result . preferred_width ) , CSS : : Length : : Type : : Px ) ;
}
float final_width = width . resolved_or_zero ( box , width_of_containing_block ) . to_px ( box ) ;
box . set_width ( final_width ) ;
2021-04-29 18:18:53 +03:00
box . box_model ( ) . margin . left = margin_left . to_px ( box ) ;
box . box_model ( ) . margin . right = margin_right . to_px ( box ) ;
box . box_model ( ) . border . left = computed_values . border_left ( ) . width ;
box . box_model ( ) . border . right = computed_values . border_right ( ) . width ;
box . box_model ( ) . padding . left = padding_left . to_px ( box ) ;
box . box_model ( ) . padding . right = padding_right . to_px ( box ) ;
2020-12-06 01:08:14 +01:00
}
2020-12-11 22:27:09 +01:00
void BlockFormattingContext : : compute_width_for_block_level_replaced_element_in_normal_flow ( ReplacedBox & box )
{
box . set_width ( compute_width_for_replaced_element ( box ) ) ;
}
2021-04-22 10:59:04 +03:00
static float compute_auto_height_for_block_level_element ( const Box & box )
{
Optional < float > top ;
Optional < float > bottom ;
if ( box . children_are_inline ( ) ) {
// If it only has inline-level children, the height is the distance between
// the top of the topmost line box and the bottom of the bottommost line box.
if ( ! box . line_boxes ( ) . is_empty ( ) ) {
for ( auto & fragment : box . line_boxes ( ) . first ( ) . fragments ( ) ) {
if ( ! top . has_value ( ) | | fragment . offset ( ) . y ( ) < top . value ( ) )
top = fragment . offset ( ) . y ( ) ;
}
for ( auto & fragment : box . line_boxes ( ) . last ( ) . fragments ( ) ) {
if ( ! bottom . has_value ( ) | | ( fragment . offset ( ) . y ( ) + fragment . height ( ) ) > bottom . value ( ) )
bottom = fragment . offset ( ) . y ( ) + fragment . height ( ) ;
}
}
} else {
// If it has block-level children, the height is the distance between
// the top margin-edge of the topmost block-level child box
// and the bottom margin-edge of the bottommost block-level child box.
box . for_each_child_of_type < Box > ( [ & ] ( Layout : : Box & child_box ) {
if ( child_box . is_absolutely_positioned ( ) )
return IterationDecision : : Continue ;
if ( ( box . computed_values ( ) . overflow_y ( ) = = CSS : : Overflow : : Visible ) & & child_box . is_floating ( ) )
return IterationDecision : : Continue ;
float child_box_top = child_box . effective_offset ( ) . y ( ) - child_box . box_model ( ) . margin_box ( ) . top ;
float child_box_bottom = child_box . effective_offset ( ) . y ( ) + child_box . height ( ) + child_box . box_model ( ) . margin_box ( ) . bottom ;
if ( ! top . has_value ( ) | | child_box_top < top . value ( ) )
top = child_box_top ;
if ( ! bottom . has_value ( ) | | child_box_bottom > bottom . value ( ) )
bottom = child_box_bottom ;
return IterationDecision : : Continue ;
} ) ;
}
return bottom . value_or ( 0 ) - top . value_or ( 0 ) ;
}
2021-05-30 21:57:13 +02:00
float BlockFormattingContext : : compute_theoretical_height ( const Box & box )
2021-03-08 22:49:34 +01:00
{
auto & computed_values = box . computed_values ( ) ;
auto & containing_block = * box . containing_block ( ) ;
2020-11-22 13:38:18 +01:00
2021-03-08 22:49:34 +01:00
// Then work out what the height is, based on box type and CSS properties.
float height = 0 ;
if ( is < ReplacedBox > ( box ) ) {
2021-06-24 19:53:42 +02:00
height = compute_height_for_replaced_element ( verify_cast < ReplacedBox > ( box ) ) ;
2021-03-08 22:49:34 +01:00
} else {
2021-04-29 18:20:56 +03:00
if ( box . computed_values ( ) . height ( ) . is_undefined_or_auto ( )
| | ( computed_values . height ( ) . is_percentage ( ) & & ! containing_block . computed_values ( ) . height ( ) . is_absolute ( ) ) ) {
2021-03-08 22:49:34 +01:00
height = compute_auto_height_for_block_level_element ( box ) ;
} else {
2021-04-29 18:20:56 +03:00
height = computed_values . height ( ) . resolved_or_auto ( box , containing_block . height ( ) ) . to_px ( box ) ;
2021-03-08 22:49:34 +01:00
}
2020-11-22 13:38:18 +01:00
}
2021-03-08 22:49:34 +01:00
2021-04-29 18:20:56 +03:00
auto specified_max_height = computed_values . max_height ( ) . resolved_or_auto ( box , containing_block . height ( ) ) ;
if ( ! specified_max_height . is_auto ( )
& & ! ( computed_values . max_height ( ) . is_percentage ( ) & & ! containing_block . computed_values ( ) . height ( ) . is_absolute ( ) ) )
height = min ( height , specified_max_height . to_px ( box ) ) ;
auto specified_min_height = computed_values . min_height ( ) . resolved_or_auto ( box , containing_block . height ( ) ) ;
if ( ! specified_min_height . is_auto ( )
& & ! ( computed_values . min_height ( ) . is_percentage ( ) & & ! containing_block . computed_values ( ) . height ( ) . is_absolute ( ) ) )
height = max ( height , specified_min_height . to_px ( box ) ) ;
2021-05-30 21:57:13 +02:00
return height ;
}
void BlockFormattingContext : : compute_height ( Box & box )
{
auto & computed_values = box . computed_values ( ) ;
auto & containing_block = * box . containing_block ( ) ;
2021-09-22 21:56:50 +02:00
2021-05-30 21:57:13 +02:00
// First, resolve the top/bottom parts of the surrounding box model.
2021-09-22 21:56:50 +02:00
// FIXME: While negative values are generally allowed for margins, for now just ignore those for height calculation
box . box_model ( ) . margin . top = max ( computed_values . margin ( ) . top . resolved_or_zero ( box , containing_block . width ( ) ) . to_px ( box ) , 0 ) ;
box . box_model ( ) . margin . bottom = max ( computed_values . margin ( ) . bottom . resolved_or_zero ( box , containing_block . width ( ) ) . to_px ( box ) , 0 ) ;
2021-05-30 21:57:13 +02:00
box . box_model ( ) . border . top = computed_values . border_top ( ) . width ;
box . box_model ( ) . border . bottom = computed_values . border_bottom ( ) . width ;
box . box_model ( ) . padding . top = computed_values . padding ( ) . top . resolved_or_zero ( box , containing_block . width ( ) ) . to_px ( box ) ;
box . box_model ( ) . padding . bottom = computed_values . padding ( ) . bottom . resolved_or_zero ( box , containing_block . width ( ) ) . to_px ( box ) ;
2021-04-29 18:20:56 +03:00
2021-05-30 21:57:13 +02:00
auto height = compute_theoretical_height ( box ) ;
2021-03-08 22:49:34 +01:00
box . set_height ( height ) ;
2020-11-22 13:38:18 +01:00
}
2021-03-29 12:04:26 -04:00
void BlockFormattingContext : : compute_position ( Box & box )
{
// 9.4.3 Relative positioning
// Once a box has been laid out according to the normal flow or floated, it may be shifted relative to this position.
auto & box_model = box . box_model ( ) ;
auto & computed_values = box . computed_values ( ) ;
float width_of_containing_block = box . width_of_logical_containing_block ( ) ;
auto specified_left = computed_values . offset ( ) . left . resolved_or_zero ( box , width_of_containing_block ) ;
auto specified_right = computed_values . offset ( ) . right . resolved_or_zero ( box , width_of_containing_block ) ;
if ( specified_left . is_auto ( ) & & specified_right . is_auto ( ) ) {
// If both 'left' and 'right' are 'auto' (their initial values), the used values are '0' (i.e., the boxes stay in their original position).
box_model . offset . left = 0 ;
box_model . offset . right = 0 ;
} else if ( specified_left . is_auto ( ) ) {
// If 'left' is 'auto', its used value is minus the value of 'right' (i.e., the boxes move to the left by the value of 'right').
box_model . offset . right = specified_right . to_px ( box ) ;
box_model . offset . left = 0 - box_model . offset . right ;
} else if ( specified_right . is_auto ( ) ) {
// If 'right' is specified as 'auto', its used value is minus the value of 'left'.
box_model . offset . left = specified_left . to_px ( box ) ;
box_model . offset . right = 0 - box_model . offset . left ;
} else {
// If neither 'left' nor 'right' is 'auto', the position is over-constrained, and one of them has to be ignored.
// If the 'direction' property of the containing block is 'ltr', the value of 'left' wins and 'right' becomes -'left'.
// If 'direction' of the containing block is 'rtl', 'right' wins and 'left' is ignored.
// FIXME: Check direction (assuming 'ltr' for now).
box_model . offset . left = specified_left . to_px ( box ) ;
box_model . offset . right = 0 - box_model . offset . left ;
}
}
2020-12-06 19:48:02 +01:00
void BlockFormattingContext : : layout_inline_children ( Box & box , LayoutMode layout_mode )
2020-11-22 13:38:18 +01:00
{
2020-12-06 19:48:02 +01:00
InlineFormattingContext context ( box , this ) ;
context . run ( box , layout_mode ) ;
2020-11-22 13:38:18 +01:00
}
2020-12-06 19:48:02 +01:00
void BlockFormattingContext : : layout_block_level_children ( Box & box , LayoutMode layout_mode )
2020-11-22 13:38:18 +01:00
{
float content_height = 0 ;
float content_width = 0 ;
2020-12-06 19:48:02 +01:00
box . for_each_child_of_type < Box > ( [ & ] ( auto & child_box ) {
2020-12-14 11:33:11 +01:00
if ( child_box . is_absolutely_positioned ( ) )
2020-12-06 19:48:02 +01:00
return IterationDecision : : Continue ;
2020-12-12 19:31:46 +01:00
if ( child_box . is_floating ( ) ) {
layout_floating_child ( child_box , box ) ;
2020-11-22 13:38:18 +01:00
return IterationDecision : : Continue ;
2020-12-12 19:31:46 +01:00
}
2020-11-22 13:38:18 +01:00
2020-12-06 19:48:02 +01:00
compute_width ( child_box ) ;
layout_inside ( child_box , layout_mode ) ;
compute_height ( child_box ) ;
2020-11-22 13:38:18 +01:00
2021-03-30 15:51:19 -04:00
if ( child_box . computed_values ( ) . position ( ) = = CSS : : Position : : Relative )
compute_position ( child_box ) ;
2021-01-01 18:55:47 +01:00
if ( is < ReplacedBox > ( child_box ) )
2020-12-06 19:48:02 +01:00
place_block_level_replaced_element_in_normal_flow ( child_box , box ) ;
2021-10-06 20:02:41 +02:00
else if ( is < BlockContainer > ( child_box ) )
2020-12-06 19:48:02 +01:00
place_block_level_non_replaced_element_in_normal_flow ( child_box , box ) ;
2020-11-22 13:38:18 +01:00
// FIXME: This should be factored differently. It's uncool that we mutate the tree *during* layout!
// Instead, we should generate the marker box during the tree build.
2020-12-06 19:48:02 +01:00
if ( is < ListItemBox > ( child_box ) )
2021-06-24 19:53:42 +02:00
verify_cast < ListItemBox > ( child_box ) . layout_marker ( ) ;
2020-11-22 13:38:18 +01:00
2020-12-12 21:02:06 +01:00
content_height = max ( content_height , child_box . effective_offset ( ) . y ( ) + child_box . height ( ) + child_box . box_model ( ) . margin_box ( ) . bottom ) ;
2021-06-24 19:53:42 +02:00
content_width = max ( content_width , verify_cast < Box > ( child_box ) . width ( ) ) ;
2020-11-22 13:38:18 +01:00
return IterationDecision : : Continue ;
} ) ;
if ( layout_mode ! = LayoutMode : : Default ) {
2021-01-06 10:34:31 +01:00
if ( box . computed_values ( ) . width ( ) . is_undefined ( ) | | box . computed_values ( ) . width ( ) . is_auto ( ) )
2020-12-06 19:48:02 +01:00
box . set_width ( content_width ) ;
2020-11-22 13:38:18 +01:00
}
}
2020-12-06 19:48:02 +01:00
void BlockFormattingContext : : place_block_level_replaced_element_in_normal_flow ( Box & child_box , Box & containing_block )
2020-11-22 13:38:18 +01:00
{
2021-02-23 20:42:32 +01:00
VERIFY ( ! containing_block . is_absolutely_positioned ( ) ) ;
2020-12-06 19:48:02 +01:00
auto & replaced_element_box_model = child_box . box_model ( ) ;
2020-11-22 13:38:18 +01:00
2021-01-06 10:34:31 +01:00
replaced_element_box_model . margin . top = child_box . computed_values ( ) . margin ( ) . top . resolved_or_zero ( containing_block , containing_block . width ( ) ) . to_px ( child_box ) ;
replaced_element_box_model . margin . bottom = child_box . computed_values ( ) . margin ( ) . bottom . resolved_or_zero ( containing_block , containing_block . width ( ) ) . to_px ( child_box ) ;
replaced_element_box_model . border . top = child_box . computed_values ( ) . border_top ( ) . width ;
replaced_element_box_model . border . bottom = child_box . computed_values ( ) . border_bottom ( ) . width ;
replaced_element_box_model . padding . top = child_box . computed_values ( ) . padding ( ) . top . resolved_or_zero ( containing_block , containing_block . width ( ) ) . to_px ( child_box ) ;
replaced_element_box_model . padding . bottom = child_box . computed_values ( ) . padding ( ) . bottom . resolved_or_zero ( containing_block , containing_block . width ( ) ) . to_px ( child_box ) ;
2020-11-22 13:38:18 +01:00
2020-12-12 21:02:06 +01:00
float x = replaced_element_box_model . margin . left
+ replaced_element_box_model . border . left
+ replaced_element_box_model . padding . left
+ replaced_element_box_model . offset . left ;
2020-11-22 13:38:18 +01:00
2020-12-12 21:02:06 +01:00
float y = replaced_element_box_model . margin_box ( ) . top + containing_block . box_model ( ) . offset . top ;
2020-11-22 13:38:18 +01:00
2020-12-06 19:48:02 +01:00
child_box . set_offset ( x , y ) ;
2020-11-22 13:38:18 +01:00
}
2020-12-06 19:48:02 +01:00
void BlockFormattingContext : : place_block_level_non_replaced_element_in_normal_flow ( Box & child_box , Box & containing_block )
2020-11-22 13:38:18 +01:00
{
2020-12-06 19:48:02 +01:00
auto & box_model = child_box . box_model ( ) ;
2021-01-06 10:34:31 +01:00
auto & computed_values = child_box . computed_values ( ) ;
2020-11-22 13:38:18 +01:00
2021-01-06 10:34:31 +01:00
box_model . margin . top = computed_values . margin ( ) . top . resolved_or_zero ( containing_block , containing_block . width ( ) ) . to_px ( child_box ) ;
box_model . margin . bottom = computed_values . margin ( ) . bottom . resolved_or_zero ( containing_block , containing_block . width ( ) ) . to_px ( child_box ) ;
box_model . border . top = computed_values . border_top ( ) . width ;
box_model . border . bottom = computed_values . border_bottom ( ) . width ;
box_model . padding . top = computed_values . padding ( ) . top . resolved_or_zero ( containing_block , containing_block . width ( ) ) . to_px ( child_box ) ;
box_model . padding . bottom = computed_values . padding ( ) . bottom . resolved_or_zero ( containing_block , containing_block . width ( ) ) . to_px ( child_box ) ;
2020-11-22 13:38:18 +01:00
2020-12-12 21:02:06 +01:00
float x = box_model . margin . left
+ box_model . border . left
+ box_model . padding . left
+ box_model . offset . left ;
2020-11-22 13:38:18 +01:00
2021-01-06 10:34:31 +01:00
if ( containing_block . computed_values ( ) . text_align ( ) = = CSS : : TextAlign : : LibwebCenter ) {
2020-12-06 19:48:02 +01:00
x = ( containing_block . width ( ) / 2 ) - child_box . width ( ) / 2 ;
2020-11-22 13:38:18 +01:00
}
2020-12-12 21:02:06 +01:00
float y = box_model . margin_box ( ) . top
+ box_model . offset . top ;
2020-11-22 13:38:18 +01:00
// NOTE: Empty (0-height) preceding siblings have their margins collapsed with *their* preceding sibling, etc.
float collapsed_bottom_margin_of_preceding_siblings = 0 ;
2021-10-06 20:02:41 +02:00
auto * relevant_sibling = child_box . previous_sibling_of_type < Layout : : BlockContainer > ( ) ;
2020-11-22 13:38:18 +01:00
while ( relevant_sibling ! = nullptr ) {
if ( ! relevant_sibling - > is_absolutely_positioned ( ) & & ! relevant_sibling - > is_floating ( ) ) {
2020-12-12 21:02:06 +01:00
collapsed_bottom_margin_of_preceding_siblings = max ( collapsed_bottom_margin_of_preceding_siblings , relevant_sibling - > box_model ( ) . margin . bottom ) ;
2021-01-02 03:46:26 +01:00
if ( relevant_sibling - > border_box_height ( ) > 0 )
2020-11-22 13:38:18 +01:00
break ;
}
relevant_sibling = relevant_sibling - > previous_sibling ( ) ;
}
if ( relevant_sibling ) {
2020-12-04 21:23:04 +01:00
y + = relevant_sibling - > effective_offset ( ) . y ( )
+ relevant_sibling - > height ( )
2020-12-12 21:02:06 +01:00
+ relevant_sibling - > box_model ( ) . border_box ( ) . bottom ;
2020-11-22 13:38:18 +01:00
// Collapse top margin with bottom margin of preceding siblings if needed
2020-12-12 21:02:06 +01:00
float my_margin_top = box_model . margin . top ;
2020-11-22 13:38:18 +01:00
if ( my_margin_top < 0 | | collapsed_bottom_margin_of_preceding_siblings < 0 ) {
// Negative margins present.
float largest_negative_margin = - min ( my_margin_top , collapsed_bottom_margin_of_preceding_siblings ) ;
float largest_positive_margin = ( my_margin_top < 0 & & collapsed_bottom_margin_of_preceding_siblings < 0 ) ? 0 : max ( my_margin_top , collapsed_bottom_margin_of_preceding_siblings ) ;
float final_margin = largest_positive_margin - largest_negative_margin ;
y + = final_margin - my_margin_top ;
} else if ( collapsed_bottom_margin_of_preceding_siblings > my_margin_top ) {
// Sibling's margin is larger than mine, adjust so we use sibling's.
y + = collapsed_bottom_margin_of_preceding_siblings - my_margin_top ;
}
}
2021-05-11 18:04:18 -04:00
auto clear_floating_boxes = [ & ] ( auto & floating_boxes ) {
if ( ! floating_boxes . is_empty ( ) ) {
2020-12-06 01:45:51 +01:00
float clearance_y = 0 ;
2021-05-11 18:04:18 -04:00
for ( auto * floating_box : floating_boxes ) {
clearance_y = max ( clearance_y , floating_box - > effective_offset ( ) . y ( ) + floating_box - > border_box_height ( ) ) ;
2020-12-06 01:45:51 +01:00
}
y = max ( y , clearance_y ) ;
2021-05-11 18:04:18 -04:00
floating_boxes . clear ( ) ;
2020-12-06 01:45:51 +01:00
}
2021-05-11 18:04:18 -04:00
} ;
2020-12-06 01:45:51 +01:00
2021-05-29 23:03:05 +02:00
// Flex-items don't float and also don't clear.
if ( ( computed_values . clear ( ) = = CSS : : Clear : : Left | | computed_values . clear ( ) = = CSS : : Clear : : Both ) & & ! child_box . is_flex_item ( ) )
2021-05-11 18:04:18 -04:00
clear_floating_boxes ( m_left_floating_boxes ) ;
2021-05-29 23:03:05 +02:00
if ( ( computed_values . clear ( ) = = CSS : : Clear : : Right | | computed_values . clear ( ) = = CSS : : Clear : : Both ) & & ! child_box . is_flex_item ( ) )
2021-05-11 18:04:18 -04:00
clear_floating_boxes ( m_right_floating_boxes ) ;
2020-12-06 01:45:51 +01:00
2020-12-06 19:48:02 +01:00
child_box . set_offset ( x , y ) ;
2020-11-22 13:38:18 +01:00
}
void BlockFormattingContext : : layout_initial_containing_block ( LayoutMode layout_mode )
{
2021-10-06 19:47:10 +02:00
auto viewport_rect = root ( ) . browsing_context ( ) . viewport_rect ( ) ;
2020-11-22 13:38:18 +01:00
2021-10-06 19:47:10 +02:00
auto & icb = verify_cast < Layout : : InitialContainingBlock > ( root ( ) ) ;
2020-11-22 13:38:18 +01:00
icb . build_stacking_context_tree ( ) ;
icb . set_width ( viewport_rect . width ( ) ) ;
2021-10-06 19:47:10 +02:00
layout_block_level_children ( root ( ) , layout_mode ) ;
2020-11-22 13:38:18 +01:00
2021-02-23 20:42:32 +01:00
VERIFY ( ! icb . children_are_inline ( ) ) ;
2020-11-22 13:38:18 +01:00
// FIXME: The ICB should have the height of the viewport.
// Instead of auto-sizing the ICB, we should spill into overflow.
float lowest_bottom = 0 ;
2020-11-22 15:53:01 +01:00
icb . for_each_child_of_type < Box > ( [ & ] ( auto & child ) {
2020-11-22 13:38:18 +01:00
lowest_bottom = max ( lowest_bottom , child . absolute_rect ( ) . bottom ( ) ) ;
} ) ;
2020-12-17 00:58:23 +01:00
// FIXME: This is a hack and should be managed by an overflow mechanism.
icb . set_height ( max ( static_cast < float > ( viewport_rect . height ( ) ) , lowest_bottom ) ) ;
2020-11-22 13:38:18 +01:00
}
2020-12-06 20:57:17 +01:00
static Gfx : : FloatRect rect_in_coordinate_space ( const Box & box , const Box & context_box )
{
2021-04-29 18:18:53 +03:00
Gfx : : FloatRect rect = box . margin_box_as_relative_rect ( ) ;
2020-12-06 20:57:17 +01:00
for ( auto * ancestor = box . parent ( ) ; ancestor ; ancestor = ancestor - > parent ( ) ) {
if ( is < Box > ( * ancestor ) ) {
2021-06-24 19:53:42 +02:00
auto offset = verify_cast < Box > ( * ancestor ) . effective_offset ( ) ;
2021-04-12 11:47:09 -07:00
rect . translate_by ( offset ) ;
2020-12-06 20:57:17 +01:00
}
if ( ancestor = = & context_box )
break ;
}
return rect ;
}
2020-12-06 19:48:02 +01:00
void BlockFormattingContext : : layout_floating_child ( Box & box , Box & containing_block )
2020-12-05 20:10:39 +01:00
{
2021-02-23 20:42:32 +01:00
VERIFY ( box . is_floating ( ) ) ;
2020-12-05 20:10:39 +01:00
compute_width ( box ) ;
layout_inside ( box , LayoutMode : : Default ) ;
compute_height ( box ) ;
2020-12-06 16:51:19 +01:00
// First we place the box normally (to get the right y coordinate.)
2020-12-06 19:48:02 +01:00
place_block_level_non_replaced_element_in_normal_flow ( box , containing_block ) ;
2020-12-06 16:51:19 +01:00
// Then we float it to the left or right.
float x = box . effective_offset ( ) . x ( ) ;
2021-10-06 19:47:10 +02:00
auto box_in_root_rect = rect_in_coordinate_space ( box , root ( ) ) ;
float y_in_root = box_in_root_rect . y ( ) ;
2020-12-06 20:57:17 +01:00
// Next, float to the left and/or right
2021-01-06 10:34:31 +01:00
if ( box . computed_values ( ) . float_ ( ) = = CSS : : Float : : Left ) {
2020-12-06 16:51:19 +01:00
if ( ! m_left_floating_boxes . is_empty ( ) ) {
auto & previous_floating_box = * m_left_floating_boxes . last ( ) ;
2021-10-06 19:47:10 +02:00
auto previous_rect = rect_in_coordinate_space ( previous_floating_box , root ( ) ) ;
if ( previous_rect . contains_vertically ( y_in_root ) ) {
2020-12-06 20:57:17 +01:00
// This box touches another already floating box. Stack to the right.
2021-04-29 18:18:53 +03:00
x = previous_floating_box . margin_box_as_relative_rect ( ) . x ( ) + previous_floating_box . margin_box_as_relative_rect ( ) . width ( ) + box . box_model ( ) . margin_box ( ) . left ;
2020-12-06 20:57:17 +01:00
} else {
// This box does not touch another floating box, go all the way to the left.
2021-04-29 18:18:53 +03:00
x = box . box_model ( ) . margin_box ( ) . left ;
2020-12-06 21:11:28 +01:00
// Also, forget all previous left-floating boxes while we're here since they're no longer relevant.
m_left_floating_boxes . clear ( ) ;
2020-12-06 20:57:17 +01:00
}
} else {
// This is the first left-floating box. Go all the way to the left.
2021-04-29 18:18:53 +03:00
x = box . box_model ( ) . margin_box ( ) . left ;
2020-12-06 16:51:19 +01:00
}
m_left_floating_boxes . append ( & box ) ;
2021-01-06 10:34:31 +01:00
} else if ( box . computed_values ( ) . float_ ( ) = = CSS : : Float : : Right ) {
2020-12-06 16:51:19 +01:00
if ( ! m_right_floating_boxes . is_empty ( ) ) {
auto & previous_floating_box = * m_right_floating_boxes . last ( ) ;
2021-10-06 19:47:10 +02:00
auto previous_rect = rect_in_coordinate_space ( previous_floating_box , root ( ) ) ;
if ( previous_rect . contains_vertically ( y_in_root ) ) {
2020-12-06 20:57:17 +01:00
// This box touches another already floating box. Stack to the left.
2021-04-29 18:18:53 +03:00
x = previous_floating_box . margin_box_as_relative_rect ( ) . x ( ) - box . box_model ( ) . margin_box ( ) . right - box . width ( ) ;
2020-12-06 20:57:17 +01:00
} else {
// This box does not touch another floating box, go all the way to the right.
2021-04-29 18:18:53 +03:00
x = containing_block . width ( ) - box . box_model ( ) . margin_box ( ) . right - box . width ( ) ;
2020-12-06 21:11:28 +01:00
// Also, forget all previous right-floating boxes while we're here since they're no longer relevant.
m_right_floating_boxes . clear ( ) ;
2020-12-06 20:57:17 +01:00
}
2020-12-06 16:51:19 +01:00
} else {
2020-12-06 20:57:17 +01:00
// This is the first right-floating box. Go all the way to the right.
2021-04-29 18:18:53 +03:00
x = containing_block . width ( ) - box . box_model ( ) . margin_box ( ) . right - box . width ( ) ;
2020-12-06 16:51:19 +01:00
}
m_right_floating_boxes . append ( & box ) ;
}
2020-12-06 20:57:17 +01:00
box . set_offset ( x , box . effective_offset ( ) . y ( ) ) ;
2020-12-05 20:10:39 +01:00
}
2020-11-22 13:38:18 +01:00
}