diff options
author | bors-servo <infra@servo.org> | 2023-06-03 13:43:17 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-06-03 13:43:17 +0200 |
commit | f26d17096d86a2119f879d4ff60c288051a97510 (patch) | |
tree | 35578d8a04a03c0e22ac6a6273578156581f13be /components | |
parent | 0f8824da6d1ec41ac8e779b9238501fddda50e20 (diff) | |
parent | 5c5cc4b7959f68a67e7ef9f99ece15f4ab98fe8c (diff) | |
download | servo-f26d17096d86a2119f879d4ff60c288051a97510.tar.gz servo-f26d17096d86a2119f879d4ff60c288051a97510.zip |
Auto merge of #29757 - Loirooriol:sequential-context-2020, r=Loirooriol
Lay out floats and handle clearance in layout 2020, but don't flow text around them yet
This is a crude rebase of #27539
<!-- Please describe your changes on the following line: -->
---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `___` with appropriate data: -->
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [ ] These changes fix #___ (GitHub issue number if applicable)
<!-- Either: -->
- [X] There are tests for these changes OR
- [ ] These changes do not require tests because ___
<!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.-->
<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->
Diffstat (limited to 'components')
-rw-r--r-- | components/config/prefs.rs | 3 | ||||
-rw-r--r-- | components/layout_2020/display_list/mod.rs | 2 | ||||
-rw-r--r-- | components/layout_2020/display_list/stacking_context.rs | 2 | ||||
-rw-r--r-- | components/layout_2020/flexbox/layout.rs | 1 | ||||
-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 | ||||
-rw-r--r-- | components/layout_2020/fragments.rs | 51 | ||||
-rw-r--r-- | components/layout_2020/geom.rs | 22 | ||||
-rw-r--r-- | components/layout_2020/positioned.rs | 3 | ||||
-rw-r--r-- | components/layout_2020/query.rs | 37 | ||||
-rw-r--r-- | components/layout_2020/tests/floats.rs | 85 | ||||
-rw-r--r-- | components/style/properties/longhands/box.mako.rs | 5 | ||||
-rw-r--r-- | components/style/values/computed/length.rs | 9 |
15 files changed, 907 insertions, 269 deletions
diff --git a/components/config/prefs.rs b/components/config/prefs.rs index d72e64ad7c8..fd213766ebe 100644 --- a/components/config/prefs.rs +++ b/components/config/prefs.rs @@ -445,6 +445,9 @@ mod gen { flexbox: { enabled: bool, }, + floats: { + enabled: bool, + }, #[serde(default = "default_layout_threads")] threads: i64, viewport: { diff --git a/components/layout_2020/display_list/mod.rs b/components/layout_2020/display_list/mod.rs index 46636064045..b1201e26add 100644 --- a/components/layout_2020/display_list/mod.rs +++ b/components/layout_2020/display_list/mod.rs @@ -193,7 +193,7 @@ impl Fragment { section: StackingContextSection, ) { match self { - Fragment::Box(b) => match b.style.get_inherited_box().visibility { + Fragment::Box(b) | Fragment::Float(b) => match b.style.get_inherited_box().visibility { Visibility::Visible => { BuilderForBoxFragment::new(b, containing_block).build(builder, section) }, diff --git a/components/layout_2020/display_list/stacking_context.rs b/components/layout_2020/display_list/stacking_context.rs index d4995d8cfc3..77421ea59a9 100644 --- a/components/layout_2020/display_list/stacking_context.rs +++ b/components/layout_2020/display_list/stacking_context.rs @@ -531,7 +531,7 @@ impl Fragment { ) { let containing_block = containing_block_info.get_containing_block_for_fragment(self); match self { - Fragment::Box(fragment) => { + Fragment::Box(fragment) | Fragment::Float(fragment) => { if mode == StackingContextBuildMode::SkipHoisted && fragment.style.clone_position().is_absolutely_positioned() { diff --git a/components/layout_2020/flexbox/layout.rs b/components/layout_2020/flexbox/layout.rs index b0744e08aa3..4a15af23005 100644 --- a/components/layout_2020/flexbox/layout.rs +++ b/components/layout_2020/flexbox/layout.rs @@ -864,6 +864,7 @@ impl FlexLine<'_> { flex_context.sides_to_flow_relative(item.padding), flex_context.sides_to_flow_relative(item.border), margin, + Length::zero(), collapsed_margin, ) }) 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(_) | diff --git a/components/layout_2020/fragments.rs b/components/layout_2020/fragments.rs index 4dbacf10182..f4b25ca2b46 100644 --- a/components/layout_2020/fragments.rs +++ b/components/layout_2020/fragments.rs @@ -26,6 +26,12 @@ use webrender_api::{FontInstanceKey, ImageKey}; #[derive(Serialize)] pub(crate) enum Fragment { Box(BoxFragment), + /// Floating content. A floated fragment is very similar to a normal + /// [BoxFragment] but it isn't positioned using normal in block flow + /// positioning rules (margin collapse, etc). Instead, they are laid out by + /// the [SequentialLayoutState] of their float containing block formatting + /// context. + Float(BoxFragment), Anonymous(AnonymousFragment), /// Absolute and fixed position fragments are hoisted up so that they /// are children of the BoxFragment that establishes their containing @@ -57,6 +63,8 @@ pub(crate) struct BoxFragment { pub border: Sides<Length>, pub margin: Sides<Length>, + pub clearance: Length, + pub block_margins_collapsed_with_children: CollapsedBlockMargins, /// The scrollable overflow of this box fragment. @@ -67,13 +75,18 @@ pub(crate) struct BoxFragment { } #[derive(Serialize)] +pub(crate) struct FloatFragment { + pub box_fragment: BoxFragment, +} + +#[derive(Serialize)] pub(crate) struct CollapsedBlockMargins { pub collapsed_through: bool, pub start: CollapsedMargin, pub end: CollapsedMargin, } -#[derive(Clone, Copy, Serialize)] +#[derive(Clone, Copy, Debug, Serialize)] pub(crate) struct CollapsedMargin { max_positive: Length, min_negative: Length, @@ -152,7 +165,7 @@ impl Fragment { pub fn offset_inline(&mut self, offset: &Length) { let position = match self { Fragment::Box(f) => &mut f.content_rect.start_corner, - Fragment::AbsoluteOrFixedPositioned(_) => return, + Fragment::Float(_) | Fragment::AbsoluteOrFixedPositioned(_) => return, Fragment::Anonymous(f) => &mut f.rect.start_corner, Fragment::Text(f) => &mut f.rect.start_corner, Fragment::Image(f) => &mut f.rect.start_corner, @@ -170,6 +183,7 @@ impl Fragment { Fragment::Anonymous(fragment) => &fragment.base, Fragment::Image(fragment) => &fragment.base, Fragment::IFrame(fragment) => &fragment.base, + Fragment::Float(fragment) => &fragment.base, }) } @@ -180,6 +194,11 @@ impl Fragment { pub fn print(&self, tree: &mut PrintTree) { match self { Fragment::Box(fragment) => fragment.print(tree), + Fragment::Float(fragment) => { + tree.new_level(format!("Float")); + fragment.print(tree); + tree.end_level(); + }, Fragment::AbsoluteOrFixedPositioned(_) => { tree.add_item("AbsoluteOrFixedPositioned".to_string()); }, @@ -195,7 +214,9 @@ impl Fragment { containing_block: &PhysicalRect<Length>, ) -> PhysicalRect<Length> { match self { - Fragment::Box(fragment) => fragment.scrollable_overflow_for_parent(&containing_block), + Fragment::Box(fragment) | Fragment::Float(fragment) => { + fragment.scrollable_overflow_for_parent(&containing_block) + }, Fragment::AbsoluteOrFixedPositioned(_) => PhysicalRect::zero(), Fragment::Anonymous(fragment) => fragment.scrollable_overflow.clone(), Fragment::Text(fragment) => fragment @@ -222,7 +243,7 @@ impl Fragment { } match self { - Fragment::Box(fragment) => { + Fragment::Box(fragment) | Fragment::Float(fragment) => { let content_rect = fragment .content_rect .to_physical(fragment.style.writing_mode, containing_block) @@ -267,16 +288,6 @@ impl Fragment { } impl AnonymousFragment { - pub fn no_op(mode: WritingMode) -> Self { - Self { - base: BaseFragment::anonymous(), - children: vec![], - rect: Rect::zero(), - mode, - scrollable_overflow: PhysicalRect::zero(), - } - } - pub fn new(rect: Rect<Length>, children: Vec<Fragment>, mode: WritingMode) -> Self { // FIXME(mrobinson, bug 25564): We should be using the containing block // here to properly convert scrollable overflow to physical geometry. @@ -325,6 +336,7 @@ impl BoxFragment { padding: Sides<Length>, border: Sides<Length>, margin: Sides<Length>, + clearance: Length, block_margins_collapsed_with_children: CollapsedBlockMargins, ) -> BoxFragment { let position = style.get_box().position; @@ -344,6 +356,7 @@ impl BoxFragment { padding, border, margin, + clearance, block_margins_collapsed_with_children, PhysicalSize::new(width_overconstrained, height_overconstrained), ) @@ -357,6 +370,7 @@ impl BoxFragment { padding: Sides<Length>, border: Sides<Length>, margin: Sides<Length>, + clearance: Length, block_margins_collapsed_with_children: CollapsedBlockMargins, overconstrained: PhysicalSize<bool>, ) -> BoxFragment { @@ -379,6 +393,7 @@ impl BoxFragment { padding, border, margin, + clearance, block_margins_collapsed_with_children, scrollable_overflow_from_children, overconstrained, @@ -419,19 +434,17 @@ impl BoxFragment { \ncontent={:?}\ \npadding rect={:?}\ \nborder rect={:?}\ + \nclearance={:?}\ \nscrollable_overflow={:?}\ - \noverflow={:?} / {:?}\ - \noverconstrained={:?} - \nstyle={:p}", + \noverflow={:?} / {:?}", self.base, self.content_rect, self.padding_rect(), self.border_rect(), + self.clearance, self.scrollable_overflow(&PhysicalRect::zero()), self.style.get_box().overflow_x, self.style.get_box().overflow_y, - self.overconstrained, - self.style, )); for child in &self.children { diff --git a/components/layout_2020/geom.rs b/components/layout_2020/geom.rs index 7d687069d49..768f7aaf80d 100644 --- a/components/layout_2020/geom.rs +++ b/components/layout_2020/geom.rs @@ -251,6 +251,16 @@ impl<T> flow_relative::Sides<T> { self.block_start + self.block_end } + pub fn sum(&self) -> flow_relative::Vec2<T::Output> + where + T: Add + Copy, + { + flow_relative::Vec2 { + inline: self.inline_sum(), + block: self.block_sum(), + } + } + pub fn to_physical(&self, mode: WritingMode) -> PhysicalSides<T> where T: Clone, @@ -290,6 +300,18 @@ impl<T> flow_relative::Sides<T> { } } +impl<T> flow_relative::Sides<T> +where + T: Copy, +{ + pub fn start_offset(&self) -> flow_relative::Vec2<T> { + flow_relative::Vec2 { + inline: self.inline_start, + block: self.block_start, + } + } +} + impl flow_relative::Sides<&'_ LengthPercentage> { pub fn percentages_relative_to(&self, basis: Length) -> flow_relative::Sides<Length> { self.map(|s| s.percentage_relative_to(basis)) diff --git a/components/layout_2020/positioned.rs b/components/layout_2020/positioned.rs index 8cbf7faedaa..1b1c931477a 100644 --- a/components/layout_2020/positioned.rs +++ b/components/layout_2020/positioned.rs @@ -666,6 +666,7 @@ impl HoistedAbsolutelyPositionedBox { pbm.padding, pbm.border, margin, + Length::zero(), CollapsedBlockMargins::zero(), physical_overconstrained, ) @@ -808,7 +809,7 @@ fn adjust_static_positions( let child_fragment_rect = match &child_fragments[original_tree_rank] { Fragment::Box(b) => &b.content_rect, - Fragment::AbsoluteOrFixedPositioned(_) => continue, + Fragment::AbsoluteOrFixedPositioned(_) | Fragment::Float(_) => continue, Fragment::Anonymous(a) => &a.rect, _ => unreachable!(), }; diff --git a/components/layout_2020/query.rs b/components/layout_2020/query.rs index 3fd02a1597c..7d19963b951 100644 --- a/components/layout_2020/query.rs +++ b/components/layout_2020/query.rs @@ -425,7 +425,7 @@ fn process_offset_parent_query_inner( // // [1]: https://github.com/w3c/csswg-drafts/issues/4541 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 @@ -483,7 +483,7 @@ fn process_offset_parent_query_inner( } else { // Record the paths of the nodes being traversed. let parent_node_address = match fragment { - Fragment::Box(fragment) => { + Fragment::Box(fragment) | Fragment::Float(fragment) => { let is_eligible_parent = match (is_body_element, fragment.style.get_box().position) { // Spec says the element is eligible as `offsetParent` if any of @@ -540,24 +540,25 @@ fn process_offset_parent_query_inner( fragment_tree .find(|fragment, _, containing_block| { match fragment { - Fragment::Box(fragment) - if fragment.base.tag == Some(offset_parent_node_tag) => - { - // Again, take the *first* associated CSS layout box. - let padding_box_corner = fragment - .padding_rect() - .to_physical(fragment.style.writing_mode, &containing_block) - .origin - .to_vector() + - containing_block.origin.to_vector(); - let padding_box_corner = Vector2D::new( - Au::from_f32_px(padding_box_corner.x.px()), - Au::from_f32_px(padding_box_corner.y.px()), - ); - Some(padding_box_corner) + Fragment::Box(fragment) | Fragment::Float(fragment) => { + if fragment.base.tag == Some(offset_parent_node_tag) { + // Again, take the *first* associated CSS layout box. + let padding_box_corner = fragment + .padding_rect() + .to_physical(fragment.style.writing_mode, &containing_block) + .origin + .to_vector() + + containing_block.origin.to_vector(); + let padding_box_corner = Vector2D::new( + Au::from_f32_px(padding_box_corner.x.px()), + Au::from_f32_px(padding_box_corner.y.px()), + ); + Some(padding_box_corner) + } else { + None + } }, Fragment::AbsoluteOrFixedPositioned(_) | - Fragment::Box(_) | Fragment::Text(_) | Fragment::Image(_) | Fragment::IFrame(_) | diff --git a/components/layout_2020/tests/floats.rs b/components/layout_2020/tests/floats.rs index cae402ac316..089981021f7 100644 --- a/components/layout_2020/tests/floats.rs +++ b/components/layout_2020/tests/floats.rs @@ -9,7 +9,7 @@ extern crate lazy_static; use euclid::num::Zero; use layout::flow::float::{ClearSide, FloatBand, FloatBandNode, FloatBandTree, FloatContext}; -use layout::flow::float::{FloatSide, PlacementInfo}; +use layout::flow::float::{ContainingBlockPositionInfo, FloatSide, PlacementInfo}; use layout::geom::flow_relative::{Rect, Vec2}; use quickcheck::{Arbitrary, Gen}; use std::f32; @@ -57,10 +57,7 @@ impl<'a> Drop for PanicMsgSuppressor<'a> { struct FloatBandWrapper(FloatBand); impl Arbitrary for FloatBandWrapper { - fn arbitrary<G>(generator: &mut G) -> FloatBandWrapper - where - G: Gen, - { + fn arbitrary(generator: &mut Gen) -> FloatBandWrapper { let top: u32 = Arbitrary::arbitrary(generator); let left: Option<u32> = Arbitrary::arbitrary(generator); let right: Option<u32> = Arbitrary::arbitrary(generator); @@ -75,23 +72,17 @@ impl Arbitrary for FloatBandWrapper { #[derive(Clone, Debug)] struct FloatRangeInput { start_index: u32, - band_count: u32, side: FloatSide, length: u32, } impl Arbitrary for FloatRangeInput { - fn arbitrary<G>(generator: &mut G) -> FloatRangeInput - where - G: Gen, - { + fn arbitrary(generator: &mut Gen) -> FloatRangeInput { let start_index: u32 = Arbitrary::arbitrary(generator); - let band_count: u32 = Arbitrary::arbitrary(generator); let is_left: bool = Arbitrary::arbitrary(generator); let length: u32 = Arbitrary::arbitrary(generator); FloatRangeInput { start_index, - band_count, side: if is_left { FloatSide::Left } else { @@ -341,20 +332,25 @@ struct FloatInput { // The float may be placed no higher than this line. This simulates the effect of line boxes // per CSS 2.1 § 9.5.1 rule 6. ceiling: u32, + /// Containing block positioning information, which is used to track the current offsets + /// from the float containing block formatting context to the current containing block. + containing_block_info: ContainingBlockPositionInfo, } impl Arbitrary for FloatInput { - fn arbitrary<G>(generator: &mut G) -> FloatInput - where - G: Gen, - { - let width: u32 = Arbitrary::arbitrary(generator); - let height: u32 = Arbitrary::arbitrary(generator); - let is_left: bool = Arbitrary::arbitrary(generator); - let ceiling: u32 = Arbitrary::arbitrary(generator); - let left_wall: u32 = Arbitrary::arbitrary(generator); - let right_wall: u32 = Arbitrary::arbitrary(generator); - let clear: u8 = Arbitrary::arbitrary(generator); + fn arbitrary(generator: &mut Gen) -> FloatInput { + // See #29819: Limit the maximum size of all f32 values here because + // massive float values will start to introduce very bad floating point + // errors. + // TODO: This should be be addressed in a better way. Perhaps we should + // reintroduce the use of app_units in Layout 2020. + let width = u32::arbitrary(generator) % 12345; + let height = u32::arbitrary(generator) % 12345; + let is_left = bool::arbitrary(generator); + let ceiling = u32::arbitrary(generator) % 12345; + let left = u32::arbitrary(generator) % 12345; + let containing_block_width = u32::arbitrary(generator) % 12345; + let clear = u8::arbitrary(generator); FloatInput { info: PlacementInfo { size: Vec2 { @@ -367,10 +363,12 @@ impl Arbitrary for FloatInput { FloatSide::Right }, clear: new_clear_side(clear), - left_wall: Length::new(left_wall as f32), - right_wall: Length::new(right_wall as f32), }, ceiling, + containing_block_info: ContainingBlockPositionInfo::new_with_inline_offsets( + Length::new(left as f32), + Length::new(left as f32 + containing_block_width as f32), + ), } } @@ -389,12 +387,12 @@ impl Arbitrary for FloatInput { this.info.clear = new_clear_side(clear_side); shrunk = true; } - if let Some(left_wall) = self.info.left_wall.px().shrink().next() { - this.info.left_wall = Length::new(left_wall); + if let Some(left) = self.containing_block_info.inline_start.px().shrink().next() { + this.containing_block_info.inline_start = Length::new(left); shrunk = true; } - if let Some(right_wall) = self.info.right_wall.px().shrink().next() { - this.info.right_wall = Length::new(right_wall); + if let Some(right) = self.containing_block_info.inline_end.px().shrink().next() { + this.containing_block_info.inline_end = Length::new(right); shrunk = true; } if let Some(ceiling) = self.ceiling.shrink().next() { @@ -430,6 +428,7 @@ struct PlacedFloat { origin: Vec2<Length>, info: PlacementInfo, ceiling: Length, + containing_block_info: ContainingBlockPositionInfo, } impl Drop for FloatPlacement { @@ -442,8 +441,12 @@ impl Drop for FloatPlacement { eprintln!("Failing float placement:"); for placed_float in &self.placed_floats { eprintln!( - " * {:?} @ {:?}, {:?}", - placed_float.info, placed_float.origin, placed_float.ceiling + " * {:?} @ {:?}, T {:?} L {:?} R {:?}", + placed_float.info, + placed_float.origin, + placed_float.ceiling, + placed_float.containing_block_info.inline_start, + placed_float.containing_block_info.inline_end, ); } eprintln!("Bands:\n{:?}\n", self.float_context.bands); @@ -466,10 +469,12 @@ impl FloatPlacement { for float in floats { let ceiling = Length::new(float.ceiling as f32); float_context.lower_ceiling(ceiling); + float_context.containing_block_info = float.containing_block_info; placed_floats.push(PlacedFloat { origin: float_context.add_float(&float.info), info: float.info, ceiling, + containing_block_info: float.containing_block_info, }) } FloatPlacement { @@ -488,9 +493,14 @@ impl FloatPlacement { fn check_floats_rule_1(placement: &FloatPlacement) { for placed_float in &placement.placed_floats { match placed_float.info.side { - FloatSide::Left => assert!(placed_float.origin.inline >= placed_float.info.left_wall), + FloatSide::Left => assert!( + placed_float.origin.inline >= placed_float.containing_block_info.inline_start + ), FloatSide::Right => { - assert!(placed_float.rect().max_inline_position() <= placed_float.info.right_wall) + assert!( + placed_float.rect().max_inline_position() <= + placed_float.containing_block_info.inline_end + ) }, } } @@ -596,12 +606,14 @@ fn check_floats_rule_7(placement: &FloatPlacement) { // Only consider floats that stick out. match placed_float.info.side { FloatSide::Left => { - if placed_float.rect().max_inline_position() <= placed_float.info.right_wall { + if placed_float.rect().max_inline_position() <= + placed_float.containing_block_info.inline_end + { continue; } }, FloatSide::Right => { - if placed_float.origin.inline >= placed_float.info.left_wall { + if placed_float.origin.inline >= placed_float.containing_block_info.inline_start { continue; } }, @@ -661,7 +673,7 @@ fn check_floats_rule_9(floats_and_perturbations: Vec<(FloatInput, u32)>) { let mut placement = placement.clone(); { - let mut placed_float = &mut placement.placed_floats[float_index]; + let placed_float = &mut placement.placed_floats[float_index]; let perturbation = Length::new(perturbation as f32); match placed_float.info.side { FloatSide::Left => { @@ -747,6 +759,7 @@ fn check_basic_float_rules(placement: &FloatPlacement) { fn test_floats_rule_1() { let f: fn(Vec<FloatInput>) = check; quickcheck::quickcheck(f); + fn check(floats: Vec<FloatInput>) { check_floats_rule_1(&FloatPlacement::place(floats)); } diff --git a/components/style/properties/longhands/box.mako.rs b/components/style/properties/longhands/box.mako.rs index 62c3da725f5..3c3532bea7a 100644 --- a/components/style/properties/longhands/box.mako.rs +++ b/components/style/properties/longhands/box.mako.rs @@ -66,8 +66,8 @@ ${helpers.predefined_type( "Float", "computed::Float::None", engines="gecko servo-2013 servo-2020", - servo_2020_pref="layout.2020.unimplemented", initial_specified_value="specified::Float::None", + servo_2020_pref="layout.floats.enabled", spec="https://drafts.csswg.org/css-box/#propdef-float", animation_value_type="discrete", servo_restyle_damage="rebuild_and_reflow", @@ -78,7 +78,8 @@ ${helpers.predefined_type( "clear", "Clear", "computed::Clear::None", - engines="gecko servo-2013", + engines="gecko servo-2013 servo-2020", + servo_2020_pref="layout.floats.enabled", animation_value_type="discrete", gecko_ffi_name="mBreakType", spec="https://drafts.csswg.org/css-box/#propdef-clear", diff --git a/components/style/values/computed/length.rs b/components/style/values/computed/length.rs index 02b42b48241..f455da5f676 100644 --- a/components/style/values/computed/length.rs +++ b/components/style/values/computed/length.rs @@ -18,7 +18,7 @@ use crate::values::{specified, CSSFloat}; use crate::Zero; use app_units::Au; use std::fmt::{self, Write}; -use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub}; +use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub, SubAssign}; use style_traits::{CSSPixel, CssWriter, ToCss}; pub use super::image::Image; @@ -399,6 +399,13 @@ impl Sub for CSSPixelLength { } } +impl SubAssign for CSSPixelLength { + #[inline] + fn sub_assign(&mut self, other: Self) { + self.0 -= other.0; + } +} + impl From<CSSPixelLength> for Au { #[inline] fn from(len: CSSPixelLength) -> Self { |