diff options
Diffstat (limited to 'components/layout_2020/flow')
-rw-r--r-- | components/layout_2020/flow/float.rs | 416 | ||||
-rw-r--r-- | components/layout_2020/flow/inline.rs | 54 | ||||
-rw-r--r-- | components/layout_2020/flow/mod.rs | 482 | ||||
-rw-r--r-- | components/layout_2020/flow/root.rs | 4 |
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(_) | |