aboutsummaryrefslogtreecommitdiffstats
path: root/components/layout_2020/flow
diff options
context:
space:
mode:
Diffstat (limited to 'components/layout_2020/flow')
-rw-r--r--components/layout_2020/flow/float.rs416
-rw-r--r--components/layout_2020/flow/inline.rs54
-rw-r--r--components/layout_2020/flow/mod.rs482
-rw-r--r--components/layout_2020/flow/root.rs4
4 files changed, 766 insertions, 190 deletions
diff --git a/components/layout_2020/flow/float.rs b/components/layout_2020/flow/float.rs
index eeae46cba61..582ca9e1242 100644
--- a/components/layout_2020/flow/float.rs
+++ b/components/layout_2020/flow/float.rs
@@ -10,12 +10,21 @@ use crate::context::LayoutContext;
use crate::dom::NodeExt;
use crate::dom_traversal::{Contents, NodeAndStyleInfo};
use crate::formatting_contexts::IndependentFormattingContext;
+use crate::fragments::{
+ BoxFragment, CollapsedBlockMargins, CollapsedMargin, FloatFragment, Fragment,
+};
use crate::geom::flow_relative::{Rect, Vec2};
-use crate::style_ext::DisplayInside;
+use crate::positioned::PositioningContext;
+use crate::style_ext::{ComputedValuesExt, DisplayInside};
+use crate::ContainingBlock;
use euclid::num::Zero;
use servo_arc::Arc;
-use std::f32;
+use std::fmt::{Debug, Formatter, Result as FmtResult};
use std::ops::Range;
+use std::{f32, mem};
+use style::computed_values::clear::T as ClearProperty;
+use style::computed_values::float::T as FloatProperty;
+use style::properties::ComputedValues;
use style::values::computed::Length;
use style::values::specified::text::TextDecorationLine;
@@ -26,6 +35,53 @@ pub(crate) struct FloatBox {
pub contents: IndependentFormattingContext,
}
+/// `FloatContext` positions floats relative to the independent block formatting
+/// context which contains the floating elements. The Fragment tree positions
+/// elements relative to their containing blocks. This data structure is used to
+/// help map between these two coordinate systems.
+#[derive(Clone, Copy, Debug)]
+pub struct ContainingBlockPositionInfo {
+ /// The distance from the block start of the independent block formatting
+ /// context that contains the floats and the block start of the current
+ /// containing block, excluding uncollapsed block start margins. Note that
+ /// this does not include uncollapsed block start margins because we don't
+ /// know the value of collapsed margins until we lay out children.
+ pub(crate) block_start: Length,
+ /// Any uncollapsed block start margins that we have collected between the
+ /// block start of the float containing independent block formatting context
+ /// and this containing block, including for this containing block.
+ pub(crate) block_start_margins_not_collapsed: CollapsedMargin,
+ /// The distance from the inline start position of the float containing
+ /// independent formatting context and the inline start of this containing
+ /// block.
+ pub inline_start: Length,
+ /// The offset from the inline start position of the float containing
+ /// independent formatting context to the inline end of this containing
+ /// block.
+ pub inline_end: Length,
+}
+
+impl Default for ContainingBlockPositionInfo {
+ fn default() -> Self {
+ Self {
+ block_start: Length::zero(),
+ block_start_margins_not_collapsed: CollapsedMargin::zero(),
+ inline_start: Length::zero(),
+ inline_end: Length::new(f32::INFINITY),
+ }
+ }
+}
+
+impl ContainingBlockPositionInfo {
+ pub fn new_with_inline_offsets(inline_start: Length, inline_end: Length) -> Self {
+ Self {
+ inline_start,
+ inline_end,
+ ..Default::default()
+ }
+ }
+}
+
/// Data kept during layout about the floats in a given block formatting context.
///
/// This is a persistent data structure. Each float has its own private copy of the float context,
@@ -40,6 +96,14 @@ pub struct FloatContext {
/// The current (logically) vertical position. No new floats may be placed (logically) above
/// this line.
pub ceiling: Length,
+ /// Details about the position of the containing block relative to the
+ /// independent block formatting context that contains all of the floats
+ /// this `FloatContext` positions.
+ pub containing_block_info: ContainingBlockPositionInfo,
+ /// The (logically) lowest margin edge of the last left float.
+ pub clear_left_position: Length,
+ /// The (logically) lowest margin edge of the last right float.
+ pub clear_right_position: Length,
}
impl FloatContext {
@@ -60,14 +124,12 @@ impl FloatContext {
FloatContext {
bands,
ceiling: Length::zero(),
+ containing_block_info: Default::default(),
+ clear_left_position: Length::zero(),
+ clear_right_position: Length::zero(),
}
}
- /// Returns the current ceiling value. No new floats may be placed (logically) above this line.
- pub fn ceiling(&self) -> Length {
- self.ceiling
- }
-
/// (Logically) lowers the ceiling to at least `new_ceiling` units.
///
/// If the ceiling is already logically lower (i.e. larger) than this, does nothing.
@@ -81,9 +143,19 @@ impl FloatContext {
/// This should be used for placing inline elements and block formatting contexts so that they
/// don't collide with floats.
pub fn place_object(&self, object: &PlacementInfo) -> Vec2<Length> {
+ let ceiling = match object.clear {
+ ClearSide::None => self.ceiling,
+ ClearSide::Left => self.ceiling.max(self.clear_left_position),
+ ClearSide::Right => self.ceiling.max(self.clear_right_position),
+ ClearSide::Both => self
+ .ceiling
+ .max(self.clear_left_position)
+ .max(self.clear_right_position),
+ };
+
// Find the first band this float fits in.
- let mut first_band = self.bands.find(self.ceiling).unwrap();
- while !first_band.object_fits(&object) {
+ let mut first_band = self.bands.find(ceiling).unwrap();
+ while !first_band.object_fits(&object, &self.containing_block_info) {
let next_band = self.bands.find_next(first_band.top).unwrap();
if next_band.top.px().is_infinite() {
break;
@@ -95,8 +167,8 @@ impl FloatContext {
match object.side {
FloatSide::Left => {
let left_object_edge = match first_band.left {
- Some(band_left) => band_left.max(object.left_wall),
- None => object.left_wall,
+ Some(band_left) => band_left.max(self.containing_block_info.inline_start),
+ None => self.containing_block_info.inline_start,
};
Vec2 {
inline: left_object_edge,
@@ -105,8 +177,8 @@ impl FloatContext {
},
FloatSide::Right => {
let right_object_edge = match first_band.right {
- Some(band_right) => band_right.min(object.right_wall),
- None => object.right_wall,
+ Some(band_right) => band_right.min(self.containing_block_info.inline_end),
+ None => self.containing_block_info.inline_end,
};
Vec2 {
inline: right_object_edge - object.size.inline,
@@ -129,6 +201,20 @@ impl FloatContext {
size: new_float.size.clone(),
};
+ // Update clear.
+ match new_float.side {
+ FloatSide::Left => {
+ self.clear_left_position = self
+ .clear_left_position
+ .max(new_float_rect.max_block_position())
+ },
+ FloatSide::Right => {
+ self.clear_right_position = self
+ .clear_right_position
+ .max(new_float_rect.max_block_position())
+ },
+ }
+
// Split the first band if necessary.
let mut first_band = self.bands.find(new_float_rect.start_corner.block).unwrap();
first_band.top = new_float_rect.start_corner.block;
@@ -164,12 +250,6 @@ pub struct PlacementInfo {
pub side: FloatSide,
/// Which side or sides to clear floats on.
pub clear: ClearSide,
- /// The distance from the logical left side of the block formatting context to the logical
- /// left side of this object's containing block.
- pub left_wall: Length,
- /// The distance from the logical *left* side of the block formatting context to the logical
- /// right side of this object's containing block.
- pub right_wall: Length,
}
/// Whether the float is left or right.
@@ -210,39 +290,41 @@ pub struct FloatBand {
pub right: Option<Length>,
}
-impl FloatBand {
- // Returns true if this band is clear of floats on the given side or sides.
- fn is_clear(&self, side: ClearSide) -> bool {
- match (side, self.left, self.right) {
- (ClearSide::Left, Some(_), _) |
- (ClearSide::Right, _, Some(_)) |
- (ClearSide::Both, Some(_), _) |
- (ClearSide::Both, _, Some(_)) => false,
- (ClearSide::None, _, _) |
- (ClearSide::Left, None, _) |
- (ClearSide::Right, _, None) |
- (ClearSide::Both, None, None) => true,
+impl FloatSide {
+ fn from_style(style: &ComputedValues) -> Option<FloatSide> {
+ match style.get_box().float {
+ FloatProperty::None => None,
+ FloatProperty::Left => Some(FloatSide::Left),
+ FloatProperty::Right => Some(FloatSide::Right),
}
}
+}
- // Determines whether an object fits in a band.
- fn object_fits(&self, object: &PlacementInfo) -> bool {
- // If we must be clear on the given side and we aren't, this object doesn't fit.
- if !self.is_clear(object.clear) {
- return false;
+impl ClearSide {
+ pub(crate) fn from_style(style: &ComputedValues) -> ClearSide {
+ match style.get_box().clear {
+ ClearProperty::None => ClearSide::None,
+ ClearProperty::Left => ClearSide::Left,
+ ClearProperty::Right => ClearSide::Right,
+ ClearProperty::Both => ClearSide::Both,
}
+ }
+}
+impl FloatBand {
+ // Determines whether an object fits in a band.
+ fn object_fits(&self, object: &PlacementInfo, walls: &ContainingBlockPositionInfo) -> bool {
match object.side {
FloatSide::Left => {
// Compute a candidate left position for the object.
let candidate_left = match self.left {
- None => object.left_wall,
- Some(left) => left.max(object.left_wall),
+ None => walls.inline_start,
+ Some(left) => left.max(walls.inline_start),
};
// If this band has an existing left float in it, then make sure that the object
// doesn't stick out past the right edge (rule 7).
- if self.left.is_some() && candidate_left + object.size.inline > object.right_wall {
+ if self.left.is_some() && candidate_left + object.size.inline > walls.inline_end {
return false;
}
@@ -257,13 +339,14 @@ impl FloatBand {
FloatSide::Right => {
// Compute a candidate right position for the object.
let candidate_right = match self.right {
- None => object.right_wall,
- Some(right) => right.min(object.right_wall),
+ None => walls.inline_end,
+ Some(right) => right.min(walls.inline_end),
};
// If this band has an existing right float in it, then make sure that the new
// object doesn't stick out past the left edge (rule 7).
- if self.right.is_some() && candidate_right - object.size.inline < object.left_wall {
+ if self.right.is_some() && candidate_right - object.size.inline < walls.inline_start
+ {
return false;
}
@@ -540,6 +623,12 @@ impl FloatBandLink {
}
}
+impl Debug for FloatFragment {
+ fn fmt(&self, formatter: &mut Formatter) -> FmtResult {
+ write!(formatter, "FloatFragment")
+ }
+}
+
// Float boxes
impl FloatBox {
@@ -561,4 +650,247 @@ impl FloatBox {
),
}
}
+
+ pub fn layout(
+ &mut self,
+ layout_context: &LayoutContext,
+ positioning_context: &mut PositioningContext,
+ containing_block: &ContainingBlock,
+ mut sequential_layout_state: Option<&mut SequentialLayoutState>,
+ ) -> Fragment {
+ let sequential_layout_state = sequential_layout_state
+ .as_mut()
+ .expect("Tried to lay out a float with no sequential placement state!");
+
+ // Speculate that the float ceiling will be located at the current block position plus the
+ // result of solving any margins we're building up. This is usually right, but it can be
+ // incorrect if there are more in-flow collapsible margins yet to be seen. An example
+ // showing when this can go wrong:
+ //
+ // <div style="margin: 5px"></div>
+ // <div style="float: left"></div>
+ // <div style="margin: 10px"></div>
+ //
+ // Assuming these are all in-flow, the float should be placed 10px down from the start, not
+ // 5px, but we can't know that because we haven't seen the block after this float yet.
+ //
+ // FIXME(pcwalton): Implement the proper behavior when speculation fails. Either detect it
+ // afterward and fix it up, or detect this situation ahead of time via lookahead and make
+ // sure `current_margin` is accurate before calling this method.
+ sequential_layout_state
+ .floats
+ .lower_ceiling(sequential_layout_state.current_block_position_including_margins());
+
+ let style = match self.contents {
+ IndependentFormattingContext::Replaced(ref replaced) => replaced.style.clone(),
+ IndependentFormattingContext::NonReplaced(ref non_replaced) => {
+ non_replaced.style.clone()
+ },
+ };
+ let float_context = &mut sequential_layout_state.floats;
+ let box_fragment = positioning_context.layout_maybe_position_relative_fragment(
+ layout_context,
+ containing_block,
+ &style,
+ |mut positioning_context| {
+ // Margin is computed this way regardless of whether the element is replaced
+ // or non-replaced.
+ let pbm = style.padding_border_margin(containing_block);
+ let margin = pbm.margin.auto_is(|| Length::zero());
+ let pbm_sums = &(&pbm.padding + &pbm.border) + &margin;
+
+ let (content_size, fragments);
+ match self.contents {
+ IndependentFormattingContext::NonReplaced(ref mut non_replaced) => {
+ // Calculate inline size.
+ // https://drafts.csswg.org/css2/#float-width
+ let box_size = non_replaced.style.content_box_size(&containing_block, &pbm);
+ let max_box_size = non_replaced
+ .style
+ .content_max_box_size(&containing_block, &pbm);
+ let min_box_size = non_replaced
+ .style
+ .content_min_box_size(&containing_block, &pbm)
+ .auto_is(Length::zero);
+
+ let tentative_inline_size = box_size.inline.auto_is(|| {
+ let available_size =
+ containing_block.inline_size - pbm_sums.inline_sum();
+ non_replaced
+ .inline_content_sizes(layout_context)
+ .shrink_to_fit(available_size)
+ });
+ let inline_size = tentative_inline_size
+ .clamp_between_extremums(min_box_size.inline, max_box_size.inline);
+
+ // Calculate block size.
+ // https://drafts.csswg.org/css2/#block-root-margin
+ // FIXME(pcwalton): Is a tree rank of zero correct here?
+ let containing_block_for_children = ContainingBlock {
+ inline_size,
+ block_size: box_size.block,
+ style: &non_replaced.style,
+ };
+ let independent_layout = non_replaced.layout(
+ layout_context,
+ &mut positioning_context,
+ &containing_block_for_children,
+ 0,
+ );
+ content_size = Vec2 {
+ inline: inline_size,
+ block: box_size
+ .block
+ .auto_is(|| independent_layout.content_block_size),
+ };
+ fragments = independent_layout.fragments;
+ },
+ IndependentFormattingContext::Replaced(ref replaced) => {
+ // https://drafts.csswg.org/css2/#float-replaced-width
+ // https://drafts.csswg.org/css2/#inline-replaced-height
+ content_size = replaced.contents.used_size_as_if_inline_element(
+ &containing_block,
+ &replaced.style,
+ None,
+ &pbm,
+ );
+ fragments = replaced
+ .contents
+ .make_fragments(&replaced.style, content_size.clone());
+ },
+ };
+
+ let margin_box_start_corner = float_context.add_float(&PlacementInfo {
+ size: &content_size + &pbm_sums.sum(),
+ side: FloatSide::from_style(&style).expect("Float box wasn't floated!"),
+ clear: ClearSide::from_style(&style),
+ });
+
+ let content_rect = Rect {
+ start_corner: &margin_box_start_corner + &pbm_sums.start_offset(),
+ size: content_size.clone(),
+ };
+
+ // Clearance is handled internally by the float placement logic, so there's no need
+ // to store it explicitly in the fragment.
+ let clearance = Length::zero();
+
+ BoxFragment::new(
+ self.contents.base_fragment_info(),
+ style.clone(),
+ fragments,
+ content_rect,
+ pbm.padding,
+ pbm.border,
+ margin,
+ clearance,
+ CollapsedBlockMargins::zero(),
+ )
+ },
+ );
+ Fragment::Float(box_fragment)
+ }
+}
+
+// Float fragment storage
+
+// Layout state that we maintain when doing sequential traversals of the box tree in document
+// order.
+//
+// This data is only needed for float placement and float interaction, and as such is only present
+// if the current block formatting context contains floats.
+//
+// All coordinates here are relative to the start of the nearest ancestor block formatting context.
+//
+// This structure is expected to be cheap to clone, in order to allow for "snapshots" that enable
+// restarting layout at any point in the tree.
+#[derive(Clone)]
+pub(crate) struct SequentialLayoutState {
+ // Holds all floats in this block formatting context.
+ pub(crate) floats: FloatContext,
+ // The (logically) bottom border edge or top padding edge of the last in-flow block. Floats
+ // cannot be placed above this line.
+ //
+ // This is often, but not always, the same as the float ceiling. The float ceiling can be lower
+ // than this value because this value is calculated based on in-flow boxes only, while
+ // out-of-flow floats can affect the ceiling as well (see CSS 2.1 § 9.5.1 rule 6).
+ pub(crate) bfc_relative_block_position: Length,
+ // Any collapsible margins that we've encountered after `bfc_relative_block_position`.
+ pub(crate) current_margin: CollapsedMargin,
+}
+
+impl SequentialLayoutState {
+ // Creates a new empty `SequentialLayoutState`.
+ pub(crate) fn new() -> SequentialLayoutState {
+ SequentialLayoutState {
+ floats: FloatContext::new(),
+ current_margin: CollapsedMargin::zero(),
+ bfc_relative_block_position: Length::zero(),
+ }
+ }
+
+ // Moves the current block position (logically) down by `block_distance`.
+ //
+ // Floats may not be placed higher than the current block position.
+ pub(crate) fn advance_block_position(&mut self, block_distance: Length) {
+ self.bfc_relative_block_position += block_distance;
+ self.floats.lower_ceiling(self.bfc_relative_block_position);
+ }
+
+ pub(crate) fn update_all_containing_block_offsets(
+ &mut self,
+ mut new_distance: ContainingBlockPositionInfo,
+ ) -> ContainingBlockPositionInfo {
+ mem::swap(&mut new_distance, &mut self.floats.containing_block_info);
+ new_distance
+ }
+
+ pub(crate) fn current_block_position_including_margins(&self) -> Length {
+ self.bfc_relative_block_position + self.current_margin.solve()
+ }
+
+ // Collapses margins, moving the block position down by the collapsed value of `current_margin`
+ // and resetting `current_margin` to zero.
+ //
+ // Call this method before laying out children when it is known that the start margin of the
+ // current fragment can't collapse with the margins of any of its children.
+ pub(crate) fn collapse_margins(&mut self) {
+ self.advance_block_position(self.current_margin.solve());
+ self.current_margin = CollapsedMargin::zero();
+ }
+
+ // Returns the amount of clearance that a block with the given `clear` value at the current
+ // `bfc_relative_block_position` (with top margin included in `current_margin` if applicable)
+ // needs to have.
+ //
+ // https://www.w3.org/TR/2011/REC-CSS2-20110607/visuren.html#flow-control
+ pub(crate) fn calculate_clearance(&self, clear_side: ClearSide) -> Length {
+ if clear_side == ClearSide::None {
+ return Length::zero();
+ }
+
+ let hypothetical_block_position = self.current_block_position_including_margins();
+ let clear_position = match clear_side {
+ ClearSide::None => unreachable!(),
+ ClearSide::Left => self
+ .floats
+ .clear_left_position
+ .max(hypothetical_block_position),
+ ClearSide::Right => self
+ .floats
+ .clear_right_position
+ .max(hypothetical_block_position),
+ ClearSide::Both => self
+ .floats
+ .clear_left_position
+ .max(self.floats.clear_right_position)
+ .max(hypothetical_block_position),
+ };
+ clear_position - hypothetical_block_position
+ }
+
+ /// Adds a new adjoining margin.
+ pub(crate) fn adjoin_assign(&mut self, margin: &CollapsedMargin) {
+ self.current_margin.adjoin_assign(margin)
+ }
}
diff --git a/components/layout_2020/flow/inline.rs b/components/layout_2020/flow/inline.rs
index c59ffca9215..962a90e9213 100644
--- a/components/layout_2020/flow/inline.rs
+++ b/components/layout_2020/flow/inline.rs
@@ -4,7 +4,7 @@
use crate::cell::ArcRefCell;
use crate::context::LayoutContext;
-use crate::flow::float::FloatBox;
+use crate::flow::float::{FloatBox, SequentialLayoutState};
use crate::flow::FlowLayout;
use crate::formatting_contexts::IndependentFormattingContext;
use crate::fragment_tree::BaseFragmentInfo;
@@ -99,6 +99,7 @@ struct InlineFormattingContextState<'box_tree, 'a, 'b> {
inline_position: Length,
partial_inline_boxes_stack: Vec<PartialInlineBoxFragment<'box_tree>>,
current_nesting_level: InlineNestingLevelState<'box_tree>,
+ sequential_layout_state: Option<&'a mut SequentialLayoutState>,
}
impl<'box_tree, 'a, 'b> InlineFormattingContextState<'box_tree, 'a, 'b> {
@@ -271,6 +272,7 @@ impl InlineFormattingContext {
positioning_context: &mut PositioningContext,
containing_block: &ContainingBlock,
tree_rank: usize,
+ sequential_layout_state: Option<&mut SequentialLayoutState>,
) -> FlowLayout {
let mut ifc = InlineFormattingContextState {
positioning_context,
@@ -298,8 +300,15 @@ impl InlineFormattingContext {
positioning_context: None,
text_decoration_line: self.text_decoration_line,
},
+ sequential_layout_state,
};
+ // FIXME(pcwalton): This assumes that margins never collapse through inline formatting
+ // contexts (i.e. that inline formatting contexts are never empty). Is that right?
+ if let Some(ref mut sequential_layout_state) = ifc.sequential_layout_state {
+ sequential_layout_state.collapse_margins();
+ }
+
loop {
if let Some(child) = ifc.current_nesting_level.remaining_boxes.next() {
match &mut *child.borrow_mut() {
@@ -342,8 +351,30 @@ impl InlineFormattingContext {
.fragments_so_far
.push(Fragment::AbsoluteOrFixedPositioned(hoisted_fragment));
},
- InlineLevelBox::OutOfFlowFloatBox(_box_) => {
- // TODO
+ InlineLevelBox::OutOfFlowFloatBox(box_) => {
+ let mut fragment = box_.layout(
+ layout_context,
+ ifc.positioning_context,
+ containing_block,
+ ifc.sequential_layout_state.as_mut().map(|c| &mut **c),
+ );
+ if let Some(state) = &ifc.sequential_layout_state {
+ let offset_from_formatting_context_to_containing_block = Vec2 {
+ inline: state.floats.containing_block_info.inline_start,
+ block: state.floats.containing_block_info.block_start +
+ state
+ .floats
+ .containing_block_info
+ .block_start_margins_not_collapsed
+ .solve(),
+ };
+ if let Fragment::Float(ref mut box_fragment) = &mut fragment {
+ box_fragment.content_rect.start_corner =
+ &box_fragment.content_rect.start_corner -
+ &offset_from_formatting_context_to_containing_block;
+ }
+ }
+ ifc.current_nesting_level.fragments_so_far.push(fragment);
},
}
} else
@@ -360,6 +391,7 @@ impl InlineFormattingContext {
ifc.lines.finish_line(
&mut ifc.current_nesting_level,
containing_block,
+ ifc.sequential_layout_state,
ifc.inline_position,
);
return FlowLayout {
@@ -377,6 +409,7 @@ impl Lines {
&mut self,
top_nesting_level: &mut InlineNestingLevelState,
containing_block: &ContainingBlock,
+ mut sequential_layout_state: Option<&mut SequentialLayoutState>,
line_content_inline_size: Length,
) {
let mut line_contents = std::mem::take(&mut top_nesting_level.fragments_so_far);
@@ -430,7 +463,11 @@ impl Lines {
inline: containing_block.inline_size,
block: line_block_size,
};
+
self.next_line_block_position += size.block;
+ if let Some(ref mut sequential_layout_state) = sequential_layout_state {
+ sequential_layout_state.advance_block_position(size.block);
+ }
self.fragments
.push(Fragment::Anonymous(AnonymousFragment::new(
@@ -519,6 +556,7 @@ impl<'box_tree> PartialInlineBoxFragment<'box_tree> {
self.padding.clone(),
self.border.clone(),
self.margin.clone(),
+ Length::zero(),
CollapsedBlockMargins::zero(),
);
let last_fragment = self.last_box_tree_fragment && !at_line_break;
@@ -583,6 +621,7 @@ fn layout_atomic(
pbm.padding,
pbm.border,
margin,
+ Length::zero(),
CollapsedBlockMargins::zero(),
)
},
@@ -658,6 +697,7 @@ fn layout_atomic(
pbm.padding,
pbm.border,
margin,
+ Length::zero(),
CollapsedBlockMargins::zero(),
)
},
@@ -861,8 +901,12 @@ impl TextRun {
partial.parent_nesting_level.inline_start = Length::zero();
nesting_level = &mut partial.parent_nesting_level;
}
- ifc.lines
- .finish_line(nesting_level, ifc.containing_block, ifc.inline_position);
+ ifc.lines.finish_line(
+ nesting_level,
+ ifc.containing_block,
+ ifc.sequential_layout_state.as_mut().map(|c| &mut **c),
+ ifc.inline_position,
+ );
ifc.inline_position = Length::zero();
}
}
diff --git a/components/layout_2020/flow/mod.rs b/components/layout_2020/flow/mod.rs
index 73630acf7bd..22c23253be4 100644
--- a/components/layout_2020/flow/mod.rs
+++ b/components/layout_2020/flow/mod.rs
@@ -4,17 +4,17 @@
//! Flow layout, also known as block-and-inline layout.
+use std::ops::DerefMut;
+
use crate::cell::ArcRefCell;
use crate::context::LayoutContext;
-use crate::flow::float::{FloatBox, FloatContext};
+use crate::flow::float::{ClearSide, ContainingBlockPositionInfo, FloatBox, SequentialLayoutState};
use crate::flow::inline::InlineFormattingContext;
use crate::formatting_contexts::{
IndependentFormattingContext, IndependentLayout, NonReplacedFormattingContext,
};
use crate::fragment_tree::BaseFragmentInfo;
-use crate::fragments::{
- AnonymousFragment, BoxFragment, CollapsedBlockMargins, CollapsedMargin, Fragment,
-};
+use crate::fragments::{BoxFragment, CollapsedBlockMargins, CollapsedMargin, Fragment};
use crate::geom::flow_relative::{Rect, Sides, Vec2};
use crate::positioned::{AbsolutelyPositionedBox, PositioningContext};
use crate::replaced::ReplacedContent;
@@ -78,26 +78,26 @@ impl BlockFormattingContext {
containing_block: &ContainingBlock,
tree_rank: usize,
) -> IndependentLayout {
- let mut float_context;
- let float_context = if self.contains_floats {
- float_context = FloatContext::new();
- Some(&mut float_context)
+ let mut sequential_layout_state = if self.contains_floats || !layout_context.use_rayon {
+ Some(SequentialLayoutState::new())
} else {
None
};
+
let flow_layout = self.contents.layout(
layout_context,
positioning_context,
containing_block,
tree_rank,
- float_context,
+ sequential_layout_state.as_mut(),
CollapsibleWithParentStartMargin(false),
);
- assert!(
+ debug_assert!(
!flow_layout
.collapsible_margins_in_children
.collapsed_through
);
+
IndependentLayout {
fragments: flow_layout.fragments,
content_block_size: flow_layout.content_block_size +
@@ -113,7 +113,7 @@ impl BlockContainer {
positioning_context: &mut PositioningContext,
containing_block: &ContainingBlock,
tree_rank: usize,
- float_context: Option<&mut FloatContext>,
+ sequential_layout_state: Option<&mut SequentialLayoutState>,
collapsible_with_parent_start_margin: CollapsibleWithParentStartMargin,
) -> FlowLayout {
match self {
@@ -123,7 +123,7 @@ impl BlockContainer {
child_boxes,
containing_block,
tree_rank,
- float_context,
+ sequential_layout_state,
collapsible_with_parent_start_margin,
),
BlockContainer::InlineFormattingContext(ifc) => ifc.layout(
@@ -131,6 +131,7 @@ impl BlockContainer {
positioning_context,
containing_block,
tree_rank,
+ sequential_layout_state,
),
}
}
@@ -169,130 +170,113 @@ fn layout_block_level_children(
child_boxes: &[ArcRefCell<BlockLevelBox>],
containing_block: &ContainingBlock,
tree_rank: usize,
- mut float_context: Option<&mut FloatContext>,
+ mut sequential_layout_state: Option<&mut SequentialLayoutState>,
collapsible_with_parent_start_margin: CollapsibleWithParentStartMargin,
) -> FlowLayout {
- fn place_block_level_fragment(fragment: &mut Fragment, placement_state: &mut PlacementState) {
- match fragment {
- Fragment::Box(fragment) => {
- let fragment_block_margins = &fragment.block_margins_collapsed_with_children;
- let fragment_block_size = fragment.padding.block_sum() +
- fragment.border.block_sum() +
- fragment.content_rect.size.block;
-
- if placement_state.next_in_flow_margin_collapses_with_parent_start_margin {
- assert_eq!(placement_state.current_margin.solve(), Length::zero());
- placement_state
- .start_margin
- .adjoin_assign(&fragment_block_margins.start);
- if fragment_block_margins.collapsed_through {
- placement_state
- .start_margin
- .adjoin_assign(&fragment_block_margins.end);
- return;
- }
- placement_state.next_in_flow_margin_collapses_with_parent_start_margin = false;
- } else {
- placement_state
- .current_margin
- .adjoin_assign(&fragment_block_margins.start);
- }
- fragment.content_rect.start_corner.block += placement_state.current_margin.solve() +
- placement_state.current_block_direction_position;
- if fragment_block_margins.collapsed_through {
- placement_state
- .current_margin
- .adjoin_assign(&fragment_block_margins.end);
- return;
- }
- placement_state.current_block_direction_position +=
- placement_state.current_margin.solve() + fragment_block_size;
- placement_state.current_margin = fragment_block_margins.end;
- },
- Fragment::AbsoluteOrFixedPositioned(fragment) => {
- let offset = Vec2 {
- block: placement_state.current_margin.solve() +
- placement_state.current_block_direction_position,
- inline: Length::new(0.),
- };
- fragment.borrow_mut().adjust_offsets(offset);
- },
- Fragment::Anonymous(_) => {},
- _ => unreachable!(),
- }
+ match sequential_layout_state {
+ Some(ref mut sequential_layout_state) => layout_block_level_children_sequentially(
+ layout_context,
+ positioning_context,
+ child_boxes,
+ containing_block,
+ tree_rank,
+ sequential_layout_state,
+ collapsible_with_parent_start_margin,
+ ),
+ None => layout_block_level_children_in_parallel(
+ layout_context,
+ positioning_context,
+ child_boxes,
+ containing_block,
+ tree_rank,
+ collapsible_with_parent_start_margin,
+ ),
}
+}
- struct PlacementState {
- next_in_flow_margin_collapses_with_parent_start_margin: bool,
- start_margin: CollapsedMargin,
- current_margin: CollapsedMargin,
- current_block_direction_position: Length,
- }
+fn layout_block_level_children_in_parallel(
+ layout_context: &LayoutContext,
+ positioning_context: &mut PositioningContext,
+ child_boxes: &[ArcRefCell<BlockLevelBox>],
+ containing_block: &ContainingBlock,
+ tree_rank: usize,
+ collapsible_with_parent_start_margin: CollapsibleWithParentStartMargin,
+) -> FlowLayout {
+ let mut placement_state = PlacementState::new(collapsible_with_parent_start_margin);
- let mut placement_state = PlacementState {
- next_in_flow_margin_collapses_with_parent_start_margin:
- collapsible_with_parent_start_margin.0,
- start_margin: CollapsedMargin::zero(),
- current_margin: CollapsedMargin::zero(),
- current_block_direction_position: Length::zero(),
- };
let fragments = positioning_context.adjust_static_positions(tree_rank, |positioning_context| {
- if float_context.is_some() || !layout_context.use_rayon {
- // Because floats are involved, we do layout for this block formatting context
- // in tree order without parallelism. This enables mutable access
- // to a `FloatContext` that tracks every float encountered so far (again in tree order).
- child_boxes
- .iter()
- .enumerate()
- .map(|(tree_rank, box_)| {
- let mut fragment = box_.borrow_mut().layout(
+ let collects_for_nearest_positioned_ancestor =
+ positioning_context.collects_for_nearest_positioned_ancestor();
+ let mut fragments: Vec<Fragment> = child_boxes
+ .par_iter()
+ .enumerate()
+ .mapfold_reduce_into(
+ positioning_context,
+ |positioning_context, (tree_rank, box_)| {
+ box_.borrow_mut().layout(
layout_context,
positioning_context,
containing_block,
tree_rank,
- float_context.as_mut().map(|c| &mut **c),
- );
- place_block_level_fragment(&mut fragment, &mut placement_state);
- fragment
- })
- .collect()
- } else {
- let collects_for_nearest_positioned_ancestor =
- positioning_context.collects_for_nearest_positioned_ancestor();
- let mut fragments = child_boxes
- .par_iter()
- .enumerate()
- .mapfold_reduce_into(
- positioning_context,
- |positioning_context, (tree_rank, box_)| {
- box_.borrow_mut().layout(
- layout_context,
- positioning_context,
- containing_block,
- tree_rank,
- /* float_context = */ None,
- )
- },
- || PositioningContext::new_for_rayon(collects_for_nearest_positioned_ancestor),
- PositioningContext::append,
- )
- .collect();
- for fragment in &mut fragments {
- place_block_level_fragment(fragment, &mut placement_state)
- }
- fragments
+ /* sequential_layout_state = */ None,
+ )
+ },
+ || PositioningContext::new_for_rayon(collects_for_nearest_positioned_ancestor),
+ PositioningContext::append,
+ )
+ .collect();
+ for fragment in fragments.iter_mut() {
+ placement_state.place_fragment(fragment);
}
+ fragments
});
FlowLayout {
fragments,
content_block_size: placement_state.current_block_direction_position,
- collapsible_margins_in_children: CollapsedBlockMargins {
- collapsed_through: placement_state
- .next_in_flow_margin_collapses_with_parent_start_margin,
- start: placement_state.start_margin,
- end: placement_state.current_margin,
- },
+ collapsible_margins_in_children: placement_state.collapsible_margins_in_children(),
+ }
+}
+
+fn layout_block_level_children_sequentially(
+ layout_context: &LayoutContext,
+ positioning_context: &mut PositioningContext,
+ child_boxes: &[ArcRefCell<BlockLevelBox>],
+ containing_block: &ContainingBlock,
+ tree_rank: usize,
+ sequential_layout_state: &mut SequentialLayoutState,
+ collapsible_with_parent_start_margin: CollapsibleWithParentStartMargin,
+) -> FlowLayout {
+ let mut placement_state = PlacementState::new(collapsible_with_parent_start_margin);
+
+ let fragments = positioning_context.adjust_static_positions(tree_rank, |positioning_context| {
+ // Because floats are involved, we do layout for this block formatting context in tree
+ // order without parallelism. This enables mutable access to a `SequentialLayoutState` that
+ // tracks every float encountered so far (again in tree order).
+ child_boxes
+ .iter()
+ .enumerate()
+ .map(|(tree_rank, child_box)| {
+ let mut fragment = child_box.borrow_mut().layout(
+ layout_context,
+ positioning_context,
+ containing_block,
+ tree_rank,
+ Some(&mut *sequential_layout_state),
+ );
+
+ placement_state.place_fragment(&mut fragment);
+ placement_state
+ .adjust_positions_of_float_children(&mut fragment, sequential_layout_state);
+ fragment
+ })
+ .collect()
+ });
+
+ FlowLayout {
+ fragments,
+ content_block_size: placement_state.current_block_direction_position,
+ collapsible_margins_in_children: placement_state.collapsible_margins_in_children(),
}
}
@@ -303,7 +287,7 @@ impl BlockLevelBox {
positioning_context: &mut PositioningContext,
containing_block: &ContainingBlock,
tree_rank: usize,
- float_context: Option<&mut FloatContext>,
+ sequential_layout_state: Option<&mut SequentialLayoutState>,
) -> Fragment {
match self {
BlockLevelBox::SameFormattingContextBlock {
@@ -323,7 +307,7 @@ impl BlockLevelBox {
style,
NonReplacedContents::SameFormattingContextBlock(contents),
tree_rank,
- float_context,
+ sequential_layout_state,
)
},
)),
@@ -339,6 +323,7 @@ impl BlockLevelBox {
replaced.base_fragment_info,
&replaced.style,
&replaced.contents,
+ sequential_layout_state,
)
},
))
@@ -359,7 +344,7 @@ impl BlockLevelBox {
non_replaced,
),
tree_rank,
- float_context,
+ sequential_layout_state,
)
},
))
@@ -379,12 +364,12 @@ impl BlockLevelBox {
positioning_context.push(hoisted_box);
Fragment::AbsoluteOrFixedPositioned(hoisted_fragment)
},
- BlockLevelBox::OutOfFlowFloatBox(_box_) => {
- // FIXME: call layout_maybe_position_relative_fragment here
- Fragment::Anonymous(AnonymousFragment::no_op(
- containing_block.style.writing_mode,
- ))
- },
+ BlockLevelBox::OutOfFlowFloatBox(box_) => box_.layout(
+ layout_context,
+ positioning_context,
+ containing_block,
+ sequential_layout_state,
+ ),
}
}
@@ -425,7 +410,7 @@ fn layout_in_flow_non_replaced_block_level(
style: &Arc<ComputedValues>,
block_level_kind: NonReplacedContents,
tree_rank: usize,
- float_context: Option<&mut FloatContext>,
+ mut sequential_layout_state: Option<&mut SequentialLayoutState>,
) -> BoxFragment {
let pbm = style.padding_border_margin(containing_block);
let box_size = style.content_box_size(containing_block, &pbm);
@@ -479,37 +464,90 @@ fn layout_in_flow_non_replaced_block_level(
block_size,
style,
};
+
// https://drafts.csswg.org/css-writing-modes/#orthogonal-flows
assert_eq!(
containing_block.style.writing_mode, containing_block_for_children.style.writing_mode,
"Mixed writing modes are not supported yet"
);
+ let block_is_same_formatting_context = match block_level_kind {
+ NonReplacedContents::SameFormattingContextBlock(_) => true,
+ NonReplacedContents::EstablishesAnIndependentFormattingContext(_) => false,
+ };
+
+ let start_margin_can_collapse_with_children = block_is_same_formatting_context &&
+ pbm.padding.block_start == Length::zero() &&
+ pbm.border.block_start == Length::zero();
+ let end_margin_can_collapse_with_children = block_is_same_formatting_context &&
+ pbm.padding.block_end == Length::zero() &&
+ pbm.border.block_end == Length::zero() &&
+ block_size == LengthOrAuto::Auto &&
+ min_box_size.block == Length::zero();
+
+ let mut clearance = Length::zero();
+ let parent_containing_block_position_info;
+ match sequential_layout_state {
+ None => parent_containing_block_position_info = None,
+ Some(ref mut sequential_layout_state) => {
+ sequential_layout_state.adjoin_assign(&CollapsedMargin::new(margin.block_start));
+ if !start_margin_can_collapse_with_children {
+ sequential_layout_state.collapse_margins();
+ }
+
+ // Introduce clearance if necessary.
+ let clear_side = ClearSide::from_style(style);
+ clearance = sequential_layout_state.calculate_clearance(clear_side);
+
+ // NB: This will be a no-op if we're collapsing margins with our children since that
+ // can only happen if we have no block-start padding and border.
+ sequential_layout_state.advance_block_position(
+ pbm.padding.block_start + pbm.border.block_start + clearance,
+ );
+
+ // We are about to lay out children. Update the offset between the block formatting
+ // context and the containing block that we create for them. This offset is used to
+ // ajust BFC relative coordinates to coordinates that are relative to our content box.
+ // Our content box establishes the containing block for non-abspos children, including
+ // floats.
+ let inline_start = sequential_layout_state
+ .floats
+ .containing_block_info
+ .inline_start +
+ pbm.padding.inline_start +
+ pbm.border.inline_start +
+ margin.inline_start;
+ let new_cb_offsets = ContainingBlockPositionInfo {
+ block_start: sequential_layout_state.bfc_relative_block_position,
+ block_start_margins_not_collapsed: sequential_layout_state.current_margin,
+ inline_start,
+ inline_end: inline_start + inline_size,
+ };
+ parent_containing_block_position_info =
+ Some(sequential_layout_state.update_all_containing_block_offsets(new_cb_offsets));
+ },
+ };
+
let mut block_margins_collapsed_with_children = CollapsedBlockMargins::from_margin(&margin);
let fragments;
let mut content_block_size;
match block_level_kind {
NonReplacedContents::SameFormattingContextBlock(contents) => {
- let start_margin_can_collapse_with_children = pbm.padding.block_start == Length::zero() &&
- pbm.border.block_start == Length::zero();
- let end_margin_can_collapse_with_children = pbm.padding.block_end == Length::zero() &&
- pbm.border.block_end == Length::zero() &&
- block_size == LengthOrAuto::Auto &&
- min_box_size.block == Length::zero();
-
let flow_layout = contents.layout(
layout_context,
positioning_context,
&containing_block_for_children,
tree_rank,
- float_context,
+ sequential_layout_state.as_mut().map(|x| &mut **x),
CollapsibleWithParentStartMargin(start_margin_can_collapse_with_children),
);
+
fragments = flow_layout.fragments;
content_block_size = flow_layout.content_block_size;
- let mut collapsible_margins_in_children = flow_layout.collapsible_margins_in_children;
+ // Update margins.
+ let mut collapsible_margins_in_children = flow_layout.collapsible_margins_in_children;
if start_margin_can_collapse_with_children {
block_margins_collapsed_with_children
.start
@@ -546,12 +584,33 @@ fn layout_in_flow_non_replaced_block_level(
content_block_size = independent_layout.content_block_size;
},
};
+
let block_size = block_size.auto_is(|| {
content_block_size.clamp_between_extremums(min_box_size.block, max_box_size.block)
});
+
+ if let Some(ref mut sequential_layout_state) = sequential_layout_state {
+ // Now that we're done laying out our children, we can restore the
+ // parent's containing block position information.
+ sequential_layout_state
+ .update_all_containing_block_offsets(parent_containing_block_position_info.unwrap());
+
+ // Account for padding and border. We also might have to readjust the
+ // `bfc_relative_block_position` if it was different from the content size (i.e. was
+ // non-`auto` and/or was affected by min/max block size).
+ sequential_layout_state.advance_block_position(
+ (block_size - content_block_size) + pbm.padding.block_end + pbm.border.block_end,
+ );
+
+ if !end_margin_can_collapse_with_children {
+ sequential_layout_state.collapse_margins();
+ }
+ sequential_layout_state.adjoin_assign(&CollapsedMargin::new(margin.block_end));
+ }
+
let content_rect = Rect {
start_corner: Vec2 {
- block: pbm.padding.block_start + pbm.border.block_start,
+ block: pbm.padding.block_start + pbm.border.block_start + clearance,
inline: pbm.padding.inline_start + pbm.border.inline_start + margin.inline_start,
},
size: Vec2 {
@@ -559,6 +618,7 @@ fn layout_in_flow_non_replaced_block_level(
inline: inline_size,
},
};
+
BoxFragment::new(
base_fragment_info,
style.clone(),
@@ -567,6 +627,7 @@ fn layout_in_flow_non_replaced_block_level(
pbm.padding,
pbm.border,
margin,
+ clearance,
block_margins_collapsed_with_children,
)
}
@@ -579,6 +640,7 @@ fn layout_in_flow_replaced_block_level<'a>(
base_fragment_info: BaseFragmentInfo,
style: &Arc<ComputedValues>,
replaced: &ReplacedContent,
+ mut sequential_layout_state: Option<&mut SequentialLayoutState>,
) -> BoxFragment {
let pbm = style.padding_border_margin(containing_block);
let size = replaced.used_size_as_if_inline_element(containing_block, style, None, &pbm);
@@ -592,14 +654,24 @@ fn layout_in_flow_replaced_block_level<'a>(
block_end: pbm.margin.block_end.auto_is(Length::zero),
};
let fragments = replaced.make_fragments(style, size.clone());
+
+ let mut clearance = Length::zero();
+ if let Some(ref mut sequential_layout_state) = sequential_layout_state {
+ sequential_layout_state.collapse_margins();
+ clearance = sequential_layout_state.calculate_clearance(ClearSide::from_style(style));
+ sequential_layout_state
+ .advance_block_position(pbm.border.block_sum() + pbm.padding.block_sum() + size.block);
+ };
+
let content_rect = Rect {
start_corner: Vec2 {
- block: pbm.padding.block_start + pbm.border.block_start,
+ block: pbm.padding.block_start + pbm.border.block_start + clearance,
inline: pbm.padding.inline_start + pbm.border.inline_start + margin.inline_start,
},
size,
};
let block_margins_collapsed_with_children = CollapsedBlockMargins::from_margin(&margin);
+
BoxFragment::new(
base_fragment_info,
style.clone(),
@@ -608,6 +680,7 @@ fn layout_in_flow_replaced_block_level<'a>(
pbm.padding,
pbm.border,
margin,
+ Length::zero(),
block_margins_collapsed_with_children,
)
}
@@ -624,3 +697,130 @@ fn solve_inline_margins_for_in_flow_block_level(
(LengthOrAuto::LengthPercentage(start), _) => (start, available - start),
}
}
+
+// State that we maintain when placing blocks.
+//
+// In parallel mode, this placement is done after all child blocks are laid out. In sequential
+// mode, this is done right after each block is laid out.
+pub(crate) struct PlacementState {
+ next_in_flow_margin_collapses_with_parent_start_margin: bool,
+ start_margin: CollapsedMargin,
+ current_margin: CollapsedMargin,
+ current_block_direction_position: Length,
+}
+
+impl PlacementState {
+ fn new(
+ collapsible_with_parent_start_margin: CollapsibleWithParentStartMargin,
+ ) -> PlacementState {
+ PlacementState {
+ next_in_flow_margin_collapses_with_parent_start_margin:
+ collapsible_with_parent_start_margin.0,
+ start_margin: CollapsedMargin::zero(),
+ current_margin: CollapsedMargin::zero(),
+ current_block_direction_position: Length::zero(),
+ }
+ }
+
+ fn place_fragment(&mut self, fragment: &mut Fragment) {
+ match fragment {
+ Fragment::Box(fragment) => {
+ let fragment_block_margins = &fragment.block_margins_collapsed_with_children;
+ let fragment_block_size = fragment.clearance +
+ fragment.padding.block_sum() +
+ fragment.border.block_sum() +
+ fragment.content_rect.size.block;
+
+ if self.next_in_flow_margin_collapses_with_parent_start_margin {
+ debug_assert_eq!(self.current_margin.solve(), Length::zero());
+ self.start_margin
+ .adjoin_assign(&fragment_block_margins.start);
+ if fragment_block_margins.collapsed_through {
+ self.start_margin.adjoin_assign(&fragment_block_margins.end);
+ return;
+ }
+ self.next_in_flow_margin_collapses_with_parent_start_margin = false;
+ } else {
+ self.current_margin
+ .adjoin_assign(&fragment_block_margins.start);
+ }
+ fragment.content_rect.start_corner.block +=
+ self.current_margin.solve() + self.current_block_direction_position;
+ if fragment_block_margins.collapsed_through {
+ self.current_margin
+ .adjoin_assign(&fragment_block_margins.end);
+ return;
+ }
+ self.current_block_direction_position +=
+ self.current_margin.solve() + fragment_block_size;
+ self.current_margin = fragment_block_margins.end;
+ },
+ Fragment::AbsoluteOrFixedPositioned(fragment) => {
+ let offset = Vec2 {
+ block: self.current_margin.solve() + self.current_block_direction_position,
+ inline: Length::new(0.),
+ };
+ fragment.borrow_mut().adjust_offsets(offset);
+ },
+ Fragment::Anonymous(_) | Fragment::Float(_) => {},
+ _ => unreachable!(),
+ }
+ }
+
+ fn collapsible_margins_in_children(&self) -> CollapsedBlockMargins {
+ CollapsedBlockMargins {
+ collapsed_through: self.next_in_flow_margin_collapses_with_parent_start_margin,
+ start: self.start_margin,
+ end: self.current_margin,
+ }
+ }
+
+ /// When Float fragments are created in block flows, they are positioned
+ /// relative to the float containing independent block formatting context.
+ /// Once we place a float's containing block, this function can be used to
+ /// fix up the float position to be relative to the containing block.
+ fn adjust_positions_of_float_children(
+ &self,
+ fragment: &mut Fragment,
+ sequential_layout_state: &mut SequentialLayoutState,
+ ) {
+ let fragment = match fragment {
+ Fragment::Box(ref mut fragment) => fragment,
+ _ => return,
+ };
+
+ // TODO(mrobinson): Will these margins be accurate if this fragment
+ // collapses through. Can a fragment collapse through when it has a
+ // non-zero sized float inside? The float won't be positioned correctly
+ // anyway (see the comment in `floats.rs` about margin collapse), but
+ // this might make the result even worse.
+ let collapsed_margins = self.collapsible_margins_in_children().start.adjoin(
+ &sequential_layout_state
+ .floats
+ .containing_block_info
+ .block_start_margins_not_collapsed,
+ );
+
+ let parent_fragment_offset_in_cb = &fragment.content_rect.start_corner;
+ let parent_fragment_offset_in_formatting_context = Vec2 {
+ inline: sequential_layout_state
+ .floats
+ .containing_block_info
+ .inline_start +
+ parent_fragment_offset_in_cb.inline,
+ block: sequential_layout_state
+ .floats
+ .containing_block_info
+ .block_start +
+ collapsed_margins.solve() +
+ parent_fragment_offset_in_cb.block,
+ };
+
+ for child_fragment in fragment.children.iter_mut() {
+ if let Fragment::Float(box_fragment) = child_fragment.borrow_mut().deref_mut() {
+ box_fragment.content_rect.start_corner = &box_fragment.content_rect.start_corner -
+ &parent_fragment_offset_in_formatting_context;
+ }
+ }
+ }
+}
diff --git a/components/layout_2020/flow/root.rs b/components/layout_2020/flow/root.rs
index 5006b65a48b..39f82e85fa0 100644
--- a/components/layout_2020/flow/root.rs
+++ b/components/layout_2020/flow/root.rs
@@ -440,7 +440,7 @@ impl FragmentTree {
}
let fragment_relative_rect = match fragment {
- Fragment::Box(fragment) => fragment
+ Fragment::Box(fragment) | Fragment::Float(fragment) => fragment
.border_rect()
.to_physical(fragment.style.writing_mode, &containing_block),
Fragment::Text(fragment) => fragment
@@ -519,7 +519,7 @@ impl FragmentTree {
}
scroll_area = match fragment {
- Fragment::Box(fragment) => fragment
+ Fragment::Box(fragment) | Fragment::Float(fragment) => fragment
.scrollable_overflow(&containing_block)
.translate(containing_block.origin.to_vector()),
Fragment::Text(_) |