Skip to content

Commit

Permalink
Bug 1312588 - Part 5: Implement the intrinsic size contribution for f…
Browse files Browse the repository at this point in the history
…it-content(). r=TYLin,emilio

We simplified to just rely on the behavior of the plain argument. That is,
width: fit-content(50%) behaves the same as width: 50%; in all circumstances,
just clamped by min/max-content.

Note: for block axis, we treat fit-content() as initial value its minimal
and maximal value are identical and equal to the initial value in block axis.

From: w3c/csswg-drafts#3731 (comment)

Note: this patch doesn't include any update on flex and grid layout. We
may have to come back to check it.

Differential Revision: https://phabricator.services.mozilla.com/D113199
  • Loading branch information
BorisChiou committed Jun 8, 2021
1 parent 6092a52 commit c0f286b
Show file tree
Hide file tree
Showing 9 changed files with 347 additions and 18 deletions.
115 changes: 97 additions & 18 deletions layout/base/nsLayoutUtils.cpp
Expand Up @@ -4514,17 +4514,15 @@ static nscoord GetDefiniteSizeTakenByBoxSizing(
return sizeTakenByBoxSizing;
}

// Type of preferred size/min size/max size.
enum class SizeProperty { Size, MinSize, MaxSize };

// Handles only max-content and min-content, and
// -moz-fit-content for min-width and max-width, since the others
// (-moz-fit-content for width, and -moz-available) have no effect on
// intrinsic widths.
static bool GetIntrinsicCoord(nsIFrame::ExtremumLength aStyle,
gfxContext* aRenderingContext, nsIFrame* aFrame,
Maybe<nscoord> aInlineSizeFromAspectRatio,
SizeProperty aProperty, nscoord& aResult) {
nsIFrame::SizeProperty aProperty,
nscoord& aResult) {
if (aStyle == nsIFrame::ExtremumLength::MozAvailable) {
return false;
}
Expand All @@ -4536,14 +4534,14 @@ static bool GetIntrinsicCoord(nsIFrame::ExtremumLength aStyle,

if (aStyle == nsIFrame::ExtremumLength::MozFitContent) {
switch (aProperty) {
case SizeProperty::Size:
case nsIFrame::SizeProperty::Size:
// handle like 'width: auto'
return false;
case SizeProperty::MaxSize:
case nsIFrame::SizeProperty::MaxSize:
// constrain large 'width' values down to max-content
aStyle = nsIFrame::ExtremumLength::MaxContent;
break;
case SizeProperty::MinSize:
case nsIFrame::SizeProperty::MinSize:
// constrain small 'width' or 'max-width' values up to min-content
aStyle = nsIFrame::ExtremumLength::MinContent;
break;
Expand Down Expand Up @@ -4572,7 +4570,8 @@ template <typename SizeOrMaxSize>
static bool GetIntrinsicCoord(const SizeOrMaxSize& aStyle,
gfxContext* aRenderingContext, nsIFrame* aFrame,
Maybe<nscoord> aInlineSizeFromAspectRatio,
SizeProperty aProperty, nscoord& aResult) {
nsIFrame::SizeProperty aProperty,
nscoord& aResult) {
auto length = nsIFrame::ToExtremumLength(aStyle);
if (!length) {
return false;
Expand All @@ -4587,6 +4586,33 @@ static bool GetIntrinsicCoord(const SizeOrMaxSize& aStyle,
static int32_t gNoiseIndent = 0;
#endif

static nscoord GetFitContentSizeForMaxOrPreferredSize(
const IntrinsicISizeType aType, const nsIFrame::SizeProperty aProperty,
const nsIFrame* aFrame, const LengthPercentage& aStyleSize,
const nscoord aInitialValue, const nscoord aMinContentSize,
const nscoord aMaxContentSize) {
MOZ_ASSERT(aProperty != nsIFrame::SizeProperty::MinSize);

nscoord size = NS_UNCONSTRAINEDSIZE;
// 1. Treat fit-content()'s arg as a plain LengthPercentage
// However, we have to handle the cyclic percentage contribution first.
//
// https://drafts.csswg.org/css-sizing-3/#cyclic-percentage-contribution
if (aType == IntrinsicISizeType::MinISize &&
aFrame->IsPercentageResolvedAgainstZero(aStyleSize, aProperty)) {
// Case (c) in the spec.
// FIXME: This doesn't follow the spec for calc(). We should fix this in
// Bug 1463700.
size = 0;
} else if (!GetAbsoluteCoord(aStyleSize, size)) {
// As initial value. Case (a) and (b) in the spec.
size = aInitialValue;
}

// 2. Clamp size by min-content and max-content.
return std::max(aMinContentSize, std::min(aMaxContentSize, size));
}

/**
* Add aOffsets which describes what to add on outside of the content box
* aContentSize (controlled by 'box-sizing') and apply min/max properties.
Expand Down Expand Up @@ -4641,38 +4667,89 @@ static nscoord AddIntrinsicSizeOffset(
coordOutsideSize += aOffsets.margin;

min += coordOutsideSize;
result = NSCoordSaturatingAdd(result, coordOutsideSize);

nscoord size;
// Compute min-content/max-content for fit-content().
nscoord minContent = 0;
nscoord maxContent = NS_UNCONSTRAINEDSIZE;
if (aStyleSize.IsFitContentFunction() ||
aStyleMaxSize.IsFitContentFunction() ||
aStyleMinSize.IsFitContentFunction()) {
if (aInlineSizeFromAspectRatio) {
minContent = maxContent = *aInlineSizeFromAspectRatio;
} else {
minContent = aFrame->GetMinISize(aRenderingContext);
maxContent = aFrame->GetPrefISize(aRenderingContext);
}
}

// Compute size.
nscoord size = NS_UNCONSTRAINEDSIZE;
if (aType == IntrinsicISizeType::MinISize &&
aFrame->IsPercentageResolvedAgainstZero(aStyleSize, aStyleMaxSize)) {
// XXX bug 1463700: this doesn't handle calc() according to spec
result = 0; // let |min| handle padding/border/margin
} else if (GetAbsoluteCoord(aStyleSize, size) ||
GetIntrinsicCoord(aStyleSize, aRenderingContext, aFrame,
aInlineSizeFromAspectRatio, SizeProperty::Size,
size)) {
aInlineSizeFromAspectRatio,
nsIFrame::SizeProperty::Size, size)) {
result = size + coordOutsideSize;
} else if (aStyleSize.IsFitContentFunction()) {
// |result| here is the content size or border size, depends on
// StyleBoxSizing. We use it as the initial value when handling the cyclic
// percentage.
nscoord initial = result;
nscoord fitContentFuncSize = GetFitContentSizeForMaxOrPreferredSize(
aType, nsIFrame::SizeProperty::Size, aFrame,
aStyleSize.AsFitContentFunction(), initial, minContent, maxContent);
// Add border and padding.
result = NSCoordSaturatingAdd(fitContentFuncSize, coordOutsideSize);
} else {
result = NSCoordSaturatingAdd(result, coordOutsideSize);
}

// Compute max-size.
nscoord maxSize = aFixedMaxSize ? *aFixedMaxSize : 0;
if (aFixedMaxSize || GetIntrinsicCoord(aStyleMaxSize, aRenderingContext,
aFrame, aInlineSizeFromAspectRatio,
SizeProperty::MaxSize, maxSize)) {
if (aFixedMaxSize ||
GetIntrinsicCoord(aStyleMaxSize, aRenderingContext, aFrame,
aInlineSizeFromAspectRatio,
nsIFrame::SizeProperty::MaxSize, maxSize)) {
maxSize += coordOutsideSize;
if (result > maxSize) {
result = maxSize;
}
} else if (aStyleMaxSize.IsFitContentFunction()) {
nscoord fitContentFuncSize = GetFitContentSizeForMaxOrPreferredSize(
aType, nsIFrame::SizeProperty::MaxSize, aFrame,
aStyleMaxSize.AsFitContentFunction(), NS_UNCONSTRAINEDSIZE, minContent,
maxContent);
maxSize = NSCoordSaturatingAdd(fitContentFuncSize, coordOutsideSize);
if (result > maxSize) {
result = maxSize;
}
}

// Compute min-size.
nscoord minSize = aFixedMinSize ? *aFixedMinSize : 0;
if (aFixedMinSize || GetIntrinsicCoord(aStyleMinSize, aRenderingContext,
aFrame, aInlineSizeFromAspectRatio,
SizeProperty::MinSize, minSize)) {
if (aFixedMinSize ||
GetIntrinsicCoord(aStyleMinSize, aRenderingContext, aFrame,
aInlineSizeFromAspectRatio,
nsIFrame::SizeProperty::MinSize, minSize)) {
minSize += coordOutsideSize;
if (result < minSize) {
result = minSize;
}
} else if (aStyleMinSize.IsFitContentFunction()) {
if (!GetAbsoluteCoord(aStyleMinSize.AsFitContentFunction(), minSize)) {
// FIXME: Bug 1463700, we should resolve only the percentage part to 0
// such as min-width: fit-content(calc(50% + 50px)).
minSize = 0;
}
nscoord fitContentFuncSize =
std::max(minContent, std::min(maxContent, minSize));
minSize = NSCoordSaturatingAdd(fitContentFuncSize, coordOutsideSize);
if (result < minSize) {
result = minSize;
}
}

if (result < min) {
Expand Down Expand Up @@ -4853,6 +4930,8 @@ nscoord nsLayoutUtils::IntrinsicForAxis(
// specified widths, but ignore box-sizing.
boxSizing = StyleBoxSizing::Content;
} else if (!styleISize.ConvertsToLength() &&
!(styleISize.IsFitContentFunction() &&
styleISize.AsFitContentFunction().ConvertsToLength()) &&
!(haveFixedMinISize && haveFixedMaxISize &&
maxISize <= minISize)) {
#ifdef DEBUG_INTRINSIC_WIDTH
Expand Down
27 changes: 27 additions & 0 deletions layout/generic/nsIFrame.cpp
Expand Up @@ -7754,6 +7754,33 @@ bool nsIFrame::IsPercentageResolvedAgainstZero(
(sizeHasPercent && FormControlShrinksForPercentSize(this));
}

// Summary of the Cyclic-Percentage Intrinsic Size Contribution Rules:
//
// Element Type | Replaced | Non-replaced
// Contribution Type | min-content max-content | min-content max-content
// ---------------------------------------------------------------------------
// min size | zero zero | zero zero
// max & preferred size | zero initial | initial initial
//
// https://drafts.csswg.org/css-sizing-3/#cyclic-percentage-contribution
bool nsIFrame::IsPercentageResolvedAgainstZero(const LengthPercentage& aSize,
SizeProperty aProperty) const {
// Early return to avoid calling the virtual function, IsFrameOfType().
if (aProperty == SizeProperty::MinSize) {
return true;
}

const bool hasPercentOnReplaced =
aSize.HasPercent() && IsFrameOfType(nsIFrame::eReplacedSizing);
if (aProperty == SizeProperty::MaxSize) {
return hasPercentOnReplaced;
}

MOZ_ASSERT(aProperty == SizeProperty::Size);
return hasPercentOnReplaced ||
(aSize.HasPercent() && FormControlShrinksForPercentSize(this));
}

bool nsIFrame::IsBlockWrapper() const {
auto pseudoType = Style()->GetPseudoType();
return pseudoType == PseudoStyleType::mozBlockInsideInlineWrapper ||
Expand Down
11 changes: 11 additions & 0 deletions layout/generic/nsIFrame.h
Expand Up @@ -3358,6 +3358,17 @@ class nsIFrame : public nsQueryFrame {
const mozilla::StyleSize& aStyleSize,
const mozilla::StyleMaxSize& aStyleMaxSize) const;

// Type of preferred size/min size/max size.
enum class SizeProperty { Size, MinSize, MaxSize };
/**
* This is simliar to the above method but accepts LengthPercentage. Return
* true if the frame's preferred size property or max size property contains
* a percentage value that should be resolved against zero. For min size, it
* always returns true.
*/
bool IsPercentageResolvedAgainstZero(const mozilla::LengthPercentage& aSize,
SizeProperty aProperty) const;

/**
* Returns true if the frame is a block wrapper.
*/
Expand Down
@@ -0,0 +1,34 @@
<!DOCTYPE html>
<title>CSS fit-content(): min-content contribution</title>
<link rel="help" href="https://drafts.csswg.org/css-sizing-3/#valdef-width-fit-content-length-percentage">
<link rel="help" href="https://drafts.csswg.org/css-sizing-3/#intrinsic-contribution">
<link rel="match" href="../reference/ref-filled-green-100px-square.xht" />
<style>
#reference-overlapped-red {
position: absolute;
background-color: red;
width: 100px;
height: 100px;
z-index: -1;
}
</style>

<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>

<div id="reference-overlapped-red"></div>

<div style="width: min-content; height: 50px; background: green;">
<div style="width: fit-content(100px);">
<div style="display: inline-block; width: 60px;"></div>
<div style="display: inline-block; width: 60px;"></div>
</div>
</div>

<!-- Cyclic percentage intrinsic size contribution, we treat width as the
initial value (i.e. auto) for min content contribution -->
<div style="width: min-content; height: 50px; background: green;">
<div style="width: fit-content(50%);">
<div style="display: inline-block; width: 100px;"></div>
<div style="display: inline-block; width: 100px;"></div>
</div>
</div>
@@ -0,0 +1,36 @@
<!DOCTYPE html>
<title>CSS fit-content(): min-content contribution for min-size</title>
<link rel="help" href="https://drafts.csswg.org/css-sizing-3/#valdef-width-fit-content-length-percentage">
<link rel="help" href="https://drafts.csswg.org/css-sizing-3/#intrinsic-contribution">
<link rel="match" href="../reference/ref-filled-green-100px-square.xht" />
<style>
#reference-overlapped-red {
position: absolute;
background-color: red;
width: 100px;
height: 100px;
z-index: -1;
}
</style>

<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>

<div id="reference-overlapped-red"></div>

<div style="width: min-content; height: 50px; background: green;">
<div style="width: 50px; min-width: fit-content(100px);">
<div style="display: inline-block; width: 60px;"></div>
<div style="display: inline-block; width: 60px;"></div>
</div>
</div>

<!-- Cyclic percentage intrinsic size contribution, we treat min-width as 0
for min content contribution. However, fit-content() should be clamped by
min-content size and max-content size, so the result min-width is equal to
min-content size here -->
<div style="width: min-content; height: 50px; background: green;">
<div style="width: 50px; min-width: fit-content(50%);">
<div style="display: inline-block; width: 100px;"></div>
<div style="display: inline-block; width: 100px;"></div>
</div>
</div>
@@ -0,0 +1,36 @@
<!DOCTYPE html>
<title>CSS fit-content(): min-content contribution for max-size</title>
<link rel="help" href="https://drafts.csswg.org/css-sizing-3/#valdef-width-fit-content-length-percentage">
<link rel="help" href="https://drafts.csswg.org/css-sizing-3/#intrinsic-contribution">
<link rel="match" href="../reference/ref-filled-green-100px-square.xht" />
<style>
#reference-overlapped-red {
position: absolute;
background-color: red;
width: 100px;
height: 100px;
z-index: -1;
}
</style>

<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>

<div id="reference-overlapped-red"></div>

<div style="width: min-content; height: 50px; background: green;">
<div style="width: 200px; max-width: fit-content(100px);">
<div style="display: inline-block; width: 60px;"></div>
<div style="display: inline-block; width: 60px;"></div>
</div>
</div>

<!-- Cyclic percentage intrinsic size contribution, we treat max-width as the
initial value for min content contribution. The initial value is None and
fit-content() should be clamped by min-content size and max-content size, so
the result max-width is equal to max-content size here -->
<div style="width: min-content; height: 50px; background: green;">
<div style="width: 200px; max-width: fit-content(50%); font-size: 0;">
<div style="display: inline-block; width: 50px;"></div>
<div style="display: inline-block; width: 50px;"></div>
</div>
</div>
@@ -0,0 +1,34 @@
<!DOCTYPE html>
<title>CSS fit-content(): max-content contribution</title>
<link rel="help" href="https://drafts.csswg.org/css-sizing-3/#valdef-width-fit-content-length-percentage">
<link rel="help" href="https://drafts.csswg.org/css-sizing-3/#intrinsic-contribution">
<link rel="match" href="../reference/ref-filled-green-100px-square.xht" />
<style>
#reference-overlapped-red {
position: absolute;
background-color: red;
width: 100px;
height: 100px;
z-index: -1;
}
</style>

<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>

<div id="reference-overlapped-red"></div>

<div style="width: max-content; height: 50px; background: green;">
<div style="width: fit-content(100px);">
<div style="display: inline-block; width: 60px;"></div>
<div style="display: inline-block; width: 60px;"></div>
</div>
</div>

<!-- Cyclic percentage intrinsic size contribution, we treat width as the
initial value (i.e. auto) for max content contribution -->
<div style="width: max-content; height: 50px; background: green;">
<div style="width: fit-content(50%); font-size: 0;">
<div style="display: inline-block; width: 50px;"></div>
<div style="display: inline-block; width: 50px;"></div>
</div>
</div>

0 comments on commit c0f286b

Please sign in to comment.