aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/layout_2020/dom_traversal.rs18
-rw-r--r--components/layout_2020/flow/construct.rs6
-rw-r--r--components/layout_2020/flow/float.rs2
-rw-r--r--components/layout_2020/flow/inline.rs42
-rw-r--r--components/layout_2020/flow/mod.rs201
-rw-r--r--components/layout_2020/flow/root.rs24
-rw-r--r--components/layout_2020/formatting_contexts.rs29
-rw-r--r--components/layout_2020/geom.rs35
-rw-r--r--components/layout_2020/lib.rs10
-rw-r--r--components/layout_2020/positioned.rs397
-rw-r--r--components/layout_2020/replaced.rs292
-rw-r--r--components/layout_2020/sizing.rs32
-rw-r--r--components/layout_2020/style_ext.rs6
-rw-r--r--components/style/values/computed/length.rs26
-rw-r--r--components/style/values/generics/length.rs9
-rw-r--r--tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-008.xht.ini2
-rw-r--r--tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-009.xht.ini2
-rw-r--r--tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-010.xht.ini2
-rw-r--r--tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-027.xht.ini2
-rw-r--r--tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-028.xht.ini2
-rw-r--r--tests/wpt/metadata-layout-2020/css/CSS2/box-display/display-change-001.xht.ini2
21 files changed, 701 insertions, 440 deletions
diff --git a/components/layout_2020/dom_traversal.rs b/components/layout_2020/dom_traversal.rs
index 056103d7205..56ee72d67ca 100644
--- a/components/layout_2020/dom_traversal.rs
+++ b/components/layout_2020/dom_traversal.rs
@@ -17,7 +17,6 @@ use std::sync::Arc;
use style::dom::TNode;
use style::properties::ComputedValues;
use style::selector_parser::PseudoElement;
-use style::values::computed::Length;
#[derive(Clone, Copy)]
pub enum WhichPseudoElement {
@@ -299,7 +298,10 @@ impl Drop for BoxSlot<'_> {
pub(crate) trait NodeExt<'dom>: 'dom + Copy + LayoutNode + Send + Sync {
fn is_element(self) -> bool;
fn as_text(self) -> Option<String>;
- fn as_image(self) -> Option<(Option<Arc<NetImage>>, Vec2<Length>)>;
+
+ /// Returns the image if it’s loaded, and its size in image pixels
+ /// adjusted for `image_density`.
+ fn as_image(self) -> Option<(Option<Arc<NetImage>>, Vec2<f64>)>;
fn first_child(self) -> Option<Self>;
fn next_sibling(self) -> Option<Self>;
fn parent_node(self) -> Option<Self>;
@@ -328,7 +330,7 @@ where
}
}
- fn as_image(self) -> Option<(Option<Arc<NetImage>>, Vec2<Length>)> {
+ fn as_image(self) -> Option<(Option<Arc<NetImage>>, Vec2<f64>)> {
let node = self.to_threadsafe();
let (resource, metadata) = node.image_data()?;
let (width, height) = resource
@@ -336,14 +338,14 @@ where
.map(|image| (image.width, image.height))
.or_else(|| metadata.map(|metadata| (metadata.width, metadata.height)))
.unwrap_or((0, 0));
- let (mut width, mut height) = (width as f32, height as f32);
+ let (mut width, mut height) = (width as f64, height as f64);
if let Some(density) = node.image_density().filter(|density| *density != 1.) {
- width = (width as f64 / density) as f32;
- height = (height as f64 / density) as f32;
+ width = width / density;
+ height = height / density;
}
let size = Vec2 {
- x: Length::new(width),
- y: Length::new(height),
+ x: width,
+ y: height,
};
Some((resource, size))
}
diff --git a/components/layout_2020/flow/construct.rs b/components/layout_2020/flow/construct.rs
index c02dcd08fd7..896293831bc 100644
--- a/components/layout_2020/flow/construct.rs
+++ b/components/layout_2020/flow/construct.rs
@@ -393,7 +393,7 @@ where
style.clone(),
display_inside,
contents,
- ContentSizesRequest::inline_if(style.inline_size_is_auto()),
+ ContentSizesRequest::inline_if(!style.inline_size_is_length()),
),
))
};
@@ -590,7 +590,7 @@ where
&style,
ContentSizesRequest::inline_if(
max_assign_in_flow_outer_content_sizes_to.is_some() &&
- style.inline_size_is_auto(),
+ !style.inline_size_is_length(),
),
);
if let Some(to) = max_assign_in_flow_outer_content_sizes_to {
@@ -607,7 +607,7 @@ where
} => {
let content_sizes = ContentSizesRequest::inline_if(
max_assign_in_flow_outer_content_sizes_to.is_some() &&
- style.inline_size_is_auto(),
+ !style.inline_size_is_length(),
);
let contents = IndependentFormattingContext::construct(
context,
diff --git a/components/layout_2020/flow/float.rs b/components/layout_2020/flow/float.rs
index 2acc2095004..df9cbbaf514 100644
--- a/components/layout_2020/flow/float.rs
+++ b/components/layout_2020/flow/float.rs
@@ -33,7 +33,7 @@ impl FloatBox {
display_inside: DisplayInside,
contents: Contents<impl NodeExt<'dom>>,
) -> Self {
- let content_sizes = ContentSizesRequest::inline_if(style.inline_size_is_auto());
+ let content_sizes = ContentSizesRequest::inline_if(!style.inline_size_is_length());
Self {
contents: IndependentFormattingContext::construct(
context,
diff --git a/components/layout_2020/flow/inline.rs b/components/layout_2020/flow/inline.rs
index 0cacfb794a7..c4b760275d9 100644
--- a/components/layout_2020/flow/inline.rs
+++ b/components/layout_2020/flow/inline.rs
@@ -465,9 +465,7 @@ fn layout_atomic<'box_tree>(
let fragment = match atomic.as_replaced() {
Ok(replaced) => {
- // FIXME: implement https://drafts.csswg.org/css2/visudet.html#inline-replaced-width
- // and https://drafts.csswg.org/css2/visudet.html#inline-replaced-height
- let size = Vec2::zero();
+ let size = replaced.used_size_as_if_inline_element(ifc.containing_block, &atomic.style);
let fragments = replaced.make_fragments(&atomic.style, size.clone());
let content_rect = Rect { start_corner, size };
BoxFragment {
@@ -482,10 +480,29 @@ fn layout_atomic<'box_tree>(
},
Err(non_replaced) => {
let box_size = atomic.style.box_size();
- let inline_size = box_size.inline.percentage_relative_to(cbis).auto_is(|| {
- let available_size = cbis - pbm.inline_sum();
- atomic.content_sizes.shrink_to_fit(available_size)
- });
+ let max_box_size = atomic
+ .style
+ .max_box_size()
+ .percentages_relative_to(ifc.containing_block);
+ let min_box_size = atomic
+ .style
+ .min_box_size()
+ .percentages_relative_to(ifc.containing_block)
+ .auto_is(Length::zero);
+
+ // https://drafts.csswg.org/css2/visudet.html#inlineblock-width
+ let tentative_inline_size =
+ box_size.inline.percentage_relative_to(cbis).auto_is(|| {
+ let available_size = cbis - pbm.inline_sum();
+ atomic.content_sizes.shrink_to_fit(available_size)
+ });
+
+ // https://drafts.csswg.org/css2/visudet.html#min-max-widths
+ // In this case “applying the rules above again” with a non-auto inline-size
+ // always results in that size.
+ let inline_size = tentative_inline_size
+ .clamp_between_extremums(min_box_size.inline, max_box_size.inline);
+
let block_size = box_size
.block
.maybe_percentage_relative_to(ifc.containing_block.block_size.non_auto());
@@ -508,7 +525,16 @@ fn layout_atomic<'box_tree>(
dummy_tree_rank,
ifc.absolutely_positioned_fragments,
);
- let block_size = block_size.auto_is(|| independent_layout.content_block_size);
+
+ // https://drafts.csswg.org/css2/visudet.html#block-root-margin
+ let tentative_block_size = block_size.auto_is(|| independent_layout.content_block_size);
+
+ // https://drafts.csswg.org/css2/visudet.html#min-max-heights
+ // In this case “applying the rules above again” with a non-auto block-size
+ // always results in that size.
+ let block_size = tentative_block_size
+ .clamp_between_extremums(min_box_size.block, max_box_size.block);
+
let content_rect = Rect {
start_corner,
size: Vec2 {
diff --git a/components/layout_2020/flow/mod.rs b/components/layout_2020/flow/mod.rs
index 7138ffe3eb0..ed578c825ef 100644
--- a/components/layout_2020/flow/mod.rs
+++ b/components/layout_2020/flow/mod.rs
@@ -21,8 +21,7 @@ use rayon_croissant::ParallelIteratorExt;
use servo_arc::Arc;
use style::computed_values::position::T as Position;
use style::properties::ComputedValues;
-use style::values::computed::{Length, LengthOrAuto, LengthPercentage, LengthPercentageOrAuto};
-use style::values::generics::length::MaxSize;
+use style::values::computed::{Length, LengthOrAuto};
use style::Zero;
mod construct;
@@ -365,10 +364,14 @@ fn layout_in_flow_non_replaced_block_level<'a>(
let pb = &padding + &border;
let pb_inline_sum = pb.inline_sum();
- let box_size = percent_resolved_box_size(style.box_size(), containing_block);
- let max_box_size = percent_resolved_max_box_size(style.max_box_size(), containing_block);
- let min_box_size =
- percent_resolved_box_size(style.min_box_size(), containing_block).auto_is(Length::zero);
+ let box_size = style.box_size().percentages_relative_to(containing_block);
+ let max_box_size = style
+ .max_box_size()
+ .percentages_relative_to(containing_block);
+ let min_box_size = style
+ .min_box_size()
+ .percentages_relative_to(containing_block)
+ .auto_is(Length::zero);
// https://drafts.csswg.org/css2/visudet.html#min-max-widths
let solve_inline_margins = |inline_size| {
@@ -411,7 +414,7 @@ fn layout_in_flow_non_replaced_block_level<'a>(
// https://drafts.csswg.org/css2/visudet.html#min-max-heights
let mut block_size = box_size.block;
if let LengthOrAuto::LengthPercentage(ref mut block_size) = block_size {
- *block_size = clamp_between_extremums(*block_size, min_box_size.block, max_box_size.block);
+ *block_size = block_size.clamp_between_extremums(min_box_size.block, max_box_size.block);
}
let containing_block_for_children = ContainingBlock {
@@ -475,11 +478,9 @@ fn layout_in_flow_non_replaced_block_level<'a>(
.collapsed_through;
let relative_adjustement = relative_adjustement(style, inline_size, block_size);
let block_size = block_size.auto_is(|| {
- clamp_between_extremums(
- flow_layout.content_block_size,
- min_box_size.block,
- max_box_size.block,
- )
+ flow_layout
+ .content_block_size
+ .clamp_between_extremums(min_box_size.block, max_box_size.block)
});
let content_rect = Rect {
start_corner: Vec2 {
@@ -520,140 +521,20 @@ fn layout_in_flow_replaced_block_level<'a>(
style: &Arc<ComputedValues>,
replaced: &ReplacedContent,
) -> BoxFragment {
+ let size = replaced.used_size_as_if_inline_element(containing_block, style);
+
let cbis = containing_block.inline_size;
let padding = style.padding().percentages_relative_to(cbis);
let border = style.border_width();
let computed_margin = style.margin().percentages_relative_to(cbis);
let pb = &padding + &border;
- let mode = style.writing_mode;
- // FIXME(nox): We shouldn't pretend we always have a fully known intrinsic size.
- let intrinsic_size = replaced.intrinsic_size.size_to_flow_relative(mode);
- // FIXME(nox): This can divide by zero.
- let intrinsic_ratio = intrinsic_size.inline.px() / intrinsic_size.block.px();
-
- let box_size = percent_resolved_box_size(style.box_size(), containing_block);
- let min_box_size =
- percent_resolved_box_size(style.min_box_size(), containing_block).auto_is(Length::zero);
- let max_box_size = percent_resolved_max_box_size(style.max_box_size(), containing_block);
-
- let clamp = |inline_size, block_size| {
- (
- clamp_between_extremums(inline_size, min_box_size.inline, max_box_size.inline),
- clamp_between_extremums(block_size, min_box_size.block, max_box_size.block),
- )
- };
- // https://drafts.csswg.org/css2/visudet.html#min-max-widths
- // https://drafts.csswg.org/css2/visudet.html#min-max-heights
- let (inline_size, block_size) = match (box_size.inline, box_size.block) {
- (LengthOrAuto::LengthPercentage(inline), LengthOrAuto::LengthPercentage(block)) => {
- clamp(inline, block)
- },
- (LengthOrAuto::LengthPercentage(inline), LengthOrAuto::Auto) => {
- clamp(inline, inline / intrinsic_ratio)
- },
- (LengthOrAuto::Auto, LengthOrAuto::LengthPercentage(block)) => {
- clamp(block * intrinsic_ratio, block)
- },
- (LengthOrAuto::Auto, LengthOrAuto::Auto) => {
- enum Violation {
- None,
- Below(Length),
- Above(Length),
- }
- let violation = |size, min_size, mut max_size: Option<Length>| {
- if let Some(max) = max_size.as_mut() {
- max.max_assign(min_size);
- }
- if size < min_size {
- return Violation::Below(min_size);
- }
- match max_size {
- Some(max_size) if size > max_size => Violation::Above(max_size),
- _ => Violation::None,
- }
- };
- match (
- violation(
- intrinsic_size.inline,
- min_box_size.inline,
- max_box_size.inline,
- ),
- violation(intrinsic_size.block, min_box_size.block, max_box_size.block),
- ) {
- // Row 1.
- (Violation::None, Violation::None) => (intrinsic_size.inline, intrinsic_size.block),
- // Row 2.
- (Violation::Above(max_inline_size), Violation::None) => {
- let block_size = (max_inline_size / intrinsic_ratio).max(min_box_size.block);
- (max_inline_size, block_size)
- },
- // Row 3.
- (Violation::Below(min_inline_size), Violation::None) => {
- let block_size =
- clamp_below_max(min_inline_size / intrinsic_ratio, max_box_size.block);
- (min_inline_size, block_size)
- },
- // Row 4.
- (Violation::None, Violation::Above(max_block_size)) => {
- let inline_size = (max_block_size * intrinsic_ratio).max(min_box_size.inline);
- (inline_size, max_block_size)
- },
- // Row 5.
- (Violation::None, Violation::Below(min_block_size)) => {
- let inline_size =
- clamp_below_max(min_block_size * intrinsic_ratio, max_box_size.inline);
- (inline_size, min_block_size)
- },
- // Rows 6-7.
- (Violation::Above(max_inline_size), Violation::Above(max_block_size)) => {
- if max_inline_size.px() / intrinsic_size.inline.px() <=
- max_block_size.px() / intrinsic_size.block.px()
- {
- // Row 6.
- let block_size =
- (max_inline_size / intrinsic_ratio).max(min_box_size.block);
- (max_inline_size, block_size)
- } else {
- // Row 7.
- let inline_size =
- (max_block_size * intrinsic_ratio).max(min_box_size.inline);
- (inline_size, max_block_size)
- }
- },
- // Rows 8-9.
- (Violation::Below(min_inline_size), Violation::Below(min_block_size)) => {
- if min_inline_size.px() / intrinsic_size.inline.px() <=
- min_block_size.px() / intrinsic_size.block.px()
- {
- // Row 8.
- let inline_size =
- clamp_below_max(min_block_size * intrinsic_ratio, max_box_size.inline);
- (inline_size, min_block_size)
- } else {
- // Row 9.
- let block_size =
- clamp_below_max(min_inline_size / intrinsic_ratio, max_box_size.block);
- (min_inline_size, block_size)
- }
- },
- // Row 10.
- (Violation::Below(min_inline_size), Violation::Above(max_block_size)) => {
- (min_inline_size, max_block_size)
- },
- // Row 11.
- (Violation::Above(max_inline_size), Violation::Below(min_block_size)) => {
- (max_inline_size, min_block_size)
- },
- }
- },
- };
let (margin_inline_start, margin_inline_end) = solve_inline_margins_for_in_flow_block_level(
containing_block,
pb.inline_sum(),
computed_margin.inline_start,
computed_margin.inline_end,
- inline_size,
+ size.inline,
);
let margin = Sides {
inline_start: margin_inline_start,
@@ -661,15 +542,11 @@ fn layout_in_flow_replaced_block_level<'a>(
block_start: computed_margin.block_start.auto_is(Length::zero),
block_end: computed_margin.block_end.auto_is(Length::zero),
};
- let size = Vec2 {
- block: block_size,
- inline: inline_size,
- };
let fragments = replaced.make_fragments(style, size.clone());
let relative_adjustement = relative_adjustement(
style,
- inline_size,
- LengthOrAuto::LengthPercentage(block_size),
+ size.inline,
+ LengthOrAuto::LengthPercentage(size.block),
);
let content_rect = Rect {
start_corner: Vec2 {
@@ -703,45 +580,3 @@ fn solve_inline_margins_for_in_flow_block_level(
(LengthOrAuto::LengthPercentage(start), _) => (start, inline_margins - start),
}
}
-
-fn clamp_between_extremums(size: Length, min_size: Length, max_size: Option<Length>) -> Length {
- clamp_below_max(size, max_size).max(min_size)
-}
-
-fn clamp_below_max(size: Length, max_size: Option<Length>) -> Length {
- max_size.map_or(size, |max_size| size.min(max_size))
-}
-
-fn percent_resolved_box_size(
- box_size: Vec2<LengthPercentageOrAuto>,
- containing_block: &ContainingBlock,
-) -> Vec2<LengthOrAuto> {
- Vec2 {
- inline: box_size
- .inline
- .percentage_relative_to(containing_block.inline_size),
- block: box_size
- .block
- .maybe_percentage_relative_to(containing_block.block_size.non_auto()),
- }
-}
-
-fn percent_resolved_max_box_size(
- max_box_size: Vec2<MaxSize<LengthPercentage>>,
- containing_block: &ContainingBlock,
-) -> Vec2<Option<Length>> {
- Vec2 {
- inline: match max_box_size.inline {
- MaxSize::LengthPercentage(max_inline_size) => {
- Some(max_inline_size.percentage_relative_to(containing_block.inline_size))
- },
- MaxSize::None => None,
- },
- block: match max_box_size.block {
- MaxSize::LengthPercentage(max_block_size) => {
- max_block_size.maybe_percentage_relative_to(containing_block.block_size.non_auto())
- },
- MaxSize::None => None,
- },
- }
-}
diff --git a/components/layout_2020/flow/root.rs b/components/layout_2020/flow/root.rs
index f1c301771f9..2415d18e0ca 100644
--- a/components/layout_2020/flow/root.rs
+++ b/components/layout_2020/flow/root.rs
@@ -16,12 +16,12 @@ use crate::positioned::AbsolutelyPositionedBox;
use crate::replaced::ReplacedContent;
use crate::sizing::ContentSizesRequest;
use crate::style_ext::{Display, DisplayGeneratingBox, DisplayInside};
-use crate::{ContainingBlock, DefiniteContainingBlock};
+use crate::DefiniteContainingBlock;
use rayon::iter::{IntoParallelRefIterator, ParallelExtend, ParallelIterator};
use script_layout_interface::wrapper_traits::LayoutNode;
use servo_arc::Arc;
use style::properties::ComputedValues;
-use style::values::computed::{Length, LengthOrAuto};
+use style::values::computed::Length;
use style::Zero;
use style_traits::CSSPixel;
@@ -99,31 +99,25 @@ impl BoxTreeRoot {
viewport: geom::Size<CSSPixel>,
) -> FragmentTreeRoot {
let style = ComputedValues::initial_values();
- let initial_containing_block_size = Vec2 {
- inline: Length::new(viewport.width),
- block: Length::new(viewport.height),
- };
-
- let initial_containing_block = ContainingBlock {
- inline_size: initial_containing_block_size.inline,
- block_size: LengthOrAuto::LengthPercentage(initial_containing_block_size.block),
+ let initial_containing_block = DefiniteContainingBlock {
+ size: Vec2 {
+ inline: Length::new(viewport.width),
+ block: Length::new(viewport.height),
+ },
// FIXME: use the document’s mode:
// https://drafts.csswg.org/css-writing-modes/#principal-flow
style,
};
+
let dummy_tree_rank = 0;
let mut absolutely_positioned_fragments = vec![];
let mut independent_layout = self.0.layout(
layout_context,
- &initial_containing_block,
+ &(&initial_containing_block).into(),
dummy_tree_rank,
&mut absolutely_positioned_fragments,
);
- let initial_containing_block = DefiniteContainingBlock {
- size: initial_containing_block_size,
- style,
- };
independent_layout.fragments.par_extend(
absolutely_positioned_fragments
.par_iter()
diff --git a/components/layout_2020/formatting_contexts.rs b/components/layout_2020/formatting_contexts.rs
index d7477182437..eb90d62c926 100644
--- a/components/layout_2020/formatting_contexts.rs
+++ b/components/layout_2020/formatting_contexts.rs
@@ -29,6 +29,8 @@ pub(crate) struct IndependentFormattingContext {
pub(crate) struct IndependentLayout {
pub fragments: Vec<Fragment>,
+
+ /// https://drafts.csswg.org/css2/visudet.html#root-height
pub content_block_size: Length,
}
@@ -57,31 +59,30 @@ impl IndependentFormattingContext {
contents: Contents<impl NodeExt<'dom>>,
content_sizes: ContentSizesRequest,
) -> Self {
- use self::IndependentFormattingContextContents as Contents;
- let (contents, content_sizes) = match contents.try_into() {
+ match contents.try_into() {
Ok(non_replaced) => match display_inside {
DisplayInside::Flow | DisplayInside::FlowRoot => {
- let (bfc, box_content_sizes) = BlockFormattingContext::construct(
+ let (bfc, content_sizes) = BlockFormattingContext::construct(
context,
&style,
non_replaced,
content_sizes,
);
- (Contents::Flow(bfc), box_content_sizes)
+ Self {
+ style,
+ content_sizes,
+ contents: IndependentFormattingContextContents::Flow(bfc),
+ }
},
},
Err(replaced) => {
- // The `content_sizes` field is not used by layout code:
- (
- Contents::Replaced(replaced),
- BoxContentSizes::NoneWereRequested,
- )
+ let content_sizes = content_sizes.compute(|| replaced.inline_content_sizes(&style));
+ Self {
+ style,
+ content_sizes,
+ contents: IndependentFormattingContextContents::Replaced(replaced),
+ }
},
- };
- Self {
- style,
- contents,
- content_sizes,
}
}
diff --git a/components/layout_2020/geom.rs b/components/layout_2020/geom.rs
index 2eafb78051e..562fad5c4f6 100644
--- a/components/layout_2020/geom.rs
+++ b/components/layout_2020/geom.rs
@@ -2,11 +2,13 @@
* 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 crate::ContainingBlock;
use std::fmt;
use std::ops::{Add, AddAssign, Sub};
use style::logical_geometry::{BlockFlowDirection, InlineBaseDirection};
use style::logical_geometry::{PhysicalCorner, WritingMode};
use style::values::computed::{Length, LengthOrAuto, LengthPercentage, LengthPercentageOrAuto};
+use style::values::generics::length::MaxSize;
use style::Zero;
use style_traits::CSSPixel;
@@ -151,6 +153,39 @@ impl flow_relative::Vec2<LengthOrAuto> {
}
}
+impl flow_relative::Vec2<LengthPercentageOrAuto> {
+ pub fn percentages_relative_to(
+ &self,
+ containing_block: &ContainingBlock,
+ ) -> flow_relative::Vec2<LengthOrAuto> {
+ flow_relative::Vec2 {
+ inline: self
+ .inline
+ .percentage_relative_to(containing_block.inline_size),
+ block: self
+ .block
+ .maybe_percentage_relative_to(containing_block.block_size.non_auto()),
+ }
+ }
+}
+
+impl flow_relative::Vec2<MaxSize<LengthPercentage>> {
+ pub fn percentages_relative_to(
+ &self,
+ containing_block: &ContainingBlock,
+ ) -> flow_relative::Vec2<Option<Length>> {
+ flow_relative::Vec2 {
+ inline: self
+ .inline
+ .to_option()
+ .map(|lp| lp.percentage_relative_to(containing_block.inline_size)),
+ block: self.block.to_option().and_then(|olp| {
+ olp.maybe_percentage_relative_to(containing_block.block_size.non_auto())
+ }),
+ }
+ }
+}
+
impl flow_relative::Rect<Length> {
pub fn zero() -> Self {
Self {
diff --git a/components/layout_2020/lib.rs b/components/layout_2020/lib.rs
index f0c56afa6f1..8e923f870d7 100644
--- a/components/layout_2020/lib.rs
+++ b/components/layout_2020/lib.rs
@@ -44,6 +44,16 @@ struct DefiniteContainingBlock<'a> {
style: &'a ComputedValues,
}
+impl<'a> From<&'_ DefiniteContainingBlock<'a>> for ContainingBlock<'a> {
+ fn from(definite: &DefiniteContainingBlock<'a>) -> Self {
+ ContainingBlock {
+ inline_size: definite.size.inline,
+ block_size: LengthOrAuto::LengthPercentage(definite.size.block),
+ style: definite.style,
+ }
+ }
+}
+
/// https://drafts.csswg.org/css2/visuren.html#relative-positioning
fn relative_adjustement(
style: &ComputedValues,
diff --git a/components/layout_2020/positioned.rs b/components/layout_2020/positioned.rs
index 7aed850a5ca..9459f3c3f84 100644
--- a/components/layout_2020/positioned.rs
+++ b/components/layout_2020/positioned.rs
@@ -30,19 +30,24 @@ pub(crate) struct AbsolutelyPositionedFragment<'box_> {
/// static positions when going up the tree.
pub(crate) tree_rank: usize,
- pub(crate) inline_start: AbsoluteBoxOffsets<LengthPercentage>,
- inline_size: LengthPercentageOrAuto,
-
- pub(crate) block_start: AbsoluteBoxOffsets<LengthPercentage>,
- block_size: LengthPercentageOrAuto,
+ box_offsets: Vec2<AbsoluteBoxOffsets>,
}
#[derive(Clone, Copy, Debug)]
-pub(crate) enum AbsoluteBoxOffsets<NonStatic> {
- StaticStart { start: Length },
- Start { start: NonStatic },
- End { end: NonStatic },
- Both { start: NonStatic, end: NonStatic },
+pub(crate) enum AbsoluteBoxOffsets {
+ StaticStart {
+ start: Length,
+ },
+ Start {
+ start: LengthPercentage,
+ },
+ End {
+ end: LengthPercentage,
+ },
+ Both {
+ start: LengthPercentage,
+ end: LengthPercentage,
+ },
}
impl AbsolutelyPositionedBox {
@@ -55,7 +60,7 @@ impl AbsolutelyPositionedBox {
// "Shrink-to-fit" in https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-width
let content_sizes = ContentSizesRequest::inline_if(
// If inline-size is non-auto, that value is used without shrink-to-fit
- style.inline_size_is_auto() &&
+ !style.inline_size_is_length() &&
// If it is, then the only case where shrink-to-fit is *not* used is
// if both offsets are non-auto, leaving inline-size as the only variable
// in the constraint equation.
@@ -77,18 +82,11 @@ impl AbsolutelyPositionedBox {
initial_start_corner: Vec2<Length>,
tree_rank: usize,
) -> AbsolutelyPositionedFragment {
- let style = &self.contents.style;
- let box_offsets = style.box_offsets();
- let box_size = style.box_size();
-
- let inline_size = box_size.inline;
- let block_size = box_size.block;
-
fn absolute_box_offsets(
initial_static_start: Length,
start: LengthPercentageOrAuto,
end: LengthPercentageOrAuto,
- ) -> AbsoluteBoxOffsets<LengthPercentage> {
+ ) -> AbsoluteBoxOffsets {
match (start.non_auto(), end.non_auto()) {
(None, None) => AbsoluteBoxOffsets::StaticStart {
start: initial_static_start,
@@ -99,24 +97,22 @@ impl AbsolutelyPositionedBox {
}
}
- let inline_start = absolute_box_offsets(
- initial_start_corner.inline,
- box_offsets.inline_start,
- box_offsets.inline_end,
- );
- let block_start = absolute_box_offsets(
- initial_start_corner.block,
- box_offsets.block_start,
- box_offsets.block_end,
- );
-
+ let box_offsets = self.contents.style.box_offsets();
AbsolutelyPositionedFragment {
absolutely_positioned_box: self,
tree_rank,
- inline_start,
- inline_size,
- block_start,
- block_size,
+ box_offsets: Vec2 {
+ inline: absolute_box_offsets(
+ initial_start_corner.inline,
+ box_offsets.inline_start,
+ box_offsets.inline_end,
+ ),
+ block: absolute_box_offsets(
+ initial_start_corner.block,
+ box_offsets.block_start,
+ box_offsets.block_end,
+ ),
+ },
}
}
}
@@ -162,168 +158,90 @@ impl<'a> AbsolutelyPositionedFragment<'a> {
let cbis = containing_block.size.inline;
let cbbs = containing_block.size.block;
+ let size;
+ let replaced_used_size;
+ match self.absolutely_positioned_box.contents.as_replaced() {
+ Ok(replaced) => {
+ // https://drafts.csswg.org/css2/visudet.html#abs-replaced-width
+ // https://drafts.csswg.org/css2/visudet.html#abs-replaced-height
+ let u = replaced.used_size_as_if_inline_element(&containing_block.into(), style);
+ size = Vec2 {
+ inline: LengthOrAuto::LengthPercentage(u.inline),
+ block: LengthOrAuto::LengthPercentage(u.block),
+ };
+ replaced_used_size = Some(u);
+ },
+ Err(_non_replaced) => {
+ let box_size = style.box_size();
+ size = Vec2 {
+ inline: box_size.inline.percentage_relative_to(cbis),
+ block: box_size.block.percentage_relative_to(cbbs),
+ };
+ replaced_used_size = None;
+ },
+ }
+
let padding = style.padding().percentages_relative_to(cbis);
let border = style.border_width();
let computed_margin = style.margin().percentages_relative_to(cbis);
let pb = &padding + &border;
- enum Anchor {
- Start(Length),
- End(Length),
- }
-
- fn solve_axis(
- containing_size: Length,
- padding_border_sum: Length,
- computed_margin_start: LengthOrAuto,
- computed_margin_end: LengthOrAuto,
- solve_margins: impl FnOnce(Length) -> (Length, Length),
- box_offsets: AbsoluteBoxOffsets<LengthPercentage>,
- size: LengthPercentageOrAuto,
- ) -> (Anchor, LengthOrAuto, Length, Length) {
- let size = size.percentage_relative_to(containing_size);
- match box_offsets {
- AbsoluteBoxOffsets::StaticStart { start } => (
- Anchor::Start(start),
- size,
- computed_margin_start.auto_is(Length::zero),
- computed_margin_end.auto_is(Length::zero),
- ),
- AbsoluteBoxOffsets::Start { start } => (
- Anchor::Start(start.percentage_relative_to(containing_size)),
- size,
- computed_margin_start.auto_is(Length::zero),
- computed_margin_end.auto_is(Length::zero),
- ),
- AbsoluteBoxOffsets::End { end } => (
- Anchor::End(end.percentage_relative_to(containing_size)),
- size,
- computed_margin_start.auto_is(Length::zero),
- computed_margin_end.auto_is(Length::zero),
- ),
- AbsoluteBoxOffsets::Both { start, end } => {
- let start = start.percentage_relative_to(containing_size);
- let end = end.percentage_relative_to(containing_size);
-
- let mut margin_start = computed_margin_start.auto_is(Length::zero);
- let mut margin_end = computed_margin_end.auto_is(Length::zero);
-
- let size = if let LengthOrAuto::LengthPercentage(size) = size {
- let margins = containing_size - start - end - padding_border_sum - size;
- match (computed_margin_start, computed_margin_end) {
- (LengthOrAuto::Auto, LengthOrAuto::Auto) => {
- let (s, e) = solve_margins(margins);
- margin_start = s;
- margin_end = e;
- },
- (LengthOrAuto::Auto, LengthOrAuto::LengthPercentage(end)) => {
- margin_start = margins - end;
- },
- (LengthOrAuto::LengthPercentage(start), LengthOrAuto::Auto) => {
- margin_end = margins - start;
- },
- (
- LengthOrAuto::LengthPercentage(_),
- LengthOrAuto::LengthPercentage(_),
- ) => {},
- }
- size
- } else {
- // FIXME(nox): What happens if that is negative?
- containing_size -
- start -
- end -
- padding_border_sum -
- margin_start -
- margin_end
- };
- (
- Anchor::Start(start),
- LengthOrAuto::LengthPercentage(size),
- margin_start,
- margin_end,
- )
- },
- }
- }
-
- let (inline_anchor, inline_size, margin_inline_start, margin_inline_end) = solve_axis(
+ let inline_axis = solve_axis(
cbis,
pb.inline_sum(),
computed_margin.inline_start,
computed_margin.inline_end,
- |margins| {
- if margins.px() >= 0. {
- (margins / 2., margins / 2.)
- } else {
- (Length::zero(), margins)
- }
- },
- self.inline_start,
- self.inline_size,
+ /* avoid_negative_margin_start */ true,
+ self.box_offsets.inline,
+ size.inline,
);
- let (block_anchor, block_size, margin_block_start, margin_block_end) = solve_axis(
+ let block_axis = solve_axis(
cbis,
pb.block_sum(),
computed_margin.block_start,
computed_margin.block_end,
- |margins| (margins / 2., margins / 2.),
- self.block_start,
- self.block_size,
+ /* avoid_negative_margin_start */ false,
+ self.box_offsets.block,
+ size.block,
);
let margin = Sides {
- inline_start: margin_inline_start,
- inline_end: margin_inline_end,
- block_start: margin_block_start,
- block_end: margin_block_end,
+ inline_start: inline_axis.margin_start,
+ inline_end: inline_axis.margin_end,
+ block_start: block_axis.margin_start,
+ block_end: block_axis.margin_end,
};
- let inline_size = inline_size.auto_is(|| {
- let available_size = match inline_anchor {
- Anchor::Start(start) => cbis - start - pb.inline_sum() - margin.inline_sum(),
- Anchor::End(end) => cbis - end - pb.inline_sum() - margin.inline_sum(),
- };
-
- if self
- .absolutely_positioned_box
- .contents
- .as_replaced()
- .is_ok()
- {
- // FIXME: implement https://drafts.csswg.org/css2/visudet.html#abs-replaced-width
- available_size
- } else {
- self.absolutely_positioned_box
- .contents
- .content_sizes
- .shrink_to_fit(available_size)
- }
- });
-
let mut absolutely_positioned_fragments = Vec::new();
- let mut independent_layout = match self.absolutely_positioned_box.contents.as_replaced() {
+ let (size, mut fragments) = match self.absolutely_positioned_box.contents.as_replaced() {
Ok(replaced) => {
- // FIXME: implement https://drafts.csswg.org/css2/visudet.html#abs-replaced-width
- // and https://drafts.csswg.org/css2/visudet.html#abs-replaced-height
- let block_size = block_size.auto_is(Length::zero);
- let fragments = replaced.make_fragments(
- &self.absolutely_positioned_box.contents.style,
- Vec2 {
- inline: inline_size,
- block: block_size,
- },
- );
- crate::formatting_contexts::IndependentLayout {
- fragments,
- content_block_size: block_size,
- }
+ // https://drafts.csswg.org/css2/visudet.html#abs-replaced-width
+ // https://drafts.csswg.org/css2/visudet.html#abs-replaced-height
+ let style = &self.absolutely_positioned_box.contents.style;
+ let size = replaced_used_size.unwrap();
+ let fragments = replaced.make_fragments(style, size.clone());
+ (size, fragments)
},
Err(non_replaced) => {
+ // https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-width
+ // https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-height
+ let inline_size = inline_axis.size.auto_is(|| {
+ let available_size = match inline_axis.anchor {
+ Anchor::Start(start) => {
+ cbis - start - pb.inline_sum() - margin.inline_sum()
+ },
+ Anchor::End(end) => cbis - end - pb.inline_sum() - margin.inline_sum(),
+ };
+ self.absolutely_positioned_box
+ .contents
+ .content_sizes
+ .shrink_to_fit(available_size)
+ });
+
let containing_block_for_children = ContainingBlock {
inline_size,
- block_size,
+ block_size: block_axis.size,
style,
};
// https://drafts.csswg.org/css-writing-modes/#orthogonal-flows
@@ -333,24 +251,30 @@ impl<'a> AbsolutelyPositionedFragment<'a> {
"Mixed writing modes are not supported yet"
);
let dummy_tree_rank = 0;
- non_replaced.layout(
+ let independent_layout = non_replaced.layout(
layout_context,
&containing_block_for_children,
dummy_tree_rank,
&mut absolutely_positioned_fragments,
- )
+ );
+
+ let size = Vec2 {
+ inline: inline_size,
+ block: block_axis
+ .size
+ .auto_is(|| independent_layout.content_block_size),
+ };
+ (size, independent_layout.fragments)
},
};
- let inline_start = match inline_anchor {
+ let inline_start = match inline_axis.anchor {
Anchor::Start(start) => start + pb.inline_start + margin.inline_start,
- Anchor::End(end) => cbbs - end - pb.inline_end - margin.inline_end - inline_size,
+ Anchor::End(end) => cbbs - end - pb.inline_end - margin.inline_end - size.inline,
};
-
- let block_size = block_size.auto_is(|| independent_layout.content_block_size);
- let block_start = match block_anchor {
+ let block_start = match block_axis.anchor {
Anchor::Start(start) => start + pb.block_start + margin.block_start,
- Anchor::End(end) => cbbs - end - pb.block_end - margin.block_end - block_size,
+ Anchor::End(end) => cbbs - end - pb.block_end - margin.block_end - size.block,
};
let content_rect = Rect {
@@ -358,16 +282,13 @@ impl<'a> AbsolutelyPositionedFragment<'a> {
inline: inline_start,
block: block_start,
},
- size: Vec2 {
- inline: inline_size,
- block: block_size,
- },
+ size,
};
AbsolutelyPositionedFragment::in_positioned_containing_block(
layout_context,
&absolutely_positioned_fragments,
- &mut independent_layout.fragments,
+ &mut fragments,
&content_rect.size,
&padding,
style,
@@ -375,7 +296,7 @@ impl<'a> AbsolutelyPositionedFragment<'a> {
Fragment::Box(BoxFragment {
style: style.clone(),
- children: independent_layout.fragments,
+ children: fragments,
content_rect,
padding,
border,
@@ -385,6 +306,110 @@ impl<'a> AbsolutelyPositionedFragment<'a> {
}
}
+enum Anchor {
+ Start(Length),
+ End(Length),
+}
+
+struct AxisResult {
+ anchor: Anchor,
+ size: LengthOrAuto,
+ margin_start: Length,
+ margin_end: Length,
+}
+
+/// This unifies some of the parts in common in:
+///
+/// * https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-width
+/// * https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-height
+///
+/// … and:
+///
+/// * https://drafts.csswg.org/css2/visudet.html#abs-replaced-width
+/// * https://drafts.csswg.org/css2/visudet.html#abs-replaced-height
+///
+/// In the replaced case, `size` is never `Auto`.
+fn solve_axis(
+ containing_size: Length,
+ padding_border_sum: Length,
+ computed_margin_start: LengthOrAuto,
+ computed_margin_end: LengthOrAuto,
+ avoid_negative_margin_start: bool,
+ box_offsets: AbsoluteBoxOffsets,
+ size: LengthOrAuto,
+) -> AxisResult {
+ match box_offsets {
+ AbsoluteBoxOffsets::StaticStart { start } => AxisResult {
+ anchor: Anchor::Start(start),
+ size,
+ margin_start: computed_margin_start.auto_is(Length::zero),
+ margin_end: computed_margin_end.auto_is(Length::zero),
+ },
+ AbsoluteBoxOffsets::Start { start } => AxisResult {
+ anchor: Anchor::Start(start.percentage_relative_to(containing_size)),
+ size,
+ margin_start: computed_margin_start.auto_is(Length::zero),
+ margin_end: computed_margin_end.auto_is(Length::zero),
+ },
+ AbsoluteBoxOffsets::End { end } => AxisResult {
+ anchor: Anchor::End(end.percentage_relative_to(containing_size)),
+ size,
+ margin_start: computed_margin_start.auto_is(Length::zero),
+ margin_end: computed_margin_end.auto_is(Length::zero),
+ },
+ AbsoluteBoxOffsets::Both { start, end } => {
+ let start = start.percentage_relative_to(containing_size);
+ let end = end.percentage_relative_to(containing_size);
+
+ let margin_start;
+ let margin_end;
+ let used_size;
+ if let LengthOrAuto::LengthPercentage(s) = size {
+ used_size = s;
+ let margins = containing_size - start - end - padding_border_sum - s;
+ match (computed_margin_start, computed_margin_end) {
+ (LengthOrAuto::Auto, LengthOrAuto::Auto) => {
+ if avoid_negative_margin_start && margins < Length::zero() {
+ margin_start = Length::zero();
+ margin_end = margins;
+ } else {
+ margin_start = margins / 2.;
+ margin_end = margins / 2.;
+ }
+ },
+ (LengthOrAuto::Auto, LengthOrAuto::LengthPercentage(end)) => {
+ margin_start = margins - end;
+ margin_end = end;
+ },
+ (LengthOrAuto::LengthPercentage(start), LengthOrAuto::Auto) => {
+ margin_start = start;
+ margin_end = margins - start;
+ },
+ (
+ LengthOrAuto::LengthPercentage(start),
+ LengthOrAuto::LengthPercentage(end),
+ ) => {
+ margin_start = start;
+ margin_end = end;
+ },
+ }
+ } else {
+ margin_start = computed_margin_start.auto_is(Length::zero);
+ margin_end = computed_margin_end.auto_is(Length::zero);
+ // FIXME(nox): What happens if that is negative?
+ used_size =
+ containing_size - start - end - padding_border_sum - margin_start - margin_end
+ };
+ AxisResult {
+ anchor: Anchor::Start(start),
+ size: LengthOrAuto::LengthPercentage(used_size),
+ margin_start,
+ margin_end,
+ }
+ },
+ }
+}
+
pub(crate) fn adjust_static_positions(
absolutely_positioned_fragments: &mut [AbsolutelyPositionedFragment],
child_fragments: &mut [Fragment],
@@ -399,11 +424,11 @@ pub(crate) fn adjust_static_positions(
abspos_fragment.tree_rank = tree_rank_in_parent;
- if let AbsoluteBoxOffsets::StaticStart { start } = &mut abspos_fragment.inline_start {
+ if let AbsoluteBoxOffsets::StaticStart { start } = &mut abspos_fragment.box_offsets.inline {
*start += child_fragment_rect.start_corner.inline;
}
- if let AbsoluteBoxOffsets::StaticStart { start } = &mut abspos_fragment.block_start {
+ if let AbsoluteBoxOffsets::StaticStart { start } = &mut abspos_fragment.box_offsets.block {
*start += child_fragment_rect.start_corner.block;
}
}
diff --git a/components/layout_2020/replaced.rs b/components/layout_2020/replaced.rs
index 8a28cbf763c..72071dbf684 100644
--- a/components/layout_2020/replaced.rs
+++ b/components/layout_2020/replaced.rs
@@ -4,17 +4,37 @@
use crate::dom_traversal::NodeExt;
use crate::fragments::{Fragment, ImageFragment};
-use crate::geom::{flow_relative, physical};
+use crate::geom::flow_relative::{Rect, Vec2};
+use crate::geom::physical;
+use crate::sizing::ContentSizes;
+use crate::style_ext::ComputedValuesExt;
+use crate::ContainingBlock;
use net_traits::image::base::Image;
use servo_arc::Arc as ServoArc;
use std::sync::Arc;
use style::properties::ComputedValues;
-use style::values::computed::Length;
+use style::values::computed::{Length, LengthOrAuto};
+use style::values::CSSFloat;
+use style::Zero;
#[derive(Debug)]
pub(crate) struct ReplacedContent {
pub kind: ReplacedContentKind,
- pub intrinsic_size: physical::Vec2<Length>,
+
+ /// * Raster images always have an instrinsic width and height, with 1 image pixel = 1px.
+ /// The intrinsic ratio should be based on dividing those.
+ /// See https://github.com/w3c/csswg-drafts/issues/4572 for the case where either is zero.
+ /// PNG specifically disallows this but I (SimonSapin) am not sure about other formats.
+ ///
+ /// * Form controls have both intrinsic width and height **but no intrinsic ratio**.
+ /// See https://github.com/w3c/csswg-drafts/issues/1044 and
+ /// https://drafts.csswg.org/css-images/#intrinsic-dimensions “In general, […]”
+ ///
+ /// * For SVG, see https://svgwg.org/svg2-draft/coords.html#SizingSVGInCSS
+ /// and again https://github.com/w3c/csswg-drafts/issues/4572.
+ intrinsic_width: Option<Length>,
+ intrinsic_height: Option<Length>,
+ intrinsic_ratio: Option<CSSFloat>,
}
#[derive(Debug)]
@@ -24,19 +44,65 @@ pub(crate) enum ReplacedContentKind {
impl ReplacedContent {
pub fn for_element<'dom>(element: impl NodeExt<'dom>) -> Option<Self> {
- if let Some((image, intrinsic_size)) = element.as_image() {
+ if let Some((image, intrinsic_size_in_dots)) = element.as_image() {
+ // FIXME: should 'image-resolution' (when implemented) be used *instead* of
+ // `script::dom::htmlimageelement::ImageRequest::current_pixel_density`?
+
+ // https://drafts.csswg.org/css-images-4/#the-image-resolution
+ let dppx = 1.0;
+
+ let width = (intrinsic_size_in_dots.x as CSSFloat) / dppx;
+ let height = (intrinsic_size_in_dots.y as CSSFloat) / dppx;
return Some(Self {
kind: ReplacedContentKind::Image(image),
- intrinsic_size,
+ intrinsic_width: Some(Length::new(width)),
+ intrinsic_height: Some(Length::new(height)),
+ // FIXME https://github.com/w3c/csswg-drafts/issues/4572
+ intrinsic_ratio: Some(width / height),
});
}
None
}
+ fn flow_relative_intrinsic_size(&self, style: &ComputedValues) -> Vec2<Option<Length>> {
+ let intrinsic_size = physical::Vec2 {
+ x: self.intrinsic_width,
+ y: self.intrinsic_height,
+ };
+ intrinsic_size.size_to_flow_relative(style.writing_mode)
+ }
+
+ fn inline_size_over_block_size_intrinsic_ratio(
+ &self,
+ style: &ComputedValues,
+ ) -> Option<CSSFloat> {
+ self.intrinsic_ratio.map(|width_over_height| {
+ if style.writing_mode.is_vertical() {
+ 1. / width_over_height
+ } else {
+ width_over_height
+ }
+ })
+ }
+
+ pub fn inline_content_sizes(&self, style: &ComputedValues) -> ContentSizes {
+ // FIXME: min/max-content of replaced elements is not defined in
+ // https://dbaron.org/css/intrinsic/
+ // This seems sensible?
+ let inline = self
+ .flow_relative_intrinsic_size(style)
+ .inline
+ .unwrap_or(Length::zero());
+ ContentSizes {
+ min_content: inline,
+ max_content: inline,
+ }
+ }
+
pub fn make_fragments<'a>(
&'a self,
style: &ServoArc<ComputedValues>,
- size: flow_relative::Vec2<Length>,
+ size: Vec2<Length>,
) -> Vec<Fragment> {
match &self.kind {
ReplacedContentKind::Image(image) => image
@@ -45,8 +111,8 @@ impl ReplacedContent {
.map(|image_key| {
Fragment::Image(ImageFragment {
style: style.clone(),
- rect: flow_relative::Rect {
- start_corner: flow_relative::Vec2::zero(),
+ rect: Rect {
+ start_corner: Vec2::zero(),
size,
},
image_key,
@@ -56,4 +122,214 @@ impl ReplacedContent {
.collect(),
}
}
+
+ /// https://drafts.csswg.org/css2/visudet.html#inline-replaced-width
+ /// https://drafts.csswg.org/css2/visudet.html#inline-replaced-height
+ ///
+ /// Also used in other cases, for example
+ /// https://drafts.csswg.org/css2/visudet.html#block-replaced-width
+ pub fn used_size_as_if_inline_element(
+ &self,
+ containing_block: &ContainingBlock,
+ style: &ComputedValues,
+ ) -> Vec2<Length> {
+ let mode = style.writing_mode;
+ let intrinsic_size = self.flow_relative_intrinsic_size(style);
+ let intrinsic_ratio = self.inline_size_over_block_size_intrinsic_ratio(style);
+
+ let box_size = style.box_size().percentages_relative_to(containing_block);
+ let min_box_size = style
+ .min_box_size()
+ .percentages_relative_to(containing_block)
+ .auto_is(Length::zero);
+ let max_box_size = style
+ .max_box_size()
+ .percentages_relative_to(containing_block);
+
+ let default_object_size = || {
+ // FIXME:
+ // “If 300px is too wide to fit the device, UAs should use the width of
+ // the largest rectangle that has a 2:1 ratio and fits the device instead.”
+ // “height of the largest rectangle that has a 2:1 ratio, has a height not greater
+ // than 150px, and has a width not greater than the device width.”
+ physical::Vec2 {
+ x: Length::new(300.),
+ y: Length::new(150.),
+ }
+ .size_to_flow_relative(mode)
+ };
+ let clamp = |inline_size: Length, block_size: Length| Vec2 {
+ inline: inline_size.clamp_between_extremums(min_box_size.inline, max_box_size.inline),
+ block: block_size.clamp_between_extremums(min_box_size.block, max_box_size.block),
+ };
+ // https://drafts.csswg.org/css2/visudet.html#min-max-widths
+ // https://drafts.csswg.org/css2/visudet.html#min-max-heights
+ match (box_size.inline, box_size.block) {
+ (LengthOrAuto::LengthPercentage(inline), LengthOrAuto::LengthPercentage(block)) => {
+ clamp(inline, block)
+ },
+ (LengthOrAuto::LengthPercentage(inline), LengthOrAuto::Auto) => {
+ let block = if let Some(i_over_b) = intrinsic_ratio {
+ inline / i_over_b
+ } else if let Some(block) = intrinsic_size.block {
+ block
+ } else {
+ default_object_size().block
+ };
+ clamp(inline, block)
+ },
+ (LengthOrAuto::Auto, LengthOrAuto::LengthPercentage(block)) => {
+ let inline = if let Some(i_over_b) = intrinsic_ratio {
+ block * i_over_b
+ } else if let Some(inline) = intrinsic_size.inline {
+ inline
+ } else {
+ default_object_size().inline
+ };
+ clamp(inline, block)
+ },
+ (LengthOrAuto::Auto, LengthOrAuto::Auto) => {
+ let inline_size =
+ match (intrinsic_size.inline, intrinsic_size.block, intrinsic_ratio) {
+ (Some(inline), _, _) => inline,
+ (None, Some(block), Some(i_over_b)) => {
+ // “used height” in CSS 2 is always gonna be the intrinsic one,
+ // since it is available.
+ block * i_over_b
+ },
+ // FIXME
+ //
+ // “If 'height' and 'width' both have computed values of 'auto'
+ // and the element has an intrinsic ratio but no intrinsic height or width,
+ // […]”
+ //
+ // In this `match` expression this would be an additional arm here:
+ //
+ // ```
+ // (Vec2 { inline: None, block: None }, Some(_)) => {…}
+ // ```
+ //
+ // “[…] then the used value of 'width' is undefined in CSS 2.
+ // However, it is suggested that, if the containing block's width
+ // does not itself depend on the replaced element's width,
+ // then the used value of 'width' is calculated from the constraint
+ // equation used for block-level, non-replaced elements in normal flow.”
+ _ => default_object_size().inline,
+ };
+ let block_size = if let Some(block) = intrinsic_size.block {
+ block
+ } else if let Some(i_over_b) = intrinsic_ratio {
+ // “used width” in CSS 2 is what we just computed above
+ inline_size / i_over_b
+ } else {
+ default_object_size().block
+ };
+
+ let i_over_b = if let Some(i_over_b) = intrinsic_ratio {
+ i_over_b
+ } else {
+ return clamp(inline_size, block_size);
+ };
+
+ // https://drafts.csswg.org/css2/visudet.html#min-max-widths
+ // “However, for replaced elements with an intrinsic ratio and both
+ // 'width' and 'height' specified as 'auto', the algorithm is as follows”
+ enum Violation {
+ None,
+ Below(Length),
+ Above(Length),
+ }
+ let violation = |size, min_size, mut max_size: Option<Length>| {
+ if let Some(max) = max_size.as_mut() {
+ max.max_assign(min_size);
+ }
+ if size < min_size {
+ return Violation::Below(min_size);
+ }
+ match max_size {
+ Some(max_size) if size > max_size => Violation::Above(max_size),
+ _ => Violation::None,
+ }
+ };
+ match (
+ violation(inline_size, min_box_size.inline, max_box_size.inline),
+ violation(block_size, min_box_size.block, max_box_size.block),
+ ) {
+ // Row 1.
+ (Violation::None, Violation::None) => Vec2 {
+ inline: inline_size,
+ block: block_size,
+ },
+ // Row 2.
+ (Violation::Above(max_inline_size), Violation::None) => Vec2 {
+ inline: max_inline_size,
+ block: (max_inline_size / i_over_b).max(min_box_size.block),
+ },
+ // Row 3.
+ (Violation::Below(min_inline_size), Violation::None) => Vec2 {
+ inline: min_inline_size,
+ block: (min_inline_size / i_over_b).clamp_below_max(max_box_size.block),
+ },
+ // Row 4.
+ (Violation::None, Violation::Above(max_block_size)) => Vec2 {
+ inline: (max_block_size * i_over_b).max(min_box_size.inline),
+ block: max_block_size,
+ },
+ // Row 5.
+ (Violation::None, Violation::Below(min_block_size)) => Vec2 {
+ inline: (min_block_size * i_over_b).clamp_below_max(max_box_size.inline),
+ block: min_block_size,
+ },
+ // Rows 6-7.
+ (Violation::Above(max_inline_size), Violation::Above(max_block_size)) => {
+ if max_inline_size.px() / inline_size.px() <=
+ max_block_size.px() / block_size.px()
+ {
+ // Row 6.
+ Vec2 {
+ inline: max_inline_size,
+ block: (max_inline_size / i_over_b).max(min_box_size.block),
+ }
+ } else {
+ // Row 7.
+ Vec2 {
+ inline: (max_block_size * i_over_b).max(min_box_size.inline),
+ block: max_block_size,
+ }
+ }
+ },
+ // Rows 8-9.
+ (Violation::Below(min_inline_size), Violation::Below(min_block_size)) => {
+ if min_inline_size.px() / inline_size.px() <=
+ min_block_size.px() / block_size.px()
+ {
+ // Row 8.
+ Vec2 {
+ inline: (min_block_size * i_over_b)
+ .clamp_below_max(max_box_size.inline),
+ block: min_block_size,
+ }
+ } else {
+ // Row 9.
+ Vec2 {
+ inline: min_inline_size,
+ block: (min_inline_size / i_over_b)
+ .clamp_below_max(max_box_size.block),
+ }
+ }
+ },
+ // Row 10.
+ (Violation::Below(min_inline_size), Violation::Above(max_block_size)) => Vec2 {
+ inline: min_inline_size,
+ block: max_block_size,
+ },
+ // Row 11.
+ (Violation::Above(max_inline_size), Violation::Below(min_block_size)) => Vec2 {
+ inline: max_inline_size,
+ block: min_block_size,
+ },
+ }
+ },
+ }
+ }
}
diff --git a/components/layout_2020/sizing.rs b/components/layout_2020/sizing.rs
index 75c13b1dcb5..e3c74d3733f 100644
--- a/components/layout_2020/sizing.rs
+++ b/components/layout_2020/sizing.rs
@@ -107,17 +107,37 @@ impl BoxContentSizes {
&self,
style: &ComputedValues,
) -> (ContentSizes, Percentage) {
- // FIXME: account for 'min-width', 'max-width', 'box-sizing'
-
+ // FIXME: account for 'box-sizing'
let inline_size = style.box_size().inline;
+ let min_inline_size = style
+ .min_box_size()
+ .inline
+ .percentage_relative_to(Length::zero())
+ .auto_is(Length::zero);
+ let max_inline_size = style
+ .max_box_size()
+ .inline
+ .to_option()
+ .and_then(|lp| lp.as_length());
+ let clamp = |l: Length| l.clamp_between_extremums(min_inline_size, max_inline_size);
+
// Percentages for 'width' are treated as 'auto'
let inline_size = inline_size.map(|lp| lp.as_length());
// The (inner) min/max-content are only used for 'auto'
let mut outer = match inline_size.non_auto().flatten() {
- None => self.expect_inline().clone(),
- Some(length) => ContentSizes {
- min_content: length,
- max_content: length,
+ None => {
+ let inner = self.expect_inline().clone();
+ ContentSizes {
+ min_content: clamp(inner.min_content),
+ max_content: clamp(inner.max_content),
+ }
+ },
+ Some(length) => {
+ let length = clamp(length);
+ ContentSizes {
+ min_content: length,
+ max_content: length,
+ }
},
};
diff --git a/components/layout_2020/style_ext.rs b/components/layout_2020/style_ext.rs
index 59c87fd633c..67d8bc78d7e 100644
--- a/components/layout_2020/style_ext.rs
+++ b/components/layout_2020/style_ext.rs
@@ -40,7 +40,7 @@ pub(crate) enum DisplayInside {
}
pub(crate) trait ComputedValuesExt {
- fn inline_size_is_auto(&self) -> bool;
+ fn inline_size_is_length(&self) -> bool;
fn inline_box_offsets_are_both_non_auto(&self) -> bool;
fn box_offsets(&self) -> flow_relative::Sides<LengthPercentageOrAuto>;
fn box_size(&self) -> flow_relative::Vec2<LengthPercentageOrAuto>;
@@ -52,14 +52,14 @@ pub(crate) trait ComputedValuesExt {
}
impl ComputedValuesExt for ComputedValues {
- fn inline_size_is_auto(&self) -> bool {
+ fn inline_size_is_length(&self) -> bool {
let position = self.get_position();
let size = if self.writing_mode.is_horizontal() {
position.width
} else {
position.height
};
- size == Size::Auto
+ matches!(size, Size::LengthPercentage(lp) if lp.0.as_length().is_some())
}
fn inline_box_offsets_are_both_non_auto(&self) -> bool {
diff --git a/components/style/values/computed/length.rs b/components/style/values/computed/length.rs
index 06631a354df..2423d447a6a 100644
--- a/components/style/values/computed/length.rs
+++ b/components/style/values/computed/length.rs
@@ -657,14 +657,14 @@ impl CSSPixelLength {
/// Return the containing pixel value.
#[inline]
- pub fn px(&self) -> CSSFloat {
+ pub fn px(self) -> CSSFloat {
self.0
}
/// Return the length with app_unit i32 type.
#[inline]
- pub fn to_i32_au(&self) -> i32 {
- Au::from(*self).0
+ pub fn to_i32_au(self) -> i32 {
+ Au::from(self).0
}
/// Return the absolute value of this length.
@@ -692,9 +692,29 @@ impl CSSPixelLength {
}
/// Sets `self` to the maximum between `self` and `other`.
+ #[inline]
pub fn max_assign(&mut self, other: Self) {
*self = self.max(other);
}
+
+ /// Clamp the value to a lower bound and an optional upper bound.
+ ///
+ /// Can be used for example with `min-width` and `max-width`.
+ #[inline]
+ pub fn clamp_between_extremums(self, min_size: Self, max_size: Option<Self>) -> Self {
+ self.clamp_below_max(max_size).max(min_size)
+ }
+
+ /// Clamp the value to an optional upper bound.
+ ///
+ /// Can be used for example with `max-width`.
+ #[inline]
+ pub fn clamp_below_max(self, max_size: Option<Self>) -> Self {
+ match max_size {
+ None => self,
+ Some(max_size) => self.min(max_size),
+ }
+ }
}
impl Zero for CSSPixelLength {
diff --git a/components/style/values/generics/length.rs b/components/style/values/generics/length.rs
index 4183f40a942..b2f34058283 100644
--- a/components/style/values/generics/length.rs
+++ b/components/style/values/generics/length.rs
@@ -207,6 +207,15 @@ impl<LengthPercentage> MaxSize<LengthPercentage> {
pub fn none() -> Self {
MaxSize::None
}
+
+ /// Convert
+ #[cfg(not(feature = "gecko"))]
+ pub fn to_option(self) -> Option<LengthPercentage> {
+ match self {
+ Self::LengthPercentage(lp) => Some(lp),
+ Self::None => None,
+ }
+ }
}
/// A generic `<length>` | `<number>` value for the `-moz-tab-size` property.
diff --git a/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-008.xht.ini b/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-008.xht.ini
new file mode 100644
index 00000000000..dd630be0f54
--- /dev/null
+++ b/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-008.xht.ini
@@ -0,0 +1,2 @@
+[containing-block-008.xht]
+ expected: FAIL
diff --git a/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-009.xht.ini b/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-009.xht.ini
new file mode 100644
index 00000000000..4d8ca58286c
--- /dev/null
+++ b/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-009.xht.ini
@@ -0,0 +1,2 @@
+[containing-block-009.xht]
+ expected: FAIL
diff --git a/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-010.xht.ini b/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-010.xht.ini
new file mode 100644
index 00000000000..af40eef0a5d
--- /dev/null
+++ b/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-010.xht.ini
@@ -0,0 +1,2 @@
+[containing-block-010.xht]
+ expected: FAIL
diff --git a/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-027.xht.ini b/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-027.xht.ini
new file mode 100644
index 00000000000..3d51008fd8a
--- /dev/null
+++ b/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-027.xht.ini
@@ -0,0 +1,2 @@
+[containing-block-027.xht]
+ expected: FAIL
diff --git a/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-028.xht.ini b/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-028.xht.ini
new file mode 100644
index 00000000000..51d02b1875c
--- /dev/null
+++ b/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-028.xht.ini
@@ -0,0 +1,2 @@
+[containing-block-028.xht]
+ expected: FAIL
diff --git a/tests/wpt/metadata-layout-2020/css/CSS2/box-display/display-change-001.xht.ini b/tests/wpt/metadata-layout-2020/css/CSS2/box-display/display-change-001.xht.ini
deleted file mode 100644
index 6b39c7c47cb..00000000000
--- a/tests/wpt/metadata-layout-2020/css/CSS2/box-display/display-change-001.xht.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[display-change-001.xht]
- expected: FAIL