Repository: servo/servo (language: python)

Issue:
Support intrinsic sizing keywords: `min-content`, `max-content`, `fit-content`, and `fit-content()`
### Describe the new feature

Servo should support the intrinsic sizing keywords `min-content`, `max-content`, `fit-content`, and `fit-content()` for `width`/`height`, `min-` and `max-` width/height, and `flex-basis`. And relatedly the `content` value of `flex-basis`.

Spec: https://drafts.csswg.org/css-sizing-3/#sizing-values

Level 4 of the css-sizing spec also defines a `stretch` keyword, but that is for extrisic sizing rather than intrinsic sizing. And it is not yet widely supported (e.g. neither Firefox nor Stylo support it although the support the similar MozAvailable) so it should probably be tracked separately.

### Motivation

These keywords are used in a number of Flexbox and particularly CSS Grid WPT tests (even when it's not what the test is supposed to be testing). So if we want to know whether we're passing those tests we'll need to support these keywords. Notably some of these tests require that block layout supports these keywords even though they are ostensibly testing flexbox/grid.

They also represent key sizing concepts within the CSS layout model which we need to be able to compute anyway, so they are a good litmus test that our layout architecture is able to correctly implement that model.

...and of course real world websites use them.



Discussion / hints:

Fix patch:
diff --git a/components/layout_2020/flexbox/layout.rs b/components/layout_2020/flexbox/layout.rs
index bb580c23f8d6b..1ce3b8c319396 100644
--- a/components/layout_2020/flexbox/layout.rs
+++ b/components/layout_2020/flexbox/layout.rs
@@ -15,7 +15,7 @@ use style::properties::longhands::box_sizing::computed_value::T as BoxSizing;
 use style::properties::longhands::flex_direction::computed_value::T as FlexDirection;
 use style::properties::longhands::flex_wrap::computed_value::T as FlexWrap;
 use style::properties::ComputedValues;
-use style::values::computed::length::Size;
+use style::values::computed::length::Size as StyleSize;
 use style::values::generics::flex::GenericFlexBasis as FlexBasis;
 use style::values::generics::length::{GenericLengthPercentageOrAuto, LengthPercentageOrNormal};
 use style::values::specified::align::AlignFlags;
@@ -29,7 +29,7 @@ use crate::cell::ArcRefCell;
 use crate::context::LayoutContext;
 use crate::formatting_contexts::{Baselines, IndependentFormattingContext, IndependentLayout};
 use crate::fragment_tree::{BoxFragment, CollapsedBlockMargins, Fragment, FragmentFlags};
-use crate::geom::{AuOrAuto, LogicalRect, LogicalSides, LogicalVec2};
+use crate::geom::{AuOrAuto, LogicalRect, LogicalSides, LogicalVec2, Size};
 use crate::positioned::{
     relative_adjustement, AbsolutelyPositionedBox, PositioningContext, PositioningContextLength,
 };
@@ -1870,11 +1870,16 @@ impl FlexItem<'_> {
                         containing_block,
                         &replaced.style,
                         LogicalVec2 {
-                            inline: AuOrAuto::LengthPercentage(inline_size),
-                            block: block_size,
+                            inline: Size::Numeric(inline_size),
+                            block: block_size.non_auto().map_or(Size::Initial, Size::Numeric),
                         },
-                        flex_axis.vec2_to_flow_relative(self.content_min_size),
-                        flex_axis.vec2_to_flow_relative(self.content_max_size),
+                        flex_axis
+                            .vec2_to_flow_relative(self.content_min_size)
+                            .map(|size| Size::Numeric(*size)),
+                        flex_axis
+                            .vec2_to_flow_relative(self.content_max_size)
+                            .map(|size| size.map_or(Size::Initial, Size::Numeric)),
+                        flex_axis.vec2_to_flow_relative(self.pbm_auto_is_zero),
                     );
                 let hypothetical_cross_size = flex_axis.vec2_to_flex_relative(size).cross;
 
@@ -2499,7 +2504,7 @@ impl FlexItemBox {
 
         let used_flex_basis = match &style.get_position().flex_basis {
             FlexBasis::Content => FlexBasis::Content,
-            FlexBasis::Size(Size::LengthPercentage(length_percentage)) => {
+            FlexBasis::Size(StyleSize::LengthPercentage(length_percentage)) => {
                 let apply_box_sizing = |length: Au| {
                     match style.get_position().box_sizing {
                         BoxSizing::ContentBox => length,
@@ -2669,9 +2674,12 @@ impl FlexItemBox {
                     .used_size_as_if_inline_element_from_content_box_sizes(
                         flex_context.containing_block,
                         &replaced.style,
-                        content_box_size,
-                        min_size,
-                        max_size,
+                        content_box_size
+                            .map(|size| size.non_auto().map_or(Size::Initial, Size::Numeric)),
+                        min_size.map(|size| Size::Numeric(*size)),
+                        max_size.map(|size| size.map_or(Size::Initial, Size::Numeric)),
+                        padding_border_margin.padding_border_sums +
+                            padding_border_margin.margin.auto_is(Au::zero).sum(),
                     )
                     .block
             },
diff --git a/components/layout_2020/flow/mod.rs b/components/layout_2020/flow/mod.rs
index 834b16c304e05..80128a0232876 100644
--- a/components/layout_2020/flow/mod.rs
+++ b/components/layout_2020/flow/mod.rs
@@ -1359,9 +1359,8 @@ fn layout_in_flow_replaced_block_level(
     replaced: &ReplacedContent,
     mut sequential_layout_state: Option<&mut SequentialLayoutState>,
 ) -> BoxFragment {
-    let content_box_sizes_and_pbm: ContentBoxSizesAndPBMDeprecated = style
-        .content_box_sizes_and_padding_border_margin(&containing_block.into())
-        .into();
+    let content_box_sizes_and_pbm =
+        style.content_box_sizes_and_padding_border_margin(&containing_block.into());
     let pbm = &content_box_sizes_and_pbm.pbm;
     let content_size = replaced.used_size_as_if_inline_element(
         containing_block,
@@ -2018,7 +2017,7 @@ impl IndependentFormattingContext {
                     .used_size_as_if_inline_element(
                         containing_block,
                         &replaced.style,
-                        &content_box_sizes_and_pbm.clone().into(),
+                        &content_box_sizes_and_pbm,
                     )
                     .to_physical_size(container_writing_mode);
                 let fragments = replaced.contents.make_fragments(
diff --git a/components/layout_2020/positioned.rs b/components/layout_2020/positioned.rs
index e12869055497f..e0e3ca8ec466d 100644
--- a/components/layout_2020/positioned.rs
+++ b/components/layout_2020/positioned.rs
@@ -466,7 +466,7 @@ impl HoistedAbsolutelyPositionedBox {
                     .used_size_as_if_inline_element(
                         containing_block,
                         &style,
-                        &content_box_sizes_and_pbm.into(),
+                        &content_box_sizes_and_pbm,
                     )
                     .map(|size| Size::Numeric(*size));
                 (used_size, Default::default(), Default::default())
diff --git a/components/layout_2020/replaced.rs b/components/layout_2020/replaced.rs
index 589f7bdf0cc94..b76dbbd69adc1 100644
--- a/components/layout_2020/replaced.rs
+++ b/components/layout_2020/replaced.rs
@@ -6,7 +6,7 @@ use std::cell::LazyCell;
 use std::fmt;
 use std::sync::{Arc, Mutex};
 
-use app_units::{Au, MAX_AU};
+use app_units::Au;
 use base::id::{BrowsingContextId, PipelineId};
 use canvas_traits::canvas::{CanvasId, CanvasMsg, FromLayoutMsg};
 use data_url::DataUrl;
@@ -28,9 +28,9 @@ use webrender_api::ImageKey;
 use crate::context::LayoutContext;
 use crate::dom::NodeExt;
 use crate::fragment_tree::{BaseFragmentInfo, Fragment, IFrameFragment, ImageFragment};
-use crate::geom::{LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSize};
+use crate::geom::{LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSize, Size};
 use crate::sizing::InlineContentSizesResult;
-use crate::style_ext::{AspectRatio, Clamp, ComputedValuesExt, ContentBoxSizesAndPBMDeprecated};
+use crate::style_ext::{AspectRatio, Clamp, ComputedValuesExt, ContentBoxSizesAndPBM};
 use crate::{AuOrAuto, ContainingBlock, IndefiniteContainingBlock};
 
 #[derive(Debug, Serialize)]
@@ -445,16 +445,16 @@ impl ReplacedContent {
         &self,
         containing_block: &ContainingBlock,
         style: &ComputedValues,
-        content_box_sizes_and_pbm: &ContentBoxSizesAndPBMDeprecated,
+        content_box_sizes_and_pbm: &ContentBoxSizesAndPBM,
     ) -> LogicalVec2<Au> {
+        let pbm = &content_box_sizes_and_pbm.pbm;
         self.used_size_as_if_inline_element_from_content_box_sizes(
             containing_block,
             style,
             content_box_sizes_and_pbm.content_box_size,
-            content_box_sizes_and_pbm
-                .content_min_box_size
-                .auto_is(Au::zero),
+            content_box_sizes_and_pbm.content_min_box_size,
             content_box_sizes_and_pbm.content_max_box_size,
+            pbm.padding_border_sums + pbm.margin.auto_is(Au::zero).sum(),
         )
     }
 
@@ -477,76 +477,138 @@ impl ReplacedContent {
     ///
     /// Also used in other cases, for example
     /// <https://drafts.csswg.org/css2/visudet.html#block-replaced-width>
+    ///
+    /// The logic differs from CSS2 in order to properly handle `aspect-ratio` and keyword sizes.
+    /// Each axis can have preferred, min and max sizing constraints, plus constraints transferred
+    /// from the other axis if there is an aspect ratio, plus a natural and default size.
+    /// In case of conflict, the order of precedence (from highest to lowest) is:
+    /// 1. Non-transferred min constraint
+    /// 2. Non-transferred max constraint
+    /// 3. Non-transferred preferred constraint
+    /// 4. Transferred min constraint
+    /// 5. Transferred max constraint
+    /// 6. Transferred preferred constraint
+    /// 7. Natural size
+    /// 8. Default object size
+    ///
+    /// <https://drafts.csswg.org/css-sizing-4/#aspect-ratio-size-transfers>
+    /// <https://github.com/w3c/csswg-drafts/issues/6071#issuecomment-2243986313>
     pub(crate) fn used_size_as_if_inline_element_from_content_box_sizes(
         &self,
         containing_block: &ContainingBlock,
         style: &ComputedValues,
-        box_size: LogicalVec2<AuOrAuto>,
-        min_box_size: LogicalVec2<Au>,
-        max_box_size: LogicalVec2<Option<Au>>,
+        box_size: LogicalVec2<Size<Au>>,
+        min_box_size: LogicalVec2<Size<Au>>,
+        max_box_size: LogicalVec2<Size<Au>>,
+        pbm_sums: LogicalVec2<Au>,
     ) -> LogicalVec2<Au> {
-        let box_size = box_size.map(|size| size.non_auto());
-        let max_box_size = max_box_size.map(|max_size| max_size.unwrap_or(MAX_AU));
+        // <https://drafts.csswg.org/css-sizing-4/#preferred-aspect-ratio>
+        let ratio = self.preferred_aspect_ratio(&containing_block.into(), style);
+
+        // <https://drafts.csswg.org/css-images-3/#natural-dimensions>
+        // <https://drafts.csswg.org/css-images-3/#default-object-size>
         let writing_mode = style.writing_mode;
         let natural_size = LazyCell::new(|| self.flow_relative_natural_size(writing_mode));
         let default_object_size =
             LazyCell::new(|| Self::flow_relative_default_object_size(writing_mode));
-        let ratio = self.preferred_aspect_ratio(&containing_block.into(), style);
+        let get_inline_fallback_size = || {
+            natural_size
+                .inline
+                .unwrap_or_else(|| default_object_size.inline)
+        };
+        let get_block_fallback_size = || {
+            natural_size
+                .block
+                .unwrap_or_else(|| default_object_size.block)
+        };
 
-        // This is a simplification of the CSS2 algorithm in a way that properly handles `aspect-ratio`.
-        // Each axis can have preferred, min and max sizing constraints, plus constraints transferred
-        // from the other axis if there is an aspect ratio, plus a natural and default size.
-        // In case of conflict, the order of precedence (from highest to lowest) is:
-        // 1. Non-transferred min constraint
-        // 2. Non-transferred max constraint
-        // 3. Non-transferred preferred constraint
-        // 4. Transferred min constraint
-        // 5. Transferred max constraint
-        // 6. Transferred preferred constraint
-        // 7. Natural size
-        // 8. Default object size
-        // <https://drafts.csswg.org/css-sizing-4/#aspect-ratio-size-transfers>
-        // <https://github.com/w3c/csswg-drafts/issues/6071#issuecomment-2243986313>
-        box_size.map_inline_and_block_axes(
-            |inline_size| {
-                let mut min = min_box_size.inline;
-                let mut max = max_box_size.inline;
-                if let Some(ratio) = ratio.filter(|_| inline_size.is_none()) {
-                    min = ratio
-                        .compute_dependent_size(Direction::Inline, min_box_size.block)
-                        .clamp_between_extremums(min, Some(max));
-                    max.min_assign(
-                        ratio.compute_dependent_size(Direction::Inline, max_box_size.block),
-                    );
-                }
-                inline_size
-                    .or_else(|| {
-                        Some(ratio?.compute_dependent_size(Direction::Inline, box_size.block?))
-                    })
-                    .or_else(|| natural_size.inline)
-                    .unwrap_or_else(|| default_object_size.inline)
-                    .clamp_between_extremums(min, Some(max))
-            },
-            |block_size| {
-                let mut min = min_box_size.block;
-                let mut max = max_box_size.block;
-                if let Some(ratio) = ratio.filter(|_| block_size.is_none()) {
-                    min = ratio
-                        .compute_dependent_size(Direction::Block, min_box_size.inline)
-                        .clamp_between_extremums(min, Some(max));
-                    max.min_assign(
-                        ratio.compute_dependent_size(Direction::Block, max_box_size.inline),
-                    );
-                }
-                block_size
-                    .or_else(|| {
-                        Some(ratio?.compute_dependent_size(Direction::Block, box_size.inline?))
-                    })
-                    .or_else(|| natural_size.block)
-                    .unwrap_or_else(|| default_object_size.block)
-                    .clamp_between_extremums(min, Some(max))
-            },
-        )
+        // <https://drafts.csswg.org/css-sizing-4/#stretch-fit-sizing>
+        let inline_stretch_size = Au::zero().max(containing_block.inline_size - pbm_sums.inline);
+        let block_stretch_size = containing_block
+            .block_size
+            .non_auto()
+            .map(|block_size| Au::zero().max(block_size - pbm_sums.block));
+
+        // <https://drafts.csswg.org/css-sizing-3/#intrinsic-sizes>
+        // FIXME: Use ReplacedContent::inline_content_sizes() once it's fixed to correctly handle
+        // min and max constraints.
+        let inline_content_size = LazyCell::new(|| {
+            let Some(ratio) = ratio else {
+                return get_inline_fallback_size();
+            };
+            let block_stretch_size = block_stretch_size.unwrap_or_else(get_block_fallback_size);
+            let transfer = |size| ratio.compute_dependent_size(Direction::Inline, size);
+            let min = transfer(
+                min_box_size
+                    .block
+                    .maybe_resolve_extrinsic(Some(block_stretch_size))
+                    .unwrap_or_default(),
+            );
+            let max = max_box_size
+                .block
+                .maybe_resolve_extrinsic(Some(block_stretch_size))
+                .map(transfer);
+            box_size
+                .block
+                .maybe_resolve_extrinsic(Some(block_stretch_size))
+                .map_or_else(get_inline_fallback_size, transfer)
+                .clamp_between_extremums(min, max)
+        });
+        let block_content_size = LazyCell::new(|| {
+            let Some(ratio) = ratio else {
+                return get_block_fallback_size();
+            };
+            let mut get_inline_content_size = || (*inline_content_size).into();
+            let transfer = |size| ratio.compute_dependent_size(Direction::Block, size);
+            let min = transfer(
+                min_box_size
+                    .inline
+                    .resolve_non_initial(inline_stretch_size, &mut get_inline_content_size)
+                    .unwrap_or_default(),
+            );
+            let max = max_box_size
+                .inline
+                .resolve_non_initial(inline_stretch_size, &mut get_inline_content_size)
+                .map(transfer);
+            box_size
+                .inline
+                .maybe_resolve_extrinsic(Some(inline_stretch_size))
+                .map_or_else(get_block_fallback_size, transfer)
+                .clamp_between_extremums(min, max)
+        });
+        let mut get_inline_content_size = || (*inline_content_size).into();
+        let mut get_block_content_size = || (*block_content_size).into();
+        let block_stretch_size = block_stretch_size.unwrap_or_else(|| *block_content_size);
+
+        // <https://drafts.csswg.org/css-sizing-3/#sizing-properties>
+        let preferred_inline = box_size.inline.resolve(
+            Size::FitContent,
+            inline_stretch_size,
+            &mut get_inline_content_size,
+        );
+        let preferred_block = box_size.block.resolve(
+            Size::FitContent,
+            block_stretch_size,
+            &mut get_block_content_size,
+        );
+        let min_inline = min_box_size
+            .inline
+            .resolve_non_initial(inline_stretch_size, &mut get_inline_content_size)
+            .unwrap_or_default();
+        let min_block = min_box_size
+            .block
+            .resolve_non_initial(block_stretch_size, &mut get_block_content_size)
+            .unwrap_or_default();
+        let max_inline = max_box_size
+            .inline
+            .resolve_non_initial(inline_stretch_size, &mut get_inline_content_size);
+        let max_block = max_box_size
+            .block
+            .resolve_non_initial(block_stretch_size, &mut get_block_content_size);
+        LogicalVec2 {
+            inline: preferred_inline.clamp_between_extremums(min_inline, max_inline),
+            block: preferred_block.clamp_between_extremums(min_block, max_block),
+        }
     }
 }
 


Test patch:
diff --git a/tests/wpt/meta/MANIFEST.json b/tests/wpt/meta/MANIFEST.json
index ed260c8f6db80..eca864626cd30 100644
--- a/tests/wpt/meta/MANIFEST.json
+++ b/tests/wpt/meta/MANIFEST.json
@@ -570955,6 +570955,13 @@
        {}
       ]
      ],
+     "keyword-sizes-on-replaced-element.html": [
+      "e26c1b7d6ac38858fd7a941a690ea01e6005c38c",
+      [
+       null,
+       {}
+      ]
+     ],
      "min-max-content-orthogonal-flow-crash-001.html": [
       "d2617f8aa2d1c966e394abb1d1617c012ea4648e",
       [
diff --git a/tests/wpt/meta/css/css-sizing/image-min-max-content-intrinsic-size-change-001.html.ini b/tests/wpt/meta/css/css-sizing/image-min-max-content-intrinsic-size-change-001.html.ini
deleted file mode 100644
index 9584350fcc9b6..0000000000000
--- a/tests/wpt/meta/css/css-sizing/image-min-max-content-intrinsic-size-change-001.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[image-min-max-content-intrinsic-size-change-001.html]
-  expected: FAIL
diff --git a/tests/wpt/meta/css/css-sizing/image-min-max-content-intrinsic-size-change-002.html.ini b/tests/wpt/meta/css/css-sizing/image-min-max-content-intrinsic-size-change-002.html.ini
deleted file mode 100644
index cf3f37da00394..0000000000000
--- a/tests/wpt/meta/css/css-sizing/image-min-max-content-intrinsic-size-change-002.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[image-min-max-content-intrinsic-size-change-002.html]
-  expected: FAIL
diff --git a/tests/wpt/meta/css/css-sizing/image-min-max-content-intrinsic-size-change-003.html.ini b/tests/wpt/meta/css/css-sizing/image-min-max-content-intrinsic-size-change-003.html.ini
deleted file mode 100644
index f8a6c079eb394..0000000000000
--- a/tests/wpt/meta/css/css-sizing/image-min-max-content-intrinsic-size-change-003.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[image-min-max-content-intrinsic-size-change-003.html]
-  expected: FAIL
diff --git a/tests/wpt/meta/css/css-sizing/image-min-max-content-intrinsic-size-change-004.html.ini b/tests/wpt/meta/css/css-sizing/image-min-max-content-intrinsic-size-change-004.html.ini
deleted file mode 100644
index bd8cf6d5aebd1..0000000000000
--- a/tests/wpt/meta/css/css-sizing/image-min-max-content-intrinsic-size-change-004.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[image-min-max-content-intrinsic-size-change-004.html]
-  expected: FAIL
diff --git a/tests/wpt/meta/css/css-sizing/image-min-max-content-intrinsic-size-change-005.html.ini b/tests/wpt/meta/css/css-sizing/image-min-max-content-intrinsic-size-change-005.html.ini
deleted file mode 100644
index 642cb65caac06..0000000000000
--- a/tests/wpt/meta/css/css-sizing/image-min-max-content-intrinsic-size-change-005.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[image-min-max-content-intrinsic-size-change-005.html]
-  expected: FAIL
diff --git a/tests/wpt/meta/css/css-sizing/image-min-max-content-intrinsic-size-change-006.html.ini b/tests/wpt/meta/css/css-sizing/image-min-max-content-intrinsic-size-change-006.html.ini
deleted file mode 100644
index 53b834e36fe43..0000000000000
--- a/tests/wpt/meta/css/css-sizing/image-min-max-content-intrinsic-size-change-006.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[image-min-max-content-intrinsic-size-change-006.html]
-  expected: FAIL
diff --git a/tests/wpt/meta/css/css-sizing/image-min-max-content-intrinsic-size-change-007.html.ini b/tests/wpt/meta/css/css-sizing/image-min-max-content-intrinsic-size-change-007.html.ini
deleted file mode 100644
index 8e4cf87a152d6..0000000000000
--- a/tests/wpt/meta/css/css-sizing/image-min-max-content-intrinsic-size-change-007.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[image-min-max-content-intrinsic-size-change-007.html]
-  expected: FAIL
diff --git a/tests/wpt/meta/css/css-sizing/image-min-max-content-intrinsic-size-change-008.html.ini b/tests/wpt/meta/css/css-sizing/image-min-max-content-intrinsic-size-change-008.html.ini
deleted file mode 100644
index 6bcf1ccbf77f2..0000000000000
--- a/tests/wpt/meta/css/css-sizing/image-min-max-content-intrinsic-size-change-008.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[image-min-max-content-intrinsic-size-change-008.html]
-  expected: FAIL
diff --git a/tests/wpt/meta/css/css-sizing/replaced-max-height-min-content.html.ini b/tests/wpt/meta/css/css-sizing/replaced-max-height-min-content.html.ini
deleted file mode 100644
index 1850f3c41c26b..0000000000000
--- a/tests/wpt/meta/css/css-sizing/replaced-max-height-min-content.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[replaced-max-height-min-content.html]
-  expected: FAIL
diff --git a/tests/wpt/meta/css/css-sizing/replaced-max-width-min-content.html.ini b/tests/wpt/meta/css/css-sizing/replaced-max-width-min-content.html.ini
deleted file mode 100644
index 70634828627b1..0000000000000
--- a/tests/wpt/meta/css/css-sizing/replaced-max-width-min-content.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[replaced-max-width-min-content.html]
-  expected: FAIL
diff --git a/tests/wpt/meta/css/css-sizing/replaced-min-height-min-content.html.ini b/tests/wpt/meta/css/css-sizing/replaced-min-height-min-content.html.ini
deleted file mode 100644
index e10292f4a064e..0000000000000
--- a/tests/wpt/meta/css/css-sizing/replaced-min-height-min-content.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[replaced-min-height-min-content.html]
-  expected: FAIL
diff --git a/tests/wpt/meta/css/css-sizing/replaced-min-width-min-content.html.ini b/tests/wpt/meta/css/css-sizing/replaced-min-width-min-content.html.ini
deleted file mode 100644
index acf6d031ca8dd..0000000000000
--- a/tests/wpt/meta/css/css-sizing/replaced-min-width-min-content.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[replaced-min-width-min-content.html]
-  expected: FAIL
diff --git a/tests/wpt/tests/css/css-sizing/keyword-sizes-on-replaced-element.html b/tests/wpt/tests/css/css-sizing/keyword-sizes-on-replaced-element.html
new file mode 100644
index 0000000000000..e26c1b7d6ac38
--- /dev/null
+++ b/tests/wpt/tests/css/css-sizing/keyword-sizes-on-replaced-element.html
@@ -0,0 +1,184 @@
+<!DOCTYPE html>
+<title>Keyword sizes on replaced element</title>
+<link rel="author" title="Oriol Brufau" href="obrufau@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-sizing-3/#sizing-values">
+<link rel="help" href="https://drafts.csswg.org/css-sizing-4/#sizing-values">
+<link rel="help" href="https://drafts.csswg.org/css2/#replaced-element">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/11006">
+<meta assert="The various keyword sizes work as expected on replaced elements.">
+
+<style>
+.test {
+  margin: 5px;
+  border: 3px solid;
+  padding: 2px;
+  width: auto;
+  height: auto;
+}
+
+/* Set the preferred size to small amount, to test that the min size works */
+.test.min-width  { width:  0px }
+.test.min-height { height: 0px }
+
+/* Set the preferred size to big amount, to test that the max size works */
+.test.max-width  { width:  500px }
+.test.max-height { height: 500px }
+
+/* stretch isn't widely supported, fall back to vendor-prefixed alternatives */
+.width.stretch      {      width: -moz-available;      width: -webkit-fill-available;      width: stretch }
+.min-width.stretch  {  min-width: -moz-available;  min-width: -webkit-fill-available;  min-width: stretch }
+.max-width.stretch  {  max-width: -moz-available;  max-width: -webkit-fill-available;  max-width: stretch }
+.height.stretch     {     height: -moz-available;     height: -webkit-fill-available;     height: stretch }
+.min-height.stretch { min-height: -moz-available; min-height: -webkit-fill-available; min-height: stretch }
+.max-height.stretch { max-height: -moz-available; max-height: -webkit-fill-available; max-height: stretch }
+</style>
+<div id="log"></div>
+
+<!-- Intrinsic keywords -->
+<div style="width: 200px; height: 100px">
+  <canvas width="100" height="100" class="test width height" style="width: 50px; height: 50px"
+          data-expected-width="60" data-expected-height="60"></canvas>
+  <canvas width="100" height="100" class="test width height" style="width: 50px; height: min-content"
+          data-expected-width="60" data-expected-height="60"></canvas>
+  <canvas width="100" height="100" class="test width height" style="width: 50px; height: fit-content"
+          data-expected-width="60" data-expected-height="60"></canvas>
+  <canvas width="100" height="100" class="test width height" style="width: 50px; height: max-content"
+          data-expected-width="60" data-expected-height="60"></canvas>
+  <canvas width="100" height="100" class="test width height" style="width: min-content; height: 50px"
+          data-expected-width="60" data-expected-height="60"></canvas>
+  <canvas width="100" height="100" class="test width height" style="width: min-content; height: min-content"
+          data-expected-width="110" data-expected-height="110"></canvas>
+  <canvas width="100" height="100" class="test width height" style="width: min-content; height: fit-content"
+          data-expected-width="110" data-expected-height="110"></canvas>
+  <canvas width="100" height="100" class="test width height" style="width: min-content; height: max-content"
+          data-expected-width="110" data-expected-height="110"></canvas>
+  <canvas width="100" height="100" class="test width height" style="width: fit-content; height: 50px"
+          data-expected-width="60" data-expected-height="60"></canvas>
+  <canvas width="100" height="100" class="test width height" style="width: fit-content; height: min-content"
+          data-expected-width="110" data-expected-height="110"></canvas>
+  <canvas width="100" height="100" class="test width height" style="width: fit-content; height: fit-content"
+          data-expected-width="110" data-expected-height="110"></canvas>
+  <canvas width="100" height="100" class="test width height" style="width: fit-content; height: max-content"
+          data-expected-width="110" data-expected-height="110"></canvas>
+  <canvas width="100" height="100" class="test width height" style="width: max-content; height: 50px"
+          data-expected-width="60" data-expected-height="60"></canvas>
+  <canvas width="100" height="100" class="test width height" style="width: max-content; height: min-content"
+          data-expected-width="110" data-expected-height="110"></canvas>
+  <canvas width="100" height="100" class="test width height" style="width: max-content; height: fit-content"
+          data-expected-width="110" data-expected-height="110"></canvas>
+  <canvas width="100" height="100" class="test width height" style="width: max-content; height: max-content"
+          data-expected-width="110" data-expected-height="110"></canvas>
+
+  <br>
+
+  <canvas width="100" height="100" class="test min-width height" style="min-width: 50px; height: 50px"
+          data-expected-width="60" data-expected-height="60"></canvas>
+  <canvas width="100" height="100" class="test min-width height" style="min-width: 50px; height: min-content"
+          data-expected-width="60" data-expected-height="60"></canvas>
+  <canvas width="100" height="100" class="test min-width height" style="min-width: 50px; height: fit-content"
+          data-expected-width="60" data-expected-height="60"></canvas>
+  <canvas width="100" height="100" class="test min-width height" style="min-width: 50px; height: max-content"
+          data-expected-width="60" data-expected-height="60"></canvas>
+  <canvas width="100" height="100" class="test min-width height" style="min-width: min-content; height: 50px"
+          data-expected-width="60" data-expected-height="60"></canvas>
+  <canvas width="100" height="100" class="test min-width height" style="min-width: min-content; height: min-content"
+          data-expected-width="110" data-expected-height="110"></canvas>
+  <canvas width="100" height="100" class="test min-width height" style="min-width: min-content; height: fit-content"
+          data-expected-width="110" data-expected-height="110"></canvas>
+  <canvas width="100" height="100" class="test min-width height" style="min-width: min-content; height: max-content"
+          data-expected-width="110" data-expected-height="110"></canvas>
+  <canvas width="100" height="100" class="test min-width height" style="min-width: fit-content; height: 50px"
+          data-expected-width="60" data-expected-height="60"></canvas>
+  <canvas width="100" height="100" class="test min-width height" style="min-width: fit-content; height: min-content"
+          data-expected-width="110" data-expected-height="110"></canvas>
+  <canvas width="100" height="100" class="test min-width height" style="min-width: fit-content; height: fit-content"
+          data-expected-width="110" data-expected-height="110"></canvas>
+  <canvas width="100" height="100" class="test min-width height" style="min-width: fit-content; height: max-content"
+          data-expected-width="110" data-expected-height="110"></canvas>
+  <canvas width="100" height="100" class="test min-width height" style="min-width: max-content; height: 50px"
+          data-expected-width="60" data-expected-height="60"></canvas>
+  <canvas width="100" height="100" class="test min-width height" style="min-width: max-content; height: min-content"
+          data-expected-width="110" data-expected-height="110"></canvas>
+  <canvas width="100" height="100" class="test min-width height" style="min-width: max-content; height: fit-content"
+          data-expected-width="110" data-expected-height="110"></canvas>
+  <canvas width="100" height="100" class="test min-width height" style="min-width: max-content; height: max-content"
+          data-expected-width="110" data-expected-height="110"></canvas>
+
+  <br>
+
+  <canvas width="100" height="100" class="test max-width height" style="max-width: 50px; height: 50px"
+          data-expected-width="60" data-expected-height="60"></canvas>
+  <canvas width="100" height="100" class="test max-width height" style="max-width: 50px; height: min-content"
+          data-expected-width="60" data-expected-height="60"></canvas>
+  <canvas width="100" height="100" class="test max-width height" style="max-width: 50px; height: fit-content"
+          data-expected-width="60" data-expected-height="60"></canvas>
+  <canvas width="100" height="100" class="test max-width height" style="max-width: 50px; height: max-content"
+          data-expected-width="60" data-expected-height="60"></canvas>
+  <canvas width="100" height="100" class="test max-width height" style="max-width: min-content; height: 50px"
+          data-expected-width="60" data-expected-height="60"></canvas>
+  <canvas width="100" height="100" class="test max-width height" style="max-width: min-content; height: min-content"
+          data-expected-width="110" data-expected-height="110"></canvas>
+  <canvas width="100" height="100" class="test max-width height" style="max-width: min-content; height: fit-content"
+          data-expected-width="110" data-expected-height="110"></canvas>
+  <canvas width="100" height="100" class="test max-width height" style="max-width: min-content; height: max-content"
+          data-expected-width="110" data-expected-height="110"></canvas>
+  <canvas width="100" height="100" class="test max-width height" style="max-width: fit-content; height: 50px"
+          data-expected-width="60" data-expected-height="60"></canvas>
+  <canvas width="100" height="100" class="test max-width height" style="max-width: fit-content; height: min-content"
+          data-expected-width="110" data-expected-height="110"></canvas>
+  <canvas width="100" height="100" class="test max-width height" style="max-width: fit-content; height: fit-content"
+          data-expected-width="110" data-expected-height="110"></canvas>
+  <canvas width="100" height="100" class="test max-width height" style="max-width: fit-content; height: max-content"
+          data-expected-width="110" data-expected-height="110"></canvas>
+  <canvas width="100" height="100" class="test max-width height" style="max-width: max-content; height: 50px"
+          data-expected-width="60" data-expected-height="60"></canvas>
+  <canvas width="100" height="100" class="test max-width height" style="max-width: max-content; height: min-content"
+          data-expected-width="110" data-expected-height="110"></canvas>
+  <canvas width="100" height="100" class="test max-width height" style="max-width: max-content; height: fit-content"
+          data-expected-width="110" data-expected-height="110"></canvas>
+  <canvas width="100" height="100" class="test max-width height" style="max-width: max-content; height: max-content"
+          data-expected-width="110" data-expected-height="110"></canvas>
+</div>
+
+<!-- Definite stretch -->
+<div style="width: 200px; height: 100px">
+  <canvas width="100" height="100" class="test width stretch"
+          data-expected-width="190" data-expected-height="190">X X</canvas>
+  <canvas width="100" height="100" class="test min-width stretch"
+          data-expected-width="190" data-expected-height="190">X X</canvas>
+  <canvas width="100" height="100" class="test max-width stretch"
+          data-expected-width="190" data-expected-height="190">X X</canvas>
+  <canvas width="100" height="100" class="test height stretch"
+          data-expected-width="90" data-expected-height="90">X X</canvas>
+  <canvas width="100" height="100" class="test min-height stretch"
+          data-expected-width="90" data-expected-height="90">X X</canvas>
+  <canvas width="100" height="100" class="test max-height stretch"
+          data-expected-width="90" data-expected-height="90">X X</canvas>
+</div>
+
+<!-- Stretch sizes can't result in a negative content size -->
+<div class="wrapper" style="width: 0px; height: 0px">
+  <canvas width="100" height="100" class="test width min-width max-width stretch"
+          data-expected-width="10" data-expected-height="10"></canvas>
+  <canvas width="100" height="100" class="test height min-height max-height stretch"
+          data-expected-width="10" data-expected-height="10"></canvas>
+  <canvas width="100" height="100" class="test width min-width max-width height min-height max-height stretch"
+          data-expected-width="10" data-expected-height="10"></canvas>
+</div>
+
+<!-- Indefinite stretch -->
+<div style="width: 200px; max-height: 100px">
+  <canvas width="100" height="100" class="test height stretch indefinite"
+          data-expected-width="110" data-expected-height="110"></canvas>
+  <canvas width="100" height="100" class="test min-height stretch indefinite"
+          data-expected-width="110" data-expected-height="110"></canvas>
+  <canvas width="100" height="100" class="test max-height stretch indefinite"
+          data-expected-width="110" data-expected-height="110"></canvas>
+</div>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<script>
+checkLayout(".test");
+</script>