From c47cea4ddf969690811899b2db4dab0e2aa42d36 Mon Sep 17 00:00:00 2001 From: Basilisk-Dev Date: Sun, 10 May 2026 14:04:56 -0400 Subject: [PATCH] Support CSS sizing math functions --- layout/style/CSSCalc.h | 108 ++++- layout/style/CSSStyleSheet.cpp | 149 +++++++ layout/style/nsCSSParser.cpp | 402 +++++++++++++++++- layout/style/nsCSSValue.cpp | 14 +- layout/style/nsCSSValue.h | 9 +- layout/style/nsComputedDOMStyle.cpp | 103 +++++ layout/style/nsRuleNode.cpp | 207 ++++++++- layout/style/nsRuleNode.h | 15 +- layout/style/nsStyleCoord.cpp | 161 ++++++- layout/style/nsStyleCoord.h | 46 +- layout/style/test/mochitest.ini | 1 + .../style/test/test_css_math_functions.html | 47 ++ 12 files changed, 1234 insertions(+), 28 deletions(-) create mode 100644 layout/style/test/test_css_math_functions.html diff --git a/layout/style/CSSCalc.h b/layout/style/CSSCalc.h index 06a570c731..b5499768aa 100644 --- a/layout/style/CSSCalc.h +++ b/layout/style/CSSCalc.h @@ -6,6 +6,7 @@ #include "nsCSSValue.h" #include "nsStyleCoord.h" +#include #include namespace mozilla { @@ -46,6 +47,13 @@ namespace css { * result_type aValue1, float aValue2); * * result_type + * MergeMinMax(nsCSSUnit aCalcFunction, + * result_type aValue1, result_type aValue2); + * + * result_type + * MergeClamp(result_type aMin, result_type aCenter, result_type aMax); + * + * result_type * ComputeLeafValue(const input_type& aValue); * * float @@ -70,6 +78,8 @@ namespace css { * MergeAdditive for Plus and Minus * MergeMultiplicativeL for Times_L (number * value) * MergeMultiplicativeR for Times_R (value * number) and Divided + * MergeMinMax for Min and Max + * MergeClamp for Clamp */ template static typename CalcOps::result_type @@ -104,6 +114,25 @@ ComputeCalc(const typename CalcOps::input_type& aValue, CalcOps &aOps) float rhs = aOps.ComputeNumber(arr->Item(1)); return aOps.MergeMultiplicativeR(CalcOps::GetUnit(aValue), lhs, rhs); } + case eCSSUnit_Calc_Min: + case eCSSUnit_Calc_Max: { + typename CalcOps::input_array_type *arr = aValue.GetArrayValue(); + MOZ_ASSERT(arr->Count() >= 1, "unexpected length"); + typename CalcOps::result_type result = ComputeCalc(arr->Item(0), aOps); + for (uint32_t i = 1; i < arr->Count(); ++i) { + typename CalcOps::result_type next = ComputeCalc(arr->Item(i), aOps); + result = aOps.MergeMinMax(CalcOps::GetUnit(aValue), result, next); + } + return result; + } + case eCSSUnit_Calc_Clamp: { + typename CalcOps::input_array_type *arr = aValue.GetArrayValue(); + MOZ_ASSERT(arr->Count() == 3, "unexpected length"); + typename CalcOps::result_type min = ComputeCalc(arr->Item(0), aOps); + typename CalcOps::result_type center = ComputeCalc(arr->Item(1), aOps); + typename CalcOps::result_type max = ComputeCalc(arr->Item(2), aOps); + return aOps.MergeClamp(min, center, max); + } default: { return aOps.ComputeLeafValue(aValue); } @@ -168,6 +197,23 @@ struct BasicCoordCalcOps } return NSCoordSaturatingMultiply(aValue1, aValue2); } + + result_type + MergeMinMax(nsCSSUnit aCalcFunction, + result_type aValue1, result_type aValue2) + { + if (aCalcFunction == eCSSUnit_Calc_Min) { + return std::min(aValue1, aValue2); + } + MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Max, "unexpected unit"); + return std::max(aValue1, aValue2); + } + + result_type + MergeClamp(result_type aMin, result_type aCenter, result_type aMax) + { + return std::max(aMin, std::min(aCenter, aMax)); + } }; struct BasicFloatCalcOps @@ -206,6 +252,23 @@ struct BasicFloatCalcOps "unexpected unit"); return aValue1 / aValue2; } + + result_type + MergeMinMax(nsCSSUnit aCalcFunction, + result_type aValue1, result_type aValue2) + { + if (aCalcFunction == eCSSUnit_Calc_Min) { + return std::min(aValue1, aValue2); + } + MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Max, "unexpected unit"); + return std::max(aValue1, aValue2); + } + + result_type + MergeClamp(result_type aMin, result_type aCenter, result_type aMax) + { + return std::max(aMin, std::min(aCenter, aMax)); + } }; /** @@ -255,8 +318,20 @@ template static void SerializeCalc(const typename CalcOps::input_type& aValue, CalcOps &aOps) { - aOps.Append("calc("); nsCSSUnit unit = CalcOps::GetUnit(aValue); + if (unit == eCSSUnit_Calc) { + const typename CalcOps::input_array_type *array = aValue.GetArrayValue(); + MOZ_ASSERT(array->Count() == 1, "unexpected length"); + nsCSSUnit childUnit = CalcOps::GetUnit(array->Item(0)); + if (childUnit == eCSSUnit_Calc_Min || + childUnit == eCSSUnit_Calc_Max || + childUnit == eCSSUnit_Calc_Clamp) { + SerializeCalcInternal(array->Item(0), aOps); + return; + } + } + + aOps.Append("calc("); if (unit == eCSSUnit_Calc) { const typename CalcOps::input_array_type *array = aValue.GetArrayValue(); MOZ_ASSERT(array->Count() == 1, "unexpected length"); @@ -282,6 +357,14 @@ IsCalcMultiplicativeUnit(nsCSSUnit aUnit) aUnit == eCSSUnit_Calc_Divided; } +static inline bool +IsCalcMinMaxClampUnit(nsCSSUnit aUnit) +{ + return aUnit == eCSSUnit_Calc_Min || + aUnit == eCSSUnit_Calc_Max || + aUnit == eCSSUnit_Calc_Clamp; +} + // Serialize a non-toplevel value in a calc() tree. See big comment // above. template @@ -348,6 +431,29 @@ SerializeCalcInternal(const typename CalcOps::input_type& aValue, CalcOps &aOps) if (needParens) { aOps.Append(")"); } + } else if (IsCalcMinMaxClampUnit(unit)) { + const typename CalcOps::input_array_type *array = aValue.GetArrayValue(); + MOZ_ASSERT(unit != eCSSUnit_Calc_Clamp || array->Count() == 3, + "unexpected length"); + MOZ_ASSERT(unit == eCSSUnit_Calc_Clamp || array->Count() >= 1, + "unexpected length"); + + if (unit == eCSSUnit_Calc_Min) { + aOps.Append("min("); + } else if (unit == eCSSUnit_Calc_Max) { + aOps.Append("max("); + } else { + MOZ_ASSERT(unit == eCSSUnit_Calc_Clamp, "unexpected unit"); + aOps.Append("clamp("); + } + + for (uint32_t i = 0; i < array->Count(); ++i) { + if (i != 0) { + aOps.Append(", "); + } + SerializeCalcInternal(array->Item(i), aOps); + } + aOps.Append(")"); } else { aOps.AppendLeafValue(aValue); } diff --git a/layout/style/CSSStyleSheet.cpp b/layout/style/CSSStyleSheet.cpp index f2f89b9127..7491c73644 100644 --- a/layout/style/CSSStyleSheet.cpp +++ b/layout/style/CSSStyleSheet.cpp @@ -7,6 +7,8 @@ #include "mozilla/CSSStyleSheet.h" +#include + #include "nsIAtom.h" #include "nsCSSRuleProcessor.h" #include "mozilla/MemoryReporting.h" @@ -130,6 +132,153 @@ int32_t DoCompare(Numeric a, Numeric b) return 1; } +struct MediaQueryTypedCalcLengthResult +{ + double mValue; + int32_t mExponent; +}; + +static bool +EvaluateMediaQueryTypedCalcLength(nsPresContext* aPresContext, + const nsCSSValue& aValue, + MediaQueryTypedCalcLengthResult& aResult) +{ + switch (aValue.GetUnit()) { + case eCSSUnit_Calc: { + nsCSSValue::Array* array = aValue.GetArrayValue(); + MOZ_ASSERT(array->Count() == 1, "unexpected length"); + return EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(0), + aResult); + } + + case eCSSUnit_Calc_Plus: + case eCSSUnit_Calc_Minus: { + nsCSSValue::Array* array = aValue.GetArrayValue(); + MOZ_ASSERT(array->Count() == 2, "unexpected length"); + MediaQueryTypedCalcLengthResult lhs; + MediaQueryTypedCalcLengthResult rhs; + if (!EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(0), lhs) || + !EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(1), rhs) || + lhs.mExponent != rhs.mExponent) { + return false; + } + aResult.mExponent = lhs.mExponent; + aResult.mValue = aValue.GetUnit() == eCSSUnit_Calc_Plus + ? lhs.mValue + rhs.mValue + : lhs.mValue - rhs.mValue; + return true; + } + + case eCSSUnit_Calc_Times_L: + case eCSSUnit_Calc_Times_R: + case eCSSUnit_Calc_Divided: { + nsCSSValue::Array* array = aValue.GetArrayValue(); + MOZ_ASSERT(array->Count() == 2, "unexpected length"); + MediaQueryTypedCalcLengthResult lhs; + MediaQueryTypedCalcLengthResult rhs; + if (!EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(0), lhs) || + !EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(1), rhs)) { + return false; + } + if (aValue.GetUnit() == eCSSUnit_Calc_Divided) { + if (rhs.mValue == 0.0) { + return false; + } + aResult.mValue = lhs.mValue / rhs.mValue; + aResult.mExponent = lhs.mExponent - rhs.mExponent; + } else { + aResult.mValue = lhs.mValue * rhs.mValue; + aResult.mExponent = lhs.mExponent + rhs.mExponent; + } + return true; + } + + case eCSSUnit_Calc_Min: + case eCSSUnit_Calc_Max: + case eCSSUnit_Calc_Clamp: { + nsCSSValue::Array* array = aValue.GetArrayValue(); + MOZ_ASSERT(aValue.GetUnit() != eCSSUnit_Calc_Clamp || + array->Count() == 3, "unexpected length"); + MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Calc_Clamp || + array->Count() >= 1, "unexpected length"); + + MediaQueryTypedCalcLengthResult result; + if (!EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(0), + result)) { + return false; + } + + if (aValue.GetUnit() == eCSSUnit_Calc_Clamp) { + MediaQueryTypedCalcLengthResult center; + MediaQueryTypedCalcLengthResult max; + if (!EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(1), + center) || + !EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(2), + max) || + result.mExponent != center.mExponent || + result.mExponent != max.mExponent) { + return false; + } + aResult.mExponent = result.mExponent; + aResult.mValue = std::max(result.mValue, + std::min(center.mValue, max.mValue)); + return true; + } + + for (uint32_t i = 1; i < array->Count(); ++i) { + MediaQueryTypedCalcLengthResult item; + if (!EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(i), + item) || + result.mExponent != item.mExponent) { + return false; + } + result.mValue = aValue.GetUnit() == eCSSUnit_Calc_Min + ? std::min(result.mValue, item.mValue) + : std::max(result.mValue, item.mValue); + } + aResult = result; + return true; + } + + case eCSSUnit_Number: + aResult.mValue = aValue.GetFloatValue(); + aResult.mExponent = 0; + return true; + + default: + break; + } + + if (!aValue.IsLengthUnit()) { + return false; + } + + aResult.mValue = double(nsRuleNode::CalcLengthWithInitialFont(aPresContext, + aValue)); + aResult.mExponent = 1; + return true; +} + +static bool +ComputeMediaQueryTypedCalcLength(nsPresContext* aPresContext, + const nsCSSValue& aValue, + nscoord& aResult) +{ + if (aValue.IsLengthUnit()) { + aResult = nsRuleNode::CalcLengthWithInitialFont(aPresContext, aValue); + return true; + } + + MediaQueryTypedCalcLengthResult result; + if (!EvaluateMediaQueryTypedCalcLength(aPresContext, aValue, result) || + result.mExponent != 1) { + return false; + } + + aResult = NSToCoordFloorClamped(result.mValue); + return true; +} + bool nsMediaExpression::Matches(nsPresContext *aPresContext, const nsCSSValue& aActualValue) const diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index 20894c70a6..8faddd1871 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -197,11 +197,213 @@ OKLabToSRGBColor(float aL, float aA, float aB, float aAlpha) float g = mozilla::clamped(LinearSRGBToEncoded(linearG), 0.0f, 1.0f); float b = mozilla::clamped(LinearSRGBToEncoded(linearB), 0.0f, 1.0f); - return NS_RGBA( - NSToIntRound(r * 255.0f), - NSToIntRound(g * 255.0f), - NSToIntRound(b * 255.0f), - nsStyleUtil::FloatToColorComponent(mozilla::clamped(aAlpha, 0.0f, 1.0f))); + if (aIdent.LowerCaseEqualsLiteral("-infinity")) { + aValue = -std::numeric_limits::infinity(); + return true; + } + + return false; +} + +static bool +IsCalcNumberFunctionName(const nsAString& aIdent) +{ + return aIdent.LowerCaseEqualsLiteral("min") || + aIdent.LowerCaseEqualsLiteral("max") || + aIdent.LowerCaseEqualsLiteral("clamp"); +} + +static void +WrapCalcNumberValue(nsCSSValue& aValue, float aNumber) +{ + RefPtr arr = nsCSSValue::Array::Create(1); + arr->Item(0).SetFloatValue(aNumber, eCSSUnit_Number); + aValue.SetArrayValue(arr, eCSSUnit_Calc); +} + +static bool +IsFiniteCalcNumber(float aValue) +{ + return !std::isnan(aValue) && !std::isinf(aValue); +} + +static bool +GetCalcLengthTypedArithmeticExponent(const nsCSSValue& aValue, + int32_t& aExponent) +{ + switch (aValue.GetUnit()) { + case eCSSUnit_Calc: { + nsCSSValue::Array* array = aValue.GetArrayValue(); + MOZ_ASSERT(array->Count() == 1, "unexpected length"); + return GetCalcLengthTypedArithmeticExponent(array->Item(0), aExponent); + } + + case eCSSUnit_Calc_Plus: + case eCSSUnit_Calc_Minus: { + nsCSSValue::Array* array = aValue.GetArrayValue(); + MOZ_ASSERT(array->Count() == 2, "unexpected length"); + int32_t lhsExponent; + int32_t rhsExponent; + if (!GetCalcLengthTypedArithmeticExponent(array->Item(0), lhsExponent) || + !GetCalcLengthTypedArithmeticExponent(array->Item(1), rhsExponent) || + lhsExponent != rhsExponent) { + return false; + } + aExponent = lhsExponent; + return true; + } + + case eCSSUnit_Calc_Times_L: + case eCSSUnit_Calc_Times_R: + case eCSSUnit_Calc_Divided: { + nsCSSValue::Array* array = aValue.GetArrayValue(); + MOZ_ASSERT(array->Count() == 2, "unexpected length"); + int32_t lhsExponent; + int32_t rhsExponent; + if (!GetCalcLengthTypedArithmeticExponent(array->Item(0), lhsExponent) || + !GetCalcLengthTypedArithmeticExponent(array->Item(1), rhsExponent)) { + return false; + } + aExponent = aValue.GetUnit() == eCSSUnit_Calc_Divided + ? lhsExponent - rhsExponent + : lhsExponent + rhsExponent; + return true; + } + + case eCSSUnit_Calc_Min: + case eCSSUnit_Calc_Max: + case eCSSUnit_Calc_Clamp: { + nsCSSValue::Array* array = aValue.GetArrayValue(); + MOZ_ASSERT(aValue.GetUnit() != eCSSUnit_Calc_Clamp || + array->Count() == 3, "unexpected length"); + MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Calc_Clamp || + array->Count() >= 1, "unexpected length"); + + int32_t exponent; + if (!GetCalcLengthTypedArithmeticExponent(array->Item(0), exponent)) { + return false; + } + for (uint32_t i = 1; i < array->Count(); ++i) { + int32_t itemExponent; + if (!GetCalcLengthTypedArithmeticExponent(array->Item(i), + itemExponent) || + itemExponent != exponent) { + return false; + } + } + aExponent = exponent; + return true; + } + + case eCSSUnit_Number: + aExponent = 0; + return true; + + default: + break; + } + + if (aValue.IsLengthUnit()) { + aExponent = 1; + return true; + } + + return false; +} + +static bool +NormalizeCalcForVariant(nsCSSValue& aValue, + uint32_t aPropertyVariantMask, + uint32_t aResultVariantMask, + bool aSawSpecialNumericValues = false) +{ + if (ShouldPreserveCalcValue(aPropertyVariantMask)) { + return true; + } + + if (aResultVariantMask == VARIANT_NUMBER) { + ReduceNumberCalcOps ops; + float value = mozilla::css::ComputeCalc(aValue, ops); + + if (aPropertyVariantMask & VARIANT_INTEGER) { + if (!IsFiniteCalcNumber(value)) { + return false; + } + aValue.SetIntValue(RoundFloatToCSSInteger(value), eCSSUnit_Integer); + } else if ((aPropertyVariantMask & VARIANT_OPACITY) && + aSawSpecialNumericValues) { + WrapCalcNumberValue(aValue, value); + } else { + aValue.SetFloatValue(value, eCSSUnit_Number); + } + return true; + } + + if (aResultVariantMask & VARIANT_PERCENT) { + ReducePercentageCalcOps ops; + float value = mozilla::css::ComputeCalc(aValue, ops); + + if (aPropertyVariantMask & VARIANT_OPACITY) { + if (aSawSpecialNumericValues) { + WrapCalcNumberValue(aValue, value); + } else { + aValue.SetFloatValue(value, eCSSUnit_Number); + } + } else { + aValue.SetPercentValue(value); + } + return true; + } + + if (aResultVariantMask & VARIANT_ANGLE) { + ReduceDimensionCalcOps ops(ReduceDimensionCalcOps::DimensionType::Angle); + aValue.SetFloatValue(mozilla::css::ComputeCalc(aValue, ops), eCSSUnit_Degree); + return true; + } + + if (aResultVariantMask & VARIANT_TIME) { + ReduceDimensionCalcOps ops(ReduceDimensionCalcOps::DimensionType::Time); + aValue.SetFloatValue(mozilla::css::ComputeCalc(aValue, ops), eCSSUnit_Seconds); + return true; + } + + if (aResultVariantMask & VARIANT_FREQUENCY) { + ReduceDimensionCalcOps ops(ReduceDimensionCalcOps::DimensionType::Frequency); + aValue.SetFloatValue(mozilla::css::ComputeCalc(aValue, ops), eCSSUnit_Hertz); + return true; + } + + MOZ_ASSERT_UNREACHABLE("unsupported calc result type"); + return false; +} + +static bool +MergeCalcFunctionVariantMask(uint32_t& aMergedMask, uint32_t aItemMask) +{ + MOZ_ASSERT(aItemMask != 0, "unexpected empty item mask"); + if (aMergedMask == 0) { + aMergedMask = aItemMask; + return true; + } + + const bool mergedIsNumber = (aMergedMask & VARIANT_NUMBER) != 0; + const bool itemIsNumber = (aItemMask & VARIANT_NUMBER) != 0; + if (mergedIsNumber || itemIsNumber) { + return mergedIsNumber == itemIsNumber; + } + + const uint32_t lengthPercentMask = VARIANT_LENGTH | VARIANT_PERCENT; + if ((aMergedMask & lengthPercentMask) && (aItemMask & lengthPercentMask)) { + aMergedMask = (aMergedMask | aItemMask) & lengthPercentMask; + return true; + } + + if ((aMergedMask & aItemMask) != 0) { + aMergedMask &= aItemMask; + return true; + } + + return false; } static_assert(css::eAuthorSheetFeatures == 0 && @@ -1023,6 +1225,10 @@ protected: uint32_t& aVariantMask, bool *aHadFinalWS); bool ParseCalcTerm(nsCSSValue& aValue, uint32_t& aVariantMask); + bool ParseCalcMinMaxClampFunction(nsCSSValue& aValue, + uint32_t& aVariantMask); + bool ParseCalcNumberExpressionValue(float& aValue); + bool ParseCalcNumberFunction(nsCSSValue& aValue, uint32_t& aVariantMask); bool RequireWhitespace(); // For "flex" shorthand property, defined in CSS Flexbox spec @@ -13431,7 +13637,8 @@ CSSParserImpl::IsCalcFunctionToken(const nsCSSToken& aToken) const { return aToken.mType == eCSSToken_Function && (aToken.mIdent.LowerCaseEqualsLiteral("calc") || - aToken.mIdent.LowerCaseEqualsLiteral("-moz-calc")); + aToken.mIdent.LowerCaseEqualsLiteral("-moz-calc") || + IsCalcNumberFunctionName(aToken.mIdent)); } // Parse one item of the background shorthand property. @@ -14770,11 +14977,39 @@ CSSParserImpl::ParseCalc(nsCSSValue &aValue, uint32_t aVariantMask) // The toplevel of a calc() is always an nsCSSValue::Array of length 1. RefPtr arr = nsCSSValue::Array::Create(1); - if (!ParseCalcAdditiveExpression(arr->Item(0), aVariantMask)) + const bool isMinMaxClamp = + mToken.mType == eCSSToken_Function && + IsCalcNumberFunctionName(mToken.mIdent); + if (isMinMaxClamp) { + if (!ParseCalcMinMaxClampFunction(arr->Item(0), resultVariantMask)) { + break; + } + } else if (!ParseCalcAdditiveExpression(arr->Item(0), resultVariantMask)) { break; + } - if (!ExpectSymbol(')', true)) + if (mCalcAllowsTypedArithmetic) { + int32_t exponent; + if (!GetCalcLengthTypedArithmeticExponent(arr->Item(0), exponent)) { + break; + } + + if (aVariantMask & VARIANT_NUMBER) { + if (exponent == 0) { + resultVariantMask = VARIANT_NUMBER; + } else if (exponent == 1) { + resultVariantMask &= ~int32_t(VARIANT_NUMBER); + } else { + break; + } + } else if (exponent != 1) { + break; + } + } + + if (!isMinMaxClamp && !ExpectSymbol(')', true)) { break; + } aValue.SetArrayValue(arr, eCSSUnit_Calc); mUnitlessLengthQuirk = oldUnitlessLengthQuirk; @@ -14948,6 +15183,14 @@ CSSParserImpl::ParseCalcTerm(nsCSSValue& aValue, uint32_t& aVariantMask) MOZ_ASSERT(aVariantMask != 0, "unexpected variant mask"); if (!GetToken(true)) return false; + if (mToken.mType == eCSSToken_Function && + IsCalcNumberFunctionName(mToken.mIdent)) { + if (!ParseCalcMinMaxClampFunction(aValue, aVariantMask)) { + SkipUntil(')'); + return false; + } + return true; + } // Either an additive expression in parentheses... if (mToken.IsSymbol('(') || // Treat nested calc() as plain parenthesis. @@ -14959,6 +15202,16 @@ CSSParserImpl::ParseCalcTerm(nsCSSValue& aValue, uint32_t& aVariantMask) } return true; } + if ((aVariantMask & VARIANT_NUMBER) != 0 && + mToken.mType == eCSSToken_Ident) { + float specialValue; + if (IsCalcSpecialNumberIdent(mToken.mIdent, specialValue)) { + mCalcHasSpecialNumericValues = true; + aValue.SetFloatValue(specialValue, eCSSUnit_Number); + aVariantMask = VARIANT_NUMBER; + return true; + } + } // ... or just a value UngetToken(); // Always pass VARIANT_NUMBER to ParseVariant so that unitless zero @@ -14983,6 +15236,139 @@ CSSParserImpl::ParseCalcTerm(nsCSSValue& aValue, uint32_t& aVariantMask) return true; } +bool +CSSParserImpl::ParseCalcMinMaxClampFunction(nsCSSValue& aValue, + uint32_t& aVariantMask) +{ + MOZ_ASSERT(mToken.mType == eCSSToken_Function, "expected function token"); + MOZ_ASSERT(IsCalcNumberFunctionName(mToken.mIdent), + "unexpected calc() math function"); + + nsCSSUnit unit; + if (mToken.mIdent.LowerCaseEqualsLiteral("min")) { + unit = eCSSUnit_Calc_Min; + } else if (mToken.mIdent.LowerCaseEqualsLiteral("max")) { + unit = eCSSUnit_Calc_Max; + } else { + MOZ_ASSERT(mToken.mIdent.LowerCaseEqualsLiteral("clamp"), + "unexpected calc() math function"); + unit = eCSSUnit_Calc_Clamp; + } + + AutoTArray arguments; + uint32_t mergedVariantMask = 0; + + for (;;) { + nsCSSValue* argument = arguments.AppendElement(); + uint32_t argumentVariantMask = aVariantMask; + if (!ParseCalcAdditiveExpression(*argument, argumentVariantMask) || + !MergeCalcFunctionVariantMask(mergedVariantMask, + argumentVariantMask)) { + return false; + } + + if (!ExpectSymbol(',', true)) { + break; + } + } + + const uint32_t argumentCount = arguments.Length(); + if ((unit == eCSSUnit_Calc_Clamp && argumentCount != 3) || + (unit != eCSSUnit_Calc_Clamp && argumentCount == 0) || + !ExpectSymbol(')', true)) { + return false; + } + + if (unit != eCSSUnit_Calc_Clamp && argumentCount == 1) { + aValue = arguments[0]; + } else { + RefPtr array = nsCSSValue::Array::Create(argumentCount); + for (uint32_t i = 0; i < argumentCount; ++i) { + array->Item(i) = arguments[i]; + } + aValue.SetArrayValue(array, unit); + } + + aVariantMask = mergedVariantMask; + return true; +} + +bool +CSSParserImpl::ParseCalcNumberExpressionValue(float& aValue) +{ + nsCSSValue expression; + uint32_t variantMask = VARIANT_NUMBER; + if (!ParseCalcAdditiveExpression(expression, variantMask) || + variantMask != VARIANT_NUMBER) { + return false; + } + + ReduceNumberCalcOps ops; + aValue = mozilla::css::ComputeCalc(expression, ops); + return true; +} + +bool +CSSParserImpl::ParseCalcNumberFunction(nsCSSValue& aValue, + uint32_t& aVariantMask) +{ + MOZ_ASSERT(mToken.mType == eCSSToken_Function, "expected function token"); + MOZ_ASSERT(IsCalcNumberFunctionName(mToken.mIdent), + "unexpected calc() number function"); + + float result; + + if (mToken.mIdent.LowerCaseEqualsLiteral("clamp")) { + float minValue; + float centerValue; + float maxValue; + if (!ParseCalcNumberExpressionValue(minValue) || + !ExpectSymbol(',', true) || + !ParseCalcNumberExpressionValue(centerValue) || + !ExpectSymbol(',', true) || + !ParseCalcNumberExpressionValue(maxValue) || + !ExpectSymbol(')', true)) { + return false; + } + + if (std::isnan(minValue) || std::isnan(centerValue) || + std::isnan(maxValue)) { + result = std::numeric_limits::quiet_NaN(); + } else { + result = std::max(minValue, std::min(centerValue, maxValue)); + } + } else { + const bool isMax = mToken.mIdent.LowerCaseEqualsLiteral("max"); + bool sawComma = false; + + if (!ParseCalcNumberExpressionValue(result)) { + return false; + } + + while (ExpectSymbol(',', true)) { + sawComma = true; + float candidate; + if (!ParseCalcNumberExpressionValue(candidate)) { + return false; + } + if (std::isnan(result) || std::isnan(candidate)) { + result = std::numeric_limits::quiet_NaN(); + } else { + result = isMax ? std::max(result, candidate) + : std::min(result, candidate); + } + } + + if (!sawComma || !ExpectSymbol(')', true)) { + return false; + } + } + + aValue.SetFloatValue(result, eCSSUnit_Number); + aVariantMask = VARIANT_NUMBER; + return true; +} + // This function consumes all consecutive whitespace and returns whether // there was any. bool diff --git a/layout/style/nsCSSValue.cpp b/layout/style/nsCSSValue.cpp index 1e43377460..c5fa6ccd19 100644 --- a/layout/style/nsCSSValue.cpp +++ b/layout/style/nsCSSValue.cpp @@ -1473,9 +1473,11 @@ nsCSSValue::AppendToString(nsCSSPropertyID aProperty, nsAString& aResult, aResult.Append(')'); } else if (IsCalcUnit()) { - MOZ_ASSERT(GetUnit() == eCSSUnit_Calc, "unexpected unit"); - CSSValueSerializeCalcOps ops(aProperty, aResult, aSerialization); - css::SerializeCalc(*this, ops); + if (!AppendNormalizedLengthPercentCalcToString(*this, aProperty, aResult, + aSerialization)) { + CSSValueSerializeCalcOps ops(aProperty, aResult, aSerialization); + css::SerializeCalc(*this, ops); + } } else if (eCSSUnit_Integer == unit) { aResult.AppendInt(GetIntValue(), 10); @@ -2037,6 +2039,9 @@ nsCSSValue::AppendToString(nsCSSPropertyID aProperty, nsAString& aResult, case eCSSUnit_Calc_Times_L: break; case eCSSUnit_Calc_Times_R: break; case eCSSUnit_Calc_Divided: break; + case eCSSUnit_Calc_Min: break; + case eCSSUnit_Calc_Max: break; + case eCSSUnit_Calc_Clamp: break; case eCSSUnit_Integer: break; case eCSSUnit_Enumerated: break; case eCSSUnit_EnumColor: break; @@ -2167,6 +2172,9 @@ nsCSSValue::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const case eCSSUnit_Calc_Times_L: case eCSSUnit_Calc_Times_R: case eCSSUnit_Calc_Divided: + case eCSSUnit_Calc_Min: + case eCSSUnit_Calc_Max: + case eCSSUnit_Calc_Clamp: break; // URL diff --git a/layout/style/nsCSSValue.h b/layout/style/nsCSSValue.h index ae50355147..b0fcbbeaa0 100644 --- a/layout/style/nsCSSValue.h +++ b/layout/style/nsCSSValue.h @@ -477,6 +477,11 @@ enum nsCSSUnit { eCSSUnit_Calc_Times_L = 33, // (nsCSSValue::Array*) num * val within calc eCSSUnit_Calc_Times_R = 34, // (nsCSSValue::Array*) val * num within calc eCSSUnit_Calc_Divided = 35, // (nsCSSValue::Array*) / within calc + // Min and Max have arrays with one or more elements. Clamp has an array + // with exactly 3 elements: lower bound, center, upper bound. + eCSSUnit_Calc_Min = 36, // (nsCSSValue::Array*) min() node + eCSSUnit_Calc_Max = 37, // (nsCSSValue::Array*) max() node + eCSSUnit_Calc_Clamp = 38, // (nsCSSValue::Array*) clamp() node eCSSUnit_URL = 40, // (nsCSSValue::URL*) value eCSSUnit_Image = 41, // (nsCSSValue::Image*) value @@ -711,12 +716,12 @@ public: bool IsTimeUnit() const { return eCSSUnit_Seconds <= mUnit && mUnit <= eCSSUnit_Milliseconds; } bool IsCalcUnit() const - { return eCSSUnit_Calc <= mUnit && mUnit <= eCSSUnit_Calc_Divided; } + { return eCSSUnit_Calc <= mUnit && mUnit <= eCSSUnit_Calc_Clamp; } bool UnitHasStringValue() const { return eCSSUnit_String <= mUnit && mUnit <= eCSSUnit_Element; } bool UnitHasArrayValue() const - { return eCSSUnit_Array <= mUnit && mUnit <= eCSSUnit_Calc_Divided; } + { return eCSSUnit_Array <= mUnit && mUnit <= eCSSUnit_Calc_Clamp; } // Checks for the nsCSSValue being of a particular type of color unit: // diff --git a/layout/style/nsComputedDOMStyle.cpp b/layout/style/nsComputedDOMStyle.cpp index b82c788edc..af512e2626 100644 --- a/layout/style/nsComputedDOMStyle.cpp +++ b/layout/style/nsComputedDOMStyle.cpp @@ -1899,6 +1899,94 @@ nsComputedDOMStyle::DoGetBackgroundColor() return val.forget(); } +static void +AppendCalcNodeToString(const nsStyleCoord::CalcNode* aNode, + nsROCSSPrimitiveValue* aPrimitive, + nsAString& aResult); + +static void +AppendCalcLeafToString(const nsStyleCoord::CalcNode* aNode, + nsROCSSPrimitiveValue* aPrimitive, + nsAString& aResult) +{ + nsAutoString tmp; + + if (!aNode->mHasPercent) { + aPrimitive->SetAppUnits(aNode->mLength); + aPrimitive->GetCssText(tmp); + aResult.Append(tmp); + return; + } + + aResult.AppendLiteral("calc("); + aPrimitive->SetAppUnits(aNode->mLength); + aPrimitive->GetCssText(tmp); + aResult.Append(tmp); + aResult.AppendLiteral(" + "); + aPrimitive->SetPercent(aNode->mPercent); + aPrimitive->GetCssText(tmp); + aResult.Append(tmp); + aResult.Append(')'); +} + +static void +AppendCalcNodeToString(const nsStyleCoord::CalcNode* aNode, + nsROCSSPrimitiveValue* aPrimitive, + nsAString& aResult) +{ + using Type = nsStyleCoord::CalcNode::Type; + + switch (aNode->mType) { + case Type::Leaf: + AppendCalcLeafToString(aNode, aPrimitive, aResult); + return; + case Type::Add: + case Type::Subtract: + aResult.AppendLiteral("calc("); + AppendCalcNodeToString(aNode->mChildren[0], aPrimitive, aResult); + aResult.Append(aNode->mType == Type::Add + ? NS_LITERAL_STRING(" + ") + : NS_LITERAL_STRING(" - ")); + AppendCalcNodeToString(aNode->mChildren[1], aPrimitive, aResult); + aResult.Append(')'); + return; + case Type::Multiply: + case Type::Divide: { + nsAutoString tmp; + aResult.AppendLiteral("calc("); + AppendCalcNodeToString(aNode->mChildren[0], aPrimitive, aResult); + aResult.Append(aNode->mType == Type::Multiply + ? NS_LITERAL_STRING(" * ") + : NS_LITERAL_STRING(" / ")); + aPrimitive->SetNumber(aNode->mNumber); + aPrimitive->GetCssText(tmp); + aResult.Append(tmp); + aResult.Append(')'); + return; + } + case Type::Min: + case Type::Max: + case Type::Clamp: + if (aNode->mType == Type::Min) { + aResult.AppendLiteral("min("); + } else if (aNode->mType == Type::Max) { + aResult.AppendLiteral("max("); + } else { + aResult.AppendLiteral("clamp("); + } + for (uint32_t i = 0; i < aNode->mChildren.Length(); ++i) { + if (i != 0) { + aResult.AppendLiteral(", "); + } + AppendCalcNodeToString(aNode->mChildren[i], aPrimitive, aResult); + } + aResult.Append(')'); + return; + } + + MOZ_ASSERT_UNREACHABLE("unexpected calc node type"); +} + static void SetValueToCalc(const nsStyleCoord::CalcValue* aCalc, nsROCSSPrimitiveValue* aValue) @@ -1925,6 +2013,21 @@ SetValueToCalc(const nsStyleCoord::CalcValue* aCalc, aValue->SetString(result); // not really SetString } +static void +SetValueToCalc(const nsStyleCoord::Calc* aCalc, + nsROCSSPrimitiveValue* aValue) +{ + if (!aCalc->HasCalcNode()) { + SetValueToCalc(static_cast(aCalc), aValue); + return; + } + + RefPtr val = new nsROCSSPrimitiveValue; + nsAutoString result; + AppendCalcNodeToString(aCalc->mNode, val, result); + aValue->SetString(result); // not really SetString +} + static void AppendCSSGradientLength(const nsStyleCoord& aValue, nsROCSSPrimitiveValue* aPrimitive, diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp index c552ec2a30..7665b1eb58 100644 --- a/layout/style/nsRuleNode.cpp +++ b/layout/style/nsRuleNode.cpp @@ -701,6 +701,30 @@ static inline nscoord CalcLength(const nsCSSValue& aValue, aPresContext, aConditions); } +static inline nscoord +CalcLengthTowardZero(const nsCSSValue& aValue, + nsStyleContext* aStyleContext, + nsPresContext* aPresContext, + RuleNodeCacheConditions& aConditions) +{ + NS_ASSERTION(aStyleContext, "Must have style data"); + + return CalcLengthWith(aValue, -1, nullptr, + aStyleContext, aPresContext, + false, true, aConditions, + AppUnitRounding::TowardZero); +} + +already_AddRefed +nsRuleNode::ComputedCalc::ToCalcNode() const +{ + if (mNode) { + RefPtr node = mNode; + return node.forget(); + } + return nsStyleCoord::CalcNode::CreateLeaf(mLength, mPercent, mHasPercent); +} + /* static */ nscoord nsRuleNode::CalcLengthWithInitialFont(nsPresContext* aPresContext, const nsCSSValue& aValue) @@ -733,27 +757,42 @@ struct LengthPercentPairCalcOps : public css::NumbersAlreadyNormalizedOps { if (aValue.GetUnit() == eCSSUnit_Percent) { mHasPercent = true; - return result_type(0, aValue.GetPercentValue()); + return result_type(0, aValue.GetPercentValue(), true); } return result_type(CalcLength(aValue, mContext, mPresContext, mConditions), - 0.0f); + 0.0f, false); } result_type MergeAdditive(nsCSSUnit aCalcFunction, result_type aValue1, result_type aValue2) { + if (aValue1.HasNode() || aValue2.HasNode()) { + RefPtr node = + nsStyleCoord::CalcNode::Create( + aCalcFunction == eCSSUnit_Calc_Plus + ? nsStyleCoord::CalcNode::Type::Add + : nsStyleCoord::CalcNode::Type::Subtract); + node->mChildren.AppendElement(aValue1.ToCalcNode()); + node->mChildren.AppendElement(aValue2.ToCalcNode()); + node->mHasPercent = node->HasPercent(); + mHasPercent = mHasPercent || node->mHasPercent; + return result_type(node.forget()); + } + if (aCalcFunction == eCSSUnit_Calc_Plus) { return result_type(NSCoordSaturatingAdd(aValue1.mLength, aValue2.mLength), - aValue1.mPercent + aValue2.mPercent); + aValue1.mPercent + aValue2.mPercent, + aValue1.mHasPercent || aValue2.mHasPercent); } MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Minus, "min() and max() are not allowed in calc() on transform"); return result_type(NSCoordSaturatingSubtract(aValue1.mLength, aValue2.mLength, 0), - aValue1.mPercent - aValue2.mPercent); + aValue1.mPercent - aValue2.mPercent, + aValue1.mHasPercent || aValue2.mHasPercent); } result_type @@ -762,8 +801,19 @@ struct LengthPercentPairCalcOps : public css::NumbersAlreadyNormalizedOps { MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Times_L, "unexpected unit"); + if (aValue2.HasNode()) { + RefPtr node = + nsStyleCoord::CalcNode::Create(nsStyleCoord::CalcNode::Type::Multiply); + node->mChildren.AppendElement(aValue2.ToCalcNode()); + node->mNumber = aValue1; + node->mHasPercent = node->HasPercent(); + mHasPercent = mHasPercent || node->mHasPercent; + return result_type(node.forget()); + } + return result_type(NSCoordSaturatingMultiply(aValue2.mLength, aValue1), - aValue1 * aValue2.mPercent); + aValue1 * aValue2.mPercent, + aValue2.mHasPercent); } result_type @@ -774,10 +824,89 @@ struct LengthPercentPairCalcOps : public css::NumbersAlreadyNormalizedOps aCalcFunction == eCSSUnit_Calc_Divided, "unexpected unit"); if (aCalcFunction == eCSSUnit_Calc_Divided) { + if (aValue1.HasNode()) { + RefPtr node = + nsStyleCoord::CalcNode::Create(nsStyleCoord::CalcNode::Type::Divide); + node->mChildren.AppendElement(aValue1.ToCalcNode()); + node->mNumber = aValue2; + node->mHasPercent = node->HasPercent(); + mHasPercent = mHasPercent || node->mHasPercent; + return result_type(node.forget()); + } aValue2 = 1.0f / aValue2; + } else if (aValue1.HasNode()) { + RefPtr node = + nsStyleCoord::CalcNode::Create(nsStyleCoord::CalcNode::Type::Multiply); + node->mChildren.AppendElement(aValue1.ToCalcNode()); + node->mNumber = aValue2; + node->mHasPercent = node->HasPercent(); + mHasPercent = mHasPercent || node->mHasPercent; + return result_type(node.forget()); } return result_type(NSCoordSaturatingMultiply(aValue1.mLength, aValue2), - aValue1.mPercent * aValue2); + aValue1.mPercent * aValue2, + aValue1.mHasPercent); + } + + result_type + MergeMinMax(nsCSSUnit aCalcFunction, + result_type aValue1, result_type aValue2) + { + const bool hasNode = aValue1.HasNode() || aValue2.HasNode(); + const bool hasPercent = aValue1.mHasPercent || aValue2.mHasPercent; + if (!hasNode && !hasPercent) { + if (aCalcFunction == eCSSUnit_Calc_Min) { + return result_type(std::min(aValue1.mLength, aValue2.mLength), + 0.0f, false); + } + MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Max, "unexpected unit"); + return result_type(std::max(aValue1.mLength, aValue2.mLength), + 0.0f, false); + } + + RefPtr node = + nsStyleCoord::CalcNode::Create( + aCalcFunction == eCSSUnit_Calc_Min + ? nsStyleCoord::CalcNode::Type::Min + : nsStyleCoord::CalcNode::Type::Max); + if (aValue1.HasNode() && + aValue1.mNode->mType == node->mType) { + node->mChildren.AppendElements(aValue1.mNode->mChildren); + } else { + node->mChildren.AppendElement(aValue1.ToCalcNode()); + } + if (aValue2.HasNode() && + aValue2.mNode->mType == node->mType) { + node->mChildren.AppendElements(aValue2.mNode->mChildren); + } else { + node->mChildren.AppendElement(aValue2.ToCalcNode()); + } + node->mHasPercent = node->HasPercent(); + mHasPercent = mHasPercent || node->mHasPercent; + return result_type(node.forget()); + } + + result_type + MergeClamp(result_type aMin, result_type aCenter, result_type aMax) + { + const bool hasNode = aMin.HasNode() || aCenter.HasNode() || aMax.HasNode(); + const bool hasPercent = aMin.mHasPercent || + aCenter.mHasPercent || + aMax.mHasPercent; + if (!hasNode && !hasPercent) { + return result_type(std::max(aMin.mLength, + std::min(aCenter.mLength, aMax.mLength)), + 0.0f, false); + } + + RefPtr node = + nsStyleCoord::CalcNode::Create(nsStyleCoord::CalcNode::Type::Clamp); + node->mChildren.AppendElement(aMin.ToCalcNode()); + node->mChildren.AppendElement(aCenter.ToCalcNode()); + node->mChildren.AppendElement(aMax.ToCalcNode()); + node->mHasPercent = node->HasPercent(); + mHasPercent = mHasPercent || node->mHasPercent; + return result_type(node.forget()); } }; @@ -795,7 +924,8 @@ SpecifiedCalcToComputedCalc(const nsCSSValue& aValue, nsStyleCoord& aCoord, calcObj->mLength = vals.mLength; calcObj->mPercent = vals.mPercent; - calcObj->mHasPercent = ops.mHasPercent; + calcObj->mHasPercent = vals.mHasPercent || ops.mHasPercent; + calcObj->mNode = vals.mNode; aCoord.SetCalcValue(calcObj); } @@ -818,8 +948,7 @@ nsRuleNode::ComputeComputedCalc(const nsStyleCoord& aValue, nscoord aPercentageBasis) { nsStyleCoord::Calc* calc = aValue.GetCalcValue(); - return calc->mLength + - NSToCoordFloorClamped(aPercentageBasis * calc->mPercent); + return calc->Resolve(aPercentageBasis); } /* static */ nscoord @@ -4797,6 +4926,34 @@ struct LengthNumberCalcOps : public css::NumbersAlreadyNormalizedOps return result; } + result_type + MergeMinMax(nsCSSUnit aCalcFunction, + result_type aValue1, result_type aValue2) + { + MOZ_ASSERT(aValue1.mIsNumber == aValue2.mIsNumber); + LengthNumberCalcObj result; + result.mIsNumber = aValue1.mIsNumber; + if (aCalcFunction == eCSSUnit_Calc_Min) { + result.mValue = std::min(aValue1.mValue, aValue2.mValue); + return result; + } + MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Max, "unexpected unit"); + result.mValue = std::max(aValue1.mValue, aValue2.mValue); + return result; + } + + result_type + MergeClamp(result_type aMin, result_type aCenter, result_type aMax) + { + MOZ_ASSERT(aMin.mIsNumber == aCenter.mIsNumber && + aCenter.mIsNumber == aMax.mIsNumber); + LengthNumberCalcObj result; + result.mIsNumber = aCenter.mIsNumber; + result.mValue = std::max(aMin.mValue, + std::min(aCenter.mValue, aMax.mValue)); + return result; + } + result_type ComputeLeafValue(const nsCSSValue& aValue) { LengthNumberCalcObj result; @@ -4887,6 +5044,38 @@ struct LengthPercentNumberCalcOps : public css::NumbersAlreadyNormalizedOps return result; } + result_type + MergeMinMax(nsCSSUnit aCalcFunction, + result_type aValue1, result_type aValue2) + { + MOZ_ASSERT(aValue1.mIsNumber == aValue2.mIsNumber); + result_type result; + result.mIsNumber = aValue1.mIsNumber; + if (aCalcFunction == eCSSUnit_Calc_Min) { + result.mLength = std::min(aValue1.mLength, aValue2.mLength); + result.mPercent = std::min(aValue1.mPercent, aValue2.mPercent); + } else { + MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Max, "unexpected unit"); + result.mLength = std::max(aValue1.mLength, aValue2.mLength); + result.mPercent = std::max(aValue1.mPercent, aValue2.mPercent); + } + return result; + } + + result_type + MergeClamp(result_type aMin, result_type aCenter, result_type aMax) + { + MOZ_ASSERT(aMin.mIsNumber == aCenter.mIsNumber && + aCenter.mIsNumber == aMax.mIsNumber); + result_type result; + result.mIsNumber = aCenter.mIsNumber; + result.mLength = std::max(aMin.mLength, + std::min(aCenter.mLength, aMax.mLength)); + result.mPercent = std::max(aMin.mPercent, + std::min(aCenter.mPercent, aMax.mPercent)); + return result; + } + result_type ComputeLeafValue(const nsCSSValue& aValue) { diff --git a/layout/style/nsRuleNode.h b/layout/style/nsRuleNode.h index 7ca3a165f9..b4118335e1 100644 --- a/layout/style/nsRuleNode.h +++ b/layout/style/nsRuleNode.h @@ -986,9 +986,22 @@ public: struct ComputedCalc { nscoord mLength; float mPercent; + bool mHasPercent; + RefPtr mNode; ComputedCalc(nscoord aLength, float aPercent) - : mLength(aLength), mPercent(aPercent) {} + : mLength(aLength), mPercent(aPercent), + mHasPercent(aPercent != 0.0f) {} + + ComputedCalc(nscoord aLength, float aPercent, bool aHasPercent) + : mLength(aLength), mPercent(aPercent), + mHasPercent(aHasPercent) {} + + explicit ComputedCalc(already_AddRefed aNode) + : mLength(0), mPercent(0.0f), mHasPercent(true), mNode(aNode) {} + + bool HasNode() const { return !!mNode; } + already_AddRefed ToCalcNode() const; }; static ComputedCalc SpecifiedCalcToComputedCalc(const nsCSSValue& aValue, diff --git a/layout/style/nsStyleCoord.cpp b/layout/style/nsStyleCoord.cpp index f93bb53f63..d26e02438c 100644 --- a/layout/style/nsStyleCoord.cpp +++ b/layout/style/nsStyleCoord.cpp @@ -8,6 +8,150 @@ #include "nsStyleCoord.h" #include "mozilla/HashFunctions.h" #include "mozilla/PodOperations.h" +#include + +already_AddRefed +nsStyleCoord::CalcNode::CreateLeaf(nscoord aLength, float aPercent, + bool aHasPercent) +{ + RefPtr node = new CalcNode(Type::Leaf); + node->mLength = aLength; + node->mPercent = aPercent; + node->mHasPercent = aHasPercent; + return node.forget(); +} + +already_AddRefed +nsStyleCoord::CalcNode::Create(Type aType) +{ + RefPtr node = new CalcNode(aType); + return node.forget(); +} + +nsStyleCoord::CalcNode::CalcNode(Type aType) + : mType(aType) + , mLength(0) + , mPercent(0.0f) + , mNumber(0.0f) + , mHasPercent(false) +{ +} + +bool +nsStyleCoord::CalcNode::HasPercent() const +{ + if (mHasPercent) { + return true; + } + for (const RefPtr& child : mChildren) { + if (child->HasPercent()) { + return true; + } + } + return false; +} + +bool +nsStyleCoord::CalcNode::Equals(const CalcNode& aOther) const +{ + if (mType != aOther.mType || + mLength != aOther.mLength || + mPercent != aOther.mPercent || + mNumber != aOther.mNumber || + mHasPercent != aOther.mHasPercent || + mChildren.Length() != aOther.mChildren.Length()) { + return false; + } + + for (uint32_t i = 0; i < mChildren.Length(); ++i) { + if (!mChildren[i]->Equals(*aOther.mChildren[i])) { + return false; + } + } + return true; +} + +uint32_t +nsStyleCoord::CalcNode::HashValue(uint32_t aHash) const +{ + aHash = mozilla::AddToHash(aHash, uint8_t(mType), mLength, mPercent, + mNumber, mHasPercent); + for (const RefPtr& child : mChildren) { + aHash = child->HashValue(aHash); + } + return aHash; +} + +static nscoord +ResolveCalcNode(const nsStyleCoord::CalcNode& aNode, nscoord aPercentageBasis) +{ + using Type = nsStyleCoord::CalcNode::Type; + + switch (aNode.mType) { + case Type::Leaf: + return aNode.mLength + + NSToCoordFloorClamped(aPercentageBasis * aNode.mPercent); + case Type::Add: + MOZ_ASSERT(aNode.mChildren.Length() == 2, "unexpected child count"); + return NSCoordSaturatingAdd( + ResolveCalcNode(*aNode.mChildren[0], aPercentageBasis), + ResolveCalcNode(*aNode.mChildren[1], aPercentageBasis)); + case Type::Subtract: + MOZ_ASSERT(aNode.mChildren.Length() == 2, "unexpected child count"); + return NSCoordSaturatingSubtract( + ResolveCalcNode(*aNode.mChildren[0], aPercentageBasis), + ResolveCalcNode(*aNode.mChildren[1], aPercentageBasis), 0); + case Type::Multiply: + MOZ_ASSERT(aNode.mChildren.Length() == 1, "unexpected child count"); + return NSCoordSaturatingMultiply( + ResolveCalcNode(*aNode.mChildren[0], aPercentageBasis), + aNode.mNumber); + case Type::Divide: + MOZ_ASSERT(aNode.mChildren.Length() == 1, "unexpected child count"); + return NSCoordSaturatingMultiply( + ResolveCalcNode(*aNode.mChildren[0], aPercentageBasis), + 1.0f / aNode.mNumber); + case Type::Min: { + MOZ_ASSERT(!aNode.mChildren.IsEmpty(), "unexpected child count"); + nscoord result = ResolveCalcNode(*aNode.mChildren[0], aPercentageBasis); + for (uint32_t i = 1; i < aNode.mChildren.Length(); ++i) { + result = std::min(result, + ResolveCalcNode(*aNode.mChildren[i], + aPercentageBasis)); + } + return result; + } + case Type::Max: { + MOZ_ASSERT(!aNode.mChildren.IsEmpty(), "unexpected child count"); + nscoord result = ResolveCalcNode(*aNode.mChildren[0], aPercentageBasis); + for (uint32_t i = 1; i < aNode.mChildren.Length(); ++i) { + result = std::max(result, + ResolveCalcNode(*aNode.mChildren[i], + aPercentageBasis)); + } + return result; + } + case Type::Clamp: + MOZ_ASSERT(aNode.mChildren.Length() == 3, "unexpected child count"); + return std::max(ResolveCalcNode(*aNode.mChildren[0], aPercentageBasis), + std::min(ResolveCalcNode(*aNode.mChildren[1], + aPercentageBasis), + ResolveCalcNode(*aNode.mChildren[2], + aPercentageBasis))); + } + + MOZ_ASSERT_UNREACHABLE("unexpected calc node type"); + return 0; +} + +nscoord +nsStyleCoord::Calc::Resolve(nscoord aPercentageBasis) const +{ + if (mNode) { + return ResolveCalcNode(*mNode, aPercentageBasis); + } + return mLength + NSToCoordFloorClamped(aPercentageBasis * mPercent); +} nsStyleCoord::nsStyleCoord(nsStyleUnit aUnit) : mUnit(aUnit) @@ -71,8 +215,15 @@ bool nsStyleCoord::operator==(const nsStyleCoord& aOther) const case eStyleUnit_Integer: case eStyleUnit_Enumerated: return mValue.mInt == aOther.mValue.mInt; - case eStyleUnit_Calc: - return *this->GetCalcValue() == *aOther.GetCalcValue(); + case eStyleUnit_Calc: { + Calc* thisCalc = GetCalcValue(); + Calc* otherCalc = aOther.GetCalcValue(); + if (thisCalc->HasCalcNode() || otherCalc->HasCalcNode()) { + return thisCalc->HasCalcNode() == otherCalc->HasCalcNode() && + thisCalc->mNode->Equals(*otherCalc->mNode); + } + return *thisCalc == *otherCalc; + } } MOZ_ASSERT(false, "unexpected unit"); return false; @@ -100,13 +251,17 @@ uint32_t nsStyleCoord::HashValue(uint32_t aHash = 0) const case eStyleUnit_Integer: case eStyleUnit_Enumerated: return mozilla::AddToHash(aHash, mValue.mInt); - case eStyleUnit_Calc: + case eStyleUnit_Calc: { Calc* calcValue = GetCalcValue(); + if (calcValue->HasCalcNode()) { + return calcValue->mNode->HashValue(aHash); + } aHash = mozilla::AddToHash(aHash, calcValue->mLength); if (HasPercent()) { return mozilla::AddToHash(aHash, calcValue->mPercent); } return aHash; + } } MOZ_ASSERT(false, "unexpected unit"); return aHash; diff --git a/layout/style/nsStyleCoord.h b/layout/style/nsStyleCoord.h index bd4f15ab51..4718726bd4 100644 --- a/layout/style/nsStyleCoord.h +++ b/layout/style/nsStyleCoord.h @@ -10,6 +10,8 @@ #include "nsCoord.h" #include "nsStyleConsts.h" +#include "nsTArray.h" +#include "mozilla/RefPtr.h" namespace mozilla { @@ -106,7 +108,44 @@ public: // If this returns true the value is definitely zero. It it returns false // it might be zero. So it's best used for conservative optimization. - bool IsDefinitelyZero() const { return mLength == 0 && mPercent == 0; } + bool IsDefinitelyZero() const { + return mLength == 0 && mPercent == 0 && !mHasPercent; + } + }; + + struct CalcNode final { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CalcNode) + + enum class Type : uint8_t { + Leaf, + Add, + Subtract, + Multiply, + Divide, + Min, + Max, + Clamp + }; + + static already_AddRefed CreateLeaf(nscoord aLength, + float aPercent, + bool aHasPercent); + static already_AddRefed Create(Type aType); + + bool HasPercent() const; + bool Equals(const CalcNode& aOther) const; + uint32_t HashValue(uint32_t aHash) const; + + Type mType; + nscoord mLength; + float mPercent; + float mNumber; + bool mHasPercent; + nsTArray> mChildren; + + private: + explicit CalcNode(Type aType); + ~CalcNode() {} }; // Reference counted calc() value. This is the type that is used to store @@ -115,6 +154,11 @@ public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Calc) Calc() {} + bool HasCalcNode() const { return !!mNode; } + nscoord Resolve(nscoord aPercentageBasis) const; + + RefPtr mNode; + private: Calc(const Calc&) = delete; ~Calc() {} diff --git a/layout/style/test/mochitest.ini b/layout/style/test/mochitest.ini index bcbb8d30fc..8cadc7dd51 100644 --- a/layout/style/test/mochitest.ini +++ b/layout/style/test/mochitest.ini @@ -74,6 +74,7 @@ support-files = file_animations_with_disabled_properties.html [test_background_blend_mode.html] [test_box_size_keywords.html] [test_bug73586.html] +[test_css_math_functions.html] [test_bug74880.html] [test_bug98997.html] [test_bug160403.html] diff --git a/layout/style/test/test_css_math_functions.html b/layout/style/test/test_css_math_functions.html new file mode 100644 index 0000000000..040251edf5 --- /dev/null +++ b/layout/style/test/test_css_math_functions.html @@ -0,0 +1,47 @@ + + + + Test CSS sizing math functions + + + + +
+
+
+
+
+
+
+
+
+
+
+
+ +