Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[CSS Math Functions] Correctly serialize mod/rem/clamp root nodes
https://bugs.webkit.org/show_bug.cgi?id=259020
rdar://111960904

Reviewed by Darin Adler.

From the spec https://drafts.csswg.org/css-values-4/#calc-simplification:

> If root is an operator node that’s not one of the calc-operator nodes, and all of its calculation children are numeric values with enough information to compute the operation root represents, return the result of running root’s operation using its children, expressed in the result’s canonical unit.

We now always try to resolve the top-level mod/rem/clamp, e.g. clamp(1px, 2px, 3px) now gives calc(2px) instead of clamp(1px, 2px, 3px).
This is consistent with calc(clamp(1px, 2px, 3px)) being serialized as calc(2px).

* LayoutTests/fast/css/calc-parsing-expected.txt:
* LayoutTests/fast/css/calc-parsing.html:
* LayoutTests/imported/w3c/web-platform-tests/css/css-values/clamp-length-serialize-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/css/css-values/clamp-length-serialize.html:
* LayoutTests/imported/w3c/web-platform-tests/css/css-values/round-mod-rem-serialize-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/css/css-values/round-mod-rem-serialize.html:
* Source/WebCore/css/calc/CSSCalcOperationNode.h:

Canonical link: https://commits.webkit.org/265886@main
  • Loading branch information
nt1m committed Jul 9, 2023
1 parent 60b5513 commit 29ee9a1
Show file tree
Hide file tree
Showing 7 changed files with 37 additions and 34 deletions.
20 changes: 10 additions & 10 deletions LayoutTests/fast/css/calc-parsing-expected.txt
Expand Up @@ -21,23 +21,23 @@ PASS element.style['width'] is "calc(200px)"
PASS getComputedStyle(element).getPropertyValue('width') is "200px"

element.style["width"] = "clamp(100px,123px,200px)"
PASS element.style['width'] is "clamp(100px, 123px, 200px)"
PASS element.style['width'] is "calc(123px)"
PASS getComputedStyle(element).getPropertyValue('width') is "123px"

element.style["width"] = "clamp(100px,300px,200px)"
PASS element.style['width'] is "clamp(100px, 300px, 200px)"
PASS element.style['width'] is "calc(200px)"
PASS getComputedStyle(element).getPropertyValue('width') is "200px"

element.style["width"] = "clamp(200px,300px,100px)"
PASS element.style['width'] is "clamp(200px, 300px, 100px)"
PASS element.style['width'] is "calc(200px)"
PASS getComputedStyle(element).getPropertyValue('width') is "200px"

element.style["width"] = "clamp((50px + 50px),40px,200px)"
PASS element.style['width'] is "clamp(100px, 40px, 200px)"
PASS element.style['width'] is "calc(100px)"
PASS getComputedStyle(element).getPropertyValue('width') is "100px"

element.style["width"] = "clamp(50px + 50px,40px,200px)"
PASS element.style['width'] is "clamp(100px, 40px, 200px)"
PASS element.style['width'] is "calc(100px)"
PASS getComputedStyle(element).getPropertyValue('width') is "100px"

element.style["width"] = "min(100px,0%)"
Expand Down Expand Up @@ -308,23 +308,23 @@ PASS element.style['min-width'] is "calc(200px)"
PASS getComputedStyle(element).getPropertyValue('min-width') is "200px"

element.style["min-width"] = "clamp(100px,123px,200px)"
PASS element.style['min-width'] is "clamp(100px, 123px, 200px)"
PASS element.style['min-width'] is "calc(123px)"
PASS getComputedStyle(element).getPropertyValue('min-width') is "123px"

element.style["min-width"] = "clamp(100px,300px,200px)"
PASS element.style['min-width'] is "clamp(100px, 300px, 200px)"
PASS element.style['min-width'] is "calc(200px)"
PASS getComputedStyle(element).getPropertyValue('min-width') is "200px"

element.style["min-width"] = "clamp(200px,300px,100px)"
PASS element.style['min-width'] is "clamp(200px, 300px, 100px)"
PASS element.style['min-width'] is "calc(200px)"
PASS getComputedStyle(element).getPropertyValue('min-width') is "200px"

element.style["min-width"] = "clamp((50px + 50px),40px,200px)"
PASS element.style['min-width'] is "clamp(100px, 40px, 200px)"
PASS element.style['min-width'] is "calc(100px)"
PASS getComputedStyle(element).getPropertyValue('min-width') is "100px"

element.style["min-width"] = "clamp(50px + 50px,40px,200px)"
PASS element.style['min-width'] is "clamp(100px, 40px, 200px)"
PASS element.style['min-width'] is "calc(100px)"
PASS getComputedStyle(element).getPropertyValue('min-width') is "100px"

element.style["min-width"] = "min(100px,0%)"
Expand Down
10 changes: 5 additions & 5 deletions LayoutTests/fast/css/calc-parsing.html
Expand Up @@ -34,11 +34,11 @@
testExpression('max(100px + 200px)', 'calc(300px)', '300px');
testExpression('max(100px , 200px)', 'calc(200px)', '200px');
testExpression('max(100px,200px)', 'calc(200px)', '200px');
testExpression('clamp(100px,123px,200px)', 'clamp(100px, 123px, 200px)', '123px');
testExpression('clamp(100px,300px,200px)', 'clamp(100px, 300px, 200px)', '200px');
testExpression('clamp(200px,300px,100px)', 'clamp(200px, 300px, 100px)', '200px');
testExpression('clamp((50px + 50px),40px,200px)', 'clamp(100px, 40px, 200px)', '100px');
testExpression('clamp(50px + 50px,40px,200px)', 'clamp(100px, 40px, 200px)', '100px');
testExpression('clamp(100px,123px,200px)', 'calc(123px)', '123px');
testExpression('clamp(100px,300px,200px)', 'calc(200px)', '200px');
testExpression('clamp(200px,300px,100px)', 'calc(200px)', '200px');
testExpression('clamp((50px + 50px),40px,200px)', 'calc(100px)', '100px');
testExpression('clamp(50px + 50px,40px,200px)', 'calc(100px)', '100px');
testExpression('min(100px,0%)', 'min(100px, 0%)', propertyName == 'width' ? '0px' : "min(100px, 0%)");
testExpression('max(100px,0%)', 'max(100px, 0%)', propertyName == 'width' ? '100px' : "max(100px, 0%)");
testExpression('clamp(100px,0%,1%)', 'clamp(100px, 0%, 1%)', propertyName == 'width' ? '100px' : "clamp(100px, 0%, 1%)");
Expand Down
@@ -1,4 +1,6 @@

PASS e.style['letter-spacing'] = "clamp(1px, 2px, 3px)" should set the property value
FAIL e.style['letter-spacing'] = "clamp(1px, 2px, clamp(2px, 3px, 4px))" should set the property value assert_equals: serialization should be canonical expected "clamp(1px, 2px, clamp(2px, 3px, 4px))" but got "clamp(1px, 2px, 3px)"
PASS e.style['letter-spacing'] = "calc(clamp(1px, 2px, 3px))" should set the property value
PASS e.style['letter-spacing'] = "clamp(1px, 2px, clamp(2px, 3px, 4px))" should set the property value
PASS e.style['letter-spacing'] = "calc(clamp(1px, 2px, clamp(2px, 3px, 4px)))" should set the property value

Expand Up @@ -7,9 +7,10 @@
<script>
function test_valid_length(value, expected) {
test_valid_value('letter-spacing', value, expected);
test_valid_value('letter-spacing', `calc(${value})`, expected);
}

test_valid_length('clamp(1px, 2px, 3px)', 'clamp(1px, 2px, 3px)');
test_valid_length('clamp(1px, 2px, clamp(2px, 3px, 4px))', 'clamp(1px, 2px, clamp(2px, 3px, 4px))');
test_valid_length('clamp(1px, 2px, 3px)', 'calc(2px)');
test_valid_length('clamp(1px, 2px, clamp(2px, 3px, 4px))', 'calc(2px)');

</script>
Expand Up @@ -3,24 +3,24 @@ FAIL 'round(1.1,1)' as a specified value should serialize as 'calc(1)'. assert_e
FAIL 'scale(round(1.1,1))' as a specified value should serialize as 'scale(calc(1))'. assert_equals: 'scale(round(1.1,1))' and 'scale(calc(1))' should serialize the same in specified values. expected "scale(calc(1))" but got "scale(round(nearest, 1.1, 1))"
PASS 'round(1.1,1)' as a computed value should serialize as '1'.
PASS 'scale(round(1.1,1))' as a computed value should serialize as 'matrix(1, 0, 0, 1, 0, 0)'.
FAIL 'mod(1,1)' as a specified value should serialize as 'calc(0)'. assert_equals: 'mod(1,1)' and 'calc(0)' should serialize the same in specified values. expected "calc(0)" but got "mod(1, 1)"
FAIL 'scale(mod(1,1))' as a specified value should serialize as 'scale(calc(0))'. assert_equals: 'scale(mod(1,1))' and 'scale(calc(0))' should serialize the same in specified values. expected "scale(calc(0))" but got "scale(mod(1, 1))"
PASS 'mod(1,1)' as a specified value should serialize as 'calc(0)'.
PASS 'scale(mod(1,1))' as a specified value should serialize as 'scale(calc(0))'.
PASS 'mod(1,1)' as a computed value should serialize as '0'.
PASS 'scale(mod(1,1))' as a computed value should serialize as 'matrix(0, 0, 0, 0, 0, 0)'.
FAIL 'rem(1,1)' as a specified value should serialize as 'calc(0)'. assert_equals: 'rem(1,1)' and 'calc(0)' should serialize the same in specified values. expected "calc(0)" but got "rem(1, 1)"
FAIL 'scale(rem(1,1))' as a specified value should serialize as 'scale(calc(0))'. assert_equals: 'scale(rem(1,1))' and 'scale(calc(0))' should serialize the same in specified values. expected "scale(calc(0))" but got "scale(rem(1, 1))"
PASS 'rem(1,1)' as a specified value should serialize as 'calc(0)'.
PASS 'scale(rem(1,1))' as a specified value should serialize as 'scale(calc(0))'.
PASS 'rem(1,1)' as a computed value should serialize as '0'.
PASS 'scale(rem(1,1))' as a computed value should serialize as 'matrix(0, 0, 0, 0, 0, 0)'.
PASS 'calc(round(1,0))' as a specified value should serialize as 'calc(NaN)'.
PASS 'scale(calc(round(1,0)))' as a specified value should serialize as 'scale(calc(NaN))'.
FAIL 'calc(round(1,0))' as a computed value should serialize as 'NaN'. assert_equals: 'NaN' should round-trip exactly in computed values. expected "NaN" but got "1"
FAIL 'scale(calc(round(1,0)))' as a computed value should serialize as 'matrix(NaN, 0, 0, NaN, 0, 0)'. assert_equals: 'matrix(NaN, 0, 0, NaN, 0, 0)' should round-trip exactly in computed values. expected "matrix(NaN, 0, 0, NaN, 0, 0)" but got "none"
FAIL 'calc(round(1,0))' as a computed value should serialize as '0'. assert_equals: 'calc(round(1,0))' and '0' should serialize the same in computed values. expected "0" but got "1"
FAIL 'scale(calc(round(1,0)))' as a computed value should serialize as 'matrix(0, 0, 0, 0, 0, 0)'. assert_equals: 'scale(calc(round(1,0)))' and 'matrix(0, 0, 0, 0, 0, 0)' should serialize the same in computed values. expected "matrix(0, 0, 0, 0, 0, 0)" but got "matrix3d(infinity, NaN, NaN, NaN, NaN, infinity, NaN, NaN, 0, 0, 1, 0, 0, 0, 0, 1)"
PASS 'calc(mod(1,0))' as a specified value should serialize as 'calc(NaN)'.
PASS 'scale(calc(mod(1,0)))' as a specified value should serialize as 'scale(calc(NaN))'.
FAIL 'calc(mod(1,0))' as a computed value should serialize as 'NaN'. assert_equals: 'NaN' should round-trip exactly in computed values. expected "NaN" but got "1"
FAIL 'scale(calc(mod(1,0)))' as a computed value should serialize as 'matrix(NaN, 0, 0, NaN, 0, 0)'. assert_equals: 'matrix(NaN, 0, 0, NaN, 0, 0)' should round-trip exactly in computed values. expected "matrix(NaN, 0, 0, NaN, 0, 0)" but got "none"
FAIL 'calc(mod(1,0))' as a computed value should serialize as '0'. assert_equals: 'calc(mod(1,0))' and '0' should serialize the same in computed values. expected "0" but got "1"
FAIL 'scale(calc(mod(1,0)))' as a computed value should serialize as 'matrix(0, 0, 0, 0, 0, 0)'. assert_equals: 'scale(calc(mod(1,0)))' and 'matrix(0, 0, 0, 0, 0, 0)' should serialize the same in computed values. expected "matrix(0, 0, 0, 0, 0, 0)" but got "matrix3d(infinity, NaN, NaN, NaN, NaN, infinity, NaN, NaN, 0, 0, 1, 0, 0, 0, 0, 1)"
PASS 'calc(rem(1,0))' as a specified value should serialize as 'calc(NaN)'.
PASS 'scale(calc(rem(1,0)))' as a specified value should serialize as 'scale(calc(NaN))'.
FAIL 'calc(rem(1,0))' as a computed value should serialize as 'NaN'. assert_equals: 'NaN' should round-trip exactly in computed values. expected "NaN" but got "1"
FAIL 'scale(calc(rem(1,0)))' as a computed value should serialize as 'matrix(NaN, 0, 0, NaN, 0, 0)'. assert_equals: 'matrix(NaN, 0, 0, NaN, 0, 0)' should round-trip exactly in computed values. expected "matrix(NaN, 0, 0, NaN, 0, 0)" but got "none"
FAIL 'calc(rem(1,0))' as a computed value should serialize as '0'. assert_equals: 'calc(rem(1,0))' and '0' should serialize the same in computed values. expected "0" but got "1"
FAIL 'scale(calc(rem(1,0)))' as a computed value should serialize as 'matrix(0, 0, 0, 0, 0, 0)'. assert_equals: 'scale(calc(rem(1,0)))' and 'matrix(0, 0, 0, 0, 0, 0)' should serialize the same in computed values. expected "matrix(0, 0, 0, 0, 0, 0)" but got "matrix3d(infinity, NaN, NaN, NaN, NaN, infinity, NaN, NaN, 0, 0, 1, 0, 0, 0, 0, 1)"

Expand Up @@ -11,7 +11,7 @@
<script>
function test_serialization(t,s,c) {
test_specified_serialization('opacity', t, s);
test_specified_serialization('transform', `scale(${t})`, `scale(calc(${c}))`);
test_specified_serialization('transform', `scale(${t})`, `scale(${s})`);
test_computed_serialization('opacity', t, c);
test_computed_serialization('transform', `scale(${t})`, `matrix(${c}, 0, 0, ${c}, 0, 0)`);
}
Expand All @@ -32,13 +32,13 @@
test_serialization(
'calc(round(1,0))',
'calc(NaN)',
'NaN');
'0');
test_serialization(
'calc(mod(1,0))',
'calc(NaN)',
'NaN');
'0');
test_serialization(
'calc(rem(1,0))',
'calc(NaN)',
'NaN');
'0');
</script>
4 changes: 2 additions & 2 deletions Source/WebCore/css/calc/CSSCalcOperationNode.h
Expand Up @@ -71,7 +71,7 @@ class CSSCalcOperationNode final : public CSSCalcExpressionNode {
bool isHypotNode() const { return m_operator == CalcOperator::Hypot; }
bool isSqrtNode() const { return m_operator == CalcOperator::Sqrt; }
bool isPowOrSqrtNode() const { return m_operator == CalcOperator::Pow || isSqrtNode(); }
bool shouldPreserveFunction() const { return isSteppedNode() || isRoundOperation() || isClampNode(); }
bool shouldPreserveFunction() const { return isRoundOperation(); }
bool isClampNode() const { return m_operator == CalcOperator::Clamp; }

void hoistChildrenWithOperator(CalcOperator);
Expand Down Expand Up @@ -145,7 +145,7 @@ class CSSCalcOperationNode final : public CSSCalcExpressionNode {
}

void makeTopLevelCalc();
bool shouldNotPreserveFunction() const { return isSignNode() || isMinOrMaxNode() || isExpNode() || isHypotNode() || isPowOrSqrtNode() || isInverseTrigNode() || isAtan2Node() || isTrigNode(); }
bool shouldNotPreserveFunction() const { return isSignNode() || isClampNode() || isMinOrMaxNode() || isExpNode() || isHypotNode() || isSteppedNode() || isPowOrSqrtNode() || isInverseTrigNode() || isAtan2Node() || isTrigNode(); }
static double evaluateOperator(CalcOperator, const Vector<double>&);
static Ref<CSSCalcExpressionNode> simplifyNode(Ref<CSSCalcExpressionNode>&&, int depth);
static Ref<CSSCalcExpressionNode> simplifyRecursive(Ref<CSSCalcExpressionNode>&&, int depth);
Expand Down

0 comments on commit 29ee9a1

Please sign in to comment.