Skip to content

Commit 95832c9

Browse files
committed
Layout 2020: Properly calculate clearance
calculate_clearance() was not taking into account that adding clearance prevents top margin from collapsing with earlier margins.
1 parent 47faf83 commit 95832c9

File tree

6 files changed

+66
-35
lines changed

6 files changed

+66
-35
lines changed

components/layout_2020/flow/float.rs

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -821,37 +821,73 @@ impl SequentialLayoutState {
821821
self.current_margin = CollapsedMargin::zero();
822822
}
823823

824-
/// Returns the amount of clearance that a block with the given `clear` value at the current
825-
/// `bfc_relative_block_position` (with top margin included in `current_margin` if applicable)
826-
/// needs to have.
824+
/// Returns the amount of clearance (if any) that a block with the given `clear` value
825+
/// needs to have at `current_block_position_including_margins()`.
826+
/// `block_start_margin` is the top margin of the block, after collapsing (if possible)
827+
/// with the margin of its contents. This must not be included in `current_margin`,
828+
/// since adding clearance will prevent `current_margin` and `block_start_margin`
829+
/// from collapsing together.
827830
///
828831
/// https://www.w3.org/TR/2011/REC-CSS2-20110607/visuren.html#flow-control
829-
pub(crate) fn calculate_clearance(&self, clear_side: ClearSide) -> Option<Length> {
832+
pub(crate) fn calculate_clearance(
833+
&self,
834+
clear_side: ClearSide,
835+
block_start_margin: &CollapsedMargin,
836+
) -> Option<Length> {
830837
if clear_side == ClearSide::None {
831838
return None;
832839
}
833840

834-
let hypothetical_block_position = self.current_block_position_including_margins();
841+
// Calculate the hypothetical position where the element's top border edge
842+
// would have been if the element's `clear` property had been `none`.
843+
let hypothetical_block_position = self.bfc_relative_block_position +
844+
self.current_margin.adjoin(&block_start_margin).solve();
845+
846+
// Check if the hypothetical position is past the relevant floats,
847+
// in that case we don't need to add clearance.
835848
let clear_position = match clear_side {
836849
ClearSide::None => unreachable!(),
837-
ClearSide::Left => self
838-
.floats
839-
.clear_left_position
840-
.max(hypothetical_block_position),
841-
ClearSide::Right => self
842-
.floats
843-
.clear_right_position
844-
.max(hypothetical_block_position),
850+
ClearSide::Left => self.floats.clear_left_position,
851+
ClearSide::Right => self.floats.clear_right_position,
845852
ClearSide::Both => self
846853
.floats
847854
.clear_left_position
848-
.max(self.floats.clear_right_position)
849-
.max(hypothetical_block_position),
855+
.max(self.floats.clear_right_position),
850856
};
851857
if hypothetical_block_position >= clear_position {
852858
return None;
853859
}
854-
Some(clear_position - hypothetical_block_position)
860+
861+
// We need to add clearance between `current_margin` and `block_start_margin`,
862+
// this prevents them from collapsing.
863+
// Compute the position of the element's top border edge if we added
864+
// a clearance of 0px.
865+
let position_with_zero_clearance = self.bfc_relative_block_position +
866+
self.current_margin.solve() +
867+
block_start_margin.solve();
868+
869+
// Set clearance to the amount necessary to place the border edge of the block
870+
// even with the bottom outer edge of the lowest float that is to be cleared.
871+
// Note that CSS2 also allows the alternative option of flooring this amount by
872+
// `hypothetical_block_position - position_with_zero_clearance`,
873+
// but other browser don't seem to do that.
874+
Some(clear_position - position_with_zero_clearance)
875+
}
876+
877+
/// Helper function that computes the clearance and adds `block_start_margin` accordingly.
878+
/// If there is no clearance, `block_start_margin` can just be adjoined to `current_margin`.
879+
/// But clearance prevents them from collapsing, so `collapse_margins()` is called.
880+
pub(crate) fn calculate_clearance_and_adjoin_margin(
881+
&mut self,
882+
style: &Arc<ComputedValues>,
883+
block_start_margin: &CollapsedMargin,
884+
) -> Option<Length> {
885+
let clearance = self.calculate_clearance(ClearSide::from_style(style), &block_start_margin);
886+
if clearance.is_some() {
887+
self.collapse_margins();
888+
}
889+
self.adjoin_assign(&block_start_margin);
890+
clearance
855891
}
856892

857893
/// Adds a new adjoining margin.

components/layout_2020/flow/mod.rs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ impl BlockFormattingContext {
191191
// (https://drafts.csswg.org/css2/#root-height), we implement this by imagining there is
192192
// an element with `clear: both` after the actual contents.
193193
let clearance = sequential_layout_state.and_then(|sequential_layout_state| {
194-
sequential_layout_state.calculate_clearance(ClearSide::Both)
194+
sequential_layout_state.calculate_clearance(ClearSide::Both, &CollapsedMargin::zero())
195195
});
196196

197197
IndependentLayout {
@@ -657,29 +657,28 @@ fn layout_in_flow_non_replaced_block_level(
657657
match sequential_layout_state {
658658
None => parent_containing_block_position_info = None,
659659
Some(ref mut sequential_layout_state) => {
660-
sequential_layout_state.adjoin_assign(&CollapsedMargin::new(margin.block_start));
660+
let mut block_start_margin = CollapsedMargin::new(margin.block_start);
661661
if start_margin_can_collapse_with_children {
662662
if let NonReplacedContents::SameFormattingContextBlock(
663663
BlockContainer::BlockLevelBoxes(child_boxes),
664664
) = block_level_kind
665665
{
666666
// The block start margin may collapse with content margins,
667667
// compute the resulting one in order to place floats correctly.
668-
let mut future_margins = CollapsedMargin::zero();
669668
BlockLevelBox::find_block_margin_collapsing_with_parent_from_slice(
670669
child_boxes,
671-
&mut future_margins,
670+
&mut block_start_margin,
672671
WritingMode::empty(),
673672
);
674-
sequential_layout_state.adjoin_assign(&future_margins);
675673
}
676-
} else {
677-
sequential_layout_state.collapse_margins();
678674
}
679675

680676
// Introduce clearance if necessary.
681-
let clear_side = ClearSide::from_style(style);
682-
clearance = sequential_layout_state.calculate_clearance(clear_side);
677+
clearance = sequential_layout_state
678+
.calculate_clearance_and_adjoin_margin(style, &block_start_margin);
679+
if !start_margin_can_collapse_with_children {
680+
sequential_layout_state.collapse_margins();
681+
}
683682

684683
// NB: This will be a no-op if we're collapsing margins with our children since that
685684
// can only happen if we have no block-start padding and border.
@@ -844,9 +843,11 @@ fn layout_in_flow_replaced_block_level<'a>(
844843

845844
let mut clearance = None;
846845
if let Some(ref mut sequential_layout_state) = sequential_layout_state {
847-
sequential_layout_state.adjoin_assign(&CollapsedMargin::new(margin.block_start));
846+
clearance = sequential_layout_state.calculate_clearance_and_adjoin_margin(
847+
style,
848+
&CollapsedMargin::new(margin.block_start),
849+
);
848850
sequential_layout_state.collapse_margins();
849-
clearance = sequential_layout_state.calculate_clearance(ClearSide::from_style(style));
850851
sequential_layout_state.advance_block_position(
851852
pbm.border.block_sum() +
852853
pbm.padding.block_sum() +
@@ -943,6 +944,8 @@ impl PlacementState {
943944
// Setting `next_in_flow_margin_collapses_with_parent_start_margin` to false
944945
// prevents collapsing with the start margin of the parent, and will set
945946
// `collapsed_through` to false, preventing the parent from collapsing through.
947+
self.current_block_direction_position += self.current_margin.solve();
948+
self.current_margin = CollapsedMargin::zero();
946949
self.next_in_flow_margin_collapses_with_parent_start_margin = false;
947950
if fragment_block_margins.collapsed_through {
948951
self.last_in_flow_margin_collapses_with_parent_end_margin = false;

tests/wpt/meta/css/CSS2/floats-clear/clear-on-child-with-margins.html.ini

Lines changed: 0 additions & 2 deletions
This file was deleted.

tests/wpt/meta/css/CSS2/floats-clear/clear-on-parent-with-margins.html.ini

Lines changed: 0 additions & 2 deletions
This file was deleted.

tests/wpt/meta/css/CSS2/floats-clear/negative-clearance-after-bottom-margin.html.ini

Lines changed: 0 additions & 2 deletions
This file was deleted.

tests/wpt/meta/css/CSS2/floats-clear/nested-clearance-new-formatting-context.html.ini

Lines changed: 0 additions & 2 deletions
This file was deleted.

0 commit comments

Comments
 (0)