diff --git a/components/layout/flexbox/layout.rs b/components/layout/flexbox/layout.rs index 3f8a6b7c80d..a76cedcff25 100644 --- a/components/layout/flexbox/layout.rs +++ b/components/layout/flexbox/layout.rs @@ -31,19 +31,17 @@ use crate::formatting_contexts::Baselines; use crate::fragment_tree::{ BoxFragment, CollapsedBlockMargins, Fragment, FragmentFlags, SpecificLayoutInfo, }; -use crate::geom::{AuOrAuto, LazySize, LogicalRect, LogicalSides, LogicalVec2, Size, Sizes}; +use crate::geom::{AuOrAuto, LogicalRect, LogicalSides, LogicalVec2}; use crate::layout_box_base::CacheableLayoutResult; use crate::positioned::{ AbsolutelyPositionedBox, PositioningContext, PositioningContextLength, relative_adjustement, }; use crate::sizing::{ ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult, IntrinsicSizingMode, + LazySize, Size, SizeConstraint, Sizes, }; use crate::style_ext::{AspectRatio, Clamp, ComputedValuesExt, ContentBoxSizesAndPBM, LayoutStyle}; -use crate::{ - ConstraintSpace, ContainingBlock, ContainingBlockSize, IndefiniteContainingBlock, - SizeConstraint, -}; +use crate::{ConstraintSpace, ContainingBlock, ContainingBlockSize, IndefiniteContainingBlock}; /// Layout parameters and intermediate results about a flex container, /// grouped to avoid passing around many parameters diff --git a/components/layout/flow/mod.rs b/components/layout/flow/mod.rs index 85fdf96939e..45902eda447 100644 --- a/components/layout/flow/mod.rs +++ b/components/layout/flow/mod.rs @@ -34,17 +34,17 @@ use crate::fragment_tree::{ Fragment, FragmentFlags, }; use crate::geom::{ - AuOrAuto, LazySize, LogicalRect, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalPoint, - PhysicalRect, PhysicalSides, Size, Sizes, ToLogical, ToLogicalWithContainingBlock, + AuOrAuto, LogicalRect, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalPoint, PhysicalRect, + PhysicalSides, ToLogical, ToLogicalWithContainingBlock, }; use crate::layout_box_base::{CacheableLayoutResult, LayoutBoxBase}; use crate::positioned::{AbsolutelyPositionedBox, PositioningContext, PositioningContextLength}; -use crate::sizing::{self, ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult}; -use crate::style_ext::{AspectRatio, ContentBoxSizesAndPBM, LayoutStyle, PaddingBorderMargin}; -use crate::{ - ConstraintSpace, ContainingBlock, ContainingBlockSize, IndefiniteContainingBlock, - SizeConstraint, +use crate::sizing::{ + self, ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult, LazySize, Size, + SizeConstraint, Sizes, }; +use crate::style_ext::{AspectRatio, ContentBoxSizesAndPBM, LayoutStyle, PaddingBorderMargin}; +use crate::{ConstraintSpace, ContainingBlock, ContainingBlockSize, IndefiniteContainingBlock}; mod construct; pub mod float; diff --git a/components/layout/formatting_contexts.rs b/components/layout/formatting_contexts.rs index 96fac43db73..3eb1da16194 100644 --- a/components/layout/formatting_contexts.rs +++ b/components/layout/formatting_contexts.rs @@ -16,13 +16,14 @@ use crate::dom_traversal::{Contents, NodeAndStyleInfo}; use crate::flexbox::FlexContainer; use crate::flow::BlockFormattingContext; use crate::fragment_tree::{BaseFragmentInfo, FragmentFlags}; -use crate::geom::LazySize; use crate::layout_box_base::{ CacheableLayoutResult, CacheableLayoutResultAndInputs, LayoutBoxBase, }; use crate::positioned::PositioningContext; use crate::replaced::ReplacedContents; -use crate::sizing::{self, ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult}; +use crate::sizing::{ + self, ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult, LazySize, +}; use crate::style_ext::{AspectRatio, DisplayInside, LayoutStyle}; use crate::table::Table; use crate::taffy::TaffyContainer; diff --git a/components/layout/geom.rs b/components/layout/geom.rs index 9995f53ede7..c39005394b8 100644 --- a/components/layout/geom.rs +++ b/components/layout/geom.rs @@ -2,24 +2,20 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use std::cell::{LazyCell, OnceCell}; use std::convert::From; use std::fmt; use std::ops::{Add, AddAssign, Neg, Sub, SubAssign}; -use app_units::{Au, MAX_AU}; +use app_units::Au; use malloc_size_of_derive::MallocSizeOf; use style::Zero; -use style::logical_geometry::{BlockFlowDirection, Direction, InlineBaseDirection, WritingMode}; -use style::values::computed::{ - CSSPixelLength, LengthPercentage, MaxSize as StyleMaxSize, Percentage, Size as StyleSize, -}; +use style::logical_geometry::{BlockFlowDirection, InlineBaseDirection, WritingMode}; +use style::values::computed::{CSSPixelLength, LengthPercentage}; use style::values::generics::length::GenericLengthPercentageOrAuto as AutoOr; use style_traits::CSSPixel; use crate::ContainingBlock; -use crate::sizing::ContentSizes; -use crate::style_ext::Clamp; +use crate::sizing::Size; pub type PhysicalPoint = euclid::Point2D; pub type PhysicalSize = euclid::Size2D; @@ -686,517 +682,3 @@ impl ToLogicalWithContainingBlock> for PhysicalRect { } } } - -/// The possible values accepted by the sizing properties. -/// -#[derive(Clone, Debug, PartialEq)] -pub(crate) enum Size { - /// Represents an `auto` value for the preferred and minimum size properties, - /// or `none` for the maximum size properties. - /// - /// - Initial, - /// - MinContent, - /// - MaxContent, - /// - FitContent, - /// - FitContentFunction(T), - /// - Stretch, - /// Represents a numeric ``, but resolved as a `T`. - /// - Numeric(T), -} - -impl Copy for Size {} - -impl Default for Size { - #[inline] - fn default() -> Self { - Self::Initial - } -} - -impl Size { - #[inline] - pub(crate) fn is_initial(&self) -> bool { - matches!(self, Self::Initial) - } -} - -impl Size { - #[inline] - pub(crate) fn to_numeric(&self) -> Option { - match self { - Self::Numeric(numeric) => Some(numeric).cloned(), - _ => None, - } - } - - #[inline] - pub(crate) fn map(&self, f: impl FnOnce(T) -> U) -> Size { - match self { - Size::Initial => Size::Initial, - Size::MinContent => Size::MinContent, - Size::MaxContent => Size::MaxContent, - Size::FitContent => Size::FitContent, - Size::FitContentFunction(size) => Size::FitContentFunction(f(size.clone())), - Size::Stretch => Size::Stretch, - Size::Numeric(numeric) => Size::Numeric(f(numeric.clone())), - } - } -} - -impl From for Size { - fn from(size: StyleSize) -> Self { - match size { - StyleSize::LengthPercentage(lp) => Size::Numeric(lp.0), - StyleSize::Auto => Size::Initial, - StyleSize::MinContent => Size::MinContent, - StyleSize::MaxContent => Size::MaxContent, - StyleSize::FitContent => Size::FitContent, - StyleSize::FitContentFunction(lp) => Size::FitContentFunction(lp.0), - StyleSize::Stretch => Size::Stretch, - StyleSize::AnchorSizeFunction(_) | StyleSize::AnchorContainingCalcFunction(_) => { - unreachable!("anchor-size() should be disabled") - }, - } - } -} - -impl From for Size { - fn from(max_size: StyleMaxSize) -> Self { - match max_size { - StyleMaxSize::LengthPercentage(lp) => Size::Numeric(lp.0), - StyleMaxSize::None => Size::Initial, - StyleMaxSize::MinContent => Size::MinContent, - StyleMaxSize::MaxContent => Size::MaxContent, - StyleMaxSize::FitContent => Size::FitContent, - StyleMaxSize::FitContentFunction(lp) => Size::FitContentFunction(lp.0), - StyleMaxSize::Stretch => Size::Stretch, - StyleMaxSize::AnchorSizeFunction(_) | StyleMaxSize::AnchorContainingCalcFunction(_) => { - unreachable!("anchor-size() should be disabled") - }, - } - } -} - -impl Size { - #[inline] - pub(crate) fn to_percentage(&self) -> Option { - self.to_numeric() - .and_then(|length_percentage| length_percentage.to_percentage()) - } - - /// Resolves percentages in a preferred size, against the provided basis. - /// If the basis is missing, percentages are considered cyclic. - /// - /// - #[inline] - pub(crate) fn resolve_percentages_for_preferred(&self, basis: Option) -> Size { - match self { - Size::Numeric(numeric) => numeric - .maybe_to_used_value(basis) - .map_or(Size::Initial, Size::Numeric), - Size::FitContentFunction(numeric) => { - // Under discussion in https://github.com/w3c/csswg-drafts/issues/11805 - numeric - .maybe_to_used_value(basis) - .map_or(Size::FitContent, Size::FitContentFunction) - }, - _ => self.map(|_| unreachable!("This shouldn't be called for keywords")), - } - } - - /// Resolves percentages in a maximum size, against the provided basis. - /// If the basis is missing, percentages are considered cyclic. - /// - /// - #[inline] - pub(crate) fn resolve_percentages_for_max(&self, basis: Option) -> Size { - match self { - Size::Numeric(numeric) => numeric - .maybe_to_used_value(basis) - .map_or(Size::Initial, Size::Numeric), - Size::FitContentFunction(numeric) => { - // Under discussion in https://github.com/w3c/csswg-drafts/issues/11805 - numeric - .maybe_to_used_value(basis) - .map_or(Size::MaxContent, Size::FitContentFunction) - }, - _ => self.map(|_| unreachable!("This shouldn't be called for keywords")), - } - } -} - -impl LogicalVec2> { - pub(crate) fn percentages_relative_to_basis( - &self, - basis: &LogicalVec2, - ) -> LogicalVec2> { - LogicalVec2 { - inline: self.inline.map(|value| value.to_used_value(basis.inline)), - block: self.block.map(|value| value.to_used_value(basis.block)), - } - } -} - -impl Size { - /// Resolves a preferred size into a numerical value. - /// - #[inline] - pub(crate) fn resolve_for_preferred ContentSizes>( - &self, - automatic_size: Size, - stretch_size: Option, - content_size: &LazyCell, - ) -> Au { - match self { - Self::Initial => { - assert!(!automatic_size.is_initial()); - automatic_size.resolve_for_preferred(automatic_size, stretch_size, content_size) - }, - Self::MinContent => content_size.min_content, - Self::MaxContent => content_size.max_content, - Self::FitContentFunction(size) => content_size.shrink_to_fit(*size), - Self::FitContent => { - content_size.shrink_to_fit(stretch_size.unwrap_or_else(|| content_size.max_content)) - }, - Self::Stretch => stretch_size.unwrap_or_else(|| content_size.max_content), - Self::Numeric(numeric) => *numeric, - } - } - - /// Resolves a minimum size into a numerical value. - /// - #[inline] - pub(crate) fn resolve_for_min ContentSizes>( - &self, - get_automatic_minimum_size: impl FnOnce() -> Au, - stretch_size: Option, - content_size: &LazyCell, - is_table: bool, - ) -> Au { - let result = match self { - Self::Initial => get_automatic_minimum_size(), - Self::MinContent => content_size.min_content, - Self::MaxContent => content_size.max_content, - Self::FitContentFunction(size) => content_size.shrink_to_fit(*size), - Self::FitContent => content_size.shrink_to_fit(stretch_size.unwrap_or_default()), - Self::Stretch => stretch_size.unwrap_or_default(), - Self::Numeric(numeric) => *numeric, - }; - if is_table { - // In addition to the specified minimum, the inline size of a table is forced to be - // at least as big as its min-content size. - // - // Note that if there are collapsed columns, only the inline size of the table grid will - // shrink, while the size of the table wrapper (being computed here) won't be affected. - // However, collapsed rows should typically affect the block size of the table wrapper, - // so it might be wrong to use this function for that case. - // This is being discussed in https://github.com/w3c/csswg-drafts/issues/11408 - result.max(content_size.min_content) - } else { - result - } - } - - /// Resolves a maximum size into a numerical value. - /// - #[inline] - pub(crate) fn resolve_for_max ContentSizes>( - &self, - stretch_size: Option, - content_size: &LazyCell, - ) -> Option { - Some(match self { - Self::Initial => return None, - Self::MinContent => content_size.min_content, - Self::MaxContent => content_size.max_content, - Self::FitContentFunction(size) => content_size.shrink_to_fit(*size), - Self::FitContent => content_size.shrink_to_fit(stretch_size.unwrap_or(MAX_AU)), - Self::Stretch => return stretch_size, - Self::Numeric(numeric) => *numeric, - }) - } - - /// Tries to resolve an extrinsic size into a numerical value. - /// Extrinsic sizes are those based on the context of an element, without regard for its contents. - /// - /// - /// Returns `None` if either: - /// - The size is intrinsic. - /// - The size is the initial one. - /// TODO: should we allow it to behave as `stretch` instead of assuming it's intrinsic? - /// - The provided `stretch_size` is `None` but we need its value. - #[inline] - pub(crate) fn maybe_resolve_extrinsic(&self, stretch_size: Option) -> Option { - match self { - Self::Initial | - Self::MinContent | - Self::MaxContent | - Self::FitContent | - Self::FitContentFunction(_) => None, - Self::Stretch => stretch_size, - Self::Numeric(numeric) => Some(*numeric), - } - } -} - -/// Represents the sizing constraint that the preferred, min and max sizing properties -/// impose on one axis. -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq)] -pub(crate) enum SizeConstraint { - /// Represents a definite preferred size, clamped by minimum and maximum sizes (if any). - Definite(Au), - /// Represents an indefinite preferred size that allows a range of values between - /// the first argument (minimum size) and the second one (maximum size). - MinMax(Au, Option), -} - -impl Default for SizeConstraint { - #[inline] - fn default() -> Self { - Self::MinMax(Au::default(), None) - } -} - -impl SizeConstraint { - #[inline] - pub(crate) fn new(preferred_size: Option, min_size: Au, max_size: Option) -> Self { - preferred_size.map_or_else( - || Self::MinMax(min_size, max_size), - |size| Self::Definite(size.clamp_between_extremums(min_size, max_size)), - ) - } - - #[inline] - pub(crate) fn is_definite(self) -> bool { - matches!(self, Self::Definite(_)) - } - - #[inline] - pub(crate) fn to_definite(self) -> Option { - match self { - Self::Definite(size) => Some(size), - _ => None, - } - } -} - -impl From> for SizeConstraint { - fn from(size: Option) -> Self { - size.map(SizeConstraint::Definite).unwrap_or_default() - } -} - -#[derive(Clone, Debug, Default)] -pub(crate) struct Sizes { - /// - pub preferred: Size, - /// - pub min: Size, - /// - pub max: Size, -} - -impl Sizes { - #[inline] - pub(crate) fn new(preferred: Size, min: Size, max: Size) -> Self { - Self { - preferred, - min, - max, - } - } - - /// Resolves the three sizes into a single numerical value. - #[inline] - pub(crate) fn resolve( - &self, - axis: Direction, - automatic_size: Size, - get_automatic_minimum_size: impl FnOnce() -> Au, - stretch_size: Option, - get_content_size: impl FnOnce() -> ContentSizes, - is_table: bool, - ) -> Au { - if is_table && axis == Direction::Block { - // The intrinsic block size of a table already takes sizing properties into account, - // but it can be a smaller amount if there are collapsed rows. - // Therefore, disregard sizing properties and just defer to the intrinsic size. - // This is being discussed in https://github.com/w3c/csswg-drafts/issues/11408 - return get_content_size().max_content; - } - let (preferred, min, max) = self.resolve_each( - automatic_size, - get_automatic_minimum_size, - stretch_size, - get_content_size, - is_table, - ); - preferred.clamp_between_extremums(min, max) - } - - /// Resolves each of the three sizes into a numerical value, separately. - /// - The 1st returned value is the resolved preferred size. - /// - The 2nd returned value is the resolved minimum size. - /// - The 3rd returned value is the resolved maximum size. `None` means no maximum. - #[inline] - pub(crate) fn resolve_each( - &self, - automatic_size: Size, - get_automatic_minimum_size: impl FnOnce() -> Au, - stretch_size: Option, - get_content_size: impl FnOnce() -> ContentSizes, - is_table: bool, - ) -> (Au, Au, Option) { - // The provided `get_content_size` is a FnOnce but we may need its result multiple times. - // A LazyCell will only invoke it once if needed, and then reuse the result. - let content_size = LazyCell::new(get_content_size); - ( - self.preferred - .resolve_for_preferred(automatic_size, stretch_size, &content_size), - self.min.resolve_for_min( - get_automatic_minimum_size, - stretch_size, - &content_size, - is_table, - ), - self.max.resolve_for_max(stretch_size, &content_size), - ) - } - - /// Tries to extrinsically resolve the three sizes into a single [`SizeConstraint`]. - /// Values that are intrinsic or need `stretch_size` when it's `None` are handled as such: - /// - On the preferred size, they make the returned value be an indefinite [`SizeConstraint::MinMax`]. - /// - On the min size, they are treated as `auto`, enforcing the automatic minimum size. - /// - On the max size, they are treated as `none`, enforcing no maximum. - #[inline] - pub(crate) fn resolve_extrinsic( - &self, - automatic_size: Size, - automatic_minimum_size: Au, - stretch_size: Option, - ) -> SizeConstraint { - let (preferred, min, max) = - self.resolve_each_extrinsic(automatic_size, automatic_minimum_size, stretch_size); - SizeConstraint::new(preferred, min, max) - } - - /// Tries to extrinsically resolve each of the three sizes into a numerical value, separately. - /// This can't resolve values that are intrinsic or need `stretch_size` but it's `None`. - /// - The 1st returned value is the resolved preferred size. If it can't be resolved then - /// the returned value is `None`. Note that this is different than treating it as `auto`. - /// TODO: This needs to be discussed in . - /// - The 2nd returned value is the resolved minimum size. If it can't be resolved then we - /// treat it as the initial `auto`, returning the automatic minimum size. - /// - The 3rd returned value is the resolved maximum size. If it can't be resolved then we - /// treat it as the initial `none`, returning `None`. - #[inline] - pub(crate) fn resolve_each_extrinsic( - &self, - automatic_size: Size, - automatic_minimum_size: Au, - stretch_size: Option, - ) -> (Option, Au, Option) { - ( - if self.preferred.is_initial() { - automatic_size.maybe_resolve_extrinsic(stretch_size) - } else { - self.preferred.maybe_resolve_extrinsic(stretch_size) - }, - self.min - .maybe_resolve_extrinsic(stretch_size) - .unwrap_or(automatic_minimum_size), - self.max.maybe_resolve_extrinsic(stretch_size), - ) - } -} - -struct LazySizeData<'a> { - sizes: &'a Sizes, - axis: Direction, - automatic_size: Size, - get_automatic_minimum_size: fn() -> Au, - stretch_size: Option, - is_table: bool, -} - -/// Represents a size that can't be fully resolved until the intrinsic size -/// is known. This is useful in the block axis, since the intrinsic size -/// depends on layout, but the other inputs are known beforehand. -pub(crate) struct LazySize<'a> { - result: OnceCell, - data: Option>, -} - -impl<'a> LazySize<'a> { - pub(crate) fn new( - sizes: &'a Sizes, - axis: Direction, - automatic_size: Size, - get_automatic_minimum_size: fn() -> Au, - stretch_size: Option, - is_table: bool, - ) -> Self { - Self { - result: OnceCell::new(), - data: Some(LazySizeData { - sizes, - axis, - automatic_size, - get_automatic_minimum_size, - stretch_size, - is_table, - }), - } - } - - /// Creates a [`LazySize`] that will resolve to the intrinsic size. - /// Should be equivalent to [`LazySize::new()`] with default parameters, - /// but avoiding the trouble of getting a reference to a [`Sizes::default()`] - /// which lives long enough. - /// - /// TODO: It's not clear what this should do if/when [`LazySize::resolve()`] - /// is changed to accept a [`ContentSizes`] as the intrinsic size. - pub(crate) fn intrinsic() -> Self { - Self { - result: OnceCell::new(), - data: None, - } - } - - /// Resolves the [`LazySize`] into [`Au`], caching the result. - /// The argument is a callback that computes the intrinsic size lazily. - /// - /// TODO: The intrinsic size should probably be a [`ContentSizes`] instead of [`Au`]. - pub(crate) fn resolve(&self, get_content_size: impl FnOnce() -> Au) -> Au { - *self.result.get_or_init(|| { - let Some(ref data) = self.data else { - return get_content_size(); - }; - data.sizes.resolve( - data.axis, - data.automatic_size, - data.get_automatic_minimum_size, - data.stretch_size, - || get_content_size().into(), - data.is_table, - ) - }) - } -} - -impl From for LazySize<'_> { - /// Creates a [`LazySize`] that will resolve to the given [`Au`], - /// ignoring the intrinsic size. - fn from(value: Au) -> Self { - let result = OnceCell::new(); - result.set(value).unwrap(); - LazySize { result, data: None } - } -} diff --git a/components/layout/layout_box_base.rs b/components/layout/layout_box_base.rs index a18584679ac..cf06327aeb2 100644 --- a/components/layout/layout_box_base.rs +++ b/components/layout/layout_box_base.rs @@ -13,9 +13,8 @@ use style::properties::ComputedValues; use crate::context::LayoutContext; use crate::formatting_contexts::Baselines; use crate::fragment_tree::{BaseFragmentInfo, CollapsedBlockMargins, Fragment, SpecificLayoutInfo}; -use crate::geom::SizeConstraint; use crate::positioned::PositioningContext; -use crate::sizing::{ComputeInlineContentSizes, InlineContentSizesResult}; +use crate::sizing::{ComputeInlineContentSizes, InlineContentSizesResult, SizeConstraint}; use crate::{ConstraintSpace, ContainingBlockSize}; /// A box tree node that handles containing information about style and the original DOM diff --git a/components/layout/lib.rs b/components/layout/lib.rs index 41952d437b5..98b72300021 100644 --- a/components/layout/lib.rs +++ b/components/layout/lib.rs @@ -42,7 +42,8 @@ use servo_arc::Arc as ServoArc; use style::logical_geometry::WritingMode; use style::properties::ComputedValues; -use crate::geom::{LogicalVec2, SizeConstraint}; +use crate::geom::LogicalVec2; +use crate::sizing::SizeConstraint; use crate::style_ext::AspectRatio; /// At times, a style is "owned" by more than one layout object. For example, text diff --git a/components/layout/positioned.rs b/components/layout/positioned.rs index b7cb5bff16a..7f1d9f4618c 100644 --- a/components/layout/positioned.rs +++ b/components/layout/positioned.rs @@ -20,15 +20,16 @@ use crate::dom_traversal::{Contents, NodeAndStyleInfo}; use crate::formatting_contexts::IndependentFormattingContext; use crate::fragment_tree::{BoxFragment, Fragment, FragmentFlags, HoistedSharedFragment}; use crate::geom::{ - AuOrAuto, LazySize, LengthPercentageOrAuto, LogicalRect, LogicalSides, LogicalSides1D, - LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize, PhysicalVec, Size, - Sizes, ToLogical, ToLogicalWithContainingBlock, + AuOrAuto, LengthPercentageOrAuto, LogicalRect, LogicalSides, LogicalSides1D, LogicalVec2, + PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize, PhysicalVec, ToLogical, + ToLogicalWithContainingBlock, }; use crate::layout_box_base::{CacheableLayoutResult, LayoutBoxBase}; +use crate::sizing::{LazySize, Size, SizeConstraint, Sizes}; use crate::style_ext::{Clamp, ComputedValuesExt, ContentBoxSizesAndPBM, DisplayInside}; use crate::{ ConstraintSpace, ContainingBlock, ContainingBlockSize, DefiniteContainingBlock, - PropagatedBoxTreeData, SizeConstraint, + PropagatedBoxTreeData, }; #[derive(Debug, MallocSizeOf)] diff --git a/components/layout/replaced.rs b/components/layout/replaced.rs index feb927e16f9..c2552fb88cf 100644 --- a/components/layout/replaced.rs +++ b/components/layout/replaced.rs @@ -29,11 +29,13 @@ use crate::dom::NodeExt; use crate::fragment_tree::{ BaseFragmentInfo, CollapsedBlockMargins, Fragment, IFrameFragment, ImageFragment, }; -use crate::geom::{LazySize, LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSize}; +use crate::geom::{LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSize}; use crate::layout_box_base::{CacheableLayoutResult, LayoutBoxBase}; -use crate::sizing::{ComputeInlineContentSizes, InlineContentSizesResult}; +use crate::sizing::{ + ComputeInlineContentSizes, InlineContentSizesResult, LazySize, SizeConstraint, +}; use crate::style_ext::{AspectRatio, Clamp, ComputedValuesExt, LayoutStyle}; -use crate::{ConstraintSpace, ContainingBlock, SizeConstraint}; +use crate::{ConstraintSpace, ContainingBlock}; #[derive(Debug, MallocSizeOf)] pub(crate) struct ReplacedContents { diff --git a/components/layout/sizing.rs b/components/layout/sizing.rs index a572db1b5ed..43571e3c27f 100644 --- a/components/layout/sizing.rs +++ b/components/layout/sizing.rs @@ -4,17 +4,18 @@ //! -use std::cell::LazyCell; +use std::cell::{LazyCell, OnceCell}; use std::ops::{Add, AddAssign}; -use app_units::Au; +use app_units::{Au, MAX_AU}; use malloc_size_of_derive::MallocSizeOf; use style::Zero; use style::logical_geometry::Direction; -use style::values::computed::LengthPercentage; +use style::values::computed::{ + LengthPercentage, MaxSize as StyleMaxSize, Percentage, Size as StyleSize, +}; use crate::context::LayoutContext; -use crate::geom::{Size, SizeConstraint}; use crate::style_ext::{AspectRatio, Clamp, ComputedValuesExt, ContentBoxSizesAndPBM, LayoutStyle}; use crate::{ConstraintSpace, IndefiniteContainingBlock, LogicalVec2}; @@ -308,3 +309,517 @@ pub(crate) trait ComputeInlineContentSizes { result } } + +/// The possible values accepted by the sizing properties. +/// +#[derive(Clone, Debug, PartialEq)] +pub(crate) enum Size { + /// Represents an `auto` value for the preferred and minimum size properties, + /// or `none` for the maximum size properties. + /// + /// + Initial, + /// + MinContent, + /// + MaxContent, + /// + FitContent, + /// + FitContentFunction(T), + /// + Stretch, + /// Represents a numeric ``, but resolved as a `T`. + /// + Numeric(T), +} + +impl Copy for Size {} + +impl Default for Size { + #[inline] + fn default() -> Self { + Self::Initial + } +} + +impl Size { + #[inline] + pub(crate) fn is_initial(&self) -> bool { + matches!(self, Self::Initial) + } +} + +impl Size { + #[inline] + pub(crate) fn to_numeric(&self) -> Option { + match self { + Self::Numeric(numeric) => Some(numeric).cloned(), + _ => None, + } + } + + #[inline] + pub(crate) fn map(&self, f: impl FnOnce(T) -> U) -> Size { + match self { + Size::Initial => Size::Initial, + Size::MinContent => Size::MinContent, + Size::MaxContent => Size::MaxContent, + Size::FitContent => Size::FitContent, + Size::FitContentFunction(size) => Size::FitContentFunction(f(size.clone())), + Size::Stretch => Size::Stretch, + Size::Numeric(numeric) => Size::Numeric(f(numeric.clone())), + } + } +} + +impl From for Size { + fn from(size: StyleSize) -> Self { + match size { + StyleSize::LengthPercentage(lp) => Size::Numeric(lp.0), + StyleSize::Auto => Size::Initial, + StyleSize::MinContent => Size::MinContent, + StyleSize::MaxContent => Size::MaxContent, + StyleSize::FitContent => Size::FitContent, + StyleSize::FitContentFunction(lp) => Size::FitContentFunction(lp.0), + StyleSize::Stretch => Size::Stretch, + StyleSize::AnchorSizeFunction(_) | StyleSize::AnchorContainingCalcFunction(_) => { + unreachable!("anchor-size() should be disabled") + }, + } + } +} + +impl From for Size { + fn from(max_size: StyleMaxSize) -> Self { + match max_size { + StyleMaxSize::LengthPercentage(lp) => Size::Numeric(lp.0), + StyleMaxSize::None => Size::Initial, + StyleMaxSize::MinContent => Size::MinContent, + StyleMaxSize::MaxContent => Size::MaxContent, + StyleMaxSize::FitContent => Size::FitContent, + StyleMaxSize::FitContentFunction(lp) => Size::FitContentFunction(lp.0), + StyleMaxSize::Stretch => Size::Stretch, + StyleMaxSize::AnchorSizeFunction(_) | StyleMaxSize::AnchorContainingCalcFunction(_) => { + unreachable!("anchor-size() should be disabled") + }, + } + } +} + +impl Size { + #[inline] + pub(crate) fn to_percentage(&self) -> Option { + self.to_numeric() + .and_then(|length_percentage| length_percentage.to_percentage()) + } + + /// Resolves percentages in a preferred size, against the provided basis. + /// If the basis is missing, percentages are considered cyclic. + /// + /// + #[inline] + pub(crate) fn resolve_percentages_for_preferred(&self, basis: Option) -> Size { + match self { + Size::Numeric(numeric) => numeric + .maybe_to_used_value(basis) + .map_or(Size::Initial, Size::Numeric), + Size::FitContentFunction(numeric) => { + // Under discussion in https://github.com/w3c/csswg-drafts/issues/11805 + numeric + .maybe_to_used_value(basis) + .map_or(Size::FitContent, Size::FitContentFunction) + }, + _ => self.map(|_| unreachable!("This shouldn't be called for keywords")), + } + } + + /// Resolves percentages in a maximum size, against the provided basis. + /// If the basis is missing, percentages are considered cyclic. + /// + /// + #[inline] + pub(crate) fn resolve_percentages_for_max(&self, basis: Option) -> Size { + match self { + Size::Numeric(numeric) => numeric + .maybe_to_used_value(basis) + .map_or(Size::Initial, Size::Numeric), + Size::FitContentFunction(numeric) => { + // Under discussion in https://github.com/w3c/csswg-drafts/issues/11805 + numeric + .maybe_to_used_value(basis) + .map_or(Size::MaxContent, Size::FitContentFunction) + }, + _ => self.map(|_| unreachable!("This shouldn't be called for keywords")), + } + } +} + +impl LogicalVec2> { + pub(crate) fn percentages_relative_to_basis( + &self, + basis: &LogicalVec2, + ) -> LogicalVec2> { + LogicalVec2 { + inline: self.inline.map(|value| value.to_used_value(basis.inline)), + block: self.block.map(|value| value.to_used_value(basis.block)), + } + } +} + +impl Size { + /// Resolves a preferred size into a numerical value. + /// + #[inline] + pub(crate) fn resolve_for_preferred ContentSizes>( + &self, + automatic_size: Size, + stretch_size: Option, + content_size: &LazyCell, + ) -> Au { + match self { + Self::Initial => { + assert!(!automatic_size.is_initial()); + automatic_size.resolve_for_preferred(automatic_size, stretch_size, content_size) + }, + Self::MinContent => content_size.min_content, + Self::MaxContent => content_size.max_content, + Self::FitContentFunction(size) => content_size.shrink_to_fit(*size), + Self::FitContent => { + content_size.shrink_to_fit(stretch_size.unwrap_or_else(|| content_size.max_content)) + }, + Self::Stretch => stretch_size.unwrap_or_else(|| content_size.max_content), + Self::Numeric(numeric) => *numeric, + } + } + + /// Resolves a minimum size into a numerical value. + /// + #[inline] + pub(crate) fn resolve_for_min ContentSizes>( + &self, + get_automatic_minimum_size: impl FnOnce() -> Au, + stretch_size: Option, + content_size: &LazyCell, + is_table: bool, + ) -> Au { + let result = match self { + Self::Initial => get_automatic_minimum_size(), + Self::MinContent => content_size.min_content, + Self::MaxContent => content_size.max_content, + Self::FitContentFunction(size) => content_size.shrink_to_fit(*size), + Self::FitContent => content_size.shrink_to_fit(stretch_size.unwrap_or_default()), + Self::Stretch => stretch_size.unwrap_or_default(), + Self::Numeric(numeric) => *numeric, + }; + if is_table { + // In addition to the specified minimum, the inline size of a table is forced to be + // at least as big as its min-content size. + // + // Note that if there are collapsed columns, only the inline size of the table grid will + // shrink, while the size of the table wrapper (being computed here) won't be affected. + // However, collapsed rows should typically affect the block size of the table wrapper, + // so it might be wrong to use this function for that case. + // This is being discussed in https://github.com/w3c/csswg-drafts/issues/11408 + result.max(content_size.min_content) + } else { + result + } + } + + /// Resolves a maximum size into a numerical value. + /// + #[inline] + pub(crate) fn resolve_for_max ContentSizes>( + &self, + stretch_size: Option, + content_size: &LazyCell, + ) -> Option { + Some(match self { + Self::Initial => return None, + Self::MinContent => content_size.min_content, + Self::MaxContent => content_size.max_content, + Self::FitContentFunction(size) => content_size.shrink_to_fit(*size), + Self::FitContent => content_size.shrink_to_fit(stretch_size.unwrap_or(MAX_AU)), + Self::Stretch => return stretch_size, + Self::Numeric(numeric) => *numeric, + }) + } + + /// Tries to resolve an extrinsic size into a numerical value. + /// Extrinsic sizes are those based on the context of an element, without regard for its contents. + /// + /// + /// Returns `None` if either: + /// - The size is intrinsic. + /// - The size is the initial one. + /// TODO: should we allow it to behave as `stretch` instead of assuming it's intrinsic? + /// - The provided `stretch_size` is `None` but we need its value. + #[inline] + pub(crate) fn maybe_resolve_extrinsic(&self, stretch_size: Option) -> Option { + match self { + Self::Initial | + Self::MinContent | + Self::MaxContent | + Self::FitContent | + Self::FitContentFunction(_) => None, + Self::Stretch => stretch_size, + Self::Numeric(numeric) => Some(*numeric), + } + } +} + +/// Represents the sizing constraint that the preferred, min and max sizing properties +/// impose on one axis. +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq)] +pub(crate) enum SizeConstraint { + /// Represents a definite preferred size, clamped by minimum and maximum sizes (if any). + Definite(Au), + /// Represents an indefinite preferred size that allows a range of values between + /// the first argument (minimum size) and the second one (maximum size). + MinMax(Au, Option), +} + +impl Default for SizeConstraint { + #[inline] + fn default() -> Self { + Self::MinMax(Au::default(), None) + } +} + +impl SizeConstraint { + #[inline] + pub(crate) fn new(preferred_size: Option, min_size: Au, max_size: Option) -> Self { + preferred_size.map_or_else( + || Self::MinMax(min_size, max_size), + |size| Self::Definite(size.clamp_between_extremums(min_size, max_size)), + ) + } + + #[inline] + pub(crate) fn is_definite(self) -> bool { + matches!(self, Self::Definite(_)) + } + + #[inline] + pub(crate) fn to_definite(self) -> Option { + match self { + Self::Definite(size) => Some(size), + _ => None, + } + } +} + +impl From> for SizeConstraint { + fn from(size: Option) -> Self { + size.map(SizeConstraint::Definite).unwrap_or_default() + } +} + +#[derive(Clone, Debug, Default)] +pub(crate) struct Sizes { + /// + pub preferred: Size, + /// + pub min: Size, + /// + pub max: Size, +} + +impl Sizes { + #[inline] + pub(crate) fn new(preferred: Size, min: Size, max: Size) -> Self { + Self { + preferred, + min, + max, + } + } + + /// Resolves the three sizes into a single numerical value. + #[inline] + pub(crate) fn resolve( + &self, + axis: Direction, + automatic_size: Size, + get_automatic_minimum_size: impl FnOnce() -> Au, + stretch_size: Option, + get_content_size: impl FnOnce() -> ContentSizes, + is_table: bool, + ) -> Au { + if is_table && axis == Direction::Block { + // The intrinsic block size of a table already takes sizing properties into account, + // but it can be a smaller amount if there are collapsed rows. + // Therefore, disregard sizing properties and just defer to the intrinsic size. + // This is being discussed in https://github.com/w3c/csswg-drafts/issues/11408 + return get_content_size().max_content; + } + let (preferred, min, max) = self.resolve_each( + automatic_size, + get_automatic_minimum_size, + stretch_size, + get_content_size, + is_table, + ); + preferred.clamp_between_extremums(min, max) + } + + /// Resolves each of the three sizes into a numerical value, separately. + /// - The 1st returned value is the resolved preferred size. + /// - The 2nd returned value is the resolved minimum size. + /// - The 3rd returned value is the resolved maximum size. `None` means no maximum. + #[inline] + pub(crate) fn resolve_each( + &self, + automatic_size: Size, + get_automatic_minimum_size: impl FnOnce() -> Au, + stretch_size: Option, + get_content_size: impl FnOnce() -> ContentSizes, + is_table: bool, + ) -> (Au, Au, Option) { + // The provided `get_content_size` is a FnOnce but we may need its result multiple times. + // A LazyCell will only invoke it once if needed, and then reuse the result. + let content_size = LazyCell::new(get_content_size); + ( + self.preferred + .resolve_for_preferred(automatic_size, stretch_size, &content_size), + self.min.resolve_for_min( + get_automatic_minimum_size, + stretch_size, + &content_size, + is_table, + ), + self.max.resolve_for_max(stretch_size, &content_size), + ) + } + + /// Tries to extrinsically resolve the three sizes into a single [`SizeConstraint`]. + /// Values that are intrinsic or need `stretch_size` when it's `None` are handled as such: + /// - On the preferred size, they make the returned value be an indefinite [`SizeConstraint::MinMax`]. + /// - On the min size, they are treated as `auto`, enforcing the automatic minimum size. + /// - On the max size, they are treated as `none`, enforcing no maximum. + #[inline] + pub(crate) fn resolve_extrinsic( + &self, + automatic_size: Size, + automatic_minimum_size: Au, + stretch_size: Option, + ) -> SizeConstraint { + let (preferred, min, max) = + self.resolve_each_extrinsic(automatic_size, automatic_minimum_size, stretch_size); + SizeConstraint::new(preferred, min, max) + } + + /// Tries to extrinsically resolve each of the three sizes into a numerical value, separately. + /// This can't resolve values that are intrinsic or need `stretch_size` but it's `None`. + /// - The 1st returned value is the resolved preferred size. If it can't be resolved then + /// the returned value is `None`. Note that this is different than treating it as `auto`. + /// TODO: This needs to be discussed in . + /// - The 2nd returned value is the resolved minimum size. If it can't be resolved then we + /// treat it as the initial `auto`, returning the automatic minimum size. + /// - The 3rd returned value is the resolved maximum size. If it can't be resolved then we + /// treat it as the initial `none`, returning `None`. + #[inline] + pub(crate) fn resolve_each_extrinsic( + &self, + automatic_size: Size, + automatic_minimum_size: Au, + stretch_size: Option, + ) -> (Option, Au, Option) { + ( + if self.preferred.is_initial() { + automatic_size.maybe_resolve_extrinsic(stretch_size) + } else { + self.preferred.maybe_resolve_extrinsic(stretch_size) + }, + self.min + .maybe_resolve_extrinsic(stretch_size) + .unwrap_or(automatic_minimum_size), + self.max.maybe_resolve_extrinsic(stretch_size), + ) + } +} + +struct LazySizeData<'a> { + sizes: &'a Sizes, + axis: Direction, + automatic_size: Size, + get_automatic_minimum_size: fn() -> Au, + stretch_size: Option, + is_table: bool, +} + +/// Represents a size that can't be fully resolved until the intrinsic size +/// is known. This is useful in the block axis, since the intrinsic size +/// depends on layout, but the other inputs are known beforehand. +pub(crate) struct LazySize<'a> { + result: OnceCell, + data: Option>, +} + +impl<'a> LazySize<'a> { + pub(crate) fn new( + sizes: &'a Sizes, + axis: Direction, + automatic_size: Size, + get_automatic_minimum_size: fn() -> Au, + stretch_size: Option, + is_table: bool, + ) -> Self { + Self { + result: OnceCell::new(), + data: Some(LazySizeData { + sizes, + axis, + automatic_size, + get_automatic_minimum_size, + stretch_size, + is_table, + }), + } + } + + /// Creates a [`LazySize`] that will resolve to the intrinsic size. + /// Should be equivalent to [`LazySize::new()`] with default parameters, + /// but avoiding the trouble of getting a reference to a [`Sizes::default()`] + /// which lives long enough. + /// + /// TODO: It's not clear what this should do if/when [`LazySize::resolve()`] + /// is changed to accept a [`ContentSizes`] as the intrinsic size. + pub(crate) fn intrinsic() -> Self { + Self { + result: OnceCell::new(), + data: None, + } + } + + /// Resolves the [`LazySize`] into [`Au`], caching the result. + /// The argument is a callback that computes the intrinsic size lazily. + /// + /// TODO: The intrinsic size should probably be a [`ContentSizes`] instead of [`Au`]. + pub(crate) fn resolve(&self, get_content_size: impl FnOnce() -> Au) -> Au { + *self.result.get_or_init(|| { + let Some(ref data) = self.data else { + return get_content_size(); + }; + data.sizes.resolve( + data.axis, + data.automatic_size, + data.get_automatic_minimum_size, + data.stretch_size, + || get_content_size().into(), + data.is_table, + ) + }) + } +} + +impl From for LazySize<'_> { + /// Creates a [`LazySize`] that will resolve to the given [`Au`], + /// ignoring the intrinsic size. + fn from(value: Au) -> Self { + let result = OnceCell::new(); + result.set(value).unwrap(); + LazySize { result, data: None } + } +} diff --git a/components/layout/style_ext.rs b/components/layout/style_ext.rs index 168e023241c..bc327600e69 100644 --- a/components/layout/style_ext.rs +++ b/components/layout/style_ext.rs @@ -36,8 +36,9 @@ use crate::dom_traversal::{Contents, NonReplacedContents}; use crate::fragment_tree::FragmentFlags; use crate::geom::{ AuOrAuto, LengthPercentageOrAuto, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalSides, - PhysicalSize, Size, Sizes, + PhysicalSize, }; +use crate::sizing::{Size, Sizes}; use crate::table::TableLayoutStyle; use crate::{ContainingBlock, IndefiniteContainingBlock}; diff --git a/components/layout/table/layout.rs b/components/layout/table/layout.rs index 9190c6932f7..d53b862b9b1 100644 --- a/components/layout/table/layout.rs +++ b/components/layout/table/layout.rs @@ -37,11 +37,13 @@ use crate::fragment_tree::{ }; use crate::geom::{ LogicalRect, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalPoint, PhysicalRect, - PhysicalSides, PhysicalVec, Size, SizeConstraint, ToLogical, ToLogicalWithContainingBlock, + PhysicalSides, PhysicalVec, ToLogical, ToLogicalWithContainingBlock, }; use crate::layout_box_base::CacheableLayoutResult; use crate::positioned::{PositioningContext, PositioningContextLength, relative_adjustement}; -use crate::sizing::{ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult}; +use crate::sizing::{ + ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult, Size, SizeConstraint, +}; use crate::style_ext::{ BorderStyleColor, Clamp, ComputedValuesExt, LayoutStyle, PaddingBorderMargin, }; diff --git a/components/layout/taffy/layout.rs b/components/layout/taffy/layout.rs index 0c5de6c9c7f..95e4bf8a7b6 100644 --- a/components/layout/taffy/layout.rs +++ b/components/layout/taffy/layout.rs @@ -20,12 +20,12 @@ use crate::formatting_contexts::{Baselines, IndependentFormattingContext}; use crate::fragment_tree::{ BoxFragment, CollapsedBlockMargins, Fragment, FragmentFlags, SpecificLayoutInfo, }; -use crate::geom::{ - LazySize, LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize, SizeConstraint, -}; +use crate::geom::{LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize}; use crate::layout_box_base::CacheableLayoutResult; use crate::positioned::{AbsolutelyPositionedBox, PositioningContext, PositioningContextLength}; -use crate::sizing::{ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult}; +use crate::sizing::{ + ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult, LazySize, SizeConstraint, +}; use crate::style_ext::LayoutStyle; use crate::{ConstraintSpace, ContainingBlock, ContainingBlockSize};