Skip to content
Permalink
Browse files
Add support for CSSNumericValue.parse()
https://bugs.webkit.org/show_bug.cgi?id=246775

Reviewed by Geoffrey Garen.

Add support for CSSNumericValue.parse():
- https://drafts.css-houdini.org/css-typed-om/#dom-cssnumericvalue-parse

* LayoutTests/imported/w3c/web-platform-tests/css/css-typed-om/stylevalue-normalization/normalize-numeric.tentative-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/css/css-typed-om/stylevalue-subclasses/numeric-objects/parse.tentative-expected.txt:
Rebaseline WPT tests now that more checks are passing.

* Source/WebCore/css/calc/CSSCalcPrimitiveValueNode.h:
* Source/WebCore/css/typedom/CSSNumericValue.cpp:
(WebCore::canonicalOperator):
(WebCore::canCombineNodes):
(WebCore::negateOrInvertIfRequired):
(WebCore::convertToExceptionOrNumericValue):
(WebCore::reifyMathExpression):
(WebCore::reifyMathExpressions):
(WebCore::CSSNumericValue::addInternal):
(WebCore::CSSNumericValue::multiplyInternal):
(WebCore::CSSNumericValue::div):
(WebCore::CSSNumericValue::min):
(WebCore::CSSNumericValue::max):
(WebCore::CSSNumericValue::parse):

Canonical link: https://commits.webkit.org/255791@main
  • Loading branch information
cdumez committed Oct 20, 2022
1 parent b5cc1fa commit 31a27bfdb5fc7151d1ab5338efce53651e1101da
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 41 deletions.
@@ -1,8 +1,8 @@

FAIL Normalizing a <number> returns a number CSSUnitValue Not implemented Error
FAIL Normalizing a <percentage> returns a percent CSSUnitValue Not implemented Error
FAIL Normalizing a <dimension> returns a CSSUnitValue with the correct unit Not implemented Error
FAIL Normalizing a <number> with a unitless zero returns 0 Not implemented Error
FAIL Normalizing a <calc> returns simplified expression Not implemented Error
PASS Normalizing a <number> returns a number CSSUnitValue
PASS Normalizing a <percentage> returns a percent CSSUnitValue
PASS Normalizing a <dimension> returns a CSSUnitValue with the correct unit
PASS Normalizing a <number> with a unitless zero returns 0
FAIL Normalizing a <calc> returns simplified expression assert_approx_equals: expected 4 +/- 0.000001 but got 1
PASS Normalizing a <dimension> with a unitless zero returns 0px

@@ -1,11 +1,11 @@

FAIL Parsing an invalid string throws SyntaxError assert_throws_dom: function "() => CSSNumericValue.parse('%#(')" threw object "NotSupportedError: Not implemented Error" that is not a DOMException SyntaxError: property "code" is equal to 9, expected 12
FAIL Parsing a string with a non numeric token throws SyntaxError assert_throws_dom: function "() => CSSNumericValue.parse('auto')" threw object "NotSupportedError: Not implemented Error" that is not a DOMException SyntaxError: property "code" is equal to 9, expected 12
FAIL Parsing a string with left over numeric tokens throws SyntaxError assert_throws_dom: function "() => CSSNumericValue.parse('1 2')" threw object "NotSupportedError: Not implemented Error" that is not a DOMException SyntaxError: property "code" is equal to 9, expected 12
FAIL Parsing a calc with incompatible units throws a SyntaxError assert_throws_dom: function "() => CSSNumericValue.parse('calc(calc(1px * 2s) + 3%)')" threw object "NotSupportedError: Not implemented Error" that is not a DOMException SyntaxError: property "code" is equal to 9, expected 12
FAIL Parsing a <dimension-token> with invalid units throws a SyntaxError assert_throws_dom: function "() => CSSNumericValue.parse('1xyz')" threw object "NotSupportedError: Not implemented Error" that is not a DOMException SyntaxError: property "code" is equal to 9, expected 12
FAIL Parsing ignores surrounding spaces Not implemented Error
FAIL Parsing min() is successful Not implemented Error
FAIL Parsing max() is successful Not implemented Error
FAIL Parsing clamp() is successful Not implemented Error
PASS Parsing an invalid string throws SyntaxError
PASS Parsing a string with a non numeric token throws SyntaxError
PASS Parsing a string with left over numeric tokens throws SyntaxError
PASS Parsing a calc with incompatible units throws a SyntaxError
PASS Parsing a <dimension-token> with invalid units throws a SyntaxError
PASS Parsing ignores surrounding spaces
PASS Parsing min() is successful
PASS Parsing max() is successful
PASS Parsing clamp() is successful

@@ -2,7 +2,9 @@
PASS Setting the current time of a pending animation to unresolved does not throw a TypeError
PASS Setting the current time of a playing animation to unresolved throws a TypeError
PASS Setting the current time of a paused animation to unresolved throws a TypeError
FAIL Validate different value types that can be used to set current time promise_test: Unhandled rejection with value: object "NotSupportedError: Not implemented Error"
FAIL Validate different value types that can be used to set current time assert_throws_dom: function "() => {
animation.currentTime = CSSNumericValue.parse("30%");
}" threw object "TypeError: The provided value is non-finite" that is not a DOMException NotSupportedError: property "code" is equal to undefined, expected 9
PASS Setting the current time of a pausing animation applies a pending playback rate
PASS Setting the current time after the end with a positive playback rate
PASS Setting a negative current time with a positive playback rate
@@ -1,5 +1,7 @@

FAIL Validate different value types that can be used to set start time promise_test: Unhandled rejection with value: object "NotSupportedError: Not implemented Error"
FAIL Validate different value types that can be used to set start time assert_throws_dom: function "() => {
animation.startTime = CSSNumericValue.parse("30%");
}" threw object "TypeError: The provided value is non-finite" that is not a DOMException NotSupportedError: property "code" is equal to undefined, expected 9
PASS Setting the start time of an animation without an active timeline
PASS Setting an unresolved start time an animation without an active timeline does not clear the current time
PASS Setting the start time clears the hold time
@@ -62,14 +62,14 @@ class CSSCalcPrimitiveValueNode final : public CSSCalcExpressionNode {
void canonicalizeUnit();

const CSSPrimitiveValue& value() const { return m_value.get(); }
double doubleValue(CSSUnitType) const final;

private:
bool isZero() const final;
bool equals(const CSSCalcExpressionNode& other) const final;
Type type() const final { return CssCalcPrimitiveValue; }

std::unique_ptr<CalcExpressionNode> createCalcExpression(const CSSToLengthConversionData&) const final;
double doubleValue(CSSUnitType) const final;

double computeLengthPx(const CSSToLengthConversionData&) const final;
void collectDirectComputationalDependencies(HashSet<CSSPropertyID>&) const final;
@@ -32,6 +32,14 @@

#if ENABLE(CSS_TYPED_OM)

#include "CSSCalcExpressionNode.h"
#include "CSSCalcExpressionNodeParser.h"
#include "CSSCalcInvertNode.h"
#include "CSSCalcNegateNode.h"
#include "CSSCalcOperationNode.h"
#include "CSSCalcPrimitiveValueNode.h"
#include "CSSCalcSymbolTable.h"
#include "CSSMathClamp.h"
#include "CSSMathInvert.h"
#include "CSSMathMax.h"
#include "CSSMathMin.h"
@@ -41,6 +49,9 @@
#include "CSSNumericArray.h"
#include "CSSNumericFactory.h"
#include "CSSNumericType.h"
#include "CSSParserTokenRange.h"
#include "CSSPropertyParserHelpers.h"
#include "CSSTokenizer.h"
#include "CSSUnitValue.h"
#include "ExceptionOr.h"
#include <wtf/Algorithms.h>
@@ -51,6 +62,128 @@ namespace WebCore {

WTF_MAKE_ISO_ALLOCATED_IMPL(CSSNumericValue);

#define RETURN_IF_EXCEPTION(resultVariable, expression) \
auto resultOrException = expression; \
if (resultOrException.hasException()) \
return resultOrException.releaseException(); \
auto resultVariable = resultOrException.releaseReturnValue()

static CalcOperator canonicalOperator(CalcOperator calcOperator)
{
if (calcOperator == CalcOperator::Add || calcOperator == CalcOperator::Subtract)
return CalcOperator::Add;
return CalcOperator::Multiply;
}

static bool canCombineNodes(const CSSCalcOperationNode& root, const CSSCalcExpressionNode& node)
{
auto operationNode = dynamicDowncast<CSSCalcOperationNode>(node);
return operationNode && canonicalOperator(root.calcOperator()) == canonicalOperator(operationNode->calcOperator());
}

static Ref<CSSNumericValue> negateOrInvertIfRequired(CalcOperator parentOperator, Ref<CSSNumericValue>&& value)
{
if (parentOperator == CalcOperator::Subtract)
return CSSMathNegate::create(WTFMove(value));
if (parentOperator == CalcOperator::Divide)
return CSSMathInvert::create(WTFMove(value));
return WTFMove(value);
}

template<typename T> static ExceptionOr<Ref<CSSNumericValue>> convertToExceptionOrNumericValue(ExceptionOr<Ref<T>>&& input)
{
RETURN_IF_EXCEPTION(result, WTFMove(input));
return static_reference_cast<CSSNumericValue>(WTFMove(result));
}

template<typename T> static ExceptionOr<Ref<CSSNumericValue>> convertToExceptionOrNumericValue(Ref<T>&& input)
{
return static_reference_cast<CSSNumericValue>(WTFMove(input));
}

static ExceptionOr<Ref<CSSNumericValue>> reifyMathExpression(const CSSCalcExpressionNode&);

static ExceptionOr<Ref<CSSNumericValue>> reifyMathExpression(const CSSCalcPrimitiveValueNode& root)
{
auto unit = root.primitiveType();
return convertToExceptionOrNumericValue(CSSUnitValue::create(root.doubleValue(unit), unit));
}

static ExceptionOr<Ref<CSSNumericValue>> reifyMathExpression(const CSSCalcNegateNode& root)
{
RETURN_IF_EXCEPTION(child, reifyMathExpression(root.child()));
return convertToExceptionOrNumericValue(CSSMathNegate::create(WTFMove(child)));
}

static ExceptionOr<Ref<CSSNumericValue>> reifyMathExpression(const CSSCalcInvertNode& root)
{
RETURN_IF_EXCEPTION(child, reifyMathExpression(root.child()));
return convertToExceptionOrNumericValue(CSSMathInvert::create(WTFMove(child)));
}

static ExceptionOr<Vector<Ref<CSSNumericValue>>> reifyMathExpressions(const Vector<Ref<CSSCalcExpressionNode>>& nodes)
{
Vector<Ref<CSSNumericValue>> values;
values.reserveInitialCapacity(nodes.size());
for (auto& node : nodes) {
RETURN_IF_EXCEPTION(value, reifyMathExpression(node));
values.uncheckedAppend(WTFMove(value));
}
return values;
}

static ExceptionOr<Ref<CSSNumericValue>> reifyMathExpression(const CSSCalcOperationNode& root)
{
if (root.calcOperator() == CalcOperator::Min) {
RETURN_IF_EXCEPTION(values, reifyMathExpressions(root.children()));
return convertToExceptionOrNumericValue(CSSMathMin::create(WTFMove(values)));
}
if (root.calcOperator() == CalcOperator::Max) {
RETURN_IF_EXCEPTION(values, reifyMathExpressions(root.children()));
return convertToExceptionOrNumericValue(CSSMathMax::create(WTFMove(values)));
}
if (root.calcOperator() == CalcOperator::Clamp) {
RETURN_IF_EXCEPTION(values, reifyMathExpressions(root.children()));
return convertToExceptionOrNumericValue(CSSMathClamp::create(WTFMove(values[0]), WTFMove(values[1]), WTFMove(values[2])));
}

Vector<Ref<CSSNumericValue>> values;
const CSSCalcExpressionNode* currentNode = &root;
do {
auto* binaryOperation = downcast<CSSCalcOperationNode>(currentNode);
ASSERT(binaryOperation->children().size() == 2);
RETURN_IF_EXCEPTION(value, reifyMathExpression(binaryOperation->children()[1].get()));
values.append(negateOrInvertIfRequired(binaryOperation->calcOperator(), WTFMove(value)));
currentNode = binaryOperation->children()[0].ptr();
} while (canCombineNodes(root, *currentNode));

ASSERT(currentNode);
RETURN_IF_EXCEPTION(reifiedCurrentNode, reifyMathExpression(*currentNode));
values.append(WTFMove(reifiedCurrentNode));

std::reverse(values.begin(), values.end());
if (root.calcOperator() == CalcOperator::Add || root.calcOperator() == CalcOperator::Subtract)
return convertToExceptionOrNumericValue(CSSMathSum::create(WTFMove(values)));
return convertToExceptionOrNumericValue(CSSMathProduct::create(WTFMove(values)));
}

// https://drafts.css-houdini.org/css-typed-om/#reify-a-math-expression
static ExceptionOr<Ref<CSSNumericValue>> reifyMathExpression(const CSSCalcExpressionNode& root)
{
switch (root.type()) {
case CSSCalcExpressionNode::CssCalcPrimitiveValue:
return reifyMathExpression(downcast<CSSCalcPrimitiveValueNode>(root));
case CSSCalcExpressionNode::CssCalcOperation:
return reifyMathExpression(downcast<CSSCalcOperationNode>(root));
case CSSCalcExpressionNode::CssCalcNegate:
return reifyMathExpression(downcast<CSSCalcNegateNode>(root));
case CSSCalcExpressionNode::CssCalcInvert:
return reifyMathExpression(downcast<CSSCalcInvertNode>(root));
}
ASSERT_NOT_REACHED();
return Exception { SyntaxError };
}

static Ref<CSSNumericValue> negate(Ref<CSSNumericValue>&& value)
{
// https://drafts.css-houdini.org/css-typed-om/#cssmath-negate-a-cssnumericvalue
@@ -115,10 +248,7 @@ ExceptionOr<Ref<CSSNumericValue>> CSSNumericValue::addInternal(Vector<Ref<CSSNum
if (auto result = operationOnValuesOfSameUnit(std::plus<double>(), values))
return { *result };

auto sum = CSSMathSum::create(WTFMove(values));
if (sum.hasException())
return sum.releaseException();
return Ref<CSSNumericValue> { sum.releaseReturnValue() };
return convertToExceptionOrNumericValue(CSSMathSum::create(WTFMove(values)));
}

ExceptionOr<Ref<CSSNumericValue>> CSSNumericValue::add(FixedVector<CSSNumberish>&& values)
@@ -163,10 +293,7 @@ ExceptionOr<Ref<CSSNumericValue>> CSSNumericValue::multiplyInternal(Vector<Ref<C
}
}

auto product = CSSMathProduct::create(WTFMove(values));
if (product.hasException())
return product.releaseException();
return Ref<CSSNumericValue> { product.releaseReturnValue() };
return convertToExceptionOrNumericValue(CSSMathProduct::create(WTFMove(values)));
}

ExceptionOr<Ref<CSSNumericValue>> CSSNumericValue::mul(FixedVector<CSSNumberish>&& values)
@@ -179,10 +306,8 @@ ExceptionOr<Ref<CSSNumericValue>> CSSNumericValue::div(FixedVector<CSSNumberish>
Vector<Ref<CSSNumericValue>> invertedValues;
invertedValues.reserveInitialCapacity(values.size());
for (auto&& value : WTFMove(values)) {
auto inverted = invert(rectifyNumberish(WTFMove(value)));
if (inverted.hasException())
return inverted.releaseException();
invertedValues.uncheckedAppend(inverted.releaseReturnValue());
RETURN_IF_EXCEPTION(inverted, invert(rectifyNumberish(WTFMove(value))));
invertedValues.uncheckedAppend(WTFMove(inverted));
}
return multiplyInternal(WTFMove(invertedValues));
}
@@ -195,10 +320,7 @@ ExceptionOr<Ref<CSSNumericValue>> CSSNumericValue::min(FixedVector<CSSNumberish>
if (auto result = operationOnValuesOfSameUnit<const double&(*)(const double&, const double&)>(std::min<double>, values))
return { *result };

auto result = CSSMathMin::create(WTFMove(values));
if (result.hasException())
return result.releaseException();
return { result.releaseReturnValue() };
return convertToExceptionOrNumericValue(CSSMathMin::create(WTFMove(values)));
}

ExceptionOr<Ref<CSSNumericValue>> CSSNumericValue::max(FixedVector<CSSNumberish>&& numberishes)
@@ -209,10 +331,7 @@ ExceptionOr<Ref<CSSNumericValue>> CSSNumericValue::max(FixedVector<CSSNumberish>
if (auto result = operationOnValuesOfSameUnit<const double&(*)(const double&, const double&)>(std::max<double>, values))
return { *result };

auto result = CSSMathMax::create(WTFMove(values));
if (result.hasException())
return result.releaseException();
return { result.releaseReturnValue() };
return convertToExceptionOrNumericValue(CSSMathMax::create(WTFMove(values)));
}

Ref<CSSNumericValue> CSSNumericValue::rectifyNumberish(CSSNumberish&& numberish)
@@ -327,14 +446,49 @@ ExceptionOr<Ref<CSSMathSum>> CSSNumericValue::toSum(FixedVector<String>&& units)
return CSSMathSum::create(WTFMove(result));
}

// https://drafts.css-houdini.org/css-typed-om/#dom-cssnumericvalue-parse
ExceptionOr<Ref<CSSNumericValue>> CSSNumericValue::parse(String&& cssText)
{
UNUSED_PARAM(cssText);
// https://drafts.css-houdini.org/css-typed-om/#dom-cssnumericvalue-parse
// FIXME: add impl.
return Exception { NotSupportedError, "Not implemented Error"_s };
CSSTokenizer tokenizer(cssText);
auto range = tokenizer.tokenRange();
range.consumeWhitespace();
if (range.atEnd())
return Exception { SyntaxError, "Failed to parse CSS text"_s };
const CSSParserToken* componentValueStart = &range.peek();
range.consumeComponentValue();
const CSSParserToken* componentValueEnd = &range.peek();
range.consumeWhitespace();
if (!range.atEnd())
return Exception { SyntaxError, "Failed to parse CSS text"_s };

auto componentValueRange = range.makeSubRange(componentValueStart, componentValueEnd);
// https://drafts.css-houdini.org/css-typed-om/#reify-a-numeric-value
switch (componentValueRange.peek().type()) {
case CSSParserTokenType::DimensionToken:
case CSSParserTokenType::NumberToken:
case CSSParserTokenType::PercentageToken: {
auto& token = componentValueRange.consumeIncludingWhitespace();
if (token.type() == CSSParserTokenType::DimensionToken && !CSSNumericType::create(token.unitType()))
return Exception { SyntaxError, "Failed to parse CSS text"_s };
return convertToExceptionOrNumericValue(CSSUnitValue::create(token.numericValue(), token.unitType()));
}
case CSSParserTokenType::FunctionToken: {
auto functionID = componentValueRange.peek().functionId();
if (functionID == CSSValueCalc || functionID == CSSValueMin || functionID == CSSValueMax || functionID == CSSValueClamp) {
CSSCalcExpressionNodeParser parser(CalculationCategory::Length, { });
if (auto expression = parser.parseCalc(CSSPropertyParserHelpers::consumeFunction(componentValueRange), functionID, false))
return reifyMathExpression(*expression);
}
break;
}
default:
break;
}
return Exception { SyntaxError, "Failed to parse CSS text"_s };
}

} // namespace WebCore

#undef RETURN_IF_EXCEPTION

#endif

0 comments on commit 31a27bf

Please sign in to comment.