Implement keyword sizes for replaced elements (#34091)

Signed-off-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Oriol Brufau 2024-11-05 14:46:58 +01:00 committed by GitHub
parent 3289e7d84d
commit ee7b207f96
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 342 additions and 106 deletions

View file

@ -18,7 +18,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;
@ -32,7 +32,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,
};
@ -1873,11 +1873,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;
@ -2505,7 +2510,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,
@ -2678,9 +2683,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
},

View file

@ -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(

View file

@ -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())

View file

@ -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)]
@ -444,16 +444,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(),
)
}
@ -476,76 +476,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);
// 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)
let get_inline_fallback_size = || {
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)
};
let get_block_fallback_size = || {
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),
}
}
}

View file

@ -570980,6 +570980,13 @@
{}
]
],
"keyword-sizes-on-replaced-element.html": [
"e26c1b7d6ac38858fd7a941a690ea01e6005c38c",
[
null,
{}
]
],
"min-max-content-orthogonal-flow-crash-001.html": [
"d2617f8aa2d1c966e394abb1d1617c012ea4648e",
[

View file

@ -1,2 +0,0 @@
[image-min-max-content-intrinsic-size-change-001.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[image-min-max-content-intrinsic-size-change-002.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[image-min-max-content-intrinsic-size-change-003.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[image-min-max-content-intrinsic-size-change-004.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[image-min-max-content-intrinsic-size-change-005.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[image-min-max-content-intrinsic-size-change-006.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[image-min-max-content-intrinsic-size-change-007.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[image-min-max-content-intrinsic-size-change-008.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[replaced-max-height-min-content.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[replaced-max-width-min-content.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[replaced-min-height-min-content.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[replaced-min-width-min-content.html]
expected: FAIL

View file

@ -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>