1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 03:37:43 +00:00

LibWeb: Margin top collapsing between parent and first child

Implement collapsing of a box margin-top and first in-flow
child margin-top by saving function that updates y position
of containing block inside BlockMarginState and then for
every child until "non-collapsed through" child is reached
y position of containing block is updated by calling
update_box_waiting_fox_final_y_position_callback.
This commit is contained in:
Aliaksandr Kalenik 2022-12-25 14:01:14 +03:00 committed by Andreas Kling
parent fe8304d5de
commit 7088a87f49
3 changed files with 70 additions and 3 deletions

View file

@ -0,0 +1,22 @@
<style>
#foo {
background-color: red;
margin-bottom: 25px;
width: 100px;
height: 100px;
}
#bar {
background-color: green;
margin-top: 100px;
width: 200px;
height: 200px;
}
#baz {
background-color: blue;
width: 100px;
margin-top: -50px;
height: 100px;
}
</style>
<div id=foo></div>
<div id=bar><div id=baz></div></div>

View file

@ -372,7 +372,8 @@ void BlockFormattingContext::layout_block_level_box(Box const& box, BlockContain
return; return;
if (box.is_floating()) { if (box.is_floating()) {
layout_floating_box(box, block_container, layout_mode, available_space, m_margin_state.current_collapsed_margin() + current_y); auto margin_top = !m_margin_state.has_block_container_waiting_for_final_y_position() ? m_margin_state.current_collapsed_margin() : 0;
layout_floating_box(box, block_container, layout_mode, available_space, margin_top + current_y);
bottom_of_lowest_margin_box = max(bottom_of_lowest_margin_box, box_state.offset.y() + box_state.content_height() + box_state.margin_box_bottom()); bottom_of_lowest_margin_box = max(bottom_of_lowest_margin_box, box_state.offset.y() + box_state.content_height() + box_state.margin_box_bottom());
return; return;
} }
@ -383,8 +384,18 @@ void BlockFormattingContext::layout_block_level_box(Box const& box, BlockContain
compute_height(box, available_space); compute_height(box, available_space);
} }
if (box.computed_values().clear() != CSS::Clear::None) {
m_margin_state.reset();
}
m_margin_state.add_margin(box_state.margin_top); m_margin_state.add_margin(box_state.margin_top);
m_margin_state.update_block_waiting_for_final_y_position();
auto margin_top = m_margin_state.current_collapsed_margin(); auto margin_top = m_margin_state.current_collapsed_margin();
if (m_margin_state.has_block_container_waiting_for_final_y_position()) {
// If first child margin top will collapse with margin-top of containing block then margin-top of child is 0
margin_top = 0;
}
place_block_level_element_in_normal_flow_vertically(box, current_y + box_state.border_box_top() + margin_top); place_block_level_element_in_normal_flow_vertically(box, current_y + box_state.border_box_top() + margin_top);
place_block_level_element_in_normal_flow_horizontally(box, available_space); place_block_level_element_in_normal_flow_horizontally(box, available_space);
@ -401,7 +412,16 @@ void BlockFormattingContext::layout_block_level_box(Box const& box, BlockContain
if (box.children_are_inline()) { if (box.children_are_inline()) {
layout_inline_children(verify_cast<BlockContainer>(box), layout_mode, box_state.available_inner_space_or_constraints_from(available_space)); layout_inline_children(verify_cast<BlockContainer>(box), layout_mode, box_state.available_inner_space_or_constraints_from(available_space));
} else { } else {
m_margin_state.reset(); if (box_state.border_top > 0 || box_state.padding_top > 0) {
// margin-top of block container can't collapse with it's children if it has non zero border or padding
m_margin_state.reset();
} else if (!m_margin_state.has_block_container_waiting_for_final_y_position()) {
// margin-top of block container can be updated during children layout hence it's final y position yet to be determined
m_margin_state.register_block_container_y_position_update_callback([&](float margin_top) {
place_block_level_element_in_normal_flow_vertically(box, margin_top + current_y + box_state.border_box_top());
});
}
layout_block_level_children(verify_cast<BlockContainer>(box), layout_mode, box_state.available_inner_space_or_constraints_from(available_space)); layout_block_level_children(verify_cast<BlockContainer>(box), layout_mode, box_state.available_inner_space_or_constraints_from(available_space));
} }
} }
@ -415,6 +435,7 @@ void BlockFormattingContext::layout_block_level_box(Box const& box, BlockContain
} }
m_margin_state.add_margin(box_state.margin_bottom); m_margin_state.add_margin(box_state.margin_bottom);
m_margin_state.update_block_waiting_for_final_y_position();
compute_inset(box); compute_inset(box);
@ -440,7 +461,11 @@ void BlockFormattingContext::layout_block_level_children(BlockContainer const& b
return IterationDecision::Continue; return IterationDecision::Continue;
}); });
// FIXME: margin-bottom of last in-flow child can be collapsed with margin-bottom of parent // FIXME: The bottom margin of an in-flow block box with a 'height' of 'auto' collapses with its last in-flow block-level child's bottom margin, if:
// the box has no bottom padding, and
// the box has no bottom border, and
// the child's bottom margin neither collapses with a top margin that has clearance, nor (if the box's min-height is non-zero) with the box's top margin.
// https://www.w3.org/TR/CSS22/box.html#collapsing-margins
m_margin_state.reset(); m_margin_state.reset();
if (layout_mode == LayoutMode::IntrinsicSizing) { if (layout_mode == LayoutMode::IntrinsicSizing) {

View file

@ -113,16 +113,36 @@ private:
struct BlockMarginState { struct BlockMarginState {
Vector<float> current_collapsible_margins; Vector<float> current_collapsible_margins;
Function<void(float)> block_container_y_position_update_callback;
void add_margin(float margin) void add_margin(float margin)
{ {
current_collapsible_margins.append(margin); current_collapsible_margins.append(margin);
} }
void register_block_container_y_position_update_callback(Function<void(float)> callback)
{
block_container_y_position_update_callback = move(callback);
}
float current_collapsed_margin() const; float current_collapsed_margin() const;
bool has_block_container_waiting_for_final_y_position() const
{
return static_cast<bool>(block_container_y_position_update_callback);
}
void update_block_waiting_for_final_y_position() const
{
if (block_container_y_position_update_callback) {
float collapsed_margin = current_collapsed_margin();
block_container_y_position_update_callback(collapsed_margin);
}
}
void reset() void reset()
{ {
block_container_y_position_update_callback = {};
current_collapsible_margins.clear(); current_collapsible_margins.clear();
} }
}; };