mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 13:12:46 +00:00 
			
		
		
		
	LibWeb: Introduce structure that maintains collapsible margins in BFC
Previously y position of boxes in block formatting context was calculated by looking at y position of previous in-flow sibling and adding collapsed margin of "collapse through" boxes lying between box currently being laid out and it's previous in-flow sibling. Here introduced BlockMarginState structure that maintains array of currently collapsible margins hence we no longer need to look at previous sibling to calculate y position of a box.
This commit is contained in:
		
							parent
							
								
									e3d5b67eaf
								
							
						
					
					
						commit
						fe8304d5de
					
				
					 5 changed files with 93 additions and 89 deletions
				
			
		|  | @ -214,6 +214,8 @@ void BlockFormattingContext::compute_width(Box const& box, AvailableSpace const& | |||
| 
 | ||||
|     box_state.margin_left = margin_left.to_px(box); | ||||
|     box_state.margin_right = margin_right.to_px(box); | ||||
| 
 | ||||
|     resolve_vertical_box_model_metrics(box, m_state); | ||||
| } | ||||
| 
 | ||||
| void BlockFormattingContext::compute_width_for_floating_box(Box const& box, AvailableSpace const& available_space) | ||||
|  | @ -291,6 +293,8 @@ void BlockFormattingContext::compute_width_for_floating_box(Box const& box, Avai | |||
|     box_state.border_right = computed_values.border_right().width; | ||||
|     box_state.padding_left = padding_left.to_px(box); | ||||
|     box_state.padding_right = padding_right.to_px(box); | ||||
| 
 | ||||
|     resolve_vertical_box_model_metrics(box, m_state); | ||||
| } | ||||
| 
 | ||||
| void BlockFormattingContext::compute_width_for_block_level_replaced_element_in_normal_flow(ReplacedBox const& box, AvailableSpace const& available_space) | ||||
|  | @ -300,8 +304,6 @@ void BlockFormattingContext::compute_width_for_block_level_replaced_element_in_n | |||
| 
 | ||||
| void BlockFormattingContext::compute_height(Box const& box, AvailableSpace const& available_space) | ||||
| { | ||||
|     resolve_vertical_box_model_metrics(box, m_state); | ||||
| 
 | ||||
|     auto const& computed_values = box.computed_values(); | ||||
|     auto containing_block_height = CSS::Length::make_px(available_space.height.to_px()); | ||||
| 
 | ||||
|  | @ -347,7 +349,16 @@ void BlockFormattingContext::layout_inline_children(BlockContainer const& block_ | |||
|         block_container_state.set_content_height(context.automatic_content_height()); | ||||
| } | ||||
| 
 | ||||
| void BlockFormattingContext::layout_block_level_box(Box const& box, BlockContainer const& block_container, LayoutMode layout_mode, float& bottom_of_lowest_margin_box, AvailableSpace const& available_space) | ||||
| static bool margins_collapse_through(Box const& box, LayoutState& state) | ||||
| { | ||||
|     // FIXME: A box's own margins collapse if the 'min-height' property is zero, and it has neither top or bottom borders
 | ||||
|     // nor top or bottom padding, and it has a 'height' of either 0 or 'auto', and it does not contain a line box, and
 | ||||
|     // all of its in-flow children's margins (if any) collapse.
 | ||||
|     // https://www.w3.org/TR/CSS22/box.html#collapsing-margins
 | ||||
|     return state.get(box).border_box_height() == 0; | ||||
| } | ||||
| 
 | ||||
| void BlockFormattingContext::layout_block_level_box(Box const& box, BlockContainer const& block_container, LayoutMode layout_mode, float& bottom_of_lowest_margin_box, AvailableSpace const& available_space, float& current_y) | ||||
| { | ||||
|     auto& box_state = m_state.get_mutable(box); | ||||
| 
 | ||||
|  | @ -361,35 +372,50 @@ void BlockFormattingContext::layout_block_level_box(Box const& box, BlockContain | |||
|         return; | ||||
| 
 | ||||
|     if (box.is_floating()) { | ||||
|         layout_floating_box(box, block_container, layout_mode, available_space); | ||||
|         layout_floating_box(box, block_container, layout_mode, available_space, m_margin_state.current_collapsed_margin() + 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()); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     compute_width(box, available_space, layout_mode); | ||||
| 
 | ||||
|     place_block_level_element_in_normal_flow_vertically(box); | ||||
|     place_block_level_element_in_normal_flow_horizontally(box, available_space); | ||||
| 
 | ||||
|     if (box_state.has_definite_height()) { | ||||
|         compute_height(box, available_space); | ||||
|     } | ||||
| 
 | ||||
|     m_margin_state.add_margin(box_state.margin_top); | ||||
|     auto margin_top = m_margin_state.current_collapsed_margin(); | ||||
| 
 | ||||
|     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); | ||||
| 
 | ||||
|     OwnPtr<FormattingContext> independent_formatting_context; | ||||
|     if (!box.is_replaced_box() && box.has_children()) { | ||||
|         independent_formatting_context = create_independent_formatting_context_if_needed(m_state, box); | ||||
|         if (independent_formatting_context) { | ||||
|             // Margins of elements that establish new formatting contexts do not collapse with their in-flow children
 | ||||
|             m_margin_state.reset(); | ||||
| 
 | ||||
|             independent_formatting_context->run(box, layout_mode, box_state.available_inner_space_or_constraints_from(available_space)); | ||||
|         } else { | ||||
|             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)); | ||||
|             else | ||||
|             } else { | ||||
|                 m_margin_state.reset(); | ||||
|                 layout_block_level_children(verify_cast<BlockContainer>(box), layout_mode, box_state.available_inner_space_or_constraints_from(available_space)); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     compute_height(box, available_space); | ||||
| 
 | ||||
|     if (!margins_collapse_through(box, m_state)) { | ||||
|         m_margin_state.reset(); | ||||
|         current_y = box_state.offset.y() + box_state.content_height() + box_state.border_box_bottom(); | ||||
|     } | ||||
| 
 | ||||
|     m_margin_state.add_margin(box_state.margin_bottom); | ||||
| 
 | ||||
|     compute_inset(box); | ||||
| 
 | ||||
|     if (is<ListItemBox>(box)) { | ||||
|  | @ -407,12 +433,16 @@ void BlockFormattingContext::layout_block_level_children(BlockContainer const& b | |||
|     VERIFY(!block_container.children_are_inline()); | ||||
| 
 | ||||
|     float bottom_of_lowest_margin_box = 0; | ||||
|     float current_y = 0; | ||||
| 
 | ||||
|     block_container.for_each_child_of_type<Box>([&](Box& box) { | ||||
|         layout_block_level_box(box, block_container, layout_mode, bottom_of_lowest_margin_box, available_space); | ||||
|         layout_block_level_box(box, block_container, layout_mode, bottom_of_lowest_margin_box, available_space, current_y); | ||||
|         return IterationDecision::Continue; | ||||
|     }); | ||||
| 
 | ||||
|     // FIXME: margin-bottom of last in-flow child can be collapsed with margin-bottom of parent
 | ||||
|     m_margin_state.reset(); | ||||
| 
 | ||||
|     if (layout_mode == LayoutMode::IntrinsicSizing) { | ||||
|         auto& block_container_state = m_state.get_mutable(block_container); | ||||
|         if (!block_container_state.has_definite_width()) | ||||
|  | @ -436,15 +466,38 @@ void BlockFormattingContext::resolve_vertical_box_model_metrics(Box const& box, | |||
|     box_state.padding_bottom = computed_values.padding().bottom().resolved(box, width_of_containing_block).to_px(box); | ||||
| } | ||||
| 
 | ||||
| void BlockFormattingContext::place_block_level_element_in_normal_flow_vertically(Box const& child_box) | ||||
| float BlockFormattingContext::BlockMarginState::current_collapsed_margin() const | ||||
| { | ||||
|     float smallest_margin = 0; | ||||
|     float largest_margin = 0; | ||||
|     size_t negative_margin_count = 0; | ||||
|     for (auto margin : current_collapsible_margins) { | ||||
|         if (margin < 0) | ||||
|             ++negative_margin_count; | ||||
|         largest_margin = max(largest_margin, margin); | ||||
|         smallest_margin = min(smallest_margin, margin); | ||||
|     } | ||||
| 
 | ||||
|     float collapsed_margin = 0; | ||||
|     if (negative_margin_count == current_collapsible_margins.size()) { | ||||
|         // When all margins are negative, the size of the collapsed margin is the smallest (most negative) margin.
 | ||||
|         collapsed_margin = smallest_margin; | ||||
|     } else if (negative_margin_count > 0) { | ||||
|         // When negative margins are involved, the size of the collapsed margin is the sum of the largest positive margin and the smallest (most negative) negative margin.
 | ||||
|         collapsed_margin = largest_margin + smallest_margin; | ||||
|     } else { | ||||
|         // Otherwise, collapse all the adjacent margins by using only the largest one.
 | ||||
|         collapsed_margin = largest_margin; | ||||
|     } | ||||
| 
 | ||||
|     return collapsed_margin; | ||||
| } | ||||
| 
 | ||||
| void BlockFormattingContext::place_block_level_element_in_normal_flow_vertically(Box const& child_box, float y) | ||||
| { | ||||
|     auto& box_state = m_state.get_mutable(child_box); | ||||
|     auto const& computed_values = child_box.computed_values(); | ||||
| 
 | ||||
|     resolve_vertical_box_model_metrics(child_box, m_state); | ||||
| 
 | ||||
|     auto y = FormattingContext::compute_box_y_position_with_respect_to_siblings(child_box); | ||||
| 
 | ||||
|     auto clear_floating_boxes = [&](FloatSideData& float_side) { | ||||
|         if (!float_side.current_boxes.is_empty()) { | ||||
|             // NOTE: Floating boxes are globally relevant within this BFC, *but* their offset coordinates
 | ||||
|  | @ -546,7 +599,7 @@ void BlockFormattingContext::layout_initial_containing_block(LayoutMode layout_m | |||
|     } | ||||
| } | ||||
| 
 | ||||
| void BlockFormattingContext::layout_floating_box(Box const& box, BlockContainer const&, LayoutMode layout_mode, AvailableSpace const& available_space, LineBuilder* line_builder) | ||||
| void BlockFormattingContext::layout_floating_box(Box const& box, BlockContainer const&, LayoutMode layout_mode, AvailableSpace const& available_space, float y, LineBuilder* line_builder) | ||||
| { | ||||
|     VERIFY(box.is_floating()); | ||||
| 
 | ||||
|  | @ -563,7 +616,7 @@ void BlockFormattingContext::layout_floating_box(Box const& box, BlockContainer | |||
|         auto y = line_builder->y_for_float_to_be_inserted_here(box); | ||||
|         box_state.set_content_y(y + box_state.margin_box_top()); | ||||
|     } else { | ||||
|         place_block_level_element_in_normal_flow_vertically(box); | ||||
|         place_block_level_element_in_normal_flow_vertically(box, y + box_state.margin_box_top()); | ||||
|         place_block_level_element_in_normal_flow_horizontally(box, available_space); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -44,9 +44,9 @@ public: | |||
| 
 | ||||
|     virtual float greatest_child_width(Box const&) override; | ||||
| 
 | ||||
|     void layout_floating_box(Box const& child, BlockContainer const& containing_block, LayoutMode, AvailableSpace const&, LineBuilder* = nullptr); | ||||
|     void layout_floating_box(Box const& child, BlockContainer const& containing_block, LayoutMode, AvailableSpace const&, float y, LineBuilder* = nullptr); | ||||
| 
 | ||||
|     void layout_block_level_box(Box const&, BlockContainer const&, LayoutMode, float& bottom_of_lowest_margin_box, AvailableSpace const&); | ||||
|     void layout_block_level_box(Box const&, BlockContainer const&, LayoutMode, float& bottom_of_lowest_margin_box, AvailableSpace const&, float& current_y); | ||||
| 
 | ||||
|     virtual bool can_determine_size_of_child() const override { return true; } | ||||
|     virtual void determine_width_of_child(Box const&, AvailableSpace const&) override; | ||||
|  | @ -66,7 +66,7 @@ private: | |||
| 
 | ||||
|     static void resolve_vertical_box_model_metrics(Box const& box, LayoutState&); | ||||
|     void place_block_level_element_in_normal_flow_horizontally(Box const& child_box, AvailableSpace const&); | ||||
|     void place_block_level_element_in_normal_flow_vertically(Box const&); | ||||
|     void place_block_level_element_in_normal_flow_vertically(Box const&, float y); | ||||
| 
 | ||||
|     void layout_list_item_marker(ListItemBox const&); | ||||
| 
 | ||||
|  | @ -111,6 +111,24 @@ private: | |||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     struct BlockMarginState { | ||||
|         Vector<float> current_collapsible_margins; | ||||
| 
 | ||||
|         void add_margin(float margin) | ||||
|         { | ||||
|             current_collapsible_margins.append(margin); | ||||
|         } | ||||
| 
 | ||||
|         float current_collapsed_margin() const; | ||||
| 
 | ||||
|         void reset() | ||||
|         { | ||||
|             current_collapsible_margins.clear(); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     BlockMarginState m_margin_state; | ||||
| 
 | ||||
|     FloatSideData m_left_floats; | ||||
|     FloatSideData m_right_floats; | ||||
| 
 | ||||
|  |  | |||
|  | @ -927,7 +927,7 @@ Gfx::FloatPoint FormattingContext::calculate_static_position(Box const& box) con | |||
|     } else { | ||||
|         x = m_state.get(box).margin_box_left(); | ||||
|         // We're among block siblings, Y can be calculated easily.
 | ||||
|         y = compute_box_y_position_with_respect_to_siblings(box); | ||||
|         y = m_state.get(box).margin_box_top(); | ||||
|     } | ||||
|     auto offset_to_static_parent = content_box_rect_in_static_position_ancestor_coordinate_space(box, *box.containing_block(), m_state); | ||||
|     return offset_to_static_parent.location().translated(x, y); | ||||
|  | @ -1364,71 +1364,6 @@ float FormattingContext::containing_block_height_for(Box const& box, LayoutState | |||
|     VERIFY_NOT_REACHED(); | ||||
| } | ||||
| 
 | ||||
| static Box const* previous_block_level_sibling(Box const& box) | ||||
| { | ||||
|     for (auto* sibling = box.previous_sibling_of_type<Box>(); sibling; sibling = sibling->previous_sibling_of_type<Box>()) { | ||||
|         if (sibling->display().is_block_outside()) | ||||
|             return sibling; | ||||
|     } | ||||
|     return nullptr; | ||||
| } | ||||
| 
 | ||||
| float FormattingContext::compute_box_y_position_with_respect_to_siblings(Box const& box) const | ||||
| { | ||||
|     auto const& box_state = m_state.get(box); | ||||
|     float y = box_state.border_box_top(); | ||||
| 
 | ||||
|     Vector<float> collapsible_margins; | ||||
| 
 | ||||
|     auto* relevant_sibling = previous_block_level_sibling(box); | ||||
|     while (relevant_sibling != nullptr) { | ||||
|         if (!relevant_sibling->is_absolutely_positioned() && !relevant_sibling->is_floating()) { | ||||
|             auto const& relevant_sibling_state = m_state.get(*relevant_sibling); | ||||
|             collapsible_margins.append(relevant_sibling_state.margin_bottom); | ||||
|             // NOTE: Empty (0-height) preceding siblings have their margins collapsed with *their* preceding sibling, etc.
 | ||||
|             if (relevant_sibling_state.border_box_height() > 0) | ||||
|                 break; | ||||
|             collapsible_margins.append(relevant_sibling_state.margin_top); | ||||
|         } | ||||
|         relevant_sibling = previous_block_level_sibling(*relevant_sibling); | ||||
|     } | ||||
| 
 | ||||
|     if (relevant_sibling) { | ||||
|         // Collapse top margin with the collapsed margin(s) of preceding siblings.
 | ||||
|         collapsible_margins.append(box_state.margin_top); | ||||
| 
 | ||||
|         float smallest_margin = 0; | ||||
|         float largest_margin = 0; | ||||
|         size_t negative_margin_count = 0; | ||||
|         for (auto margin : collapsible_margins) { | ||||
|             if (margin < 0) | ||||
|                 ++negative_margin_count; | ||||
|             largest_margin = max(largest_margin, margin); | ||||
|             smallest_margin = min(smallest_margin, margin); | ||||
|         } | ||||
| 
 | ||||
|         float collapsed_margin = 0; | ||||
|         if (negative_margin_count == collapsible_margins.size()) { | ||||
|             // When all margins are negative, the size of the collapsed margin is the smallest (most negative) margin.
 | ||||
|             collapsed_margin = smallest_margin; | ||||
|         } else if (negative_margin_count > 0) { | ||||
|             // When negative margins are involved, the size of the collapsed margin is the sum of the largest positive margin and the smallest (most negative) negative margin.
 | ||||
|             collapsed_margin = largest_margin + smallest_margin; | ||||
|         } else { | ||||
|             // Otherwise, collapse all the adjacent margins by using only the largest one.
 | ||||
|             collapsed_margin = largest_margin; | ||||
|         } | ||||
| 
 | ||||
|         auto const& relevant_sibling_state = m_state.get(*relevant_sibling); | ||||
|         return y + relevant_sibling_state.offset.y() | ||||
|             + relevant_sibling_state.content_height() | ||||
|             + relevant_sibling_state.border_box_bottom() | ||||
|             + collapsed_margin; | ||||
|     } else { | ||||
|         return y + box_state.margin_top; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // https://drafts.csswg.org/css-sizing-3/#stretch-fit-size
 | ||||
| float FormattingContext::calculate_stretch_fit_width(Box const& box, AvailableSize const& available_width) const | ||||
| { | ||||
|  |  | |||
|  | @ -71,8 +71,6 @@ public: | |||
|     static float containing_block_width_for(Box const&, LayoutState const&); | ||||
|     static float containing_block_height_for(Box const&, LayoutState const&); | ||||
| 
 | ||||
|     float compute_box_y_position_with_respect_to_siblings(Box const&) const; | ||||
| 
 | ||||
|     [[nodiscard]] float calculate_stretch_fit_width(Box const&, AvailableSize const&) const; | ||||
|     [[nodiscard]] float calculate_stretch_fit_height(Box const&, AvailableSize const&) const; | ||||
| 
 | ||||
|  |  | |||
|  | @ -263,7 +263,7 @@ void InlineFormattingContext::generate_line_boxes(LayoutMode layout_mode) | |||
| 
 | ||||
|         case InlineLevelIterator::Item::Type::FloatingElement: | ||||
|             if (is<Box>(*item.node)) | ||||
|                 parent().layout_floating_box(static_cast<Layout::Box const&>(*item.node), containing_block(), layout_mode, *m_available_space, &line_builder); | ||||
|                 parent().layout_floating_box(static_cast<Layout::Box const&>(*item.node), containing_block(), layout_mode, *m_available_space, 0, &line_builder); | ||||
|             break; | ||||
| 
 | ||||
|         case InlineLevelIterator::Item::Type::Text: { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Aliaksandr Kalenik
						Aliaksandr Kalenik