blob: 55bbd543ff52e5f0cc41ddcdab639052bdc70f0c [file] [log] [blame]
/*
* Copyright (c) 2013, 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 <algorithm>
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/core/css/css_length_resolver.h"
#include "third_party/blink/renderer/core/css/css_math_operator.h"
#include "third_party/blink/renderer/core/css/css_numeric_literal_value.h"
#include "third_party/blink/renderer/core/css/css_primitive_value.h"
#include "third_party/blink/renderer/core/css/css_property_value_set.h"
#include "third_party/blink/renderer/core/css/css_to_length_conversion_data.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_context.h"
#include "third_party/blink/renderer/core/css/parser/css_tokenizer.h"
#include "third_party/blink/renderer/core/execution_context/security_context.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/platform/geometry/calculation_expression_node.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
namespace blink {
void PrintTo(const CSSLengthArray& length_array, ::std::ostream* os) {
for (double x : length_array.values) {
*os << x << ' ';
}
}
namespace {
void TestAccumulatePixelsAndPercent(
const CSSToLengthConversionData& conversion_data,
CSSMathExpressionNode* expression,
float expected_pixels,
float expected_percent) {
scoped_refptr<const CalculationExpressionNode> value =
expression->ToCalculationExpression(conversion_data);
EXPECT_TRUE(value->IsPixelsAndPercent());
EXPECT_EQ(expected_pixels,
To<CalculationExpressionPixelsAndPercentNode>(*value).Pixels());
EXPECT_EQ(expected_percent,
To<CalculationExpressionPixelsAndPercentNode>(*value).Percent());
std::optional<PixelsAndPercent> pixels_and_percent =
expression->ToPixelsAndPercent(conversion_data);
EXPECT_TRUE(pixels_and_percent.has_value());
EXPECT_EQ(expected_pixels, pixels_and_percent->pixels);
EXPECT_EQ(expected_percent, pixels_and_percent->percent);
}
bool AccumulateLengthArray(String text, CSSLengthArray& length_array) {
auto* property_set =
MakeGarbageCollected<MutableCSSPropertyValueSet>(kHTMLQuirksMode);
property_set->ParseAndSetProperty(CSSPropertyID::kLeft, text,
/* important */ false,
SecureContextMode::kInsecureContext);
return To<CSSPrimitiveValue>(
property_set->GetPropertyCSSValue(CSSPropertyID::kLeft))
->AccumulateLengthArray(length_array);
}
CSSLengthArray& SetLengthArray(String text, CSSLengthArray& length_array) {
std::fill(length_array.values.begin(), length_array.values.end(), 0);
AccumulateLengthArray(text, length_array);
return length_array;
}
TEST(CSSCalculationValue, AccumulatePixelsAndPercent) {
ComputedStyleBuilder builder(*ComputedStyle::GetInitialStyleSingleton());
builder.SetEffectiveZoom(5);
const ComputedStyle* style = builder.TakeStyle();
CSSToLengthConversionData::Flags ignored_flags = 0;
CSSToLengthConversionData conversion_data(
*style, style, style, CSSToLengthConversionData::ViewportSize(nullptr),
CSSToLengthConversionData::ContainerSizes(),
CSSToLengthConversionData::AnchorData(), style->EffectiveZoom(),
ignored_flags);
TestAccumulatePixelsAndPercent(
conversion_data,
CSSMathExpressionNumericLiteral::Create(CSSNumericLiteralValue::Create(
10, CSSPrimitiveValue::UnitType::kPixels)),
50, 0);
TestAccumulatePixelsAndPercent(
conversion_data,
CSSMathExpressionOperation::CreateArithmeticOperation(
CSSMathExpressionNumericLiteral::Create(
CSSNumericLiteralValue::Create(
10, CSSPrimitiveValue::UnitType::kPixels)),
CSSMathExpressionNumericLiteral::Create(
CSSNumericLiteralValue::Create(
20, CSSPrimitiveValue::UnitType::kPixels)),
CSSMathOperator::kAdd),
150, 0);
TestAccumulatePixelsAndPercent(
conversion_data,
CSSMathExpressionOperation::CreateArithmeticOperation(
CSSMathExpressionNumericLiteral::Create(
CSSNumericLiteralValue::Create(
1, CSSPrimitiveValue::UnitType::kInches)),
CSSMathExpressionNumericLiteral::Create(
CSSNumericLiteralValue::Create(
2, CSSPrimitiveValue::UnitType::kNumber)),
CSSMathOperator::kMultiply),
960, 0);
TestAccumulatePixelsAndPercent(
conversion_data,
CSSMathExpressionOperation::CreateArithmeticOperation(
CSSMathExpressionOperation::CreateArithmeticOperation(
CSSMathExpressionNumericLiteral::Create(
CSSNumericLiteralValue::Create(
50, CSSPrimitiveValue::UnitType::kPixels)),
CSSMathExpressionNumericLiteral::Create(
CSSNumericLiteralValue::Create(
0.25, CSSPrimitiveValue::UnitType::kNumber)),
CSSMathOperator::kMultiply),
CSSMathExpressionOperation::CreateArithmeticOperation(
CSSMathExpressionNumericLiteral::Create(
CSSNumericLiteralValue::Create(
20, CSSPrimitiveValue::UnitType::kPixels)),
CSSMathExpressionNumericLiteral::Create(
CSSNumericLiteralValue::Create(
40, CSSPrimitiveValue::UnitType::kPercentage)),
CSSMathOperator::kSubtract),
CSSMathOperator::kSubtract),
-37.5, 40);
}
TEST(CSSCalculationValue, RefCount) {
scoped_refptr<const CalculationValue> calc = CalculationValue::Create(
PixelsAndPercent(1, 2, /*has_explicit_pixels=*/true,
/*has_explicit_percent=*/true),
Length::ValueRange::kAll);
// FIXME: Test the Length construction without using the ref count value.
EXPECT_TRUE(calc->HasOneRef());
{
Length length_a(calc);
EXPECT_FALSE(calc->HasOneRef());
Length length_b;
length_b = length_a;
Length length_c(calc);
length_c = length_a;
Length length_d(CalculationValue::Create(
PixelsAndPercent(1, 2, /*has_explicit_pixels=*/true,
/*has_explicit_percent=*/true),
Length::ValueRange::kAll));
length_d = length_a;
}
EXPECT_TRUE(calc->HasOneRef());
}
TEST(CSSCalculationValue, AddToLengthUnitValues) {
CSSLengthArray expectation, actual;
EXPECT_EQ(expectation.values, SetLengthArray("0", actual).values);
expectation.values.at(CSSPrimitiveValue::kUnitTypePixels) = 10;
EXPECT_EQ(expectation.values, SetLengthArray("10px", actual).values);
expectation.values.at(CSSPrimitiveValue::kUnitTypePixels) = 0;
expectation.values.at(CSSPrimitiveValue::kUnitTypePercentage) = 20;
EXPECT_EQ(expectation.values, SetLengthArray("20%", actual).values);
expectation.values.at(CSSPrimitiveValue::kUnitTypePixels) = 30;
expectation.values.at(CSSPrimitiveValue::kUnitTypePercentage) = -40;
EXPECT_EQ(expectation.values,
SetLengthArray("calc(30px - 40%)", actual).values);
expectation.values.at(CSSPrimitiveValue::kUnitTypePixels) = 90;
expectation.values.at(CSSPrimitiveValue::kUnitTypePercentage) = 10;
EXPECT_EQ(expectation.values,
SetLengthArray("calc(1in + 10% - 6px)", actual).values);
expectation.values.at(CSSPrimitiveValue::kUnitTypePixels) = 15;
expectation.values.at(CSSPrimitiveValue::kUnitTypeFontSize) = 20;
expectation.values.at(CSSPrimitiveValue::kUnitTypePercentage) = -40;
EXPECT_EQ(
expectation.values,
SetLengthArray("calc((1 * 2) * (5px + 20em / 2) - 80% / (3 - 1) + 5px)",
actual)
.values);
}
TEST(CSSCalculationValue, CSSLengthArrayUnits) {
CSSLengthArray unused;
// Supported units:
EXPECT_TRUE(AccumulateLengthArray("1px", unused));
EXPECT_TRUE(AccumulateLengthArray("1%", unused));
EXPECT_TRUE(AccumulateLengthArray("1em", unused));
EXPECT_TRUE(AccumulateLengthArray("1ex", unused));
EXPECT_TRUE(AccumulateLengthArray("1rem", unused));
EXPECT_TRUE(AccumulateLengthArray("1ch", unused));
EXPECT_TRUE(AccumulateLengthArray("1vw", unused));
EXPECT_TRUE(AccumulateLengthArray("1vh", unused));
EXPECT_TRUE(AccumulateLengthArray("1vi", unused));
EXPECT_TRUE(AccumulateLengthArray("1vb", unused));
EXPECT_TRUE(AccumulateLengthArray("1vmin", unused));
EXPECT_TRUE(AccumulateLengthArray("1vmax", unused));
// Unsupported units:
EXPECT_FALSE(AccumulateLengthArray("1svw", unused));
EXPECT_FALSE(AccumulateLengthArray("1svh", unused));
EXPECT_FALSE(AccumulateLengthArray("1svi", unused));
EXPECT_FALSE(AccumulateLengthArray("1svb", unused));
EXPECT_FALSE(AccumulateLengthArray("1svmin", unused));
EXPECT_FALSE(AccumulateLengthArray("1svmax", unused));
EXPECT_FALSE(AccumulateLengthArray("1lvw", unused));
EXPECT_FALSE(AccumulateLengthArray("1lvh", unused));
EXPECT_FALSE(AccumulateLengthArray("1lvi", unused));
EXPECT_FALSE(AccumulateLengthArray("1lvb", unused));
EXPECT_FALSE(AccumulateLengthArray("1lvmin", unused));
EXPECT_FALSE(AccumulateLengthArray("1lvmax", unused));
EXPECT_FALSE(AccumulateLengthArray("1dvw", unused));
EXPECT_FALSE(AccumulateLengthArray("1dvh", unused));
EXPECT_FALSE(AccumulateLengthArray("1dvi", unused));
EXPECT_FALSE(AccumulateLengthArray("1dvb", unused));
EXPECT_FALSE(AccumulateLengthArray("1dvmin", unused));
EXPECT_FALSE(AccumulateLengthArray("1dvmax", unused));
EXPECT_FALSE(AccumulateLengthArray("1cqw", unused));
EXPECT_FALSE(AccumulateLengthArray("1cqh", unused));
EXPECT_FALSE(AccumulateLengthArray("1cqi", unused));
EXPECT_FALSE(AccumulateLengthArray("1cqb", unused));
EXPECT_FALSE(AccumulateLengthArray("1cqmin", unused));
EXPECT_FALSE(AccumulateLengthArray("1cqmax", unused));
EXPECT_TRUE(AccumulateLengthArray("calc(1em + calc(1ex + 1px))", unused));
EXPECT_FALSE(AccumulateLengthArray("calc(1dvh + calc(1ex + 1px))", unused));
EXPECT_FALSE(AccumulateLengthArray("calc(1em + calc(1dvh + 1px))", unused));
EXPECT_FALSE(AccumulateLengthArray("calc(1em + calc(1ex + 1dvh))", unused));
}
using Flag = CSSMathExpressionNode::Flag;
using Flags = CSSMathExpressionNode::Flags;
TEST(CSSMathExpressionNode, TestParseDeeplyNestedExpression) {
enum Kind {
kCalc,
kMin,
kMax,
kClamp,
};
// Ref: https://bugs.chromium.org/p/chromium/issues/detail?id=1211283
const struct TestCase {
const Kind kind;
const int nest_num;
const bool expected;
} test_cases[] = {
{kCalc, 1, true},
{kCalc, 10, true},
{kCalc, kMaxExpressionDepth - 1, true},
{kCalc, kMaxExpressionDepth, false},
{kCalc, kMaxExpressionDepth + 1, false},
{kMin, 1, true},
{kMin, 10, true},
{kMin, kMaxExpressionDepth - 1, true},
{kMin, kMaxExpressionDepth, false},
{kMin, kMaxExpressionDepth + 1, false},
{kMax, 1, true},
{kMax, 10, true},
{kMax, kMaxExpressionDepth - 1, true},
{kMax, kMaxExpressionDepth, false},
{kMax, kMaxExpressionDepth + 1, false},
{kClamp, 1, true},
{kClamp, 10, true},
{kClamp, kMaxExpressionDepth - 1, true},
{kClamp, kMaxExpressionDepth, false},
{kClamp, kMaxExpressionDepth + 1, false},
};
for (const auto& test_case : test_cases) {
std::stringstream ss;
// Make nested expression as follows:
// calc(1px + calc(1px + calc(1px)))
// min(1px, 1px + min(1px, 1px + min(1px, 1px)))
// max(1px, 1px + max(1px, 1px + max(1px, 1px)))
// clamp(1px, 1px, 1px + clamp(1px, 1px, 1px + clamp(1px, 1px, 1px)))
for (int i = 0; i < test_case.nest_num; i++) {
if (i) {
ss << " + ";
}
switch (test_case.kind) {
case kCalc:
ss << "calc(1px";
break;
case kMin:
ss << "min(1px, 1px";
break;
case kMax:
ss << "max(1px, 1px";
break;
case kClamp:
ss << "clamp(1px, 1px, 1px";
break;
}
}
for (int i = 0; i < test_case.nest_num; i++) {
ss << ")";
}
CSSTokenizer tokenizer(String(ss.str().c_str()));
const auto tokens = tokenizer.TokenizeToEOF();
const CSSParserTokenRange range(tokens);
const CSSParserContext* context = MakeGarbageCollected<CSSParserContext>(
kHTMLStandardMode, SecureContextMode::kInsecureContext);
const CSSMathExpressionNode* res = CSSMathExpressionNode::ParseMathFunction(
CSSValueID::kCalc, range, *context, Flags({Flag::AllowPercent}),
kCSSAnchorQueryTypesNone);
if (test_case.expected) {
EXPECT_TRUE(res);
EXPECT_TRUE(!res->HasPercentage());
} else {
EXPECT_FALSE(res);
}
}
}
TEST(CSSMathExpressionNode, TestSteppedValueFunctions) {
const struct TestCase {
const std::string input;
const double output;
} test_cases[] = {
{"round(10, 10)", 10.0f},
{"calc(round(up, 101, 10))", 110.0f},
{"calc(round(down, 106, 10))", 100.0f},
{"mod(18,5)", 3.0f},
{"rem(18,5)", 3.0f},
};
for (const auto& test_case : test_cases) {
CSSTokenizer tokenizer(String(test_case.input.c_str()));
const auto tokens = tokenizer.TokenizeToEOF();
const CSSParserTokenRange range(tokens);
const CSSParserContext* context = MakeGarbageCollected<CSSParserContext>(
kHTMLStandardMode, SecureContextMode::kInsecureContext);
const CSSMathExpressionNode* res = CSSMathExpressionNode::ParseMathFunction(
CSSValueID::kCalc, range, *context, Flags({Flag::AllowPercent}),
kCSSAnchorQueryTypesNone);
EXPECT_EQ(res->DoubleValue(), test_case.output);
CSSToLengthConversionData resolver{};
scoped_refptr<const CalculationExpressionNode> node =
res->ToCalculationExpression(resolver);
EXPECT_EQ(node->Evaluate(FLT_MAX, {}), test_case.output);
EXPECT_TRUE(!res->HasPercentage());
}
}
TEST(CSSMathExpressionNode, TestSteppedValueFunctionsToCalculationExpression) {
const struct TestCase {
const CSSMathOperator op;
const double output;
} test_cases[] = {
{CSSMathOperator::kRoundNearest, 10}, {CSSMathOperator::kRoundUp, 10},
{CSSMathOperator::kRoundDown, 10}, {CSSMathOperator::kRoundToZero, 10},
{CSSMathOperator::kMod, 0}, {CSSMathOperator::kRem, 0}};
for (const auto& test_case : test_cases) {
CSSMathExpressionOperation::Operands operands{
CSSMathExpressionNumericLiteral::Create(
10, CSSPrimitiveValue::UnitType::kNumber),
CSSMathExpressionNumericLiteral::Create(
10, CSSPrimitiveValue::UnitType::kNumber)};
const auto* operation = MakeGarbageCollected<CSSMathExpressionOperation>(
kCalcNumber, std::move(operands), test_case.op);
CSSToLengthConversionData resolver{};
scoped_refptr<const CalculationExpressionNode> node =
operation->ToCalculationExpression(resolver);
EXPECT_EQ(node->Evaluate(FLT_MAX, {}), test_case.output);
const CSSMathExpressionNode* css_node =
CSSMathExpressionOperation::Create(*node);
EXPECT_NE(css_node, nullptr);
}
}
TEST(CSSMathExpressionNode, TestSteppedValueFunctionsSerialization) {
const struct TestCase {
const String input;
} test_cases[] = {
{"round(10%, 10%)"}, {"round(up, 10%, 10%)"},
{"round(down, 10%, 10%)"}, {"round(to-zero, 10%, 10%)"},
{"mod(10%, 10%)"}, {"rem(10%, 10%)"},
};
for (const auto& test_case : test_cases) {
CSSTokenizer tokenizer(test_case.input);
const auto tokens = tokenizer.TokenizeToEOF();
const CSSParserTokenRange range(tokens);
const CSSParserContext* context = MakeGarbageCollected<CSSParserContext>(
kHTMLStandardMode, SecureContextMode::kInsecureContext);
const CSSMathExpressionNode* res = CSSMathExpressionNode::ParseMathFunction(
CSSValueID::kCalc, range, *context, Flags({Flag::AllowPercent}),
kCSSAnchorQueryTypesNone);
EXPECT_EQ(res->CustomCSSText(), test_case.input);
}
}
TEST(CSSMathExpressionNode, TestExponentialFunctions) {
const struct TestCase {
const std::string input;
const double output;
} test_cases[] = {
{"hypot(3, 4)", 5.0f}, {"log(100, 10)", 2.0f}, {"sqrt(144)", 12.0f},
{"exp(0)", 1.0f}, {"pow(2, 2)", 4.0f},
};
for (const auto& test_case : test_cases) {
CSSTokenizer tokenizer(String(test_case.input.c_str()));
const auto tokens = tokenizer.TokenizeToEOF();
const CSSParserTokenRange range(tokens);
const CSSParserContext* context = MakeGarbageCollected<CSSParserContext>(
kHTMLStandardMode, SecureContextMode::kInsecureContext);
const CSSMathExpressionNode* res = CSSMathExpressionNode::ParseMathFunction(
CSSValueID::kCalc, range, *context, Flags({Flag::AllowPercent}),
kCSSAnchorQueryTypesNone);
EXPECT_EQ(res->DoubleValue(), test_case.output);
CSSToLengthConversionData resolver;
scoped_refptr<const CalculationExpressionNode> node =
res->ToCalculationExpression(resolver);
EXPECT_EQ(node->Evaluate(FLT_MAX, {}), test_case.output);
EXPECT_TRUE(!res->HasPercentage());
}
}
TEST(CSSMathExpressionNode, TestExponentialFunctionsSerialization) {
const struct TestCase {
const String input;
const bool can_be_simplified_with_conversion_data;
} test_cases[] = {
{"hypot(3em, 4rem)", true},
{"hypot(3%, 4%)", false},
{"hypot(hypot(3%, 4%), 5em)", false},
};
for (const auto& test_case : test_cases) {
CSSTokenizer tokenizer(test_case.input);
const auto tokens = tokenizer.TokenizeToEOF();
const CSSParserTokenRange range(tokens);
const CSSParserContext* context = MakeGarbageCollected<CSSParserContext>(
kHTMLStandardMode, SecureContextMode::kInsecureContext);
const CSSMathExpressionNode* res = CSSMathExpressionNode::ParseMathFunction(
CSSValueID::kCalc, range, *context, Flags({Flag::AllowPercent}),
kCSSAnchorQueryTypesNone);
EXPECT_EQ(res->CustomCSSText(), test_case.input);
EXPECT_EQ(!res->HasPercentage(),
test_case.can_be_simplified_with_conversion_data);
}
}
TEST(CSSMathExpressionNode, TestExponentialFunctionsToCalculationExpression) {
const struct TestCase {
const CSSMathOperator op;
const double output;
} test_cases[] = {{CSSMathOperator::kHypot, 5.0f}};
for (const auto& test_case : test_cases) {
CSSMathExpressionOperation::Operands operands{
CSSMathExpressionNumericLiteral::Create(
3.0f, CSSPrimitiveValue::UnitType::kNumber),
CSSMathExpressionNumericLiteral::Create(
4.0f, CSSPrimitiveValue::UnitType::kNumber)};
const auto* operation = MakeGarbageCollected<CSSMathExpressionOperation>(
kCalcNumber, std::move(operands), test_case.op);
CSSToLengthConversionData resolver{};
scoped_refptr<const CalculationExpressionNode> node =
operation->ToCalculationExpression(resolver);
EXPECT_EQ(node->Evaluate(FLT_MAX, {}), test_case.output);
const CSSMathExpressionNode* css_node =
CSSMathExpressionOperation::Create(*node);
EXPECT_NE(css_node, nullptr);
}
}
TEST(CSSMathExpressionNode, IdentifierLiteralConversion) {
const CSSMathExpressionIdentifierLiteral* css_node =
CSSMathExpressionIdentifierLiteral::Create(AtomicString("test"));
EXPECT_TRUE(css_node->IsIdentifierLiteral());
EXPECT_EQ(css_node->Category(), kCalcIdent);
EXPECT_EQ(css_node->GetValue(), AtomicString("test"));
scoped_refptr<const CalculationExpressionNode> calc_node =
css_node->ToCalculationExpression(CSSToLengthConversionData());
EXPECT_TRUE(calc_node->IsIdentifier());
EXPECT_EQ(To<CalculationExpressionIdentifierNode>(*calc_node).Value(),
AtomicString("test"));
auto* node = CSSMathExpressionNode::Create(*calc_node);
EXPECT_TRUE(node->IsIdentifierLiteral());
EXPECT_EQ(To<CSSMathExpressionIdentifierLiteral>(node)->GetValue(),
AtomicString("test"));
}
TEST(CSSMathExpressionNode, TestProgressNotation) {
const struct TestCase {
const std::string input;
const double output;
} test_cases[] = {
{"progress(1px from 0px to 4px)", 0.25f},
{"progress(10deg from 0deg to 10deg)", 1.0f},
{"progress(progress(10% from 0% to 40%) * 1px from 0.5px to 1px)", -0.5f},
};
for (const auto& test_case : test_cases) {
CSSTokenizer tokenizer(String(test_case.input.c_str()));
const auto tokens = tokenizer.TokenizeToEOF();
const CSSParserTokenRange range(tokens);
const CSSParserContext* context = MakeGarbageCollected<CSSParserContext>(
kHTMLStandardMode, SecureContextMode::kInsecureContext);
const CSSMathExpressionNode* res = CSSMathExpressionNode::ParseMathFunction(
CSSValueID::kCalc, range, *context, Flags({Flag::AllowPercent}),
kCSSAnchorQueryTypesNone);
EXPECT_EQ(res->DoubleValue(), test_case.output);
CSSToLengthConversionData resolver;
scoped_refptr<const CalculationExpressionNode> node =
res->ToCalculationExpression(resolver);
EXPECT_EQ(node->Evaluate(FLT_MAX, {}), test_case.output);
}
}
TEST(CSSMathExpressionNode, TestProgressNotationComplex) {
const struct TestCase {
const std::string input;
const double output;
} test_cases[] = {
{"progress(abs(5%) from hypot(3%, 4%) to 10%)", 0.0f},
};
for (const auto& test_case : test_cases) {
CSSTokenizer tokenizer(String(test_case.input.c_str()));
const auto tokens = tokenizer.TokenizeToEOF();
const CSSParserTokenRange range(tokens);
const CSSParserContext* context = MakeGarbageCollected<CSSParserContext>(
kHTMLStandardMode, SecureContextMode::kInsecureContext);
const CSSMathExpressionNode* res = CSSMathExpressionNode::ParseMathFunction(
CSSValueID::kCalc, range, *context, Flags({Flag::AllowPercent}),
kCSSAnchorQueryTypesNone);
EXPECT_TRUE(res);
EXPECT_TRUE(res->IsOperation());
CSSToLengthConversionData resolver;
scoped_refptr<const CalculationExpressionNode> node =
res->ToCalculationExpression(resolver);
// Very close to 0.0f, but not exactly 0.0f for unknown reason.
EXPECT_NEAR(node->Evaluate(FLT_MAX, {}), test_case.output, 0.001);
}
}
TEST(CSSMathExpressionNode, TestInvalidProgressNotation) {
const std::string test_cases[] = {
"progress(1% from 0px to 4px)",
"progress(1px, 0px, 4px)",
"progress(10deg from 0 to 10deg)",
};
for (const auto& test_case : test_cases) {
CSSTokenizer tokenizer(String(test_case.c_str()));
const auto tokens = tokenizer.TokenizeToEOF();
const CSSParserTokenRange range(tokens);
const CSSParserContext* context = MakeGarbageCollected<CSSParserContext>(
kHTMLStandardMode, SecureContextMode::kInsecureContext);
const CSSMathExpressionNode* res = CSSMathExpressionNode::ParseMathFunction(
CSSValueID::kCalc, range, *context, Flags({Flag::AllowPercent}),
kCSSAnchorQueryTypesNone);
EXPECT_FALSE(res);
}
}
TEST(CSSMathExpressionNode, TestFunctionsWithNumberReturn) {
const struct TestCase {
const String input;
const CalculationResultCategory category;
const double output;
} test_cases[] = {
{"10 * sign(10%)", CalculationResultCategory::kCalcNumber, 10.0},
{"10px * sign(10%)", CalculationResultCategory::kCalcLength, 10.0},
{"10 + 2 * (1 + sign(10%))", CalculationResultCategory::kCalcNumber,
14.0},
};
for (const auto& test_case : test_cases) {
CSSTokenizer tokenizer(test_case.input);
const auto tokens = tokenizer.TokenizeToEOF();
const CSSParserTokenRange range(tokens);
const CSSParserContext* context = MakeGarbageCollected<CSSParserContext>(
kHTMLStandardMode, SecureContextMode::kInsecureContext);
const CSSMathExpressionNode* css_node =
CSSMathExpressionNode::ParseMathFunction(
CSSValueID::kCalc, range, *context, Flags({Flag::AllowPercent}),
kCSSAnchorQueryTypesNone);
EXPECT_EQ(css_node->CustomCSSText(), test_case.input);
EXPECT_EQ(css_node->Category(), test_case.category);
EXPECT_TRUE(css_node->IsOperation());
scoped_refptr<const CalculationExpressionNode> calc_node =
css_node->ToCalculationExpression(CSSToLengthConversionData());
EXPECT_TRUE(calc_node->IsOperation());
EXPECT_EQ(calc_node->Evaluate(100.0, {}), test_case.output);
css_node = CSSMathExpressionNode::Create(*calc_node);
EXPECT_EQ(css_node->CustomCSSText(), test_case.input);
}
}
} // anonymous namespace
} // namespace blink