From 79800a91130e51b2138b7a1090d5b677a0875e6e Mon Sep 17 00:00:00 2001 From: Basilisk-Dev Date: Wed, 6 May 2026 07:58:56 -0400 Subject: [PATCH] Issue #1826 - Add typed calc() arithmetic for media queries --- layout/style/CSSCalc.h | 12 +-- layout/style/CSSStyleSheet.cpp | 7 +- layout/style/nsCSSParser.cpp | 117 +++++++++++++++++++++- layout/style/test/test_media_queries.html | 15 +++ 4 files changed, 134 insertions(+), 17 deletions(-) diff --git a/layout/style/CSSCalc.h b/layout/style/CSSCalc.h index 06a570c731..665fcd3613 100644 --- a/layout/style/CSSCalc.h +++ b/layout/style/CSSCalc.h @@ -318,11 +318,7 @@ SerializeCalcInternal(const typename CalcOps::input_type& aValue, CalcOps &aOps) if (needParens) { aOps.Append("("); } - if (unit == eCSSUnit_Calc_Times_L) { - aOps.AppendNumber(array->Item(0)); - } else { - SerializeCalcInternal(array->Item(0), aOps); - } + SerializeCalcInternal(array->Item(0), aOps); if (needParens) { aOps.Append(")"); } @@ -340,11 +336,7 @@ SerializeCalcInternal(const typename CalcOps::input_type& aValue, CalcOps &aOps) if (needParens) { aOps.Append("("); } - if (unit == eCSSUnit_Calc_Times_L) { - SerializeCalcInternal(array->Item(1), aOps); - } else { - aOps.AppendNumber(array->Item(1)); - } + SerializeCalcInternal(array->Item(1), aOps); if (needParens) { aOps.Append(")"); } diff --git a/layout/style/CSSStyleSheet.cpp b/layout/style/CSSStyleSheet.cpp index 4a5d2c39b5..8ecf9c85ee 100644 --- a/layout/style/CSSStyleSheet.cpp +++ b/layout/style/CSSStyleSheet.cpp @@ -314,8 +314,11 @@ nsMediaExpression::Matches(nsPresContext *aPresContext, "bad required value"); nscoord actualCoord = nsRuleNode::CalcLengthWithInitialFont( aPresContext, actual); - nscoord requiredCoord = nsRuleNode::CalcLengthWithInitialFont( - aPresContext, required); + nscoord requiredCoord; + if (!ComputeMediaQueryTypedCalcLength(aPresContext, required, + requiredCoord)) { + return false; + } cmp = DoCompare(actualCoord, requiredCoord); } break; diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index 0ed0773844..7153d5cba3 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -262,6 +262,65 @@ RoundFloatToCSSInteger(float aValue) return int32_t(rounded); } +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_Number: + aExponent = 0; + return true; + + default: + break; + } + + if (aValue.IsLengthUnit()) { + aExponent = 1; + return true; + } + + return false; +} + static bool NormalizeCalcForVariant(nsCSSValue& aValue, uint32_t aPropertyVariantMask, @@ -1727,6 +1786,7 @@ protected: // not be set to false if any nsCSSValues created during parsing can escape // out of the parser. bool mSheetPrincipalRequired; + bool mCalcAllowsTypedArithmetic; // This enum helps us track whether we've unprefixed "display: -webkit-box" // (treating it as "display: flex") in an earlier declaration within a series @@ -1831,6 +1891,7 @@ CSSParserImpl::CSSParserImpl() mInFailingSupportsRule(false), mSuppressErrors(false), mSheetPrincipalRequired(true), + mCalcAllowsTypedArithmetic(false), mWebkitBoxUnprefixState(eNotParsingDecls), mNextFree(nullptr) { @@ -3998,8 +4059,12 @@ CSSParserImpl::ParseMediaQueryExpression(nsMediaQuery* aQuery) bool rv = false; switch (feature->mValueType) { case nsMediaFeature::eLength: - rv = ParseSingleTokenNonNegativeVariant(expr->mValue, VARIANT_LCALC, - nullptr); + { + AutoRestore autoRestore(mCalcAllowsTypedArithmetic); + mCalcAllowsTypedArithmetic = true; + rv = ParseSingleTokenNonNegativeVariant(expr->mValue, VARIANT_LCALC, + nullptr); + } break; case nsMediaFeature::eInteger: case nsMediaFeature::eBoolInteger: @@ -14848,6 +14913,25 @@ CSSParserImpl::ParseCalc(nsCSSValue& aValue, uint32_t aVariantMask, if (!ParseCalcAdditiveExpression(arr->Item(0), resultVariantMask)) break; + 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 (!ExpectSymbol(')', true)) break; @@ -14928,13 +15012,16 @@ CSSParserImpl::ParseCalcMultiplicativeExpression(nsCSSValue& aValue, bool *aHadFinalWS) { MOZ_ASSERT(aVariantMask != 0, "unexpected variant mask"); + bool allowTypedArithmetic = mCalcAllowsTypedArithmetic; bool gotValue = false; // already got the part with the unit bool afterDivision = false; nsCSSValue *storage = &aValue; for (;;) { uint32_t variantMask; - if (afterDivision || gotValue) { + if (allowTypedArithmetic) { + variantMask = aVariantMask | VARIANT_NUMBER; + } else if (afterDivision || gotValue) { variantMask = VARIANT_NUMBER; } else { variantMask = aVariantMask | VARIANT_NUMBER; @@ -14955,7 +15042,7 @@ CSSParserImpl::ParseCalcMultiplicativeExpression(nsCSSValue& aValue, if (number == 0.0 && afterDivision) return false; storage->SetFloatValue(number, eCSSUnit_Number); - } else { + } else if (!allowTypedArithmetic) { gotValue = true; if (storage != &aValue) { @@ -14977,7 +15064,10 @@ CSSParserImpl::ParseCalcMultiplicativeExpression(nsCSSValue& aValue, } nsCSSUnit unit; if (mToken.IsSymbol('*')) { - unit = gotValue ? eCSSUnit_Calc_Times_R : eCSSUnit_Calc_Times_L; + unit = allowTypedArithmetic + ? eCSSUnit_Calc_Times_R + : (gotValue ? eCSSUnit_Calc_Times_R + : eCSSUnit_Calc_Times_L); afterDivision = false; } else if (mToken.IsSymbol('/')) { unit = eCSSUnit_Calc_Divided; @@ -14996,6 +15086,23 @@ CSSParserImpl::ParseCalcMultiplicativeExpression(nsCSSValue& aValue, // Adjust aVariantMask (see comments above function) to reflect which // option we took. + if (allowTypedArithmetic) { + int32_t exponent; + if (!GetCalcLengthTypedArithmeticExponent(aValue, exponent)) { + return false; + } + + if (aVariantMask & VARIANT_NUMBER) { + if (exponent == 0) { + aVariantMask = VARIANT_NUMBER; + } else { + aVariantMask &= ~int32_t(VARIANT_NUMBER); + } + } + + return true; + } + if (aVariantMask & VARIANT_NUMBER) { if (gotValue) { aVariantMask &= ~int32_t(VARIANT_NUMBER); diff --git a/layout/style/test/test_media_queries.html b/layout/style/test/test_media_queries.html index c2176f9283..5a4df7d6dc 100644 --- a/layout/style/test/test_media_queries.html +++ b/layout/style/test/test_media_queries.html @@ -321,6 +321,21 @@ function run() { (Math.floor(value/em_size) - 1) + "rem)"); } + change_state(function() { + iframe_style.width = "100px"; + iframe_style.height = "10px"; + }); + should_apply("all and (width: calc(100px / 1em * 1em))"); + should_apply("all and (width: calc((50vh * 5em) / 4px))"); + should_apply("all and (width: calc(10vw / 10px * 1000vh))"); + should_apply("all and (width: calc(8000vh * 1vw / 1em * 8px / 40vh))"); + should_not_apply("all and (width: calc(100px / 1em * 2em))"); + + change_state(function() { + iframe_style.width = width_val + "px"; + iframe_style.height = height_val + "px"; + }); + change_state(function() { iframe_style.width = "0"; });