gecko-dev/servo/components/layout/block.rs
Martin Robinson b2f0b2a107 servo: Merge #20214 - Stop using LocalClip::RoundedRect (from mrobinson:stop-using-using-localclip-roundedrect); r=glennw
We would like to remove this functionality from WebRender, so convert
its use to clip scroll nodes. This change also removes the redundant
BlocBlockStackingContextType in favor of Option<StackingContextType>,
which is just as expressive. This simplifies the code a bit.

<!-- Please describe your changes on the following line: -->

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: -->
- [x] `./mach build -d` does not report any errors
- [x] `./mach test-tidy` does not report any errors
- [ ] These changes fix #__ (github issue number if applicable).

<!-- Either: -->
- [ ] There are tests for these changes OR
- [x] These changes do not require tests because they should not change behavior.

<!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.-->

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->

Source-Repo: https://github.com/servo/servo
Source-Revision: 26d2e77410f8fbc308f63282f435fbc8788c9bcc

--HG--
extra : subtree_source : https%3A//hg.mozilla.org/projects/converted-servo-linear
extra : subtree_revision : 57683d5fd5d1ac4fc01a266fd1288f808e52a8a2
2018-03-06 15:53:11 -05:00

3112 lines
152 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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