/* 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 https://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:
//!
//!
//!
//! "INTRINSIC" refers to L. David Baron's "More Precise Definitions of Inline Layout and Table
//! Layout" available here:
//!
//!
//!
//! "CSS-SIZING" refers to the W3C "CSS Intrinsic & Extrinsic Sizing Module Level 3" document
//! available here:
//!
//!
use std::cmp::{max, min};
use std::fmt;
use std::sync::Arc;
use app_units::{Au, MAX_AU};
use base::print_tree::PrintTree;
use bitflags::bitflags;
use euclid::default::{Point2D, Rect, SideOffsets2D, Size2D};
use log::{debug, trace};
use serde::{Serialize, Serializer};
use servo_geometry::MaxRect;
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::{Margin, MaxSize, Size};
use crate::context::LayoutContext;
use crate::display_list::items::DisplayListSection;
use crate::display_list::{
BorderPaintingMode, DisplayListBuildState, StackingContextCollectionFlags,
StackingContextCollectionState,
};
use crate::floats::{ClearType, FloatKind, Floats, PlacementInfo};
use crate::flow::{
BaseFlow, EarlyAbsolutePositionInfo, Flow, FlowClass, FlowFlags, ForceNonfloatedFlag,
FragmentationContext, GetBaseFlow, ImmutableFlowUtils, LateAbsolutePositionInfo, OpaqueFlow,
};
use crate::flow_list::FlowList;
use crate::fragment::{
CoordinateSystem, Fragment, FragmentBorderBoxIterator, FragmentFlags, Overflow,
};
use crate::incremental::RelayoutMode;
use crate::model::{
AdjoiningMargins, CollapsibleMargins, IntrinsicISizes, MarginCollapseInfo, MaybeAuto,
};
use crate::sequential;
use crate::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,
}
}
}
/// 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_size,
margin_block_start,
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 = MaybeAuto::from_option(
fragment
.style
.content_block_size()
.maybe_to_used_value(block_container_block_size),
);
let max_block_size = fragment
.style
.max_block_size()
.maybe_to_used_value(block_container_block_size);
let min_block_size = MaybeAuto::from_option(
fragment
.style
.min_block_size()
.maybe_to_used_value(block_container_block_size),
)
.specified_or_zero();
// 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 += 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 PreorderFlowTraversal for AbsoluteAssignBSizesTraversal<'_> {
#[inline]
fn process(&self, flow: &mut dyn 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 crate::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! {
#[derive(Clone, Copy)]
struct BlockFlowFlags: u8 {
/// If this is set, then this block flow is the root flow.
const IS_ROOT = 0b0000_0001;
/// 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,
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 {
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 += 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 at root flow.
///
/// 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 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 -= 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 += 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 += delta;
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 -= 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 += 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 early until the absolute flow tree traversal at root flow.
// Assigning block size for absolute flow will happen in `traverse_absolute_flows` below.
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;
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 &dyn 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 {
Size::LengthPercentage(ref lp) => lp.maybe_to_used_value(containing_block_size),
_ => {
let container_size = containing_block_size?;
let (block_start, block_end) = {
let position = self.fragment.style().logical_position();
(
MaybeAuto::from_inset(position.block_start, container_size),
MaybeAuto::from_inset(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 {
Margin::Auto => MaybeAuto::Auto,
_ => MaybeAuto::Specified(self.fragment.margin.block_start),
};
let margin_block_end = match margin.block_end {
Margin::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 {
Margin::Auto => MaybeAuto::Auto,
_ => MaybeAuto::Specified(self.fragment.margin.block_start),
};
let margin_block_end = match margin.block_end {
Margin::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_inset(position.block_start, containing_block_block_size);
block_end = MaybeAuto::from_inset(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 -
self.fragment.border_padding.block_start_end();
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 dyn Flow, usize, Au, WritingMode, &mut Au, &mut Au),
{
let flags = self.base.flags;
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;
for (i, kid) in self.base.child_iter_mut().enumerate().peekable() {
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_inherited_text().text_align;
kid.mut_base().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_inherited_text()
.text_indent
.length
.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
.base
.flags
.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED)
{
return FormattingContextType::Other;
}
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.
// TODO(servo#30572) revert to debug_assert!() once underlying bug is fixed
#[cfg(debug_assertions)]
if self.is_inline_flex_item() {
log::warn!("debug assertion failed! !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)
.unwrap_or(Au(0));
let specified_inline_size = self.fragment.style().content_inline_size();
let container_size = self.base.block_container_inline_size;
let inline_size = match specified_inline_size.to_used_value(container_size) {
Some(size) => match self.fragment.style().get_position().box_sizing {
BoxSizing::BorderBox => size,
BoxSizing::ContentBox => size + self.fragment.border_padding.inline_start_end(),
},
None => 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 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),
) {
(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,
);
},
(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,
)
},
(Some(FloatKind::Left), _) => {
left_float_width_accumulator +=
child_base.intrinsic_inline_sizes.preferred_inline_size;
},
(Some(FloatKind::Right), _) => {
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"
}
);
trace!("BlockFlow before assigning: {:?}", &self);
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();
trace!("BlockFlow after assigning: {:?}", &self);
}
/// 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 !matches!(self.fragment.style.max_inline_size(), MaxSize::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_inset(offsets.block_start, containing_block_size.inline),
MaybeAuto::from_inset(offsets.inline_end, containing_block_size.inline),
MaybeAuto::from_inset(offsets.block_end, containing_block_size.inline),
MaybeAuto::from_inset(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.
// FIXME(emilio): This should probably be writing-mode-aware.
let consult_children = self
.fragment
.style()
.get_position()
.width
.maybe_to_used_value(None)
.is_none();
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 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 &dyn 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() {
// 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!("{}", self.is_root());
trace!("BlockFlow before assigning: {:?}", &self);
let flow = self.assign_block_size_block_base(
layout_context,
fragmentation_context,
MarginsMayCollapseFlag::MarginsMayNotCollapse,
);
trace!("BlockFlow after assigning: {:?}", &self);
flow
} else {
trace!("BlockFlow before assigning: {:?}", &self);
let flow = self.assign_block_size_block_base(
layout_context,
fragmentation_context,
MarginsMayCollapseFlag::MarginsMayCollapse,
);
trace!("BlockFlow after assigning: {:?}", &self);
flow
}
}
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
.is_auto() &&
self.fragment
.style()
.logical_position()
.inline_end
.is_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
.is_auto() &&
self.fragment.style().logical_position().block_end.is_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: &crate::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);
self.fragment.compute_overflow(
&flow_size,
&self
.base
.early_absolute_position_info
.relative_containing_block_size,
)
}
fn iterate_through_fragment_border_boxes(
&self,
iterator: &mut dyn 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 dyn 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, "{:?} {:?}", self.class(), 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 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,
available_inline_size: Au,
) -> ISizeConstraintInput {
ISizeConstraintInput {
computed_inline_size,
inline_start_margin,
inline_end_margin,
inline_start,
inline_end,
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,
margin_inline_start,
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_size,
margin_inline_start,
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_margin(margin.inline_start, containing_block_inline_size),
MaybeAuto::from_margin(margin.inline_end, containing_block_inline_size),
MaybeAuto::from_inset(position.inline_start, containing_block_inline_size),
MaybeAuto::from_inset(position.inline_end, containing_block_inline_size),
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 {
let inline_size =
self.containing_block_inline_size(block, parent_flow_inline_size, shared_context);
MaybeAuto::from_option(
block
.fragment()
.style()
.content_inline_size()
.to_used_value(inline_size),
)
}
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)
.unwrap_or(Au(0));
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.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::MozCenter {
// 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::MozLeft => block_mode.is_bidi_ltr(),
TextAlign::MozRight => !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,
)
}
}