Treat % as 0 for the min-content contribution of replaced elements (#32103)

`width` and `max-width` typically treat expressions with percentages as
their initial value, but for the min-content contribution of replaced
elements, they should instead be treated as zero.

https://drafts.csswg.org/css-sizing-3/#replaced-percentage-min-contribution

Signed-off-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Oriol Brufau 2025-01-06 06:43:30 -08:00 committed by GitHub
parent dd9164f49a
commit 1c321a17ac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 87 additions and 11 deletions

View file

@ -431,6 +431,7 @@ fn compute_inline_content_sizes_for_block_level_boxes(
containing_block,
&LogicalVec2::zero(),
false, /* auto_block_size_stretches_to_containing_block */
false, /* is_replaced */
false, /* is_table */
!matches!(base.style.pseudo(), Some(PseudoElement::ServoAnonymousBox)),
|_| None, /* TODO: support preferred aspect ratios on non-replaced boxes */

View file

@ -215,6 +215,7 @@ impl IndependentFormattingContext {
containing_block,
auto_minimum,
auto_block_size_stretches_to_containing_block,
self.is_replaced(),
is_table,
true, /* establishes_containing_block */
|padding_border_sums| self.preferred_aspect_ratio(padding_border_sums),

View file

@ -10,6 +10,7 @@ use std::ops::{Add, AddAssign};
use app_units::Au;
use serde::Serialize;
use style::properties::ComputedValues;
use style::values::computed::LengthPercentage;
use style::Zero;
use crate::context::LayoutContext;
@ -115,6 +116,7 @@ pub(crate) fn outer_inline(
containing_block: &IndefiniteContainingBlock,
auto_minimum: &LogicalVec2<Au>,
auto_block_size_stretches_to_containing_block: bool,
is_replaced: bool,
is_table: bool,
establishes_containing_block: bool,
get_preferred_aspect_ratio: impl FnOnce(&LogicalVec2<Au>) -> Option<AspectRatio>,
@ -187,7 +189,7 @@ pub(crate) fn outer_inline(
),
})
};
let (preferred_min_content, preferred_max_content, preferred_depends_on_block_constraints) =
let (mut preferred_min_content, preferred_max_content, preferred_depends_on_block_constraints) =
resolve_non_initial(content_box_sizes.inline.preferred)
.unwrap_or_else(|| resolve_non_initial(Size::FitContent).unwrap());
let (mut min_min_content, mut min_max_content, mut min_depends_on_block_constraints) =
@ -196,7 +198,7 @@ pub(crate) fn outer_inline(
auto_minimum.inline,
false,
));
let (max_min_content, max_max_content, max_depends_on_block_constraints) =
let (mut max_min_content, max_max_content, max_depends_on_block_constraints) =
resolve_non_initial(content_box_sizes.inline.max)
.map(|(min_content, max_content, depends_on_block_constraints)| {
(
@ -207,6 +209,30 @@ pub(crate) fn outer_inline(
})
.unwrap_or_default();
// https://drafts.csswg.org/css-sizing-3/#replaced-percentage-min-contribution
// > If the box is replaced, a cyclic percentage in the value of any max size property
// > or preferred size property (width/max-width/height/max-height), is resolved against
// > zero when calculating the min-content contribution in the corresponding axis.
//
// This means that e.g. the min-content contribution of `width: calc(100% + 100px)`
// should be 100px, but it's just zero on other browsers, so we do the same.
if is_replaced {
let has_percentage = |size: Size<LengthPercentage>| {
// We need a comment here to avoid breaking `./mach test-tidy`.
matches!(size, Size::Numeric(numeric) if numeric.has_percentage())
};
if content_box_sizes.inline.preferred.is_initial() &&
has_percentage(style.box_size(containing_block.writing_mode).inline)
{
preferred_min_content = Au::zero();
}
if content_box_sizes.inline.max.is_initial() &&
has_percentage(style.max_box_size(containing_block.writing_mode).inline)
{
max_min_content = Some(Au::zero());
}
}
// Regardless of their sizing properties, tables are always forced to be at least
// as big as their min-content size, so floor the minimums.
if is_table {

View file

@ -581052,6 +581052,13 @@
{}
]
],
"intrinsic-percent-replaced-028.html": [
"f54245a1e1f0aabff40ed8823279b8c0bb48f93d",
[
null,
{}
]
],
"intrinsic-size-fallback-replaced.html": [
"a3325b0aea01c008ec322a20e0f279d5bd765b1c",
[

View file

@ -1,9 +0,0 @@
[keyword-sizes-on-abspos.html]
[.test 17]
expected: FAIL
[.test 22]
expected: FAIL
[.test 27]
expected: FAIL

View file

@ -0,0 +1,50 @@
<!DOCTYPE html>
<meta charset="utf-8">
<link rel="author" title="Oriol Brufau" href="obrufau@igalia.com">
<link rel="help" href="https://drafts.csswg.org/css-sizing-3/#replaced-percentage-min-contribution">
<meta name="assert" content="A preferred or max inline size property set to a percentage is treated as zero when computing the min-content contribution.">
<style>
.wrapper {
display: inline-block;
border: solid;
margin: 5px;
}
</style>
<div style="width: 0px">
<!-- Set 'width' to a percentage -->
<div class="wrapper" data-expected-client-width="0">
<canvas style="width: 0%; max-width: 100px"></canvas>
</div>
<div class="wrapper" data-expected-client-width="0">
<canvas style="width: 50%; max-width: 100px"></canvas>
</div>
<div class="wrapper" data-expected-client-width="0">
<canvas style="width: 100%; max-width: 100px"></canvas>
</div>
<div class="wrapper" data-expected-client-width="0">
<canvas style="width: 200%; max-width: 100px"></canvas>
</div>
<!-- Set 'max-width' to a percentage -->
<div class="wrapper" data-expected-client-width="0">
<canvas style="width: 100px; max-width: 0%"></canvas>
</div>
<div class="wrapper" data-expected-client-width="0">
<canvas style="width: 100px; max-width: 50%"></canvas>
</div>
<div class="wrapper" data-expected-client-width="0">
<canvas style="width: 100px; max-width: 100%"></canvas>
</div>
<div class="wrapper" data-expected-client-width="0">
<canvas style="width: 100px; max-width: 200%"></canvas>
</div>
</div>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/check-layout-th.js"></script>
<script>
checkLayout(".wrapper");
</script>