| /* |
| * Copyright (C) 2011, 2012 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "third_party/blink/renderer/core/css/css_math_expression_node.h" |
| |
| #include "third_party/blink/renderer/core/css/css_numeric_literal_value.h" |
| #include "third_party/blink/renderer/core/css/css_primitive_value_mappings.h" |
| #include "third_party/blink/renderer/core/css/css_value_clamping_utils.h" |
| #include "third_party/blink/renderer/core/css/properties/css_parsing_utils.h" |
| #include "third_party/blink/renderer/core/css/resolver/style_resolver.h" |
| #include "third_party/blink/renderer/platform/geometry/calculation_expression_node.h" |
| #include "third_party/blink/renderer/platform/wtf/math_extras.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_builder.h" |
| |
| static const int maxExpressionDepth = 100; |
| |
| enum ParseState { OK, TooDeep, NoMoreTokens }; |
| |
| namespace blink { |
| |
| static CalculationCategory UnitCategory(CSSPrimitiveValue::UnitType type) { |
| switch (type) { |
| case CSSPrimitiveValue::UnitType::kNumber: |
| case CSSPrimitiveValue::UnitType::kInteger: |
| return kCalcNumber; |
| case CSSPrimitiveValue::UnitType::kPercentage: |
| return kCalcPercent; |
| case CSSPrimitiveValue::UnitType::kEms: |
| case CSSPrimitiveValue::UnitType::kExs: |
| case CSSPrimitiveValue::UnitType::kPixels: |
| case CSSPrimitiveValue::UnitType::kCentimeters: |
| case CSSPrimitiveValue::UnitType::kMillimeters: |
| case CSSPrimitiveValue::UnitType::kQuarterMillimeters: |
| case CSSPrimitiveValue::UnitType::kInches: |
| case CSSPrimitiveValue::UnitType::kPoints: |
| case CSSPrimitiveValue::UnitType::kPicas: |
| case CSSPrimitiveValue::UnitType::kUserUnits: |
| case CSSPrimitiveValue::UnitType::kRems: |
| case CSSPrimitiveValue::UnitType::kChs: |
| case CSSPrimitiveValue::UnitType::kViewportWidth: |
| case CSSPrimitiveValue::UnitType::kViewportHeight: |
| case CSSPrimitiveValue::UnitType::kViewportMin: |
| case CSSPrimitiveValue::UnitType::kViewportMax: |
| return kCalcLength; |
| case CSSPrimitiveValue::UnitType::kDegrees: |
| case CSSPrimitiveValue::UnitType::kGradians: |
| case CSSPrimitiveValue::UnitType::kRadians: |
| case CSSPrimitiveValue::UnitType::kTurns: |
| return kCalcAngle; |
| case CSSPrimitiveValue::UnitType::kMilliseconds: |
| case CSSPrimitiveValue::UnitType::kSeconds: |
| return kCalcTime; |
| case CSSPrimitiveValue::UnitType::kHertz: |
| case CSSPrimitiveValue::UnitType::kKilohertz: |
| return kCalcFrequency; |
| default: |
| return kCalcOther; |
| } |
| } |
| |
| static bool HasDoubleValue(CSSPrimitiveValue::UnitType type) { |
| switch (type) { |
| case CSSPrimitiveValue::UnitType::kNumber: |
| case CSSPrimitiveValue::UnitType::kPercentage: |
| case CSSPrimitiveValue::UnitType::kEms: |
| case CSSPrimitiveValue::UnitType::kExs: |
| case CSSPrimitiveValue::UnitType::kChs: |
| case CSSPrimitiveValue::UnitType::kRems: |
| case CSSPrimitiveValue::UnitType::kPixels: |
| case CSSPrimitiveValue::UnitType::kCentimeters: |
| case CSSPrimitiveValue::UnitType::kMillimeters: |
| case CSSPrimitiveValue::UnitType::kQuarterMillimeters: |
| case CSSPrimitiveValue::UnitType::kInches: |
| case CSSPrimitiveValue::UnitType::kPoints: |
| case CSSPrimitiveValue::UnitType::kPicas: |
| case CSSPrimitiveValue::UnitType::kUserUnits: |
| case CSSPrimitiveValue::UnitType::kDegrees: |
| case CSSPrimitiveValue::UnitType::kRadians: |
| case CSSPrimitiveValue::UnitType::kGradians: |
| case CSSPrimitiveValue::UnitType::kTurns: |
| case CSSPrimitiveValue::UnitType::kMilliseconds: |
| case CSSPrimitiveValue::UnitType::kSeconds: |
| case CSSPrimitiveValue::UnitType::kHertz: |
| case CSSPrimitiveValue::UnitType::kKilohertz: |
| case CSSPrimitiveValue::UnitType::kViewportWidth: |
| case CSSPrimitiveValue::UnitType::kViewportHeight: |
| case CSSPrimitiveValue::UnitType::kViewportMin: |
| case CSSPrimitiveValue::UnitType::kViewportMax: |
| case CSSPrimitiveValue::UnitType::kDotsPerPixel: |
| case CSSPrimitiveValue::UnitType::kDotsPerInch: |
| case CSSPrimitiveValue::UnitType::kDotsPerCentimeter: |
| case CSSPrimitiveValue::UnitType::kFraction: |
| case CSSPrimitiveValue::UnitType::kInteger: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| namespace { |
| |
| const PixelsAndPercent CreateClampedSamePixelsAndPercent(float value) { |
| return PixelsAndPercent(CSSValueClampingUtils::ClampLength(value), |
| CSSValueClampingUtils::ClampLength(value)); |
| } |
| |
| bool IsNaN(PixelsAndPercent value, bool allows_negative_percentage_reference) { |
| if (std::isnan(value.pixels + value.percent) || |
| (allows_negative_percentage_reference && std::isinf(value.percent))) { |
| return true; |
| } |
| return false; |
| } |
| |
| base::Optional<PixelsAndPercent> EvaluateValueIfNaNorInfinity( |
| scoped_refptr<const blink::CalculationExpressionNode> value, |
| bool allows_negative_percentage_reference) { |
| float evaluated_value = value->Evaluate(1); |
| if (std::isnan(evaluated_value) || std::isinf(evaluated_value)) { |
| return CreateClampedSamePixelsAndPercent(evaluated_value); |
| } |
| if (allows_negative_percentage_reference) { |
| evaluated_value = value->Evaluate(-1); |
| if (std::isnan(evaluated_value) || std::isinf(evaluated_value)) { |
| return CreateClampedSamePixelsAndPercent(evaluated_value); |
| } |
| } |
| return base::nullopt; |
| } |
| |
| } // namespace |
| |
| // ------ Start of CSSMathExpressionNumericLiteral member functions ------ |
| |
| // static |
| CSSMathExpressionNumericLiteral* CSSMathExpressionNumericLiteral::Create( |
| const CSSNumericLiteralValue* value, |
| bool is_integer) { |
| return MakeGarbageCollected<CSSMathExpressionNumericLiteral>(value, |
| is_integer); |
| } |
| |
| // static |
| CSSMathExpressionNumericLiteral* CSSMathExpressionNumericLiteral::Create( |
| double value, |
| CSSPrimitiveValue::UnitType type, |
| bool is_integer) { |
| if (!RuntimeEnabledFeatures::CSSCalcInfinityAndNaNEnabled() && |
| (std::isnan(value) || std::isinf(value))) |
| return nullptr; |
| return MakeGarbageCollected<CSSMathExpressionNumericLiteral>( |
| CSSNumericLiteralValue::Create(value, type), is_integer); |
| } |
| |
| CSSMathExpressionNumericLiteral::CSSMathExpressionNumericLiteral( |
| const CSSNumericLiteralValue* value, |
| bool is_integer) |
| : CSSMathExpressionNode(UnitCategory(value->GetType()), |
| is_integer, |
| false /* has_comparisons*/), |
| value_(value) {} |
| |
| bool CSSMathExpressionNumericLiteral::IsZero() const { |
| return !value_->GetDoubleValue(); |
| } |
| |
| String CSSMathExpressionNumericLiteral::CustomCSSText() const { |
| return value_->CssText(); |
| } |
| |
| base::Optional<PixelsAndPercent> |
| CSSMathExpressionNumericLiteral::ToPixelsAndPercent( |
| const CSSToLengthConversionData& conversion_data) const { |
| PixelsAndPercent value(0, 0); |
| switch (category_) { |
| case kCalcLength: |
| // When CSSCalcInfinityAndNaN is enabled, we allow infinity and NaN in |
| // PixelsAndPercent. Therefore, we need to use a function that doesn't |
| // internally clamp the result to the float range. |
| if (RuntimeEnabledFeatures::CSSCalcInfinityAndNaNEnabled()) |
| value.pixels = value_->ComputeLengthPx(conversion_data); |
| else |
| value.pixels = value_->ComputeLength<float>(conversion_data); |
| break; |
| case kCalcPercent: |
| DCHECK(value_->IsPercentage()); |
| // When CSSCalcInfinityAndNaN is enabled, we allow infinity and NaN in |
| // PixelsAndPercent. Therefore, we need to use a function that doesn't |
| // internally clamp the result to the float range. |
| if (RuntimeEnabledFeatures::CSSCalcInfinityAndNaNEnabled()) |
| value.percent = value_->GetDoubleValue(); |
| else |
| value.percent = value_->GetFloatValue(); |
| break; |
| case kCalcNumber: |
| // TODO(alancutter): Stop treating numbers like pixels unconditionally |
| // in calcs to be able to accomodate border-image-width |
| // https://drafts.csswg.org/css-backgrounds-3/#the-border-image-width |
| value.pixels = value_->GetFloatValue() * conversion_data.Zoom(); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| return value; |
| } |
| |
| scoped_refptr<const CalculationExpressionNode> |
| CSSMathExpressionNumericLiteral::ToCalculationExpression( |
| const CSSToLengthConversionData& conversion_data) const { |
| return base::MakeRefCounted<CalculationExpressionLeafNode>( |
| *ToPixelsAndPercent(conversion_data)); |
| } |
| |
| double CSSMathExpressionNumericLiteral::DoubleValue() const { |
| if (HasDoubleValue(ResolvedUnitType())) |
| return value_->GetDoubleValue(); |
| NOTREACHED(); |
| return 0; |
| } |
| |
| base::Optional<double> |
| CSSMathExpressionNumericLiteral::ComputeValueInCanonicalUnit() const { |
| switch (category_) { |
| case kCalcNumber: |
| case kCalcPercent: |
| return value_->DoubleValue(); |
| case kCalcLength: |
| if (CSSPrimitiveValue::IsRelativeUnit(value_->GetType())) |
| return base::nullopt; |
| U_FALLTHROUGH; |
| case kCalcAngle: |
| case kCalcTime: |
| case kCalcFrequency: |
| return value_->DoubleValue() * |
| CSSPrimitiveValue::ConversionToCanonicalUnitsScaleFactor( |
| value_->GetType()); |
| default: |
| return base::nullopt; |
| } |
| } |
| |
| double CSSMathExpressionNumericLiteral::ComputeLengthPx( |
| const CSSToLengthConversionData& conversion_data) const { |
| switch (category_) { |
| case kCalcLength: |
| // When CSSCalcInfinityAndNaN is enabled, we allow infinity and NaN in |
| // PixelsAndPercent. Therefore, we need to use a function that doesn't |
| // internally clamp the result to the float range. |
| if (RuntimeEnabledFeatures::CSSCalcInfinityAndNaNEnabled()) |
| return value_->ComputeLengthPx(conversion_data); |
| return value_->ComputeLength<double>(conversion_data); |
| case kCalcNumber: |
| case kCalcPercent: |
| case kCalcAngle: |
| case kCalcFrequency: |
| case kCalcPercentLength: |
| case kCalcTime: |
| case kCalcOther: |
| NOTREACHED(); |
| break; |
| } |
| NOTREACHED(); |
| return 0; |
| } |
| |
| bool CSSMathExpressionNumericLiteral::AccumulateLengthArray( |
| CSSLengthArray& length_array, |
| double multiplier) const { |
| DCHECK_NE(Category(), kCalcNumber); |
| return value_->AccumulateLengthArray(length_array, multiplier); |
| } |
| |
| void CSSMathExpressionNumericLiteral::AccumulateLengthUnitTypes( |
| CSSPrimitiveValue::LengthTypeFlags& types) const { |
| value_->AccumulateLengthUnitTypes(types); |
| } |
| |
| bool CSSMathExpressionNumericLiteral::operator==( |
| const CSSMathExpressionNode& other) const { |
| if (!other.IsNumericLiteral()) |
| return false; |
| |
| return DataEquivalent(value_, |
| To<CSSMathExpressionNumericLiteral>(other).value_); |
| } |
| |
| CSSPrimitiveValue::UnitType CSSMathExpressionNumericLiteral::ResolvedUnitType() |
| const { |
| return value_->GetType(); |
| } |
| |
| bool CSSMathExpressionNumericLiteral::IsComputationallyIndependent() const { |
| return value_->IsComputationallyIndependent(); |
| } |
| |
| void CSSMathExpressionNumericLiteral::Trace(Visitor* visitor) const { |
| visitor->Trace(value_); |
| CSSMathExpressionNode::Trace(visitor); |
| } |
| |
| #if DCHECK_IS_ON() |
| bool CSSMathExpressionNumericLiteral::InvolvesPercentageComparisons() const { |
| return false; |
| } |
| #endif |
| |
| // ------ End of CSSMathExpressionNumericLiteral member functions |
| |
| static const CalculationCategory kAddSubtractResult[kCalcOther][kCalcOther] = { |
| /* CalcNumber */ {kCalcNumber, kCalcOther, kCalcOther, kCalcOther, |
| kCalcOther, kCalcOther, kCalcOther}, |
| /* CalcLength */ |
| {kCalcOther, kCalcLength, kCalcPercentLength, kCalcPercentLength, |
| kCalcOther, kCalcOther, kCalcOther}, |
| /* CalcPercent */ |
| {kCalcOther, kCalcPercentLength, kCalcPercent, kCalcPercentLength, |
| kCalcOther, kCalcOther, kCalcOther}, |
| /* CalcPercentLength */ |
| {kCalcOther, kCalcPercentLength, kCalcPercentLength, kCalcPercentLength, |
| kCalcOther, kCalcOther, kCalcOther}, |
| /* CalcAngle */ |
| {kCalcOther, kCalcOther, kCalcOther, kCalcOther, kCalcAngle, kCalcOther, |
| kCalcOther}, |
| /* CalcTime */ |
| {kCalcOther, kCalcOther, kCalcOther, kCalcOther, kCalcOther, kCalcTime, |
| kCalcOther}, |
| /* CalcFrequency */ |
| {kCalcOther, kCalcOther, kCalcOther, kCalcOther, kCalcOther, kCalcOther, |
| kCalcFrequency}}; |
| |
| static CalculationCategory DetermineCategory( |
| const CSSMathExpressionNode& left_side, |
| const CSSMathExpressionNode& right_side, |
| CSSMathOperator op) { |
| CalculationCategory left_category = left_side.Category(); |
| CalculationCategory right_category = right_side.Category(); |
| |
| if (left_category == kCalcOther || right_category == kCalcOther) |
| return kCalcOther; |
| |
| switch (op) { |
| case CSSMathOperator::kAdd: |
| case CSSMathOperator::kSubtract: |
| return kAddSubtractResult[left_category][right_category]; |
| case CSSMathOperator::kMultiply: |
| if (left_category != kCalcNumber && right_category != kCalcNumber) |
| return kCalcOther; |
| return left_category == kCalcNumber ? right_category : left_category; |
| case CSSMathOperator::kDivide: |
| if (right_category != kCalcNumber || right_side.IsZero()) |
| return kCalcOther; |
| return left_category; |
| default: |
| break; |
| } |
| |
| NOTREACHED(); |
| return kCalcOther; |
| } |
| |
| static bool IsIntegerResult(const CSSMathExpressionNode* left_side, |
| const CSSMathExpressionNode* right_side, |
| CSSMathOperator op) { |
| // Not testing for actual integer values. |
| // Performs W3C spec's type checking for calc integers. |
| // http://www.w3.org/TR/css3-values/#calc-type-checking |
| return op != CSSMathOperator::kDivide && left_side->IsInteger() && |
| right_side->IsInteger(); |
| } |
| |
| // ------ Start of CSSMathExpressionBinaryOperation member functions ------ |
| |
| // static |
| CSSMathExpressionNode* CSSMathExpressionBinaryOperation::Create( |
| const CSSMathExpressionNode* left_side, |
| const CSSMathExpressionNode* right_side, |
| CSSMathOperator op) { |
| DCHECK_NE(left_side->Category(), kCalcOther); |
| DCHECK_NE(right_side->Category(), kCalcOther); |
| |
| CalculationCategory new_category = |
| DetermineCategory(*left_side, *right_side, op); |
| if (new_category == kCalcOther) |
| return nullptr; |
| |
| return MakeGarbageCollected<CSSMathExpressionBinaryOperation>( |
| left_side, right_side, op, new_category); |
| } |
| |
| // static |
| CSSMathExpressionNode* CSSMathExpressionBinaryOperation::CreateSimplified( |
| const CSSMathExpressionNode* left_side, |
| const CSSMathExpressionNode* right_side, |
| CSSMathOperator op) { |
| if (left_side->IsMathFunction() || right_side->IsMathFunction()) |
| return Create(left_side, right_side, op); |
| |
| CalculationCategory left_category = left_side->Category(); |
| CalculationCategory right_category = right_side->Category(); |
| DCHECK_NE(left_category, kCalcOther); |
| DCHECK_NE(right_category, kCalcOther); |
| |
| bool is_integer = IsIntegerResult(left_side, right_side, op); |
| |
| // Simplify numbers. |
| if (left_category == kCalcNumber && right_category == kCalcNumber) { |
| return CSSMathExpressionNumericLiteral::Create( |
| EvaluateOperator(left_side->DoubleValue(), right_side->DoubleValue(), |
| op), |
| CSSPrimitiveValue::UnitType::kNumber, is_integer); |
| } |
| |
| // Simplify addition and subtraction between same types. |
| if (op == CSSMathOperator::kAdd || op == CSSMathOperator::kSubtract) { |
| if (left_category == right_side->Category()) { |
| CSSPrimitiveValue::UnitType left_type = left_side->ResolvedUnitType(); |
| if (HasDoubleValue(left_type)) { |
| CSSPrimitiveValue::UnitType right_type = right_side->ResolvedUnitType(); |
| if (left_type == right_type) { |
| return CSSMathExpressionNumericLiteral::Create( |
| EvaluateOperator(left_side->DoubleValue(), |
| right_side->DoubleValue(), op), |
| left_type, is_integer); |
| } |
| CSSPrimitiveValue::UnitCategory left_unit_category = |
| CSSPrimitiveValue::UnitTypeToUnitCategory(left_type); |
| if (left_unit_category != CSSPrimitiveValue::kUOther && |
| left_unit_category == |
| CSSPrimitiveValue::UnitTypeToUnitCategory(right_type)) { |
| CSSPrimitiveValue::UnitType canonical_type = |
| CSSPrimitiveValue::CanonicalUnitTypeForCategory( |
| left_unit_category); |
| if (canonical_type != CSSPrimitiveValue::UnitType::kUnknown) { |
| double left_value = clampTo<double>( |
| left_side->DoubleValue() * |
| CSSPrimitiveValue::ConversionToCanonicalUnitsScaleFactor( |
| left_type)); |
| double right_value = clampTo<double>( |
| right_side->DoubleValue() * |
| CSSPrimitiveValue::ConversionToCanonicalUnitsScaleFactor( |
| right_type)); |
| return CSSMathExpressionNumericLiteral::Create( |
| EvaluateOperator(left_value, right_value, op), canonical_type, |
| is_integer); |
| } |
| } |
| } |
| } |
| } else { |
| // Simplify multiplying or dividing by a number for simplifiable types. |
| DCHECK(op == CSSMathOperator::kMultiply || op == CSSMathOperator::kDivide); |
| const CSSMathExpressionNode* number_side = |
| GetNumberSide(left_side, right_side); |
| if (!number_side) |
| return Create(left_side, right_side, op); |
| if (number_side == left_side && op == CSSMathOperator::kDivide) |
| return nullptr; |
| const CSSMathExpressionNode* other_side = |
| left_side == number_side ? right_side : left_side; |
| |
| double number = number_side->DoubleValue(); |
| |
| if (!RuntimeEnabledFeatures::CSSCalcInfinityAndNaNEnabled()) { |
| if (std::isnan(number) || std::isinf(number)) |
| return nullptr; |
| if (op == CSSMathOperator::kDivide && !number) |
| return nullptr; |
| } |
| |
| CSSPrimitiveValue::UnitType other_type = other_side->ResolvedUnitType(); |
| if (HasDoubleValue(other_type)) { |
| return CSSMathExpressionNumericLiteral::Create( |
| EvaluateOperator(other_side->DoubleValue(), number, op), other_type, |
| is_integer); |
| } |
| } |
| |
| return Create(left_side, right_side, op); |
| } |
| |
| CSSMathExpressionBinaryOperation::CSSMathExpressionBinaryOperation( |
| const CSSMathExpressionNode* left_side, |
| const CSSMathExpressionNode* right_side, |
| CSSMathOperator op, |
| CalculationCategory category) |
| : CSSMathExpressionNode( |
| category, |
| IsIntegerResult(left_side, right_side, op), |
| left_side->HasComparisons() || right_side->HasComparisons()), |
| left_side_(left_side), |
| right_side_(right_side), |
| operator_(op) {} |
| |
| bool CSSMathExpressionBinaryOperation::IsZero() const { |
| return !DoubleValue(); |
| } |
| |
| base::Optional<PixelsAndPercent> |
| CSSMathExpressionBinaryOperation::ToPixelsAndPercent( |
| const CSSToLengthConversionData& conversion_data) const { |
| base::Optional<PixelsAndPercent> result; |
| switch (operator_) { |
| case CSSMathOperator::kAdd: |
| case CSSMathOperator::kSubtract: { |
| result = left_side_->ToPixelsAndPercent(conversion_data); |
| if (!result) |
| return base::nullopt; |
| |
| base::Optional<PixelsAndPercent> other_side = |
| right_side_->ToPixelsAndPercent(conversion_data); |
| if (!other_side) |
| return base::nullopt; |
| if (operator_ == CSSMathOperator::kAdd) { |
| result->pixels += other_side->pixels; |
| result->percent += other_side->percent; |
| } else { |
| result->pixels -= other_side->pixels; |
| result->percent -= other_side->percent; |
| } |
| break; |
| } |
| case CSSMathOperator::kMultiply: |
| case CSSMathOperator::kDivide: { |
| const CSSMathExpressionNode* number_side = |
| GetNumberSide(left_side_, right_side_); |
| const CSSMathExpressionNode* other_side = |
| left_side_ == number_side ? right_side_ : left_side_; |
| result = other_side->ToPixelsAndPercent(conversion_data); |
| if (!result) |
| return base::nullopt; |
| float number = number_side->DoubleValue(); |
| if (operator_ == CSSMathOperator::kDivide) |
| number = 1.0 / number; |
| result->pixels *= number; |
| result->percent *= number; |
| break; |
| } |
| default: |
| NOTREACHED(); |
| } |
| return result; |
| } |
| |
| scoped_refptr<const CalculationExpressionNode> |
| CSSMathExpressionBinaryOperation::ToCalculationExpression( |
| const CSSToLengthConversionData& conversion_data) const { |
| switch (operator_) { |
| case CSSMathOperator::kAdd: |
| return CalculationExpressionAdditiveNode::CreateSimplified( |
| left_side_->ToCalculationExpression(conversion_data), |
| right_side_->ToCalculationExpression(conversion_data), |
| CalculationExpressionAdditiveNode::Type::kAdd); |
| case CSSMathOperator::kSubtract: |
| return CalculationExpressionAdditiveNode::CreateSimplified( |
| left_side_->ToCalculationExpression(conversion_data), |
| right_side_->ToCalculationExpression(conversion_data), |
| CalculationExpressionAdditiveNode::Type::kSubtract); |
| case CSSMathOperator::kMultiply: |
| DCHECK_NE((left_side_->Category() == kCalcNumber), |
| (right_side_->Category() == kCalcNumber)); |
| if (left_side_->Category() == kCalcNumber) { |
| return CalculationExpressionMultiplicationNode::CreateSimplified( |
| right_side_->ToCalculationExpression(conversion_data), |
| left_side_->DoubleValue()); |
| } |
| return CalculationExpressionMultiplicationNode::CreateSimplified( |
| left_side_->ToCalculationExpression(conversion_data), |
| right_side_->DoubleValue()); |
| case CSSMathOperator::kDivide: |
| DCHECK_EQ(right_side_->Category(), kCalcNumber); |
| return CalculationExpressionMultiplicationNode::CreateSimplified( |
| left_side_->ToCalculationExpression(conversion_data), |
| 1.0 / right_side_->DoubleValue()); |
| default: |
| NOTREACHED(); |
| return nullptr; |
| } |
| } |
| |
| double CSSMathExpressionBinaryOperation::DoubleValue() const { |
| DCHECK(HasDoubleValue(ResolvedUnitType())) << CustomCSSText(); |
| return Evaluate(left_side_->DoubleValue(), right_side_->DoubleValue()); |
| } |
| |
| static bool HasCanonicalUnit(CalculationCategory category) { |
| return category == kCalcNumber || category == kCalcLength || |
| category == kCalcPercent || category == kCalcAngle || |
| category == kCalcTime || category == kCalcFrequency; |
| } |
| |
| base::Optional<double> |
| CSSMathExpressionBinaryOperation::ComputeValueInCanonicalUnit() const { |
| if (!HasCanonicalUnit(category_)) |
| return base::nullopt; |
| |
| base::Optional<double> left_value = left_side_->ComputeValueInCanonicalUnit(); |
| if (!left_value) |
| return base::nullopt; |
| |
| base::Optional<double> right_value = |
| right_side_->ComputeValueInCanonicalUnit(); |
| if (!right_value) |
| return base::nullopt; |
| |
| return Evaluate(*left_value, *right_value); |
| } |
| |
| double CSSMathExpressionBinaryOperation::ComputeLengthPx( |
| const CSSToLengthConversionData& conversion_data) const { |
| DCHECK_EQ(kCalcLength, Category()); |
| double left_value; |
| if (left_side_->Category() == kCalcLength) { |
| left_value = left_side_->ComputeLengthPx(conversion_data); |
| } else { |
| DCHECK_EQ(kCalcNumber, left_side_->Category()); |
| left_value = left_side_->DoubleValue(); |
| } |
| double right_value; |
| if (right_side_->Category() == kCalcLength) { |
| right_value = right_side_->ComputeLengthPx(conversion_data); |
| } else { |
| DCHECK_EQ(kCalcNumber, right_side_->Category()); |
| right_value = right_side_->DoubleValue(); |
| } |
| return Evaluate(left_value, right_value); |
| } |
| |
| bool CSSMathExpressionBinaryOperation::AccumulateLengthArray( |
| CSSLengthArray& length_array, |
| double multiplier) const { |
| switch (operator_) { |
| case CSSMathOperator::kAdd: |
| if (!left_side_->AccumulateLengthArray(length_array, multiplier)) |
| return false; |
| if (!right_side_->AccumulateLengthArray(length_array, multiplier)) |
| return false; |
| return true; |
| case CSSMathOperator::kSubtract: |
| if (!left_side_->AccumulateLengthArray(length_array, multiplier)) |
| return false; |
| if (!right_side_->AccumulateLengthArray(length_array, -multiplier)) |
| return false; |
| return true; |
| case CSSMathOperator::kMultiply: |
| DCHECK_NE((left_side_->Category() == kCalcNumber), |
| (right_side_->Category() == kCalcNumber)); |
| if (left_side_->Category() == kCalcNumber) { |
| return right_side_->AccumulateLengthArray( |
| length_array, multiplier * left_side_->DoubleValue()); |
| } else { |
| return left_side_->AccumulateLengthArray( |
| length_array, multiplier * right_side_->DoubleValue()); |
| } |
| case CSSMathOperator::kDivide: |
| DCHECK_EQ(right_side_->Category(), kCalcNumber); |
| return left_side_->AccumulateLengthArray( |
| length_array, multiplier / right_side_->DoubleValue()); |
| default: |
| NOTREACHED(); |
| return false; |
| } |
| } |
| |
| void CSSMathExpressionBinaryOperation::AccumulateLengthUnitTypes( |
| CSSPrimitiveValue::LengthTypeFlags& types) const { |
| left_side_->AccumulateLengthUnitTypes(types); |
| right_side_->AccumulateLengthUnitTypes(types); |
| } |
| |
| bool CSSMathExpressionBinaryOperation::IsComputationallyIndependent() const { |
| if (Category() != kCalcLength && Category() != kCalcPercentLength) |
| return true; |
| return left_side_->IsComputationallyIndependent() && |
| right_side_->IsComputationallyIndependent(); |
| } |
| |
| String CSSMathExpressionBinaryOperation::CustomCSSText() const { |
| StringBuilder result; |
| |
| const bool left_side_needs_parentheses = |
| left_side_->IsBinaryOperation() && operator_ != CSSMathOperator::kAdd; |
| if (left_side_needs_parentheses) |
| result.Append('('); |
| result.Append(left_side_->CustomCSSText()); |
| if (left_side_needs_parentheses) |
| result.Append(')'); |
| |
| result.Append(' '); |
| result.Append(ToString(operator_)); |
| result.Append(' '); |
| |
| const bool right_side_needs_parentheses = |
| right_side_->IsBinaryOperation() && operator_ != CSSMathOperator::kAdd; |
| if (right_side_needs_parentheses) |
| result.Append('('); |
| result.Append(right_side_->CustomCSSText()); |
| if (right_side_needs_parentheses) |
| result.Append(')'); |
| |
| return result.ToString(); |
| } |
| |
| bool CSSMathExpressionBinaryOperation::operator==( |
| const CSSMathExpressionNode& exp) const { |
| if (!exp.IsBinaryOperation()) |
| return false; |
| |
| const CSSMathExpressionBinaryOperation& other = |
| To<CSSMathExpressionBinaryOperation>(exp); |
| return DataEquivalent(left_side_, other.left_side_) && |
| DataEquivalent(right_side_, other.right_side_) && |
| operator_ == other.operator_; |
| } |
| |
| CSSPrimitiveValue::UnitType CSSMathExpressionBinaryOperation::ResolvedUnitType() |
| const { |
| switch (category_) { |
| case kCalcNumber: |
| DCHECK_EQ(left_side_->Category(), kCalcNumber); |
| DCHECK_EQ(right_side_->Category(), kCalcNumber); |
| return CSSPrimitiveValue::UnitType::kNumber; |
| case kCalcLength: |
| case kCalcPercent: { |
| if (left_side_->Category() == kCalcNumber) |
| return right_side_->ResolvedUnitType(); |
| if (right_side_->Category() == kCalcNumber) |
| return left_side_->ResolvedUnitType(); |
| CSSPrimitiveValue::UnitType left_type = left_side_->ResolvedUnitType(); |
| if (left_type == right_side_->ResolvedUnitType()) |
| return left_type; |
| return CSSPrimitiveValue::UnitType::kUnknown; |
| } |
| case kCalcAngle: |
| return CSSPrimitiveValue::UnitType::kDegrees; |
| case kCalcTime: |
| return CSSPrimitiveValue::UnitType::kMilliseconds; |
| case kCalcFrequency: |
| return CSSPrimitiveValue::UnitType::kHertz; |
| case kCalcPercentLength: |
| case kCalcOther: |
| return CSSPrimitiveValue::UnitType::kUnknown; |
| } |
| NOTREACHED(); |
| return CSSPrimitiveValue::UnitType::kUnknown; |
| } |
| |
| void CSSMathExpressionBinaryOperation::Trace(Visitor* visitor) const { |
| visitor->Trace(left_side_); |
| visitor->Trace(right_side_); |
| CSSMathExpressionNode::Trace(visitor); |
| } |
| |
| // static |
| const CSSMathExpressionNode* CSSMathExpressionBinaryOperation::GetNumberSide( |
| const CSSMathExpressionNode* left_side, |
| const CSSMathExpressionNode* right_side) { |
| if (left_side->Category() == kCalcNumber) |
| return left_side; |
| if (right_side->Category() == kCalcNumber) |
| return right_side; |
| return nullptr; |
| } |
| |
| // static |
| double CSSMathExpressionBinaryOperation::EvaluateOperator(double left_value, |
| double right_value, |
| CSSMathOperator op) { |
| // Design doc for infinity and NaN: https://bit.ly/349gXjq |
| switch (op) { |
| case CSSMathOperator::kAdd: |
| if (RuntimeEnabledFeatures::CSSCalcInfinityAndNaNEnabled()) |
| return left_value + right_value; |
| return clampTo<double>(left_value + right_value); |
| case CSSMathOperator::kSubtract: |
| if (RuntimeEnabledFeatures::CSSCalcInfinityAndNaNEnabled()) |
| return left_value - right_value; |
| return clampTo<double>(left_value - right_value); |
| case CSSMathOperator::kMultiply: |
| if (RuntimeEnabledFeatures::CSSCalcInfinityAndNaNEnabled()) |
| return left_value * right_value; |
| return clampTo<double>(left_value * right_value); |
| case CSSMathOperator::kDivide: |
| if (RuntimeEnabledFeatures::CSSCalcInfinityAndNaNEnabled()) |
| return left_value / right_value; |
| if (right_value) |
| return clampTo<double>(left_value / right_value); |
| return std::numeric_limits<double>::quiet_NaN(); |
| default: |
| NOTREACHED(); |
| break; |
| } |
| return 0; |
| } |
| |
| #if DCHECK_IS_ON() |
| bool CSSMathExpressionBinaryOperation::InvolvesPercentageComparisons() const { |
| return left_side_->InvolvesPercentageComparisons() || |
| right_side_->InvolvesPercentageComparisons(); |
| } |
| #endif |
| |
| // ------ End of CSSMathExpressionBinaryOperation member functions ------ |
| |
| // ------ Start of CSSMathExpressionVariadicOperation member functions ------ |
| |
| // static |
| CSSMathExpressionVariadicOperation* CSSMathExpressionVariadicOperation::Create( |
| Operands&& operands, |
| CSSMathOperator op) { |
| DCHECK(op == CSSMathOperator::kMin || op == CSSMathOperator::kMax); |
| DCHECK(operands.size()); |
| bool is_first = true; |
| CalculationCategory category; |
| bool is_integer; |
| for (const auto& operand : operands) { |
| if (is_first) { |
| category = operand->Category(); |
| is_integer = operand->IsInteger(); |
| } else { |
| category = kAddSubtractResult[category][operand->Category()]; |
| if (!operand->IsInteger()) |
| is_integer = false; |
| } |
| is_first = false; |
| if (category == kCalcOther) |
| return nullptr; |
| } |
| return MakeGarbageCollected<CSSMathExpressionVariadicOperation>( |
| category, is_integer, std::move(operands), op); |
| } |
| |
| CSSMathExpressionVariadicOperation::CSSMathExpressionVariadicOperation( |
| CalculationCategory category, |
| bool is_integer_result, |
| Operands&& operands, |
| CSSMathOperator op) |
| : CSSMathExpressionNode(category, |
| is_integer_result, |
| true /* has_comparisons */), |
| operands_(std::move(operands)), |
| operator_(op) {} |
| |
| void CSSMathExpressionVariadicOperation::Trace(Visitor* visitor) const { |
| visitor->Trace(operands_); |
| CSSMathExpressionNode::Trace(visitor); |
| } |
| |
| bool CSSMathExpressionVariadicOperation::IsZero() const { |
| base::Optional<double> maybe_value = ComputeValueInCanonicalUnit(); |
| return maybe_value && !*maybe_value; |
| } |
| |
| double CSSMathExpressionVariadicOperation::EvaluateBinary(double lhs, |
| double rhs) const { |
| if (std::isnan(lhs) || std::isnan(rhs)) |
| return std::numeric_limits<double>::quiet_NaN(); |
| |
| switch (operator_) { |
| case CSSMathOperator::kMin: |
| return std::min(lhs, rhs); |
| case CSSMathOperator::kMax: |
| return std::max(lhs, rhs); |
| default: |
| NOTREACHED(); |
| return 0; |
| } |
| } |
| |
| base::Optional<double> |
| CSSMathExpressionVariadicOperation::ComputeValueInCanonicalUnit() const { |
| base::Optional<double> first_value = |
| operands_.front()->ComputeValueInCanonicalUnit(); |
| if (!first_value) |
| return base::nullopt; |
| |
| double result = *first_value; |
| for (const auto& operand : SecondToLastOperands()) { |
| base::Optional<double> maybe_value = operand->ComputeValueInCanonicalUnit(); |
| if (!maybe_value) |
| return base::nullopt; |
| result = EvaluateBinary(result, *maybe_value); |
| } |
| return result; |
| } |
| |
| double CSSMathExpressionVariadicOperation::DoubleValue() const { |
| DCHECK(HasDoubleValue(ResolvedUnitType())); |
| double result = operands_.front()->DoubleValue(); |
| for (const auto& operand : SecondToLastOperands()) |
| result = EvaluateBinary(result, operand->DoubleValue()); |
| return result; |
| } |
| |
| double CSSMathExpressionVariadicOperation::ComputeLengthPx( |
| const CSSToLengthConversionData& data) const { |
| DCHECK_EQ(kCalcLength, Category()); |
| double result = operands_.front()->ComputeLengthPx(data); |
| for (const auto& operand : SecondToLastOperands()) |
| result = EvaluateBinary(result, operand->ComputeLengthPx(data)); |
| return result; |
| } |
| |
| String CSSMathExpressionVariadicOperation::CSSTextAsClamp() const { |
| DCHECK(is_clamp_); |
| DCHECK_EQ(CSSMathOperator::kMax, operator_); |
| DCHECK_EQ(2u, operands_.size()); |
| DCHECK(operands_[1]->IsVariadicOperation()); |
| const auto& nested = To<CSSMathExpressionVariadicOperation>(*operands_[1]); |
| DCHECK(!nested.is_clamp_); |
| DCHECK_EQ(CSSMathOperator::kMin, nested.operator_); |
| DCHECK_EQ(2u, nested.operands_.size()); |
| |
| StringBuilder result; |
| result.Append("clamp("); |
| result.Append(operands_[0]->CustomCSSText()); |
| result.Append(", "); |
| result.Append(nested.operands_[0]->CustomCSSText()); |
| result.Append(", "); |
| result.Append(nested.operands_[1]->CustomCSSText()); |
| result.Append(")"); |
| return result.ToString(); |
| } |
| |
| String CSSMathExpressionVariadicOperation::CustomCSSText() const { |
| if (is_clamp_) |
| return CSSTextAsClamp(); |
| |
| StringBuilder result; |
| result.Append(ToString(operator_)); |
| result.Append('('); |
| result.Append(operands_.front()->CustomCSSText()); |
| for (const auto& operand : SecondToLastOperands()) { |
| result.Append(", "); |
| result.Append(operand->CustomCSSText()); |
| } |
| result.Append(')'); |
| |
| return result.ToString(); |
| } |
| |
| base::Optional<PixelsAndPercent> |
| CSSMathExpressionVariadicOperation::ToPixelsAndPercent( |
| const CSSToLengthConversionData& conversion_data) const { |
| return base::nullopt; |
| } |
| |
| scoped_refptr<const CalculationExpressionNode> |
| CSSMathExpressionVariadicOperation::ToCalculationExpression( |
| const CSSToLengthConversionData& data) const { |
| Vector<scoped_refptr<const CalculationExpressionNode>> operands; |
| operands.ReserveCapacity(operands_.size()); |
| for (const auto& operand : operands_) |
| operands.push_back(operand->ToCalculationExpression(data)); |
| auto expression_type = operator_ == CSSMathOperator::kMin |
| ? CalculationExpressionComparisonNode::Type::kMin |
| : CalculationExpressionComparisonNode::Type::kMax; |
| return CalculationExpressionComparisonNode::CreateSimplified( |
| std::move(operands), expression_type); |
| } |
| |
| bool CSSMathExpressionVariadicOperation::AccumulateLengthArray(CSSLengthArray&, |
| double) const { |
| // When comparison function are involved, we can't resolve the expression into |
| // a length array. |
| // TODO(crbug.com/991672): We need a more general length interpolation |
| // implemetation that doesn't rely on CSSLengthArray. |
| return false; |
| } |
| |
| void CSSMathExpressionVariadicOperation::AccumulateLengthUnitTypes( |
| CSSPrimitiveValue::LengthTypeFlags& types) const { |
| for (const auto& operand : operands_) |
| operand->AccumulateLengthUnitTypes(types); |
| } |
| |
| bool CSSMathExpressionVariadicOperation::IsComputationallyIndependent() const { |
| for (const auto& operand : operands_) { |
| if (!operand->IsComputationallyIndependent()) |
| return false; |
| } |
| return true; |
| } |
| |
| bool CSSMathExpressionVariadicOperation::operator==( |
| const CSSMathExpressionNode& exp) const { |
| if (!exp.IsVariadicOperation()) |
| return false; |
| const CSSMathExpressionVariadicOperation& other = |
| To<CSSMathExpressionVariadicOperation>(exp); |
| if (operator_ != other.operator_) |
| return false; |
| if (operands_.size() != other.operands_.size()) |
| return false; |
| for (wtf_size_t i = 0; i < operands_.size(); ++i) { |
| if (!DataEquivalent(operands_[i], other.operands_[i])) |
| return false; |
| } |
| return true; |
| } |
| |
| CSSPrimitiveValue::UnitType |
| CSSMathExpressionVariadicOperation::ResolvedUnitType() const { |
| if (Category() == kCalcNumber) |
| return CSSPrimitiveValue::UnitType::kNumber; |
| |
| CSSPrimitiveValue::UnitType result = operands_.front()->ResolvedUnitType(); |
| if (result == CSSPrimitiveValue::UnitType::kUnknown) |
| return CSSPrimitiveValue::UnitType::kUnknown; |
| for (const auto& operand : SecondToLastOperands()) { |
| CSSPrimitiveValue::UnitType next = operand->ResolvedUnitType(); |
| if (next == CSSPrimitiveValue::UnitType::kUnknown || next != result) |
| return CSSPrimitiveValue::UnitType::kUnknown; |
| } |
| return result; |
| } |
| |
| #if DCHECK_IS_ON() |
| bool CSSMathExpressionVariadicOperation::InvolvesPercentageComparisons() const { |
| if (Category() == kCalcPercent && operands_.size() > 1u) |
| return true; |
| for (const auto& operand : operands_) { |
| if (operand->InvolvesPercentageComparisons()) |
| return true; |
| } |
| return false; |
| } |
| #endif |
| |
| // ------ End of CSSMathExpressionVariadicOperation member functions |
| |
| static ParseState CheckDepthAndIndex(int* depth, CSSParserTokenRange tokens) { |
| (*depth)++; |
| if (tokens.AtEnd()) |
| return NoMoreTokens; |
| if (*depth > maxExpressionDepth) |
| return TooDeep; |
| return OK; |
| } |
| |
| class CSSMathExpressionNodeParser { |
| STACK_ALLOCATED(); |
| |
| public: |
| CSSMathExpressionNodeParser() {} |
| |
| CSSMathExpressionNode* ParseCalc(CSSParserTokenRange tokens) { |
| tokens.ConsumeWhitespace(); |
| CSSMathExpressionNode* result = ParseValueExpression(tokens, 0); |
| if (!result || !tokens.AtEnd()) |
| return nullptr; |
| return result; |
| } |
| |
| CSSMathExpressionNode* ParseMinOrMax(CSSParserTokenRange tokens, |
| CSSMathOperator op, |
| int depth) { |
| DCHECK(op == CSSMathOperator::kMin || op == CSSMathOperator::kMax); |
| if (CheckDepthAndIndex(&depth, tokens) != OK) |
| return nullptr; |
| |
| CSSMathExpressionVariadicOperation::Operands operands; |
| bool last_token_is_comma = false; |
| while (!tokens.AtEnd()) { |
| tokens.ConsumeWhitespace(); |
| CSSMathExpressionNode* operand = ParseValueExpression(tokens, depth); |
| if (!operand) |
| return nullptr; |
| |
| last_token_is_comma = false; |
| operands.push_back(operand); |
| |
| if (!css_parsing_utils::ConsumeCommaIncludingWhitespace(tokens)) |
| break; |
| last_token_is_comma = true; |
| } |
| |
| if (operands.IsEmpty() || !tokens.AtEnd() || last_token_is_comma) |
| return nullptr; |
| |
| return CSSMathExpressionVariadicOperation::Create(std::move(operands), op); |
| } |
| |
| CSSMathExpressionNode* ParseClamp(CSSParserTokenRange tokens, int depth) { |
| if (CheckDepthAndIndex(&depth, tokens) != OK) |
| return nullptr; |
| |
| CSSMathExpressionNode* min_operand = ParseValueExpression(tokens, depth); |
| if (!min_operand) |
| return nullptr; |
| |
| if (!css_parsing_utils::ConsumeCommaIncludingWhitespace(tokens)) |
| return nullptr; |
| |
| CSSMathExpressionNode* val_operand = ParseValueExpression(tokens, depth); |
| if (!val_operand) |
| return nullptr; |
| |
| if (!css_parsing_utils::ConsumeCommaIncludingWhitespace(tokens)) |
| return nullptr; |
| |
| CSSMathExpressionNode* max_operand = ParseValueExpression(tokens, depth); |
| if (!max_operand) |
| return nullptr; |
| |
| if (!tokens.AtEnd()) |
| return nullptr; |
| |
| // clamp(MIN, VAL, MAX) is identical to max(MIN, min(VAL, MAX)) |
| |
| auto* nested = CSSMathExpressionVariadicOperation::Create( |
| {val_operand, max_operand}, CSSMathOperator::kMin); |
| if (!nested) |
| return nullptr; |
| |
| auto* result = CSSMathExpressionVariadicOperation::Create( |
| {min_operand, nested}, CSSMathOperator::kMax); |
| if (!result) |
| return nullptr; |
| |
| result->SetIsClamp(); |
| return result; |
| } |
| |
| private: |
| CSSMathExpressionNode* ParseValue(CSSParserTokenRange& tokens) { |
| CSSParserToken token = tokens.ConsumeIncludingWhitespace(); |
| if (RuntimeEnabledFeatures::CSSCalcInfinityAndNaNEnabled()) { |
| if (token.Id() == CSSValueID::kInfinity) { |
| return CSSMathExpressionNumericLiteral::Create( |
| std::numeric_limits<double>::infinity(), |
| CSSPrimitiveValue::UnitType::kNumber, false); |
| } |
| if (token.Id() == CSSValueID::kNegativeInfinity) { |
| return CSSMathExpressionNumericLiteral::Create( |
| -std::numeric_limits<double>::infinity(), |
| CSSPrimitiveValue::UnitType::kNumber, false); |
| } |
| if (token.Id() == CSSValueID::kNan) { |
| return CSSMathExpressionNumericLiteral::Create( |
| std::numeric_limits<double>::quiet_NaN(), |
| CSSPrimitiveValue::UnitType::kNumber, false); |
| } |
| } |
| if (!(token.GetType() == kNumberToken || |
| token.GetType() == kPercentageToken || |
| token.GetType() == kDimensionToken)) |
| return nullptr; |
| |
| CSSPrimitiveValue::UnitType type = token.GetUnitType(); |
| if (UnitCategory(type) == kCalcOther) |
| return nullptr; |
| |
| return CSSMathExpressionNumericLiteral::Create( |
| CSSNumericLiteralValue::Create(token.NumericValue(), type), |
| token.GetNumericValueType() == kIntegerValueType); |
| } |
| |
| CSSMathExpressionNode* ParseValueTerm(CSSParserTokenRange& tokens, |
| int depth) { |
| if (CheckDepthAndIndex(&depth, tokens) != OK) |
| return nullptr; |
| |
| if (tokens.Peek().GetType() == kLeftParenthesisToken || |
| tokens.Peek().FunctionId() == CSSValueID::kCalc) { |
| CSSParserTokenRange inner_range = tokens.ConsumeBlock(); |
| tokens.ConsumeWhitespace(); |
| inner_range.ConsumeWhitespace(); |
| CSSMathExpressionNode* result = ParseValueExpression(inner_range, depth); |
| if (!result) |
| return nullptr; |
| result->SetIsNestedCalc(); |
| return result; |
| } |
| |
| if (tokens.Peek().GetType() == kFunctionToken) { |
| CSSValueID function_id = tokens.Peek().FunctionId(); |
| CSSParserTokenRange inner_range = tokens.ConsumeBlock(); |
| tokens.ConsumeWhitespace(); |
| inner_range.ConsumeWhitespace(); |
| switch (function_id) { |
| case CSSValueID::kMin: |
| return ParseMinOrMax(inner_range, CSSMathOperator::kMin, depth); |
| case CSSValueID::kMax: |
| return ParseMinOrMax(inner_range, CSSMathOperator::kMax, depth); |
| case CSSValueID::kClamp: |
| return ParseClamp(inner_range, depth); |
| default: |
| break; |
| } |
| } |
| |
| return ParseValue(tokens); |
| } |
| |
| CSSMathExpressionNode* ParseValueMultiplicativeExpression( |
| CSSParserTokenRange& tokens, |
| int depth) { |
| if (CheckDepthAndIndex(&depth, tokens) != OK) |
| return nullptr; |
| |
| CSSMathExpressionNode* result = ParseValueTerm(tokens, depth); |
| if (!result) |
| return nullptr; |
| |
| while (!tokens.AtEnd()) { |
| CSSMathOperator math_operator = ParseCSSArithmeticOperator(tokens.Peek()); |
| if (math_operator != CSSMathOperator::kMultiply && |
| math_operator != CSSMathOperator::kDivide) |
| break; |
| tokens.ConsumeIncludingWhitespace(); |
| |
| CSSMathExpressionNode* rhs = ParseValueTerm(tokens, depth); |
| if (!rhs) |
| return nullptr; |
| |
| result = CSSMathExpressionBinaryOperation::CreateSimplified( |
| result, rhs, math_operator); |
| |
| if (!result) |
| return nullptr; |
| } |
| |
| return result; |
| } |
| |
| CSSMathExpressionNode* ParseAdditiveValueExpression( |
| CSSParserTokenRange& tokens, |
| int depth) { |
| if (CheckDepthAndIndex(&depth, tokens) != OK) |
| return nullptr; |
| |
| CSSMathExpressionNode* result = |
| ParseValueMultiplicativeExpression(tokens, depth); |
| if (!result) |
| return nullptr; |
| |
| while (!tokens.AtEnd()) { |
| CSSMathOperator math_operator = ParseCSSArithmeticOperator(tokens.Peek()); |
| if (math_operator != CSSMathOperator::kAdd && |
| math_operator != CSSMathOperator::kSubtract) |
| break; |
| if ((&tokens.Peek() - 1)->GetType() != kWhitespaceToken) |
| return nullptr; // calc(1px+ 2px) is invalid |
| tokens.Consume(); |
| if (tokens.Peek().GetType() != kWhitespaceToken) |
| return nullptr; // calc(1px +2px) is invalid |
| tokens.ConsumeIncludingWhitespace(); |
| |
| CSSMathExpressionNode* rhs = |
| ParseValueMultiplicativeExpression(tokens, depth); |
| if (!rhs) |
| return nullptr; |
| |
| result = CSSMathExpressionBinaryOperation::CreateSimplified( |
| result, rhs, math_operator); |
| |
| if (!result) |
| return nullptr; |
| } |
| |
| return result; |
| } |
| |
| CSSMathExpressionNode* ParseValueExpression(CSSParserTokenRange& tokens, |
| int depth) { |
| return ParseAdditiveValueExpression(tokens, depth); |
| } |
| }; |
| |
| scoped_refptr<CalculationValue> CSSMathExpressionNode::ToCalcValue( |
| const CSSToLengthConversionData& conversion_data, |
| ValueRange range, |
| bool allows_negative_percentage_reference) const { |
| if (auto maybe_pixels_and_percent = ToPixelsAndPercent(conversion_data)) { |
| // Clamping if pixels + percent could result in NaN. In special case, |
| // inf px + inf % could evaluate to nan when |
| // allows_negative_percentage_reference is true. |
| if (RuntimeEnabledFeatures::CSSCalcInfinityAndNaNEnabled()) { |
| if (IsNaN(*maybe_pixels_and_percent, |
| allows_negative_percentage_reference)) { |
| maybe_pixels_and_percent = CreateClampedSamePixelsAndPercent( |
| std::numeric_limits<float>::quiet_NaN()); |
| } else { |
| maybe_pixels_and_percent->pixels = CSSValueClampingUtils::ClampLength( |
| maybe_pixels_and_percent->pixels); |
| maybe_pixels_and_percent->percent = CSSValueClampingUtils::ClampLength( |
| maybe_pixels_and_percent->percent); |
| } |
| } |
| return CalculationValue::Create(*maybe_pixels_and_percent, range); |
| } |
| |
| auto value = ToCalculationExpression(conversion_data); |
| if (RuntimeEnabledFeatures::CSSCalcInfinityAndNaNEnabled()) { |
| base::Optional<PixelsAndPercent> evaluated_value = |
| EvaluateValueIfNaNorInfinity(value, |
| allows_negative_percentage_reference); |
| if (evaluated_value.has_value()) { |
| return CalculationValue::Create(evaluated_value.value(), range); |
| } |
| } |
| return CalculationValue::CreateSimplified(value, range); |
| } |
| |
| // static |
| CSSMathExpressionNode* CSSMathExpressionNode::Create( |
| const CalculationValue& calc) { |
| if (calc.IsExpression()) |
| return Create(*calc.GetOrCreateExpression()); |
| return Create(calc.GetPixelsAndPercent()); |
| } |
| |
| // static |
| CSSMathExpressionNode* CSSMathExpressionNode::Create(PixelsAndPercent value) { |
| double percent = value.percent; |
| double pixels = value.pixels; |
| CSSMathOperator op = CSSMathOperator::kAdd; |
| if (pixels < 0) { |
| pixels = -pixels; |
| op = CSSMathOperator::kSubtract; |
| } |
| return CSSMathExpressionBinaryOperation::Create( |
| CSSMathExpressionNumericLiteral::Create( |
| CSSNumericLiteralValue::Create( |
| percent, CSSPrimitiveValue::UnitType::kPercentage), |
| percent == trunc(percent)), |
| CSSMathExpressionNumericLiteral::Create( |
| CSSNumericLiteralValue::Create(pixels, |
| CSSPrimitiveValue::UnitType::kPixels), |
| pixels == trunc(pixels)), |
| op); |
| } |
| |
| // static |
| CSSMathExpressionNode* CSSMathExpressionNode::Create( |
| const CalculationExpressionNode& node) { |
| if (node.IsLeaf()) { |
| const auto& leaf = To<CalculationExpressionLeafNode>(node); |
| return Create(leaf.GetPixelsAndPercent()); |
| } |
| |
| if (node.IsMultiplication()) { |
| const auto& multiplication = |
| To<CalculationExpressionMultiplicationNode>(node); |
| double factor = multiplication.GetFactor(); |
| return CSSMathExpressionBinaryOperation::Create( |
| Create(multiplication.GetChild()), |
| CSSMathExpressionNumericLiteral::Create( |
| CSSNumericLiteralValue::Create( |
| factor, CSSPrimitiveValue::UnitType::kNumber), |
| factor == trunc(factor)), |
| CSSMathOperator::kMultiply); |
| } |
| |
| if (node.IsAdditive()) { |
| const auto& add_or_subtract = To<CalculationExpressionAdditiveNode>(node); |
| auto* lhs = Create(add_or_subtract.GetLeftSide()); |
| auto* rhs = Create(add_or_subtract.GetRightSide()); |
| CSSMathOperator op = add_or_subtract.IsAdd() ? CSSMathOperator::kAdd |
| : CSSMathOperator::kSubtract; |
| return CSSMathExpressionBinaryOperation::Create(lhs, rhs, op); |
| } |
| |
| DCHECK(node.IsComparison()); |
| const auto& comparison = To<CalculationExpressionComparisonNode>(node); |
| CSSMathExpressionVariadicOperation::Operands operands; |
| for (const auto& operand : comparison.GetOperands()) |
| operands.push_back(Create(*operand)); |
| CSSMathOperator op = |
| comparison.IsMin() ? CSSMathOperator::kMin : CSSMathOperator::kMax; |
| return CSSMathExpressionVariadicOperation::Create(std::move(operands), op); |
| } |
| |
| // static |
| CSSMathExpressionNode* CSSMathExpressionNode::ParseCalc( |
| const CSSParserTokenRange& tokens) { |
| CSSMathExpressionNodeParser parser; |
| return parser.ParseCalc(tokens); |
| } |
| |
| // static |
| CSSMathExpressionNode* CSSMathExpressionNode::ParseMin( |
| const CSSParserTokenRange& tokens) { |
| CSSMathExpressionNodeParser parser; |
| return parser.ParseMinOrMax(tokens, CSSMathOperator::kMin, 0); |
| } |
| |
| // static |
| CSSMathExpressionNode* CSSMathExpressionNode::ParseMax( |
| const CSSParserTokenRange& tokens) { |
| CSSMathExpressionNodeParser parser; |
| return parser.ParseMinOrMax(tokens, CSSMathOperator::kMax, 0); |
| } |
| |
| // static |
| CSSMathExpressionNode* CSSMathExpressionNode::ParseClamp( |
| const CSSParserTokenRange& tokens) { |
| CSSMathExpressionNodeParser parser; |
| return parser.ParseClamp(tokens, 0); |
| } |
| |
| } // namespace blink |