/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ //! Layout for CSS block-level elements. //! //! As a terminology note, the term *absolute positioning* here refers to elements with position //! `absolute` or `fixed`. The term *positioned element* refers to elements with position //! `relative`, `absolute`, and `fixed`. The term *containing block* (occasionally abbreviated as //! *CB*) is the containing block for the current flow, which differs from the static containing //! block if the flow is absolutely-positioned. //! //! "CSS 2.1" or "CSS 2.2" refers to the editor's draft of the W3C "Cascading Style Sheets Level 2 //! Revision 2 (CSS 2.2) Specification" available here: //! //! http://dev.w3.org/csswg/css2/ //! //! "INTRINSIC" refers to L. David Baron's "More Precise Definitions of Inline Layout and Table //! Layout" available here: //! //! http://dbaron.org/css/intrinsic/ //! //! "CSS-SIZING" refers to the W3C "CSS Intrinsic & Extrinsic Sizing Module Level 3" document //! available here: //! //! http://dev.w3.org/csswg/css-sizing/ #![deny(unsafe_code)] use app_units::{Au, MAX_AU}; use context::LayoutContext; use display_list::{BlockFlowDisplayListBuilding, BorderPaintingMode}; use display_list::{DisplayListBuildState, StackingContextCollectionFlags}; use display_list::StackingContextCollectionState; use euclid::{Point2D, Rect, SideOffsets2D, Size2D}; use floats::{ClearType, FloatKind, Floats, PlacementInfo}; use flow::{BaseFlow, EarlyAbsolutePositionInfo, Flow, FlowClass, ForceNonfloatedFlag, GetBaseFlow}; use flow::{ImmutableFlowUtils, LateAbsolutePositionInfo, OpaqueFlow, FragmentationContext, FlowFlags}; use flow_list::FlowList; use fragment::{CoordinateSystem, Fragment, FragmentBorderBoxIterator, Overflow, FragmentFlags}; use gfx::display_list::DisplayListSection; use gfx_traits::print_tree::PrintTree; use incremental::RelayoutMode; use layout_debug; use model::{AdjoiningMargins, CollapsibleMargins, IntrinsicISizes, MarginCollapseInfo, MaybeAuto}; use sequential; use serde::{Serialize, Serializer}; use servo_geometry::MaxRect; use std::cmp::{max, min}; use std::fmt; use std::sync::Arc; use style::computed_values::box_sizing::T as BoxSizing; use style::computed_values::display::T as Display; use style::computed_values::float::T as Float; use style::computed_values::overflow_x::T as StyleOverflow; use style::computed_values::position::T as Position; use style::computed_values::text_align::T as TextAlign; use style::context::SharedStyleContext; use style::logical_geometry::{LogicalMargin, LogicalPoint, LogicalRect, LogicalSize, WritingMode}; use style::properties::ComputedValues; use style::servo::restyle_damage::ServoRestyleDamage; use style::values::computed::{LengthOrPercentageOrNone, LengthOrPercentage}; use style::values::computed::LengthOrPercentageOrAuto; use traversal::PreorderFlowTraversal; /// Information specific to floated blocks. #[derive(Clone, Serialize)] pub struct FloatedBlockInfo { /// The amount of inline size that is available for the float. pub containing_inline_size: Au, /// The float ceiling, relative to `BaseFlow::position::cur_b` (i.e. the top part of the border /// box). pub float_ceiling: Au, /// Left or right? pub float_kind: FloatKind, } impl FloatedBlockInfo { pub fn new(float_kind: FloatKind) -> FloatedBlockInfo { FloatedBlockInfo { containing_inline_size: Au(0), float_ceiling: Au(0), float_kind: float_kind, } } } /// The solutions for the block-size-and-margins constraint equation. #[derive(Clone, Copy)] struct BSizeConstraintSolution { block_start: Au, block_size: Au, margin_block_start: Au, margin_block_end: Au } impl BSizeConstraintSolution { fn new(block_start: Au, block_size: Au, margin_block_start: Au, margin_block_end: Au) -> BSizeConstraintSolution { BSizeConstraintSolution { block_start: block_start, block_size: block_size, margin_block_start: margin_block_start, margin_block_end: margin_block_end, } } /// Solve the vertical constraint equation for absolute non-replaced elements. /// /// CSS Section 10.6.4 /// Constraint equation: /// block-start + block-end + block-size + margin-block-start + margin-block-end /// = absolute containing block block-size - (vertical padding and border) /// [aka available_block-size] /// /// Return the solution for the equation. fn solve_vertical_constraints_abs_nonreplaced(block_size: MaybeAuto, block_start_margin: MaybeAuto, block_end_margin: MaybeAuto, block_start: MaybeAuto, block_end: MaybeAuto, content_block_size: Au, available_block_size: Au) -> BSizeConstraintSolution { let (block_start, block_size, margin_block_start, margin_block_end) = match (block_start, block_end, block_size) { (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Auto) => { let margin_block_start = block_start_margin.specified_or_zero(); let margin_block_end = block_end_margin.specified_or_zero(); // Now it is the same situation as block-start Specified and block-end // and block-size Auto. let block_size = content_block_size; // Use a dummy value for `block_start`, since it has the static position. (Au(0), block_size, margin_block_start, margin_block_end) } (MaybeAuto::Specified(block_start), MaybeAuto::Specified(block_end), MaybeAuto::Specified(block_size)) => { match (block_start_margin, block_end_margin) { (MaybeAuto::Auto, MaybeAuto::Auto) => { let total_margin_val = available_block_size - block_start - block_end - block_size; (block_start, block_size, total_margin_val.scale_by(0.5), total_margin_val.scale_by(0.5)) } (MaybeAuto::Specified(margin_block_start), MaybeAuto::Auto) => { let sum = block_start + block_end + block_size + margin_block_start; (block_start, block_size, margin_block_start, available_block_size - sum) } (MaybeAuto::Auto, MaybeAuto::Specified(margin_block_end)) => { let sum = block_start + block_end + block_size + margin_block_end; (block_start, block_size, available_block_size - sum, margin_block_end) } (MaybeAuto::Specified(margin_block_start), MaybeAuto::Specified(margin_block_end)) => { // Values are over-constrained. Ignore value for 'block-end'. (block_start, block_size, margin_block_start, margin_block_end) } } } // For the rest of the cases, auto values for margin are set to 0 // If only one is Auto, solve for it (MaybeAuto::Auto, MaybeAuto::Specified(block_end), MaybeAuto::Specified(block_size)) => { let margin_block_start = block_start_margin.specified_or_zero(); let margin_block_end = block_end_margin.specified_or_zero(); let sum = block_end + block_size + margin_block_start + margin_block_end; (available_block_size - sum, block_size, margin_block_start, margin_block_end) } (MaybeAuto::Specified(block_start), MaybeAuto::Auto, MaybeAuto::Specified(block_size)) => { let margin_block_start = block_start_margin.specified_or_zero(); let margin_block_end = block_end_margin.specified_or_zero(); (block_start, block_size, margin_block_start, margin_block_end) } (MaybeAuto::Specified(block_start), MaybeAuto::Specified(block_end), MaybeAuto::Auto) => { let margin_block_start = block_start_margin.specified_or_zero(); let margin_block_end = block_end_margin.specified_or_zero(); let sum = block_start + block_end + margin_block_start + margin_block_end; (block_start, available_block_size - sum, margin_block_start, margin_block_end) } // If block-size is auto, then block-size is content block-size. Solve for the // non-auto value. (MaybeAuto::Specified(block_start), MaybeAuto::Auto, MaybeAuto::Auto) => { let margin_block_start = block_start_margin.specified_or_zero(); let margin_block_end = block_end_margin.specified_or_zero(); let block_size = content_block_size; (block_start, block_size, margin_block_start, margin_block_end) } (MaybeAuto::Auto, MaybeAuto::Specified(block_end), MaybeAuto::Auto) => { let margin_block_start = block_start_margin.specified_or_zero(); let margin_block_end = block_end_margin.specified_or_zero(); let block_size = content_block_size; let sum = block_end + block_size + margin_block_start + margin_block_end; (available_block_size - sum, block_size, margin_block_start, margin_block_end) } (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Specified(block_size)) => { let margin_block_start = block_start_margin.specified_or_zero(); let margin_block_end = block_end_margin.specified_or_zero(); // Use a dummy value for `block_start`, since it has the static position. (Au(0), block_size, margin_block_start, margin_block_end) } }; BSizeConstraintSolution::new(block_start, block_size, margin_block_start, margin_block_end) } /// Solve the vertical constraint equation for absolute replaced elements. /// /// Assumption: The used value for block-size has already been calculated. /// /// CSS Section 10.6.5 /// Constraint equation: /// block-start + block-end + block-size + margin-block-start + margin-block-end /// = absolute containing block block-size - (vertical padding and border) /// [aka available block-size] /// /// Return the solution for the equation. fn solve_vertical_constraints_abs_replaced(block_size: Au, block_start_margin: MaybeAuto, block_end_margin: MaybeAuto, block_start: MaybeAuto, block_end: MaybeAuto, _: Au, available_block_size: Au) -> BSizeConstraintSolution { let (block_start, block_size, margin_block_start, margin_block_end) = match (block_start, block_end) { (MaybeAuto::Auto, MaybeAuto::Auto) => { let margin_block_start = block_start_margin.specified_or_zero(); let margin_block_end = block_end_margin.specified_or_zero(); // Use a dummy value for `block_start`, since it has the static position. (Au(0), block_size, margin_block_start, margin_block_end) } (MaybeAuto::Specified(block_start), MaybeAuto::Specified(block_end)) => { match (block_start_margin, block_end_margin) { (MaybeAuto::Auto, MaybeAuto::Auto) => { let total_margin_val = available_block_size - block_start - block_end - block_size; (block_start, block_size, total_margin_val.scale_by(0.5), total_margin_val.scale_by(0.5)) } (MaybeAuto::Specified(margin_block_start), MaybeAuto::Auto) => { let sum = block_start + block_end + block_size + margin_block_start; (block_start, block_size, margin_block_start, available_block_size - sum) } (MaybeAuto::Auto, MaybeAuto::Specified(margin_block_end)) => { let sum = block_start + block_end + block_size + margin_block_end; (block_start, block_size, available_block_size - sum, margin_block_end) } (MaybeAuto::Specified(margin_block_start), MaybeAuto::Specified(margin_block_end)) => { // Values are over-constrained. Ignore value for 'block-end'. (block_start, block_size, margin_block_start, margin_block_end) } } } // If only one is Auto, solve for it (MaybeAuto::Auto, MaybeAuto::Specified(block_end)) => { let margin_block_start = block_start_margin.specified_or_zero(); let margin_block_end = block_end_margin.specified_or_zero(); let sum = block_end + block_size + margin_block_start + margin_block_end; (available_block_size - sum, block_size, margin_block_start, margin_block_end) } (MaybeAuto::Specified(block_start), MaybeAuto::Auto) => { let margin_block_start = block_start_margin.specified_or_zero(); let margin_block_end = block_end_margin.specified_or_zero(); (block_start, block_size, margin_block_start, margin_block_end) } }; BSizeConstraintSolution::new(block_start, block_size, margin_block_start, margin_block_end) } } /// Performs block-size calculations potentially multiple times, taking /// (assuming an horizontal writing mode) `height`, `min-height`, and `max-height` /// into account. After each call to `next()`, the caller must call `.try()` with the /// current calculated value of `height`. /// /// See CSS 2.1 § 10.7. pub struct CandidateBSizeIterator { block_size: MaybeAuto, max_block_size: Option, min_block_size: Au, pub candidate_value: Au, status: CandidateBSizeIteratorStatus, } impl CandidateBSizeIterator { /// Creates a new candidate block-size iterator. `block_container_block-size` is `None` if the block-size /// of the block container has not been determined yet. It will always be `Some` in the case of /// absolutely-positioned containing blocks. pub fn new(fragment: &Fragment, block_container_block_size: Option) -> CandidateBSizeIterator { // Per CSS 2.1 § 10.7, (assuming an horizontal writing mode,) // percentages in `min-height` and `max-height` refer to the height of // the containing block. // If that is not determined yet by the time we need to resolve // `min-height` and `max-height`, percentage values are ignored. let block_size = match (fragment.style.content_block_size(), block_container_block_size) { (LengthOrPercentageOrAuto::Percentage(percent), Some(block_container_block_size)) => { MaybeAuto::Specified(block_container_block_size.scale_by(percent.0)) } (LengthOrPercentageOrAuto::Calc(calc), _) => { MaybeAuto::from_option(calc.to_used_value(block_container_block_size)) } (LengthOrPercentageOrAuto::Percentage(_), None) | (LengthOrPercentageOrAuto::Auto, _) => MaybeAuto::Auto, (LengthOrPercentageOrAuto::Length(length), _) => MaybeAuto::Specified(Au::from(length)), }; let max_block_size = match (fragment.style.max_block_size(), block_container_block_size) { (LengthOrPercentageOrNone::Percentage(percent), Some(block_container_block_size)) => { Some(block_container_block_size.scale_by(percent.0)) } (LengthOrPercentageOrNone::Calc(calc), _) => { calc.to_used_value(block_container_block_size) } (LengthOrPercentageOrNone::Percentage(_), None) | (LengthOrPercentageOrNone::None, _) => None, (LengthOrPercentageOrNone::Length(length), _) => Some(Au::from(length)), }; let min_block_size = match (fragment.style.min_block_size(), block_container_block_size) { (LengthOrPercentage::Percentage(percent), Some(block_container_block_size)) => { block_container_block_size.scale_by(percent.0) } (LengthOrPercentage::Calc(calc), _) => { calc.to_used_value(block_container_block_size).unwrap_or(Au(0)) } (LengthOrPercentage::Percentage(_), None) => Au(0), (LengthOrPercentage::Length(length), _) => Au::from(length), }; // If the style includes `box-sizing: border-box`, subtract the border and padding. let adjustment_for_box_sizing = match fragment.style.get_position().box_sizing { BoxSizing::BorderBox => fragment.border_padding.block_start_end(), BoxSizing::ContentBox => Au(0), }; return CandidateBSizeIterator { block_size: block_size.map(|size| adjust(size, adjustment_for_box_sizing)), max_block_size: max_block_size.map(|size| adjust(size, adjustment_for_box_sizing)), min_block_size: adjust(min_block_size, adjustment_for_box_sizing), candidate_value: Au(0), status: CandidateBSizeIteratorStatus::Initial, }; fn adjust(size: Au, delta: Au) -> Au { max(size - delta, Au(0)) } } } impl Iterator for CandidateBSizeIterator { type Item = MaybeAuto; fn next(&mut self) -> Option { self.status = match self.status { CandidateBSizeIteratorStatus::Initial => CandidateBSizeIteratorStatus::Trying, CandidateBSizeIteratorStatus::Trying => { match self.max_block_size { Some(max_block_size) if self.candidate_value > max_block_size => { CandidateBSizeIteratorStatus::TryingMax } _ if self.candidate_value < self.min_block_size => { CandidateBSizeIteratorStatus::TryingMin } _ => CandidateBSizeIteratorStatus::Found, } } CandidateBSizeIteratorStatus::TryingMax => { if self.candidate_value < self.min_block_size { CandidateBSizeIteratorStatus::TryingMin } else { CandidateBSizeIteratorStatus::Found } } CandidateBSizeIteratorStatus::TryingMin | CandidateBSizeIteratorStatus::Found => { CandidateBSizeIteratorStatus::Found } }; match self.status { CandidateBSizeIteratorStatus::Trying => Some(self.block_size), CandidateBSizeIteratorStatus::TryingMax => { Some(MaybeAuto::Specified(self.max_block_size.unwrap())) } CandidateBSizeIteratorStatus::TryingMin => { Some(MaybeAuto::Specified(self.min_block_size)) } CandidateBSizeIteratorStatus::Found => None, CandidateBSizeIteratorStatus::Initial => panic!(), } } } enum CandidateBSizeIteratorStatus { Initial, Trying, TryingMax, TryingMin, Found, } // A helper function used in block-size calculation. fn translate_including_floats(cur_b: &mut Au, delta: Au, floats: &mut Floats) { *cur_b = *cur_b + delta; let writing_mode = floats.writing_mode; floats.translate(LogicalSize::new(writing_mode, Au(0), -delta)); } /// The real assign-block-sizes traversal for flows with position 'absolute'. /// /// This is a traversal of an Absolute Flow tree. /// - Relatively positioned flows and the Root flow start new Absolute flow trees. /// - The kids of a flow in this tree will be the flows for which it is the /// absolute Containing Block. /// - Thus, leaf nodes and inner non-root nodes are all Absolute Flows. /// /// A Flow tree can have several Absolute Flow trees (depending on the number /// of relatively positioned flows it has). /// /// Note that flows with position 'fixed' just form a flat list as they all /// have the Root flow as their CB. pub struct AbsoluteAssignBSizesTraversal<'a>(pub &'a SharedStyleContext<'a>); impl<'a> PreorderFlowTraversal for AbsoluteAssignBSizesTraversal<'a> { #[inline] fn process(&self, flow: &mut Flow) { if !flow.is_block_like() { return } // This flow might not be an absolutely positioned flow if it is the root of the tree. let block = flow.as_mut_block(); if !block.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) { return; } if !block.base.restyle_damage.intersects(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW) { return } block.calculate_absolute_block_size_and_margins(self.0); } } pub enum BlockType { Replaced, NonReplaced, AbsoluteReplaced, AbsoluteNonReplaced, FloatReplaced, FloatNonReplaced, InlineBlockReplaced, InlineBlockNonReplaced, InlineFlexItem, } #[derive(Clone, PartialEq)] pub enum MarginsMayCollapseFlag { MarginsMayCollapse, MarginsMayNotCollapse, } #[derive(Debug, PartialEq)] pub enum FormattingContextType { None, Block, Other, } #[allow(unsafe_code)] unsafe impl ::flow::HasBaseFlow for BlockFlow {} // A block formatting context. #[derive(Serialize)] #[repr(C)] pub struct BlockFlow { /// Data common to all flows. pub base: BaseFlow, /// The associated fragment. pub fragment: Fragment, /// Additional floating flow members. pub float: Option>, /// Various flags. flags: BlockFlowFlags, } bitflags! { struct BlockFlowFlags: u8 { #[doc = "If this is set, then this block flow is the root flow."] const IS_ROOT = 0b0000_0001; #[doc = "If this is set, then this block flow has overflow and it will scroll."] const HAS_SCROLLING_OVERFLOW = 0b0000_0010; } } impl Serialize for BlockFlowFlags { fn serialize(&self, serializer: S) -> Result { self.bits().serialize(serializer) } } impl BlockFlow { pub fn from_fragment(fragment: Fragment) -> BlockFlow { BlockFlow::from_fragment_and_float_kind(fragment, None) } pub fn from_fragment_and_float_kind(fragment: Fragment, float_kind: Option) -> BlockFlow { let writing_mode = fragment.style().writing_mode; BlockFlow { base: BaseFlow::new(Some(fragment.style()), writing_mode, match float_kind { Some(_) => ForceNonfloatedFlag::FloatIfNecessary, None => ForceNonfloatedFlag::ForceNonfloated, }), fragment: fragment, float: float_kind.map(|kind| Box::new(FloatedBlockInfo::new(kind))), flags: BlockFlowFlags::empty(), } } /// Return the type of this block. /// /// This determines the algorithm used to calculate inline-size, block-size, and the /// relevant margins for this Block. pub fn block_type(&self) -> BlockType { if self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) { if self.fragment.is_replaced() { BlockType::AbsoluteReplaced } else { BlockType::AbsoluteNonReplaced } } else if self.is_inline_flex_item() { BlockType::InlineFlexItem } else if self.base.flags.is_float() { if self.fragment.is_replaced() { BlockType::FloatReplaced } else { BlockType::FloatNonReplaced } } else if self.is_inline_block_or_inline_flex() { if self.fragment.is_replaced() { BlockType::InlineBlockReplaced } else { BlockType::InlineBlockNonReplaced } } else { if self.fragment.is_replaced() { BlockType::Replaced } else { BlockType::NonReplaced } } } /// Compute the actual inline size and position for this block. pub fn compute_used_inline_size(&mut self, shared_context: &SharedStyleContext, containing_block_inline_size: Au) { let block_type = self.block_type(); match block_type { BlockType::AbsoluteReplaced => { let inline_size_computer = AbsoluteReplaced; inline_size_computer.compute_used_inline_size(self, shared_context, containing_block_inline_size); } BlockType::AbsoluteNonReplaced => { let inline_size_computer = AbsoluteNonReplaced; inline_size_computer.compute_used_inline_size(self, shared_context, containing_block_inline_size); } BlockType::FloatReplaced => { let inline_size_computer = FloatReplaced; inline_size_computer.compute_used_inline_size(self, shared_context, containing_block_inline_size); } BlockType::FloatNonReplaced => { let inline_size_computer = FloatNonReplaced; inline_size_computer.compute_used_inline_size(self, shared_context, containing_block_inline_size); } BlockType::InlineBlockReplaced => { let inline_size_computer = InlineBlockReplaced; inline_size_computer.compute_used_inline_size(self, shared_context, containing_block_inline_size); } BlockType::InlineBlockNonReplaced => { let inline_size_computer = InlineBlockNonReplaced; inline_size_computer.compute_used_inline_size(self, shared_context, containing_block_inline_size); } BlockType::Replaced => { let inline_size_computer = BlockReplaced; inline_size_computer.compute_used_inline_size(self, shared_context, containing_block_inline_size); } BlockType::NonReplaced => { let inline_size_computer = BlockNonReplaced; inline_size_computer.compute_used_inline_size(self, shared_context, containing_block_inline_size); } BlockType::InlineFlexItem => { let inline_size_computer = InlineFlexItem; inline_size_computer.compute_used_inline_size(self, shared_context, containing_block_inline_size); } } } /// Return this flow's fragment. pub fn fragment(&mut self) -> &mut Fragment { &mut self.fragment } pub fn stacking_relative_border_box(&self, coor: CoordinateSystem) -> Rect { return self.fragment.stacking_relative_border_box( &self.base.stacking_relative_position, &self.base.early_absolute_position_info.relative_containing_block_size, self.base.early_absolute_position_info.relative_containing_block_mode, coor); } /// Return the size of the containing block for the given immediate absolute descendant of this /// flow. /// /// Right now, this only gets the containing block size for absolutely positioned elements. /// Note: We assume this is called in a top-down traversal, so it is ok to reference the CB. #[inline] pub fn containing_block_size(&self, viewport_size: &Size2D, descendant: OpaqueFlow) -> LogicalSize { debug_assert!(self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED)); if self.is_fixed() || self.is_root() { // Initial containing block is the CB for the root LogicalSize::from_physical(self.base.writing_mode, *viewport_size) } else { self.base.absolute_cb.generated_containing_block_size(descendant) } } /// Return shrink-to-fit inline-size. /// /// This is where we use the preferred inline-sizes and minimum inline-sizes /// calculated in the bubble-inline-sizes traversal. pub fn get_shrink_to_fit_inline_size(&self, available_inline_size: Au) -> Au { let content_intrinsic_inline_sizes = self.content_intrinsic_inline_sizes(); min(content_intrinsic_inline_sizes.preferred_inline_size, max(content_intrinsic_inline_sizes.minimum_inline_size, available_inline_size)) } /// If this is the root flow, shifts all kids down and adjusts our size to account for /// root flow margins, which should never be collapsed according to CSS § 8.3.1. /// /// TODO(#2017, pcwalton): This is somewhat inefficient (traverses kids twice); can we do /// better? fn adjust_fragments_for_collapsed_margins_if_root(&mut self, shared_context: &SharedStyleContext) { if !self.is_root() { return } let (block_start_margin_value, block_end_margin_value) = match self.base.collapsible_margins { CollapsibleMargins::CollapseThrough(_) => { panic!("Margins unexpectedly collapsed through root flow.") } CollapsibleMargins::Collapse(block_start_margin, block_end_margin) => { (block_start_margin.collapse(), block_end_margin.collapse()) } CollapsibleMargins::None(block_start, block_end) => (block_start, block_end), }; // Shift all kids down (or up, if margins are negative) if necessary. if block_start_margin_value != Au(0) { for kid in self.base.child_iter_mut() { let kid_base = kid.mut_base(); kid_base.position.start.b = kid_base.position.start.b + block_start_margin_value } } // FIXME(#2003, pcwalton): The max is taken here so that you can scroll the page, but this // is not correct behavior according to CSS 2.1 § 10.5. Instead I think we should treat the // root element as having `overflow: scroll` and use the layers-based scrolling // infrastructure to make it scrollable. let viewport_size = LogicalSize::from_physical(self.fragment.style.writing_mode, shared_context.viewport_size()); let block_size = max(viewport_size.block, self.fragment.border_box.size.block + block_start_margin_value + block_end_margin_value); self.base.position.size.block = block_size; self.fragment.border_box.size.block = block_size; } // FIXME: Record enough info to deal with fragmented decorations. // See https://drafts.csswg.org/css-break/#break-decoration // For borders, this might be `enum FragmentPosition { First, Middle, Last }` fn clone_with_children(&self, new_children: FlowList) -> BlockFlow { BlockFlow { base: self.base.clone_with_children(new_children), fragment: self.fragment.clone(), float: self.float.clone(), ..*self } } /// Writes in the size of the relative containing block for children. (This information /// is also needed to handle RTL.) fn propagate_early_absolute_position_info_to_children(&mut self) { for kid in self.base.child_iter_mut() { kid.mut_base().early_absolute_position_info = EarlyAbsolutePositionInfo { relative_containing_block_size: self.fragment.content_box().size, relative_containing_block_mode: self.fragment.style().writing_mode, } } } /// Assign block-size for current flow. /// /// * Collapse margins for flow's children and set in-flow child flows' block offsets now that /// we know their block-sizes. /// * Calculate and set the block-size of the current flow. /// * Calculate block-size, vertical margins, and block offset for the flow's box using CSS § /// 10.6.7. /// /// For absolute flows, we store the calculated content block-size for the flow. We defer the /// calculation of the other values until a later traversal. /// /// When `fragmentation_context` is given (not `None`), this should fit as much of the content /// as possible within the available block size. /// If there is more content (that doesn’t fit), this flow is *fragmented* /// with the extra content moved to another fragment (a flow like this one) which is returned. /// See `Flow::fragment`. /// /// The return value is always `None` when `fragmentation_context` is `None`. /// /// `inline(always)` because this is only ever called by in-order or non-in-order top-level /// methods. #[inline(always)] pub fn assign_block_size_block_base(&mut self, layout_context: &LayoutContext, mut fragmentation_context: Option, margins_may_collapse: MarginsMayCollapseFlag) -> Option> { let _scope = layout_debug_scope!("assign_block_size_block_base {:x}", self.base.debug_id()); let mut break_at = None; let content_box = self.fragment.content_box(); if self.base.restyle_damage.contains(ServoRestyleDamage::REFLOW) { // Our current border-box position. let mut cur_b = Au(0); // Absolute positioning establishes a block formatting context. Don't propagate floats // in or out. (But do propagate them between kids.) if self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) || margins_may_collapse != MarginsMayCollapseFlag::MarginsMayCollapse { self.base.floats = Floats::new(self.fragment.style.writing_mode); } let writing_mode = self.base.floats.writing_mode; self.base.floats.translate(LogicalSize::new( writing_mode, -self.fragment.inline_start_offset(), Au(0))); // The sum of our block-start border and block-start padding. let block_start_offset = self.fragment.border_padding.block_start; translate_including_floats(&mut cur_b, block_start_offset, &mut self.base.floats); let can_collapse_block_start_margin_with_kids = margins_may_collapse == MarginsMayCollapseFlag::MarginsMayCollapse && !self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) && self.fragment.border_padding.block_start == Au(0); let mut margin_collapse_info = MarginCollapseInfo::initialize_block_start_margin( &self.fragment, can_collapse_block_start_margin_with_kids); // At this point, `cur_b` is at the content edge of our box. Now iterate over children. let mut floats = self.base.floats.clone(); let thread_id = self.base.thread_id; let (mut had_floated_children, mut had_children_with_clearance) = (false, false); for (child_index, kid) in self.base.child_iter_mut().enumerate() { if kid.base().flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) { // Assume that the *hypothetical box* for an absolute flow starts immediately // after the margin-end border edge of the previous flow. if kid.base().flags.contains(FlowFlags::BLOCK_POSITION_IS_STATIC) { let previous_bottom_margin = margin_collapse_info.current_float_ceiling(); kid.mut_base().position.start.b = cur_b + kid.base().collapsible_margins .block_start_margin_for_noncollapsible_context() + previous_bottom_margin } kid.place_float_if_applicable(); if !kid.base().flags.is_float() { kid.assign_block_size_for_inorder_child_if_necessary(layout_context, thread_id, content_box); } // Skip the collapsing and float processing for absolute flow kids and continue // with the next flow. continue } let previous_b = cur_b; if let Some(ctx) = fragmentation_context { let child_ctx = FragmentationContext { available_block_size: ctx.available_block_size - cur_b, this_fragment_is_empty: ctx.this_fragment_is_empty, }; if let Some(remaining) = kid.fragment(layout_context, Some(child_ctx)) { break_at = Some((child_index + 1, Some(remaining))); } } // Assign block-size now for the child if it might have floats in and we couldn't // before. kid.mut_base().floats = floats.clone(); if kid.base().flags.is_float() { had_floated_children = true; kid.mut_base().position.start.b = cur_b; { let kid_block = kid.as_mut_block(); let float_ceiling = margin_collapse_info.current_float_ceiling(); kid_block.float.as_mut().unwrap().float_ceiling = float_ceiling } kid.place_float_if_applicable(); let kid_base = kid.mut_base(); floats = kid_base.floats.clone(); continue } // If we have clearance, assume there are no floats in. // // FIXME(#2008, pcwalton): This could be wrong if we have `clear: left` or `clear: // right` and there are still floats to impact, of course. But this gets // complicated with margin collapse. Possibly the right thing to do is to lay out // the block again in this rare case. (Note that WebKit can lay blocks out twice; // this may be related, although I haven't looked into it closely.) if kid.base().flags.clears_floats() { kid.mut_base().floats = Floats::new(self.fragment.style.writing_mode) } // Lay the child out if this was an in-order traversal. let need_to_process_child_floats = kid.assign_block_size_for_inorder_child_if_necessary(layout_context, thread_id, content_box); if !had_children_with_clearance && floats.is_present() && (kid.base().flags.contains(FlowFlags::CLEARS_LEFT) || kid.base().flags.contains(FlowFlags::CLEARS_RIGHT)) { had_children_with_clearance = true } // Handle any (possibly collapsed) top margin. let delta = margin_collapse_info.advance_block_start_margin( &kid.base().collapsible_margins, !had_children_with_clearance); translate_including_floats(&mut cur_b, delta, &mut floats); // Collapse-through margins should be placed at the top edge, // so we'll handle the delta after the bottom margin is processed if let CollapsibleMargins::CollapseThrough(_) = kid.base().collapsible_margins { cur_b = cur_b - delta; } // Clear past the floats that came in, if necessary. let clearance = match (kid.base().flags.contains(FlowFlags::CLEARS_LEFT), kid.base().flags.contains(FlowFlags::CLEARS_RIGHT)) { (false, false) => Au(0), (true, false) => floats.clearance(ClearType::Left), (false, true) => floats.clearance(ClearType::Right), (true, true) => floats.clearance(ClearType::Both), }; translate_including_floats(&mut cur_b, clearance, &mut floats); // At this point, `cur_b` is at the border edge of the child. kid.mut_base().position.start.b = cur_b; // Now pull out the child's outgoing floats. We didn't do this immediately after // the `assign_block_size_for_inorder_child_if_necessary` call because clearance on // a block operates on the floats that come *in*, not the floats that go *out*. if need_to_process_child_floats { floats = kid.mut_base().floats.clone() } // Move past the child's border box. Do not use the `translate_including_floats` // function here because the child has already translated floats past its border // box. let kid_base = kid.mut_base(); cur_b = cur_b + kid_base.position.size.block; // Handle any (possibly collapsed) block-end margin. let delta = margin_collapse_info.advance_block_end_margin(&kid_base.collapsible_margins); translate_including_floats(&mut cur_b, delta, &mut floats); // Collapse-through margin should be placed at the top edge of the flow. let collapse_delta = match kid_base.collapsible_margins { CollapsibleMargins::CollapseThrough(_) => { let delta = margin_collapse_info.current_float_ceiling(); cur_b = cur_b + delta; kid_base.position.start.b = kid_base.position.start.b + delta; delta } _ => Au(0) }; if break_at.is_some() { break } if let Some(ref mut ctx) = fragmentation_context { if cur_b > ctx.available_block_size && !ctx.this_fragment_is_empty { break_at = Some((child_index, None)); cur_b = previous_b; break } ctx.this_fragment_is_empty = false } // For consecutive collapse-through flows, their top margin should be calculated // from the same baseline. cur_b = cur_b - collapse_delta; } // Add in our block-end margin and compute our collapsible margins. let can_collapse_block_end_margin_with_kids = margins_may_collapse == MarginsMayCollapseFlag::MarginsMayCollapse && !self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) && self.fragment.border_padding.block_end == Au(0); let (collapsible_margins, delta) = margin_collapse_info.finish_and_compute_collapsible_margins( &self.fragment, self.base.block_container_explicit_block_size, can_collapse_block_end_margin_with_kids, !had_floated_children); self.base.collapsible_margins = collapsible_margins; translate_including_floats(&mut cur_b, delta, &mut floats); let mut block_size = cur_b - block_start_offset; let is_root = self.is_root(); if is_root || self.formatting_context_type() != FormattingContextType::None || self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) { // The content block-size includes all the floats per CSS 2.1 § 10.6.7. The easiest // way to handle this is to just treat it as clearance. block_size = block_size + floats.clearance(ClearType::Both); } if self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) { // FIXME(#2003, pcwalton): The max is taken here so that you can scroll the page, // but this is not correct behavior according to CSS 2.1 § 10.5. Instead I think we // should treat the root element as having `overflow: scroll` and use the layers- // based scrolling infrastructure to make it scrollable. if is_root { let viewport_size = LogicalSize::from_physical(self.fragment.style.writing_mode, layout_context.shared_context().viewport_size()); block_size = max(viewport_size.block, block_size) } // Store the content block-size for use in calculating the absolute flow's // dimensions later. // // FIXME(pcwalton): This looks not idempotent. Is it? self.fragment.border_box.size.block = block_size; } if self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) { self.propagate_early_absolute_position_info_to_children(); return None } // Compute any explicitly-specified block size. // Can't use `for` because we assign to `candidate_block_size_iterator.candidate_value`. let mut candidate_block_size_iterator = CandidateBSizeIterator::new( &self.fragment, self.base.block_container_explicit_block_size); while let Some(candidate_block_size) = candidate_block_size_iterator.next() { candidate_block_size_iterator.candidate_value = match candidate_block_size { MaybeAuto::Auto => block_size, MaybeAuto::Specified(value) => value } } // Adjust `cur_b` as necessary to account for the explicitly-specified block-size. block_size = candidate_block_size_iterator.candidate_value; let delta = block_size - (cur_b - block_start_offset); translate_including_floats(&mut cur_b, delta, &mut floats); // Take border and padding into account. let block_end_offset = self.fragment.border_padding.block_end; translate_including_floats(&mut cur_b, block_end_offset, &mut floats); // Now that `cur_b` is at the block-end of the border box, compute the final border box // position. self.fragment.border_box.size.block = cur_b; self.fragment.border_box.start.b = Au(0); self.base.position.size.block = cur_b; self.propagate_early_absolute_position_info_to_children(); // Translate the current set of floats back into the parent coordinate system in the // inline direction, and store them in the flow so that flows that come later in the // document can access them. floats.translate(LogicalSize::new(writing_mode, self.fragment.inline_start_offset(), Au(0))); self.base.floats = floats.clone(); self.adjust_fragments_for_collapsed_margins_if_root(layout_context.shared_context()); } else { // We don't need to reflow, but we still need to perform in-order traversals if // necessary. let thread_id = self.base.thread_id; for kid in self.base.child_iter_mut() { kid.assign_block_size_for_inorder_child_if_necessary(layout_context, thread_id, content_box); } } if (&*self as &Flow).contains_roots_of_absolute_flow_tree() { // Assign block-sizes for all flows in this absolute flow tree. // This is preorder because the block-size of an absolute flow may depend on // the block-size of its containing block, which may also be an absolute flow. let assign_abs_b_sizes = AbsoluteAssignBSizesTraversal(layout_context.shared_context()); assign_abs_b_sizes.traverse_absolute_flows(&mut *self); } // Don't remove the dirty bits yet if we're absolutely-positioned, since our final size // has not been calculated yet. (See `calculate_absolute_block_size_and_margins` for that.) // Also don't remove the dirty bits if we're a block formatting context since our inline // size has not yet been computed. (See `assign_inline_position_for_formatting_context()`.) if (self.base.flags.is_float() || self.formatting_context_type() == FormattingContextType::None) && !self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) { self.base.restyle_damage.remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW); self.fragment.restyle_damage.remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW); } break_at.and_then(|(i, child_remaining)| { if i == self.base.children.len() && child_remaining.is_none() { None } else { let mut children = self.base.children.split_off(i); if let Some(child) = child_remaining { children.push_front_arc(child); } Some(Arc::new(self.clone_with_children(children)) as Arc) } }) } /// Add placement information about current float flow for use by the parent. /// /// Also, use information given by parent about other floats to find out our relative position. /// /// This does not give any information about any float descendants because they do not affect /// elements outside of the subtree rooted at this float. /// /// This function is called on a kid flow by a parent. Therefore, `assign_block_size_float` was /// already called on this kid flow by the traversal function. So, the values used are /// well-defined. pub fn place_float(&mut self) { let block_size = self.fragment.border_box.size.block; let clearance = match self.fragment.clear() { None => Au(0), Some(clear) => self.base.floats.clearance(clear), }; let float_info: FloatedBlockInfo = (**self.float.as_ref().unwrap()).clone(); // Our `position` field accounts for positive margins, but not negative margins. (See // calculation of `extra_inline_size_from_margin` below.) Negative margins must be taken // into account for float placement, however. So we add them in here. let inline_size_for_float_placement = self.base.position.size.inline + min(Au(0), self.fragment.margin.inline_start_end()); let info = PlacementInfo { size: LogicalSize::new( self.fragment.style.writing_mode, inline_size_for_float_placement, block_size + self.fragment.margin.block_start_end()) .convert(self.fragment.style.writing_mode, self.base.floats.writing_mode), ceiling: clearance + float_info.float_ceiling, max_inline_size: float_info.containing_inline_size, kind: float_info.float_kind, }; // Place the float and return the `Floats` back to the parent flow. // After, grab the position and use that to set our position. self.base.floats.add_float(&info); // FIXME (mbrubeck) Get the correct container size for self.base.floats; let container_size = Size2D::new(self.base.block_container_inline_size, Au(0)); // Move in from the margin edge, as per CSS 2.1 § 9.5, floats may not overlap anything on // their margin edges. let float_offset = self.base.floats.last_float_pos().unwrap() .convert(self.base.floats.writing_mode, self.base.writing_mode, container_size) .start; let margin_offset = LogicalPoint::new(self.base.writing_mode, Au(0), self.fragment.margin.block_start); let mut origin = LogicalPoint::new(self.base.writing_mode, self.base.position.start.i, self.base.position.start.b); origin = origin.add_point(&float_offset).add_point(&margin_offset); self.base.position = LogicalRect::from_point_size(self.base.writing_mode, origin, self.base.position.size); } pub fn explicit_block_containing_size(&self, shared_context: &SharedStyleContext) -> Option { if self.is_root() || self.is_fixed() { let viewport_size = LogicalSize::from_physical(self.fragment.style.writing_mode, shared_context.viewport_size()); Some(viewport_size.block) } else if self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) && self.base.block_container_explicit_block_size.is_none() { self.base.absolute_cb.explicit_block_containing_size(shared_context) } else { self.base.block_container_explicit_block_size } } pub fn explicit_block_size(&self, containing_block_size: Option) -> Option { let content_block_size = self.fragment.style().content_block_size(); match (content_block_size, containing_block_size) { (LengthOrPercentageOrAuto::Calc(calc), _) => { calc.to_used_value(containing_block_size) } (LengthOrPercentageOrAuto::Length(length), _) => Some(Au::from(length)), (LengthOrPercentageOrAuto::Percentage(percent), Some(container_size)) => { Some(container_size.scale_by(percent.0)) } (LengthOrPercentageOrAuto::Percentage(_), None) | (LengthOrPercentageOrAuto::Auto, None) => { None } (LengthOrPercentageOrAuto::Auto, Some(container_size)) => { let (block_start, block_end) = { let position = self.fragment.style().logical_position(); (MaybeAuto::from_style(position.block_start, container_size), MaybeAuto::from_style(position.block_end, container_size)) }; match (block_start, block_end) { (MaybeAuto::Specified(block_start), MaybeAuto::Specified(block_end)) => { let available_block_size = container_size - self.fragment.border_padding.block_start_end(); // Non-auto margin-block-start and margin-block-end values have already been // calculated during assign-inline-size. let margin = self.fragment.style().logical_margin(); let margin_block_start = match margin.block_start { LengthOrPercentageOrAuto::Auto => MaybeAuto::Auto, _ => MaybeAuto::Specified(self.fragment.margin.block_start) }; let margin_block_end = match margin.block_end { LengthOrPercentageOrAuto::Auto => MaybeAuto::Auto, _ => MaybeAuto::Specified(self.fragment.margin.block_end) }; let margin_block_start = margin_block_start.specified_or_zero(); let margin_block_end = margin_block_end.specified_or_zero(); let sum = block_start + block_end + margin_block_start + margin_block_end; Some(available_block_size - sum) } (_, _) => { None } } } } } fn calculate_absolute_block_size_and_margins(&mut self, shared_context: &SharedStyleContext) { let opaque_self = OpaqueFlow::from_flow(self); let containing_block_block_size = self.containing_block_size(&shared_context.viewport_size(), opaque_self).block; // This is the stored content block-size value from assign-block-size let content_block_size = self.fragment.border_box.size.block; let mut solution = None; { // Non-auto margin-block-start and margin-block-end values have already been // calculated during assign-inline-size. let margin = self.fragment.style().logical_margin(); let margin_block_start = match margin.block_start { LengthOrPercentageOrAuto::Auto => MaybeAuto::Auto, _ => MaybeAuto::Specified(self.fragment.margin.block_start) }; let margin_block_end = match margin.block_end { LengthOrPercentageOrAuto::Auto => MaybeAuto::Auto, _ => MaybeAuto::Specified(self.fragment.margin.block_end) }; let block_start; let block_end; { let position = self.fragment.style().logical_position(); block_start = MaybeAuto::from_style(position.block_start, containing_block_block_size); block_end = MaybeAuto::from_style(position.block_end, containing_block_block_size); } let available_block_size = containing_block_block_size - self.fragment.border_padding.block_start_end(); if self.fragment.is_replaced() { // Calculate used value of block-size just like we do for inline replaced elements. // TODO: Pass in the containing block block-size when Fragment's // assign-block-size can handle it correctly. self.fragment.assign_replaced_block_size_if_necessary(); // TODO: Right now, this content block-size value includes the // margin because of erroneous block-size calculation in fragment. // Check this when that has been fixed. let block_size_used_val = self.fragment.border_box.size.block; solution = Some(BSizeConstraintSolution::solve_vertical_constraints_abs_replaced( block_size_used_val, margin_block_start, margin_block_end, block_start, block_end, content_block_size, available_block_size)) } else { let mut candidate_block_size_iterator = CandidateBSizeIterator::new(&self.fragment, Some(containing_block_block_size)); // Can't use `for` because we assign to // `candidate_block_size_iterator.candidate_value`. while let Some(block_size_used_val) = candidate_block_size_iterator.next() { solution = Some( BSizeConstraintSolution::solve_vertical_constraints_abs_nonreplaced( block_size_used_val, margin_block_start, margin_block_end, block_start, block_end, content_block_size, available_block_size)); candidate_block_size_iterator.candidate_value = solution.unwrap().block_size; } } } let solution = solution.unwrap(); self.fragment.margin.block_start = solution.margin_block_start; self.fragment.margin.block_end = solution.margin_block_end; self.fragment.border_box.start.b = Au(0); if !self.base.flags.contains(FlowFlags::BLOCK_POSITION_IS_STATIC) { self.base.position.start.b = solution.block_start + self.fragment.margin.block_start } let block_size = solution.block_size + self.fragment.border_padding.block_start_end(); self.fragment.border_box.size.block = block_size; self.base.position.size.block = block_size; self.base.restyle_damage.remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW); self.fragment.restyle_damage.remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW); } /// Compute inline size based using the `block_container_inline_size` set by the parent flow. /// /// This is run in the `AssignISizes` traversal. fn propagate_and_compute_used_inline_size(&mut self, shared_context: &SharedStyleContext) { let containing_block_inline_size = self.base.block_container_inline_size; self.compute_used_inline_size(shared_context, containing_block_inline_size); if self.base.flags.is_float() { self.float.as_mut().unwrap().containing_inline_size = containing_block_inline_size } } /// Assigns the computed inline-start content edge and inline-size to all the children of this /// block flow. The given `callback`, if supplied, will be called once per child; it is /// currently used to push down column sizes for tables. /// /// `#[inline(always)]` because this is called only from block or table inline-size assignment /// and the code for block layout is significantly simpler. #[inline(always)] pub fn propagate_assigned_inline_size_to_children(&mut self, shared_context: &SharedStyleContext, inline_start_content_edge: Au, inline_end_content_edge: Au, content_inline_size: Au, mut callback: F) where F: FnMut(&mut Flow, usize, Au, WritingMode, &mut Au, &mut Au) { let flags = self.base.flags.clone(); let opaque_self = OpaqueFlow::from_flow(self); // Calculate non-auto block size to pass to children. let box_border = match self.fragment.style().get_position().box_sizing { BoxSizing::BorderBox => self.fragment.border_padding.block_start_end(), BoxSizing::ContentBox => Au(0), }; let parent_container_size = self.explicit_block_containing_size(shared_context); // https://drafts.csswg.org/css-ui-3/#box-sizing let mut explicit_content_size = self .explicit_block_size(parent_container_size) .map(|x| if x < box_border { Au(0) } else { x - box_border }); if self.is_root() { explicit_content_size = max(parent_container_size, explicit_content_size); } // Calculate containing block inline size. let containing_block_size = if flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) { self.containing_block_size(&shared_context.viewport_size(), opaque_self).inline } else { content_inline_size }; // FIXME (mbrubeck): Get correct mode for absolute containing block let containing_block_mode = self.base.writing_mode; let mut inline_start_margin_edge = inline_start_content_edge; let mut inline_end_margin_edge = inline_end_content_edge; let mut iterator = self.base.child_iter_mut().enumerate().peekable(); while let Some((i, kid)) = iterator.next() { kid.mut_base().block_container_explicit_block_size = explicit_content_size; // The inline-start margin edge of the child flow is at our inline-start content edge, // and its inline-size is our content inline-size. let kid_mode = kid.base().writing_mode; { // Don't assign positions to children unless they're going to be reflowed. // Otherwise, the position we assign might be incorrect and never fixed up. (Issue // #13704.) // // For instance, floats have their true inline position calculated in // `assign_block_size()`, which won't do anything unless `REFLOW` is set. So, if a // float child does not have `REFLOW` set, we must be careful to avoid touching its // inline position, as no logic will run afterward to set its true value. let kid_base = kid.mut_base(); let reflow_damage = if kid_base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) { ServoRestyleDamage::REFLOW_OUT_OF_FLOW } else { ServoRestyleDamage::REFLOW }; if kid_base.flags.contains(FlowFlags::INLINE_POSITION_IS_STATIC) && kid_base.restyle_damage.contains(reflow_damage) { kid_base.position.start.i = if kid_mode.is_bidi_ltr() == containing_block_mode.is_bidi_ltr() { inline_start_content_edge } else { // The kid's inline 'start' is at the parent's 'end' inline_end_content_edge }; } kid_base.block_container_inline_size = content_inline_size; kid_base.block_container_writing_mode = containing_block_mode; } // Call the callback to propagate extra inline size information down to the child. This // is currently used for tables. callback(kid, i, content_inline_size, containing_block_mode, &mut inline_start_margin_edge, &mut inline_end_margin_edge); // Per CSS 2.1 § 16.3.1, text alignment propagates to all children in flow. // // TODO(#2265, pcwalton): Do this in the cascade instead. let containing_block_text_align = self.fragment.style().get_inheritedtext().text_align; kid.mut_base().flags.set_text_align(containing_block_text_align); // Handle `text-indent` on behalf of any inline children that we have. This is // necessary because any percentages are relative to the containing block, which only // we know. if kid.is_inline_flow() { kid.as_mut_inline().first_line_indentation = self.fragment.style().get_inheritedtext().text_indent .to_used_value(containing_block_size); } } } /// Determines the type of formatting context this is. See the definition of /// `FormattingContextType`. pub fn formatting_context_type(&self) -> FormattingContextType { if self.is_inline_flex_item() || self.is_block_flex_item() { return FormattingContextType::Other } let style = self.fragment.style(); if style.get_box().float != Float::None { return FormattingContextType::Other } match style.get_box().display { Display::TableCell | Display::TableCaption | Display::TableRowGroup | Display::Table | Display::InlineBlock | Display::Flex => { FormattingContextType::Other } _ if style.get_box().overflow_x != StyleOverflow::Visible || style.get_box().overflow_y != StyleOverflow::Visible || style.is_multicol() => { FormattingContextType::Block } _ => FormattingContextType::None, } } /// Per CSS 2.1 § 9.5, block formatting contexts' inline widths and positions are affected by /// the presence of floats. This is the part of the assign-heights traversal that computes /// the final inline position and width for such flows. /// /// Note that this is part of the assign-block-sizes traversal, not the assign-inline-sizes /// traversal as one might expect. That is because, in general, float placement cannot occur /// until heights are assigned. To work around this unfortunate circular dependency, by the /// time we get here we have already estimated the width of the block formatting context based /// on the floats we could see at the time of inline-size assignment. The job of this function, /// therefore, is not only to assign the final size but also to perform the layout again for /// this block formatting context if our speculation was wrong. fn assign_inline_position_for_formatting_context(&mut self, layout_context: &LayoutContext, content_box: LogicalRect) { debug_assert_ne!(self.formatting_context_type(), FormattingContextType::None); if !self.base.restyle_damage.intersects(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW) { return } // We do this first to avoid recomputing our inline size when we propagate it. self.base.restyle_damage.remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW); self.fragment.restyle_damage.remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW); // The code below would completely wreck the layout if run on a flex item, however: // * Flex items are always the children of flex containers. // * Flex containers only contain flex items. // * Floats cannot intrude into flex containers. // * Floats cannot escape flex items. // * Flex items cannot also be floats. // Therefore, a flex item cannot be impacted by a float. // See also: https://www.w3.org/TR/css-flexbox-1/#flex-containers if !self.base.might_have_floats_in() { return } // If you remove the might_have_floats_in conditional, this will go off. debug_assert!(!self.is_inline_flex_item()); // Compute the available space for us, based on the actual floats. let rect = self.base.floats.available_rect(Au(0), self.fragment.border_box.size.block, content_box.size.inline); let available_inline_size = if let Some(rect) = rect { // Offset our position by whatever displacement is needed to not impact the floats. // Also, account for margins sliding behind floats. let inline_offset = if self.fragment.margin.inline_start < rect.start.i { // Do not do anything for negative margins; those are handled separately. rect.start.i - max(Au(0), self.fragment.margin.inline_start) } else { Au(0) }; self.base.position.start.i = content_box.start.i + inline_offset; // Handle the end margin sliding behind the float. let end = content_box.size.inline - rect.start.i - rect.size.inline; let inline_end_offset = if self.fragment.margin.inline_end < end { end - max(Au(0), self.fragment.margin.inline_end) } else { Au(0) }; content_box.size.inline - inline_offset - inline_end_offset } else { content_box.size.inline } - self.fragment.margin.inline_start_end(); let max_inline_size = self.fragment.style().max_inline_size() .to_used_value(self.base.block_container_inline_size) .unwrap_or(MAX_AU); let min_inline_size = self.fragment.style().min_inline_size().to_used_value(self.base.block_container_inline_size); let specified_inline_size = self.fragment.style().content_inline_size(); let container_size = self.base.block_container_inline_size; let inline_size = if let MaybeAuto::Specified(size) = MaybeAuto::from_style(specified_inline_size, container_size) { match self.fragment.style().get_position().box_sizing { BoxSizing::BorderBox => size, BoxSizing::ContentBox => size + self.fragment.border_padding.inline_start_end(), } } else { max(min_inline_size, min(available_inline_size, max_inline_size)) }; self.base.position.size.inline = inline_size + self.fragment.margin.inline_start_end(); // If float speculation failed, fixup our layout, and re-layout all the children. if self.fragment.margin_box_inline_size() != self.base.position.size.inline { debug!("assign_inline_position_for_formatting_context: float speculation failed"); // Fix-up our own layout. // We can't just traverse_flow_tree_preorder ourself, because that would re-run // float speculation, instead of acting on the actual results. self.fragment.border_box.size.inline = inline_size; // Assign final-final inline sizes on all our children. self.assign_inline_sizes(layout_context); // Re-run layout on our children. for child in self.base.child_iter_mut() { sequential::reflow(child, layout_context, RelayoutMode::Force); } // Assign our final-final block size. self.assign_block_size(layout_context); } debug_assert_eq!(self.fragment.margin_box_inline_size(), self.base.position.size.inline); } fn is_inline_block_or_inline_flex(&self) -> bool { self.fragment.style().get_box().display == Display::InlineBlock || self.fragment.style().get_box().display == Display::InlineFlex } /// Computes the content portion (only) of the intrinsic inline sizes of this flow. This is /// used for calculating shrink-to-fit width. Assumes that intrinsic sizes have already been /// computed for this flow. fn content_intrinsic_inline_sizes(&self) -> IntrinsicISizes { let (border_padding, margin) = self.fragment.surrounding_intrinsic_inline_size(); IntrinsicISizes { minimum_inline_size: self.base.intrinsic_inline_sizes.minimum_inline_size - border_padding - margin, preferred_inline_size: self.base.intrinsic_inline_sizes.preferred_inline_size - border_padding - margin, } } /// Computes intrinsic inline sizes for a block. pub fn bubble_inline_sizes_for_block(&mut self, consult_children: bool) { let _scope = layout_debug_scope!("block::bubble_inline_sizes {:x}", self.base.debug_id()); let mut flags = self.base.flags; if self.definitely_has_zero_block_size() { // This is kind of a hack for Acid2. But it's a harmless one, because (a) this behavior // is unspecified; (b) it matches the behavior one would intuitively expect, since // floats don't flow around blocks that take up no space in the block direction. flags.remove(FlowFlags::CONTAINS_TEXT_OR_REPLACED_FRAGMENTS); } else if self.fragment.is_text_or_replaced() { flags.insert(FlowFlags::CONTAINS_TEXT_OR_REPLACED_FRAGMENTS); } else { flags.remove(FlowFlags::CONTAINS_TEXT_OR_REPLACED_FRAGMENTS); for kid in self.base.children.iter() { if kid.base().flags.contains(FlowFlags::CONTAINS_TEXT_OR_REPLACED_FRAGMENTS) { flags.insert(FlowFlags::CONTAINS_TEXT_OR_REPLACED_FRAGMENTS); break } } } // Find the maximum inline-size from children. // // See: https://lists.w3.org/Archives/Public/www-style/2014Nov/0085.html // // FIXME(pcwalton): This doesn't exactly follow that algorithm at the moment. // FIXME(pcwalton): This should consider all float descendants, not just children. let mut computation = self.fragment.compute_intrinsic_inline_sizes(); let (mut left_float_width, mut right_float_width) = (Au(0), Au(0)); let (mut left_float_width_accumulator, mut right_float_width_accumulator) = (Au(0), Au(0)); let mut preferred_inline_size_of_children_without_text_or_replaced_fragments = Au(0); for kid in self.base.child_iter_mut() { if kid.base().flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) || !consult_children { continue } let child_base = kid.mut_base(); let float_kind = child_base.flags.float_kind(); computation.content_intrinsic_sizes.minimum_inline_size = max(computation.content_intrinsic_sizes.minimum_inline_size, child_base.intrinsic_inline_sizes.minimum_inline_size); if child_base.flags.contains(FlowFlags::CLEARS_LEFT) { left_float_width = max(left_float_width, left_float_width_accumulator); left_float_width_accumulator = Au(0) } if child_base.flags.contains(FlowFlags::CLEARS_RIGHT) { right_float_width = max(right_float_width, right_float_width_accumulator); right_float_width_accumulator = Au(0) } match (float_kind, child_base.flags.contains(FlowFlags::CONTAINS_TEXT_OR_REPLACED_FRAGMENTS)) { (Float::None, true) => { computation.content_intrinsic_sizes.preferred_inline_size = max(computation.content_intrinsic_sizes.preferred_inline_size, child_base.intrinsic_inline_sizes.preferred_inline_size); } (Float::None, false) => { preferred_inline_size_of_children_without_text_or_replaced_fragments = max( preferred_inline_size_of_children_without_text_or_replaced_fragments, child_base.intrinsic_inline_sizes.preferred_inline_size) } (Float::Left, _) => { left_float_width_accumulator = left_float_width_accumulator + child_base.intrinsic_inline_sizes.preferred_inline_size; } (Float::Right, _) => { right_float_width_accumulator = right_float_width_accumulator + child_base.intrinsic_inline_sizes.preferred_inline_size; } } } left_float_width = max(left_float_width, left_float_width_accumulator); right_float_width = max(right_float_width, right_float_width_accumulator); computation.content_intrinsic_sizes.preferred_inline_size = computation.content_intrinsic_sizes.preferred_inline_size + left_float_width + right_float_width; computation.content_intrinsic_sizes.preferred_inline_size = max(computation.content_intrinsic_sizes.preferred_inline_size, preferred_inline_size_of_children_without_text_or_replaced_fragments); self.base.intrinsic_inline_sizes = computation.finish(); self.base.flags = flags } pub fn overflow_style_may_require_clip_scroll_node(&self) -> bool { match (self.fragment.style().get_box().overflow_x, self.fragment.style().get_box().overflow_y) { (StyleOverflow::Auto, _) | (StyleOverflow::Scroll, _) | (StyleOverflow::Hidden, _) | (_, StyleOverflow::Auto) | (_, StyleOverflow::Scroll) | (_, StyleOverflow::Hidden) => true, (_, _) => false, } } pub fn compute_inline_sizes(&mut self, shared_context: &SharedStyleContext) { if !self.base.restyle_damage.intersects(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW) { return } debug!("assign_inline_sizes({}): assigning inline_size for flow", if self.base.flags.is_float() { "float" } else { "block" }); self.base.floats = Floats::new(self.base.writing_mode); self.initialize_container_size_for_root(shared_context); // Our inline-size was set to the inline-size of the containing block by the flow's parent. // Now compute the real value. self.propagate_and_compute_used_inline_size(shared_context); self.guess_inline_size_for_block_formatting_context_if_necessary() } /// If this is the root flow, initialize values that would normally be set by the parent. /// /// Should be called during `assign_inline_sizes` for flows that may be the root. pub fn initialize_container_size_for_root(&mut self, shared_context: &SharedStyleContext) { if self.is_root() { debug!("Setting root position"); self.base.position.start = LogicalPoint::zero(self.base.writing_mode); self.base.block_container_inline_size = LogicalSize::from_physical( self.base.writing_mode, shared_context.viewport_size()).inline; self.base.block_container_writing_mode = self.base.writing_mode; } } fn guess_inline_size_for_block_formatting_context_if_necessary(&mut self) { // We don't need to guess anything unless this is a block formatting context. if self.formatting_context_type() != FormattingContextType::Block { return } // If `max-width` is set, then don't perform this speculation. We guess that the // page set `max-width` in order to avoid hitting floats. The search box on Google // SERPs falls into this category. if self.fragment.style.max_inline_size() != LengthOrPercentageOrNone::None { return } // At this point, we know we can't precisely compute the inline-size of this block now, // because floats might affect it. Speculate that its inline-size is equal to the // inline-size computed above minus the inline-size of the previous left and/or right // floats. let speculated_left_float_size = if self.fragment.margin.inline_start >= Au(0) && self.base.speculated_float_placement_in.left > self.fragment.margin.inline_start { self.base.speculated_float_placement_in.left - self.fragment.margin.inline_start } else { Au(0) }; let speculated_right_float_size = if self.fragment.margin.inline_end >= Au(0) && self.base.speculated_float_placement_in.right > self.fragment.margin.inline_end { self.base.speculated_float_placement_in.right - self.fragment.margin.inline_end } else { Au(0) }; self.fragment.border_box.size.inline = self.fragment.border_box.size.inline - speculated_left_float_size - speculated_right_float_size } fn definitely_has_zero_block_size(&self) -> bool { if !self.fragment.style.content_block_size().is_definitely_zero() { return false } let border_width = self.fragment.border_width(); if border_width.block_start != Au(0) || border_width.block_end != Au(0) { return false } let padding = self.fragment.style.logical_padding(); padding.block_start.is_definitely_zero() && padding.block_end.is_definitely_zero() } pub fn is_inline_flex_item(&self) -> bool { self.fragment.flags.contains(FragmentFlags::IS_INLINE_FLEX_ITEM) } pub fn is_block_flex_item(&self) -> bool { self.fragment.flags.contains(FragmentFlags::IS_BLOCK_FLEX_ITEM) } pub fn mark_scrolling_overflow(&mut self, has_scrolling_overflow: bool) { if has_scrolling_overflow { self.flags.insert(BlockFlowFlags::HAS_SCROLLING_OVERFLOW); } else { self.flags.remove(BlockFlowFlags::HAS_SCROLLING_OVERFLOW); } } pub fn has_scrolling_overflow(&self) -> bool { self.flags.contains(BlockFlowFlags::HAS_SCROLLING_OVERFLOW) } // Return offset from original position because of `position: sticky`. pub fn sticky_position(&self) -> SideOffsets2D { let containing_block_size = &self.base.early_absolute_position_info .relative_containing_block_size; let writing_mode = self.base.early_absolute_position_info.relative_containing_block_mode; let offsets = self.fragment.style().logical_position(); let as_margins = LogicalMargin::new(writing_mode, MaybeAuto::from_style(offsets.block_start, containing_block_size.inline), MaybeAuto::from_style(offsets.inline_end, containing_block_size.inline), MaybeAuto::from_style(offsets.block_end, containing_block_size.inline), MaybeAuto::from_style(offsets.inline_start, containing_block_size.inline)); as_margins.to_physical(writing_mode) } pub fn background_border_section(&self) -> DisplayListSection { if self.base.flags.is_float() { DisplayListSection::BackgroundAndBorders } else if self.base .flags .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) { if self.fragment.establishes_stacking_context() { DisplayListSection::BackgroundAndBorders } else { DisplayListSection::BlockBackgroundsAndBorders } } else { DisplayListSection::BlockBackgroundsAndBorders } } } impl Flow for BlockFlow { fn class(&self) -> FlowClass { FlowClass::Block } fn as_mut_block(&mut self) -> &mut BlockFlow { self } fn as_block(&self) -> &BlockFlow { self } /// Pass 1 of reflow: computes minimum and preferred inline-sizes. /// /// Recursively (bottom-up) determine the flow's minimum and preferred inline-sizes. When /// called on this flow, all child flows have had their minimum and preferred inline-sizes set. /// This function must decide minimum/preferred inline-sizes based on its children's /// inline-sizes and the dimensions of any fragments it is responsible for flowing. fn bubble_inline_sizes(&mut self) { // If this block has a fixed width, just use that for the minimum and preferred width, // rather than bubbling up children inline width. let consult_children = match self.fragment.style().get_position().width { LengthOrPercentageOrAuto::Length(_) => false, _ => true, }; self.bubble_inline_sizes_for_block(consult_children); self.fragment.restyle_damage.remove(ServoRestyleDamage::BUBBLE_ISIZES); } /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. /// When called on this context, the context has had its inline-size set by the parent context. /// /// Dual fragments consume some inline-size first, and the remainder is assigned to all child /// (block) contexts. fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) { let _scope = layout_debug_scope!("block::assign_inline_sizes {:x}", self.base.debug_id()); let shared_context = layout_context.shared_context(); self.compute_inline_sizes(shared_context); // Move in from the inline-start border edge. let inline_start_content_edge = self.fragment.border_box.start.i + self.fragment.border_padding.inline_start; let padding_and_borders = self.fragment.border_padding.inline_start_end(); // Distance from the inline-end margin edge to the inline-end content edge. let inline_end_content_edge = self.fragment.margin.inline_end + self.fragment.border_padding.inline_end; let content_inline_size = self.fragment.border_box.size.inline - padding_and_borders; self.propagate_assigned_inline_size_to_children(shared_context, inline_start_content_edge, inline_end_content_edge, content_inline_size, |_, _, _, _, _, _| {}); } fn place_float_if_applicable<'a>(&mut self) { if self.base.flags.is_float() { self.place_float(); } } fn assign_block_size_for_inorder_child_if_necessary(&mut self, layout_context: &LayoutContext, parent_thread_id: u8, content_box: LogicalRect) -> bool { if self.base.flags.is_float() { return false } let is_formatting_context = self.formatting_context_type() != FormattingContextType::None; if !self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) && is_formatting_context { self.assign_inline_position_for_formatting_context(layout_context, content_box); } if (self as &Flow).floats_might_flow_through() { self.base.thread_id = parent_thread_id; if self.base.restyle_damage.intersects(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW) { self.assign_block_size(layout_context); // Don't remove the restyle damage; `assign_block_size` decides whether that is // appropriate (which in the case of e.g. absolutely-positioned flows, it is not). } return true } if is_formatting_context { // If this is a formatting context and definitely did not have floats in, then we must // translate the floats past us. let writing_mode = self.base.floats.writing_mode; let delta = self.base.position.size.block; self.base.floats.translate(LogicalSize::new(writing_mode, Au(0), -delta)); return true } false } fn assign_block_size(&mut self, ctx: &LayoutContext) { let remaining = Flow::fragment(self, ctx, None); debug_assert!(remaining.is_none()); } fn fragment(&mut self, layout_context: &LayoutContext, fragmentation_context: Option) -> Option> { if self.fragment.is_replaced() { let _scope = layout_debug_scope!("assign_replaced_block_size_if_necessary {:x}", self.base.debug_id()); // Assign block-size for fragment if it is an image fragment. self.fragment.assign_replaced_block_size_if_necessary(); if !self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) { self.base.position.size.block = self.fragment.border_box.size.block; let mut block_start = AdjoiningMargins::from_margin(self.fragment.margin.block_start); let block_end = AdjoiningMargins::from_margin(self.fragment.margin.block_end); if self.fragment.border_box.size.block == Au(0) { block_start.union(block_end); self.base.collapsible_margins = CollapsibleMargins::CollapseThrough(block_start); } else { self.base.collapsible_margins = CollapsibleMargins::Collapse(block_start, block_end); } self.base.restyle_damage.remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW); self.fragment.restyle_damage.remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW); } None } else if self.is_root() || self.formatting_context_type() != FormattingContextType::None || self.base.flags.contains(FlowFlags::MARGINS_CANNOT_COLLAPSE) { // Root element margins should never be collapsed according to CSS § 8.3.1. debug!("assign_block_size: assigning block_size for root flow {:?}", self.base().debug_id()); self.assign_block_size_block_base( layout_context, fragmentation_context, MarginsMayCollapseFlag::MarginsMayNotCollapse) } else { debug!("assign_block_size: assigning block_size for block {:?}", self.base().debug_id()); self.assign_block_size_block_base( layout_context, fragmentation_context, MarginsMayCollapseFlag::MarginsMayCollapse) } } fn compute_stacking_relative_position(&mut self, _layout_context: &LayoutContext) { // FIXME (mbrubeck): Get the real container size, taking the container writing mode into // account. Must handle vertical writing modes. let container_size = Size2D::new(self.base.block_container_inline_size, Au(0)); if self.is_root() { self.base.clip = Rect::max_rect(); } if self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) { let position_start = self.base.position.start.to_physical(self.base.writing_mode, container_size); // Compute our position relative to the nearest ancestor stacking context. This will be // passed down later as part of containing block details for absolute descendants. let absolute_stacking_relative_position = if self.is_fixed() { // The viewport is initially at (0, 0). position_start } else { // Absolute position of the containing block + position of absolute // flow w.r.t. the containing block. self.base .late_absolute_position_info .stacking_relative_position_of_absolute_containing_block + position_start.to_vector() }; if !self.base.writing_mode.is_vertical() { if !self.base.flags.contains(FlowFlags::INLINE_POSITION_IS_STATIC) { self.base.stacking_relative_position.x = absolute_stacking_relative_position.x } if !self.base.flags.contains(FlowFlags::BLOCK_POSITION_IS_STATIC) { self.base.stacking_relative_position.y = absolute_stacking_relative_position.y } } else { if !self.base.flags.contains(FlowFlags::INLINE_POSITION_IS_STATIC) { self.base.stacking_relative_position.y = absolute_stacking_relative_position.y } if !self.base.flags.contains(FlowFlags::BLOCK_POSITION_IS_STATIC) { self.base.stacking_relative_position.x = absolute_stacking_relative_position.x } } } // For relatively-positioned descendants, the containing block formed by a block is just // the content box. The containing block for absolutely-positioned descendants, on the // other hand, is established in other circumstances (see `is_absolute_containing_block'). let relative_offset = self.fragment.relative_position(&self.base .early_absolute_position_info .relative_containing_block_size); if self.is_absolute_containing_block() { let border_box_origin = (self.fragment.border_box - self.fragment.style.logical_border_width()).start; self.base .late_absolute_position_info .stacking_relative_position_of_absolute_containing_block = self.base.stacking_relative_position.to_point() + (border_box_origin + relative_offset).to_physical(self.base.writing_mode, container_size).to_vector() } // Compute absolute position info for children. let stacking_relative_position_of_absolute_containing_block_for_children = if self.fragment.establishes_stacking_context() { let logical_border_width = self.fragment.style().logical_border_width(); let position = LogicalPoint::new(self.base.writing_mode, logical_border_width.inline_start, logical_border_width.block_start); let position = position.to_physical(self.base.writing_mode, container_size); // Some blocks establish a stacking context, but not a containing block for // absolutely positioned elements. An example of this might be a block that has // `position: static` and `opacity` set. In these cases, absolutely-positioned // children will not be positioned relative to us but will instead be positioned // relative to our containing block. if self.is_absolute_containing_block() { position } else { position - self.base.stacking_relative_position } } else { self.base .late_absolute_position_info .stacking_relative_position_of_absolute_containing_block }; let late_absolute_position_info_for_children = LateAbsolutePositionInfo { stacking_relative_position_of_absolute_containing_block: stacking_relative_position_of_absolute_containing_block_for_children, }; let container_size_for_children = self.base.position.size.to_physical(self.base.writing_mode); // Compute the origin and clipping rectangle for children. let relative_offset = relative_offset.to_physical(self.base.writing_mode).to_vector(); let is_stacking_context = self.fragment.establishes_stacking_context(); let origin_for_children = if is_stacking_context { // We establish a stacking context, so the position of our children is vertically // correct, but has to be adjusted to accommodate horizontal margins. (Note the // calculation involving `position` below and recall that inline-direction flow // positions are relative to the edges of the margin box.) // // FIXME(pcwalton): Is this vertical-writing-direction-safe? let margin = self.fragment.margin.to_physical(self.base.writing_mode); Point2D::new(-margin.left, Au(0)) } else { self.base.stacking_relative_position.to_point() + relative_offset }; // Process children. for kid in self.base.child_iter_mut() { if kid.base().flags.contains(FlowFlags::INLINE_POSITION_IS_STATIC) || kid.base().flags.contains(FlowFlags::BLOCK_POSITION_IS_STATIC) { let kid_base = kid.mut_base(); let physical_position = kid_base.position.to_physical(kid_base.writing_mode, container_size_for_children); // Set the inline and block positions as necessary. if !kid_base.writing_mode.is_vertical() { if kid_base.flags.contains(FlowFlags::INLINE_POSITION_IS_STATIC) { kid_base.stacking_relative_position.x = origin_for_children.x + physical_position.origin.x } if kid_base.flags.contains(FlowFlags::BLOCK_POSITION_IS_STATIC) { kid_base.stacking_relative_position.y = origin_for_children.y + physical_position.origin.y } } else { if kid_base.flags.contains(FlowFlags::INLINE_POSITION_IS_STATIC) { kid_base.stacking_relative_position.y = origin_for_children.y + physical_position.origin.y } if kid_base.flags.contains(FlowFlags::BLOCK_POSITION_IS_STATIC) { kid_base.stacking_relative_position.x = origin_for_children.x + physical_position.origin.x } } } kid.mut_base().late_absolute_position_info = late_absolute_position_info_for_children; } } fn mark_as_root(&mut self) { self.flags.insert(BlockFlowFlags::IS_ROOT) } fn is_root(&self) -> bool { self.flags.contains(BlockFlowFlags::IS_ROOT) } /// The 'position' property of this flow. fn positioning(&self) -> Position { self.fragment.style.get_box().position } /// Return the dimensions of the containing block generated by this flow for absolutely- /// positioned descendants. For block flows, this is the padding box. fn generated_containing_block_size(&self, _: OpaqueFlow) -> LogicalSize { (self.fragment.border_box - self.fragment.style().logical_border_width()).size } /// Returns true if this flow contains fragments that are roots of an absolute flow tree. fn contains_roots_of_absolute_flow_tree(&self) -> bool { self.contains_relatively_positioned_fragments() || self.is_root() || self.fragment.has_filter_transform_or_perspective() } /// Returns true if this is an absolute containing block. fn is_absolute_containing_block(&self) -> bool { self.contains_positioned_fragments() || self.fragment.has_filter_transform_or_perspective() } fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) { if self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) && self.fragment.style().logical_position().inline_start == LengthOrPercentageOrAuto::Auto && self.fragment.style().logical_position().inline_end == LengthOrPercentageOrAuto::Auto { self.base.position.start.i = inline_position } } fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) { if self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) && self.fragment.style().logical_position().block_start == LengthOrPercentageOrAuto::Auto && self.fragment.style().logical_position().block_end == LengthOrPercentageOrAuto::Auto { self.base.position.start.b = block_position } } fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) { self.collect_stacking_contexts_for_block(state, StackingContextCollectionFlags::empty()); } fn build_display_list(&mut self, state: &mut DisplayListBuildState) { self.build_display_list_for_block(state, BorderPaintingMode::Separate); } fn repair_style(&mut self, new_style: &::ServoArc) { self.fragment.repair_style(new_style) } fn compute_overflow(&self) -> Overflow { let flow_size = self.base.position.size.to_physical(self.base.writing_mode); let overflow = self.fragment.compute_overflow(&flow_size, &self.base .early_absolute_position_info .relative_containing_block_size); overflow } fn iterate_through_fragment_border_boxes(&self, iterator: &mut FragmentBorderBoxIterator, level: i32, stacking_context_position: &Point2D) { if !iterator.should_process(&self.fragment) { return } iterator.process(&self.fragment, level, &self.fragment .stacking_relative_border_box(&self.base.stacking_relative_position, &self.base .early_absolute_position_info .relative_containing_block_size, self.base .early_absolute_position_info .relative_containing_block_mode, CoordinateSystem::Own) .translate(&stacking_context_position.to_vector())); } fn mutate_fragments(&mut self, mutator: &mut FnMut(&mut Fragment)) { (*mutator)(&mut self.fragment) } fn print_extra_flow_children(&self, print_tree: &mut PrintTree) { print_tree.add_item(format!("↑↑ Fragment for block:{:?}", self.fragment)); } } impl fmt::Debug for BlockFlow { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}({:x}) {:?}", self.class(), self.base.debug_id(), self.base) } } /// The inputs for the inline-sizes-and-margins constraint equation. #[derive(Clone, Copy, Debug)] pub struct ISizeConstraintInput { pub computed_inline_size: MaybeAuto, pub inline_start_margin: MaybeAuto, pub inline_end_margin: MaybeAuto, pub inline_start: MaybeAuto, pub inline_end: MaybeAuto, pub text_align: TextAlign, pub available_inline_size: Au, } impl ISizeConstraintInput { pub fn new(computed_inline_size: MaybeAuto, inline_start_margin: MaybeAuto, inline_end_margin: MaybeAuto, inline_start: MaybeAuto, inline_end: MaybeAuto, text_align: TextAlign, available_inline_size: Au) -> ISizeConstraintInput { ISizeConstraintInput { computed_inline_size: computed_inline_size, inline_start_margin: inline_start_margin, inline_end_margin: inline_end_margin, inline_start: inline_start, inline_end: inline_end, text_align: text_align, available_inline_size: available_inline_size, } } } /// The solutions for the inline-size-and-margins constraint equation. #[derive(Clone, Copy, Debug)] pub struct ISizeConstraintSolution { pub inline_start: Au, pub inline_size: Au, pub margin_inline_start: Au, pub margin_inline_end: Au } impl ISizeConstraintSolution { pub fn new(inline_size: Au, margin_inline_start: Au, margin_inline_end: Au) -> ISizeConstraintSolution { ISizeConstraintSolution { inline_start: Au(0), inline_size: inline_size, margin_inline_start: margin_inline_start, margin_inline_end: margin_inline_end, } } fn for_absolute_flow(inline_start: Au, inline_size: Au, margin_inline_start: Au, margin_inline_end: Au) -> ISizeConstraintSolution { ISizeConstraintSolution { inline_start: inline_start, inline_size: inline_size, margin_inline_start: margin_inline_start, margin_inline_end: margin_inline_end, } } } // Trait to encapsulate the ISize and Margin calculation. // // CSS Section 10.3 pub trait ISizeAndMarginsComputer { /// Instructs the fragment to compute its border and padding. fn compute_border_and_padding(&self, block: &mut BlockFlow, containing_block_inline_size: Au) { block.fragment.compute_border_and_padding(containing_block_inline_size); } /// Compute the inputs for the ISize constraint equation. /// /// This is called only once to compute the initial inputs. For calculations involving /// minimum and maximum inline-size, we don't need to recompute these. fn compute_inline_size_constraint_inputs(&self, block: &mut BlockFlow, parent_flow_inline_size: Au, shared_context: &SharedStyleContext) -> ISizeConstraintInput { let containing_block_inline_size = self.containing_block_inline_size(block, parent_flow_inline_size, shared_context); block.fragment.compute_block_direction_margins(containing_block_inline_size); block.fragment.compute_inline_direction_margins(containing_block_inline_size); self.compute_border_and_padding(block, containing_block_inline_size); let mut computed_inline_size = self.initial_computed_inline_size(block, parent_flow_inline_size, shared_context); let style = block.fragment.style(); match (computed_inline_size, style.get_position().box_sizing) { (MaybeAuto::Specified(size), BoxSizing::BorderBox) => { computed_inline_size = MaybeAuto::Specified(size - block.fragment.border_padding.inline_start_end()) } (MaybeAuto::Auto, BoxSizing::BorderBox) | (_, BoxSizing::ContentBox) => {} } let margin = style.logical_margin(); let position = style.logical_position(); let available_inline_size = containing_block_inline_size - block.fragment.border_padding.inline_start_end(); ISizeConstraintInput::new(computed_inline_size, MaybeAuto::from_style(margin.inline_start, containing_block_inline_size), MaybeAuto::from_style(margin.inline_end, containing_block_inline_size), MaybeAuto::from_style(position.inline_start, containing_block_inline_size), MaybeAuto::from_style(position.inline_end, containing_block_inline_size), style.get_inheritedtext().text_align, available_inline_size) } /// Set the used values for inline-size and margins from the relevant constraint equation. /// This is called only once. /// /// Set: /// * Used values for content inline-size, inline-start margin, and inline-end margin for this /// flow's box; /// * Inline-start coordinate of this flow's box; /// * Inline-start coordinate of the flow with respect to its containing block (if this is an /// absolute flow). fn set_inline_size_constraint_solutions(&self, block: &mut BlockFlow, solution: ISizeConstraintSolution) { let inline_size; let extra_inline_size_from_margin; { let block_mode = block.base.writing_mode; // FIXME (mbrubeck): Get correct containing block for positioned blocks? let container_mode = block.base.block_container_writing_mode; let container_size = block.base.block_container_inline_size; let fragment = block.fragment(); fragment.margin.inline_start = solution.margin_inline_start; fragment.margin.inline_end = solution.margin_inline_end; // The associated fragment has the border box of this flow. inline_size = solution.inline_size + fragment.border_padding.inline_start_end(); fragment.border_box.size.inline = inline_size; // Start border edge. // FIXME (mbrubeck): Handle vertical writing modes. fragment.border_box.start.i = if container_mode.is_bidi_ltr() == block_mode.is_bidi_ltr() { fragment.margin.inline_start } else { // The parent's "start" direction is the child's "end" direction. container_size - inline_size - fragment.margin.inline_end }; // To calculate the total size of this block, we also need to account for any // additional size contribution from positive margins. Negative margins means the block // isn't made larger at all by the margin. extra_inline_size_from_margin = max(Au(0), fragment.margin.inline_start) + max(Au(0), fragment.margin.inline_end); } // We also resize the block itself, to ensure that overflow is not calculated // as the inline-size of our parent. We might be smaller and we might be larger if we // overflow. block.mut_base().position.size.inline = inline_size + extra_inline_size_from_margin; } /// Set the inline coordinate of the given flow if it is absolutely positioned. fn set_inline_position_of_flow_if_necessary(&self, _: &mut BlockFlow, _: ISizeConstraintSolution) {} /// Solve the inline-size and margins constraints for this block flow. fn solve_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution; fn initial_computed_inline_size(&self, block: &mut BlockFlow, parent_flow_inline_size: Au, shared_context: &SharedStyleContext) -> MaybeAuto { MaybeAuto::from_style(block.fragment().style().content_inline_size(), self.containing_block_inline_size(block, parent_flow_inline_size, shared_context)) } fn containing_block_inline_size(&self, _: &mut BlockFlow, parent_flow_inline_size: Au, _: &SharedStyleContext) -> Au { parent_flow_inline_size } /// Compute the used value of inline-size, taking care of min-inline-size and max-inline-size. /// /// CSS Section 10.4: Minimum and Maximum inline-sizes fn compute_used_inline_size(&self, block: &mut BlockFlow, shared_context: &SharedStyleContext, parent_flow_inline_size: Au) { let mut input = self.compute_inline_size_constraint_inputs(block, parent_flow_inline_size, shared_context); let containing_block_inline_size = self.containing_block_inline_size(block, parent_flow_inline_size, shared_context); let mut solution = self.solve_inline_size_constraints(block, &input); // If the tentative used inline-size is greater than 'max-inline-size', inline-size should // be recalculated, but this time using the computed value of 'max-inline-size' as the // computed value for 'inline-size'. match block.fragment().style().max_inline_size().to_used_value(containing_block_inline_size) { Some(max_inline_size) if max_inline_size < solution.inline_size => { input.computed_inline_size = MaybeAuto::Specified(max_inline_size); solution = self.solve_inline_size_constraints(block, &input); } _ => {} } // If the resulting inline-size is smaller than 'min-inline-size', inline-size should be // recalculated, but this time using the value of 'min-inline-size' as the computed value // for 'inline-size'. let computed_min_inline_size = block.fragment().style().min_inline_size().to_used_value(containing_block_inline_size); if computed_min_inline_size > solution.inline_size { input.computed_inline_size = MaybeAuto::Specified(computed_min_inline_size); solution = self.solve_inline_size_constraints(block, &input); } self.set_inline_size_constraint_solutions(block, solution); self.set_inline_position_of_flow_if_necessary(block, solution); } /// Computes inline-start and inline-end margins and inline-size. /// /// This is used by both replaced and non-replaced Blocks. /// /// CSS 2.1 Section 10.3.3. /// Constraint Equation: margin-inline-start + margin-inline-end + inline-size = /// available_inline-size /// where available_inline-size = CB inline-size - (horizontal border + padding) fn solve_block_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution { let (computed_inline_size, inline_start_margin, inline_end_margin, available_inline_size) = (input.computed_inline_size, input.inline_start_margin, input.inline_end_margin, input.available_inline_size); // Check for direction of parent flow (NOT Containing Block) let block_mode = block.base.writing_mode; let container_mode = block.base.block_container_writing_mode; let block_align = block.base.flags.text_align(); // FIXME (mbrubeck): Handle vertical writing modes. let parent_has_same_direction = container_mode.is_bidi_ltr() == block_mode.is_bidi_ltr(); // If inline-size is not 'auto', and inline-size + margins > available_inline-size, all // 'auto' margins are treated as 0. let (inline_start_margin, inline_end_margin) = match computed_inline_size { MaybeAuto::Auto => (inline_start_margin, inline_end_margin), MaybeAuto::Specified(inline_size) => { let inline_start = inline_start_margin.specified_or_zero(); let inline_end = inline_end_margin.specified_or_zero(); if (inline_start + inline_end + inline_size) > available_inline_size { (MaybeAuto::Specified(inline_start), MaybeAuto::Specified(inline_end)) } else { (inline_start_margin, inline_end_margin) } } }; // Invariant: inline-start_margin + inline-size + inline-end_margin == // available_inline-size let (inline_start_margin, inline_size, inline_end_margin) = match (inline_start_margin, computed_inline_size, inline_end_margin) { // If all have a computed value other than 'auto', the system is over-constrained. (MaybeAuto::Specified(margin_start), MaybeAuto::Specified(inline_size), MaybeAuto::Specified(margin_end)) => { // servo_left, servo_right, and servo_center are used to implement // the "align descendants" rule in HTML5 § 14.2. if block_align == TextAlign::ServoCenter { // Ignore any existing margins, and make the inline-start and // inline-end margins equal. let margin = (available_inline_size - inline_size).scale_by(0.5); (margin, inline_size, margin) } else { let ignore_end_margin = match block_align { TextAlign::ServoLeft => block_mode.is_bidi_ltr(), TextAlign::ServoRight => !block_mode.is_bidi_ltr(), _ => parent_has_same_direction, }; if ignore_end_margin { (margin_start, inline_size, available_inline_size - (margin_start + inline_size)) } else { (available_inline_size - (margin_end + inline_size), inline_size, margin_end) } } } // If exactly one value is 'auto', solve for it (MaybeAuto::Auto, MaybeAuto::Specified(inline_size), MaybeAuto::Specified(margin_end)) => (available_inline_size - (inline_size + margin_end), inline_size, margin_end), (MaybeAuto::Specified(margin_start), MaybeAuto::Auto, MaybeAuto::Specified(margin_end)) => { (margin_start, available_inline_size - (margin_start + margin_end), margin_end) } (MaybeAuto::Specified(margin_start), MaybeAuto::Specified(inline_size), MaybeAuto::Auto) => { (margin_start, inline_size, available_inline_size - (margin_start + inline_size)) } // If inline-size is set to 'auto', any other 'auto' value becomes '0', // and inline-size is solved for (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Specified(margin_end)) => { (Au(0), available_inline_size - margin_end, margin_end) } (MaybeAuto::Specified(margin_start), MaybeAuto::Auto, MaybeAuto::Auto) => { (margin_start, available_inline_size - margin_start, Au(0)) } (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Auto) => { (Au(0), available_inline_size, Au(0)) } // If inline-start and inline-end margins are auto, they become equal (MaybeAuto::Auto, MaybeAuto::Specified(inline_size), MaybeAuto::Auto) => { let margin = (available_inline_size - inline_size).scale_by(0.5); (margin, inline_size, margin) } }; ISizeConstraintSolution::new(inline_size, inline_start_margin, inline_end_margin) } } /// The different types of Blocks. /// /// They mainly differ in the way inline-size and block-sizes and margins are calculated /// for them. pub struct AbsoluteNonReplaced; pub struct AbsoluteReplaced; pub struct BlockNonReplaced; pub struct BlockReplaced; pub struct FloatNonReplaced; pub struct FloatReplaced; pub struct InlineBlockNonReplaced; pub struct InlineBlockReplaced; pub struct InlineFlexItem; impl ISizeAndMarginsComputer for AbsoluteNonReplaced { /// Solve the horizontal constraint equation for absolute non-replaced elements. /// /// CSS Section 10.3.7 /// Constraint equation: /// inline-start + inline-end + inline-size + margin-inline-start + margin-inline-end /// = absolute containing block inline-size - (horizontal padding and border) /// [aka available inline-size] /// /// Return the solution for the equation. fn solve_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution { let &ISizeConstraintInput { computed_inline_size, inline_start_margin, inline_end_margin, inline_start, inline_end, available_inline_size, .. } = input; // Check for direction of parent flow (NOT Containing Block) let block_mode = block.base.writing_mode; let container_mode = block.base.block_container_writing_mode; // FIXME (mbrubeck): Handle vertical writing modes. let parent_has_same_direction = container_mode.is_bidi_ltr() == block_mode.is_bidi_ltr(); let (inline_start, inline_size, margin_inline_start, margin_inline_end) = match (inline_start, inline_end, computed_inline_size) { (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Auto) => { let margin_start = inline_start_margin.specified_or_zero(); let margin_end = inline_end_margin.specified_or_zero(); // Now it is the same situation as inline-start Specified and inline-end // and inline-size Auto. // Set inline-end to zero to calculate inline-size. let inline_size = block.get_shrink_to_fit_inline_size(available_inline_size - (margin_start + margin_end)); (Au(0), inline_size, margin_start, margin_end) } (MaybeAuto::Specified(inline_start), MaybeAuto::Specified(inline_end), MaybeAuto::Specified(inline_size)) => { match (inline_start_margin, inline_end_margin) { (MaybeAuto::Auto, MaybeAuto::Auto) => { let total_margin_val = available_inline_size - inline_start - inline_end - inline_size; if total_margin_val < Au(0) { if parent_has_same_direction { // margin-inline-start becomes 0 (inline_start, inline_size, Au(0), total_margin_val) } else { // margin-inline-end becomes 0, because it's toward the parent's // inline-start edge. (inline_start, inline_size, total_margin_val, Au(0)) } } else { // Equal margins (inline_start, inline_size, total_margin_val.scale_by(0.5), total_margin_val.scale_by(0.5)) } } (MaybeAuto::Specified(margin_start), MaybeAuto::Auto) => { let sum = inline_start + inline_end + inline_size + margin_start; (inline_start, inline_size, margin_start, available_inline_size - sum) } (MaybeAuto::Auto, MaybeAuto::Specified(margin_end)) => { let sum = inline_start + inline_end + inline_size + margin_end; (inline_start, inline_size, available_inline_size - sum, margin_end) } (MaybeAuto::Specified(margin_start), MaybeAuto::Specified(margin_end)) => { // Values are over-constrained. let sum = inline_start + inline_size + margin_start + margin_end; if parent_has_same_direction { // Ignore value for 'inline-end' (inline_start, inline_size, margin_start, margin_end) } else { // Ignore value for 'inline-start' (available_inline_size - sum, inline_size, margin_start, margin_end) } } } } // For the rest of the cases, auto values for margin are set to 0 // If only one is Auto, solve for it (MaybeAuto::Auto, MaybeAuto::Specified(inline_end), MaybeAuto::Specified(inline_size)) => { let margin_start = inline_start_margin.specified_or_zero(); let margin_end = inline_end_margin.specified_or_zero(); let sum = inline_end + inline_size + margin_start + margin_end; (available_inline_size - sum, inline_size, margin_start, margin_end) } (MaybeAuto::Specified(inline_start), MaybeAuto::Auto, MaybeAuto::Specified(inline_size)) => { let margin_start = inline_start_margin.specified_or_zero(); let margin_end = inline_end_margin.specified_or_zero(); (inline_start, inline_size, margin_start, margin_end) } (MaybeAuto::Specified(inline_start), MaybeAuto::Specified(inline_end), MaybeAuto::Auto) => { let margin_start = inline_start_margin.specified_or_zero(); let margin_end = inline_end_margin.specified_or_zero(); let sum = inline_start + inline_end + margin_start + margin_end; (inline_start, available_inline_size - sum, margin_start, margin_end) } // If inline-size is auto, then inline-size is shrink-to-fit. Solve for the // non-auto value. (MaybeAuto::Specified(inline_start), MaybeAuto::Auto, MaybeAuto::Auto) => { let margin_start = inline_start_margin.specified_or_zero(); let margin_end = inline_end_margin.specified_or_zero(); // Set inline-end to zero to calculate inline-size let inline_size = block.get_shrink_to_fit_inline_size(available_inline_size - (margin_start + margin_end)); (inline_start, inline_size, margin_start, margin_end) } (MaybeAuto::Auto, MaybeAuto::Specified(inline_end), MaybeAuto::Auto) => { let margin_start = inline_start_margin.specified_or_zero(); let margin_end = inline_end_margin.specified_or_zero(); // Set inline-start to zero to calculate inline-size let inline_size = block.get_shrink_to_fit_inline_size(available_inline_size - (margin_start + margin_end)); let sum = inline_end + inline_size + margin_start + margin_end; (available_inline_size - sum, inline_size, margin_start, margin_end) } (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Specified(inline_size)) => { let margin_start = inline_start_margin.specified_or_zero(); let margin_end = inline_end_margin.specified_or_zero(); // Setting 'inline-start' to static position because direction is 'ltr'. // TODO: Handle 'rtl' when it is implemented. (Au(0), inline_size, margin_start, margin_end) } }; ISizeConstraintSolution::for_absolute_flow(inline_start, inline_size, margin_inline_start, margin_inline_end) } fn containing_block_inline_size(&self, block: &mut BlockFlow, _: Au, shared_context: &SharedStyleContext) -> Au { let opaque_block = OpaqueFlow::from_flow(block); block.containing_block_size(&shared_context.viewport_size(), opaque_block).inline } fn set_inline_position_of_flow_if_necessary(&self, block: &mut BlockFlow, solution: ISizeConstraintSolution) { // Set the inline position of the absolute flow wrt to its containing block. if !block.base.flags.contains(FlowFlags::INLINE_POSITION_IS_STATIC) { block.base.position.start.i = solution.inline_start; } } } impl ISizeAndMarginsComputer for AbsoluteReplaced { /// Solve the horizontal constraint equation for absolute replaced elements. /// /// CSS Section 10.3.8 /// Constraint equation: /// inline-start + inline-end + inline-size + margin-inline-start + margin-inline-end /// = absolute containing block inline-size - (horizontal padding and border) /// [aka available_inline-size] /// /// Return the solution for the equation. fn solve_inline_size_constraints(&self, _: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution { let &ISizeConstraintInput { computed_inline_size, inline_start_margin, inline_end_margin, inline_start, inline_end, available_inline_size, .. } = input; // TODO: Check for direction of static-position Containing Block (aka // parent flow, _not_ the actual Containing Block) when right-to-left // is implemented // Assume direction is 'ltr' for now // TODO: Handle all the cases for 'rtl' direction. let inline_size = match computed_inline_size { MaybeAuto::Specified(w) => w, _ => panic!("{} {}", "The used value for inline_size for absolute replaced flow", "should have already been calculated by now.") }; let (inline_start, inline_size, margin_inline_start, margin_inline_end) = match (inline_start, inline_end) { (MaybeAuto::Auto, MaybeAuto::Auto) => { let margin_start = inline_start_margin.specified_or_zero(); let margin_end = inline_end_margin.specified_or_zero(); (Au(0), inline_size, margin_start, margin_end) } // If only one is Auto, solve for it (MaybeAuto::Auto, MaybeAuto::Specified(inline_end)) => { let margin_start = inline_start_margin.specified_or_zero(); let margin_end = inline_end_margin.specified_or_zero(); let sum = inline_end + inline_size + margin_start + margin_end; (available_inline_size - sum, inline_size, margin_start, margin_end) } (MaybeAuto::Specified(inline_start), MaybeAuto::Auto) => { let margin_start = inline_start_margin.specified_or_zero(); let margin_end = inline_end_margin.specified_or_zero(); (inline_start, inline_size, margin_start, margin_end) } (MaybeAuto::Specified(inline_start), MaybeAuto::Specified(inline_end)) => { match (inline_start_margin, inline_end_margin) { (MaybeAuto::Auto, MaybeAuto::Auto) => { let total_margin_val = available_inline_size - inline_start - inline_end - inline_size; if total_margin_val < Au(0) { // margin-inline-start becomes 0 because direction is 'ltr'. (inline_start, inline_size, Au(0), total_margin_val) } else { // Equal margins (inline_start, inline_size, total_margin_val.scale_by(0.5), total_margin_val.scale_by(0.5)) } } (MaybeAuto::Specified(margin_start), MaybeAuto::Auto) => { let sum = inline_start + inline_end + inline_size + margin_start; (inline_start, inline_size, margin_start, available_inline_size - sum) } (MaybeAuto::Auto, MaybeAuto::Specified(margin_end)) => { let sum = inline_start + inline_end + inline_size + margin_end; (inline_start, inline_size, available_inline_size - sum, margin_end) } (MaybeAuto::Specified(margin_start), MaybeAuto::Specified(margin_end)) => { // Values are over-constrained. // Ignore value for 'inline-end' cos direction is 'ltr'. (inline_start, inline_size, margin_start, margin_end) } } } }; ISizeConstraintSolution::for_absolute_flow(inline_start, inline_size, margin_inline_start, margin_inline_end) } /// Calculate used value of inline-size just like we do for inline replaced elements. fn initial_computed_inline_size(&self, block: &mut BlockFlow, _: Au, shared_context: &SharedStyleContext) -> MaybeAuto { let opaque_block = OpaqueFlow::from_flow(block); let containing_block_inline_size = block.containing_block_size(&shared_context.viewport_size(), opaque_block).inline; let container_block_size = block.explicit_block_containing_size(shared_context); let fragment = block.fragment(); fragment.assign_replaced_inline_size_if_necessary(containing_block_inline_size, container_block_size); // For replaced absolute flow, the rest of the constraint solving will // take inline-size to be specified as the value computed here. MaybeAuto::Specified(fragment.content_box().size.inline) } fn containing_block_inline_size(&self, block: &mut BlockFlow, _: Au, shared_context: &SharedStyleContext) -> Au { let opaque_block = OpaqueFlow::from_flow(block); block.containing_block_size(&shared_context.viewport_size(), opaque_block).inline } fn set_inline_position_of_flow_if_necessary(&self, block: &mut BlockFlow, solution: ISizeConstraintSolution) { // Set the x-coordinate of the absolute flow wrt to its containing block. block.base.position.start.i = solution.inline_start; } } impl ISizeAndMarginsComputer for BlockNonReplaced { /// Compute inline-start and inline-end margins and inline-size. fn solve_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution { self.solve_block_inline_size_constraints(block, input) } } impl ISizeAndMarginsComputer for BlockReplaced { /// Compute inline-start and inline-end margins and inline-size. /// /// ISize has already been calculated. We now calculate the margins just /// like for non-replaced blocks. fn solve_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution { match input.computed_inline_size { MaybeAuto::Specified(_) => {}, MaybeAuto::Auto => { panic!("BlockReplaced: inline_size should have been computed by now") } }; self.solve_block_inline_size_constraints(block, input) } /// Calculate used value of inline-size just like we do for inline replaced elements. fn initial_computed_inline_size(&self, block: &mut BlockFlow, parent_flow_inline_size: Au, shared_context: &SharedStyleContext) -> MaybeAuto { let container_block_size = block.explicit_block_containing_size(shared_context); let fragment = block.fragment(); fragment.assign_replaced_inline_size_if_necessary(parent_flow_inline_size, container_block_size); // For replaced block flow, the rest of the constraint solving will // take inline-size to be specified as the value computed here. MaybeAuto::Specified(fragment.content_box().size.inline) } } impl ISizeAndMarginsComputer for FloatNonReplaced { /// CSS Section 10.3.5 /// /// If inline-size is computed as 'auto', the used value is the 'shrink-to-fit' inline-size. fn solve_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution { let (computed_inline_size, inline_start_margin, inline_end_margin, available_inline_size) = (input.computed_inline_size, input.inline_start_margin, input.inline_end_margin, input.available_inline_size); let margin_inline_start = inline_start_margin.specified_or_zero(); let margin_inline_end = inline_end_margin.specified_or_zero(); let available_inline_size_float = available_inline_size - margin_inline_start - margin_inline_end; let shrink_to_fit = block.get_shrink_to_fit_inline_size(available_inline_size_float); let inline_size = computed_inline_size.specified_or_default(shrink_to_fit); debug!("assign_inline_sizes_float -- inline_size: {:?}", inline_size); ISizeConstraintSolution::new(inline_size, margin_inline_start, margin_inline_end) } } impl ISizeAndMarginsComputer for FloatReplaced { /// CSS Section 10.3.5 /// /// If inline-size is computed as 'auto', the used value is the 'shrink-to-fit' inline-size. fn solve_inline_size_constraints(&self, _: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution { let (computed_inline_size, inline_start_margin, inline_end_margin) = (input.computed_inline_size, input.inline_start_margin, input.inline_end_margin); let margin_inline_start = inline_start_margin.specified_or_zero(); let margin_inline_end = inline_end_margin.specified_or_zero(); let inline_size = match computed_inline_size { MaybeAuto::Specified(w) => w, MaybeAuto::Auto => panic!("FloatReplaced: inline_size should have been computed by now") }; debug!("assign_inline_sizes_float -- inline_size: {:?}", inline_size); ISizeConstraintSolution::new(inline_size, margin_inline_start, margin_inline_end) } /// Calculate used value of inline-size just like we do for inline replaced elements. fn initial_computed_inline_size(&self, block: &mut BlockFlow, parent_flow_inline_size: Au, shared_context: &SharedStyleContext) -> MaybeAuto { let container_block_size = block.explicit_block_containing_size(shared_context); let fragment = block.fragment(); fragment.assign_replaced_inline_size_if_necessary(parent_flow_inline_size, container_block_size); // For replaced block flow, the rest of the constraint solving will // take inline-size to be specified as the value computed here. MaybeAuto::Specified(fragment.content_box().size.inline) } } impl ISizeAndMarginsComputer for InlineBlockNonReplaced { /// Compute inline-start and inline-end margins and inline-size. fn solve_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution { let (computed_inline_size, inline_start_margin, inline_end_margin, available_inline_size) = (input.computed_inline_size, input.inline_start_margin, input.inline_end_margin, input.available_inline_size); // For inline-blocks, `auto` margins compute to 0. let inline_start_margin = inline_start_margin.specified_or_zero(); let inline_end_margin = inline_end_margin.specified_or_zero(); // If inline-size is set to 'auto', and this is an inline block, use the // shrink to fit algorithm (see CSS 2.1 § 10.3.9) let inline_size = match computed_inline_size { MaybeAuto::Auto => { block.get_shrink_to_fit_inline_size(available_inline_size - (inline_start_margin + inline_end_margin)) } MaybeAuto::Specified(inline_size) => inline_size, }; ISizeConstraintSolution::new(inline_size, inline_start_margin, inline_end_margin) } } impl ISizeAndMarginsComputer for InlineBlockReplaced { /// Compute inline-start and inline-end margins and inline-size. /// /// ISize has already been calculated. We now calculate the margins just /// like for non-replaced blocks. fn solve_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution { debug_assert!(match input.computed_inline_size { MaybeAuto::Specified(_) => true, MaybeAuto::Auto => false, }); let (computed_inline_size, inline_start_margin, inline_end_margin, available_inline_size) = (input.computed_inline_size, input.inline_start_margin, input.inline_end_margin, input.available_inline_size); // For inline-blocks, `auto` margins compute to 0. let inline_start_margin = inline_start_margin.specified_or_zero(); let inline_end_margin = inline_end_margin.specified_or_zero(); // If inline-size is set to 'auto', and this is an inline block, use the // shrink to fit algorithm (see CSS 2.1 § 10.3.9) let inline_size = match computed_inline_size { MaybeAuto::Auto => { block.get_shrink_to_fit_inline_size(available_inline_size - (inline_start_margin + inline_end_margin)) } MaybeAuto::Specified(inline_size) => inline_size, }; ISizeConstraintSolution::new(inline_size, inline_start_margin, inline_end_margin) } /// Calculate used value of inline-size just like we do for inline replaced elements. fn initial_computed_inline_size(&self, block: &mut BlockFlow, parent_flow_inline_size: Au, shared_context: &SharedStyleContext) -> MaybeAuto { let container_block_size = block.explicit_block_containing_size(shared_context); let fragment = block.fragment(); fragment.assign_replaced_inline_size_if_necessary(parent_flow_inline_size, container_block_size); // For replaced block flow, the rest of the constraint solving will // take inline-size to be specified as the value computed here. MaybeAuto::Specified(fragment.content_box().size.inline) } } impl ISizeAndMarginsComputer for InlineFlexItem { // Replace the default method directly to prevent recalculating and setting margins again // which has already been set by its parent. fn compute_used_inline_size(&self, block: &mut BlockFlow, shared_context: &SharedStyleContext, parent_flow_inline_size: Au) { let container_block_size = block.explicit_block_containing_size(shared_context); block.fragment.assign_replaced_inline_size_if_necessary(parent_flow_inline_size, container_block_size); } // The used inline size and margins are set by parent flex flow, do nothing here. fn solve_inline_size_constraints(&self, block: &mut BlockFlow, _: &ISizeConstraintInput) -> ISizeConstraintSolution { let fragment = block.fragment(); ISizeConstraintSolution::new(fragment.border_box.size.inline, fragment.margin.inline_start, fragment.margin.inline_end) } }