Commit 7377d9c6 authored by Rune Lillesveen's avatar Rune Lillesveen Committed by Commit Bot

Only allow calc() combinations as per spec.

We allowed calc expressions combining numbers and lengths, numbers and
percentages, and even a combination of numbers, percentages, and
lengths. None of those are allowed per the CSS Values and Units spec,
and it made it possible to combine user units, lengths, and percentages
into the same calc() for certain SVG CSS properties and attributes.

See also: https://github.com/w3c/csswg-drafts/issues/4874

This improves SVG interop with Firefox. Firefox does not support calc()
for numbers, though.

Bug: 1061714
Change-Id: I9e5c492122242e51064310a40e28a233530e357c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2134251
Commit-Queue: Rune Lillesveen <futhark@chromium.org>
Reviewed-by: default avatarFredrik Söderquist <fs@opera.com>
Cr-Commit-Position: refs/heads/master@{#756226}
parent df7b1383
...@@ -229,10 +229,7 @@ double CSSMathExpressionNumericLiteral::ComputeLengthPx( ...@@ -229,10 +229,7 @@ double CSSMathExpressionNumericLiteral::ComputeLengthPx(
case kCalcAngle: case kCalcAngle:
case kCalcFrequency: case kCalcFrequency:
case kCalcPercentLength: case kCalcPercentLength:
case kCalcPercentNumber:
case kCalcTime: case kCalcTime:
case kCalcLengthNumber:
case kCalcPercentLengthNumber:
case kCalcOther: case kCalcOther:
NOTREACHED(); NOTREACHED();
break; break;
...@@ -285,43 +282,26 @@ bool CSSMathExpressionNumericLiteral::InvolvesPercentageComparisons() const { ...@@ -285,43 +282,26 @@ bool CSSMathExpressionNumericLiteral::InvolvesPercentageComparisons() const {
// ------ End of CSSMathExpressionNumericLiteral member functions // ------ End of CSSMathExpressionNumericLiteral member functions
static const CalculationCategory kAddSubtractResult[kCalcOther][kCalcOther] = { static const CalculationCategory kAddSubtractResult[kCalcOther][kCalcOther] = {
/* CalcNumber */ {kCalcNumber, kCalcLengthNumber, kCalcPercentNumber, /* CalcNumber */ {kCalcNumber, kCalcOther, kCalcOther, kCalcOther,
kCalcPercentNumber, kCalcOther, kCalcOther, kCalcOther, kCalcOther, kCalcOther, kCalcOther},
kCalcOther, kCalcLengthNumber, kCalcPercentLengthNumber},
/* CalcLength */ /* CalcLength */
{kCalcLengthNumber, kCalcLength, kCalcPercentLength, kCalcOther, {kCalcOther, kCalcLength, kCalcPercentLength, kCalcPercentLength,
kCalcPercentLength, kCalcOther, kCalcOther, kCalcOther, kCalcLengthNumber, kCalcOther, kCalcOther, kCalcOther},
kCalcPercentLengthNumber},
/* CalcPercent */ /* CalcPercent */
{kCalcPercentNumber, kCalcPercentLength, kCalcPercent, kCalcPercentNumber, {kCalcOther, kCalcPercentLength, kCalcPercent, kCalcPercentLength,
kCalcPercentLength, kCalcOther, kCalcOther, kCalcOther, kCalcOther, kCalcOther, kCalcOther},
kCalcPercentLengthNumber, kCalcPercentLengthNumber},
/* CalcPercentNumber */
{kCalcPercentNumber, kCalcPercentLengthNumber, kCalcPercentNumber,
kCalcPercentNumber, kCalcPercentLengthNumber, kCalcOther, kCalcOther,
kCalcOther, kCalcOther, kCalcPercentLengthNumber},
/* CalcPercentLength */ /* CalcPercentLength */
{kCalcPercentLengthNumber, kCalcPercentLength, kCalcPercentLength, {kCalcOther, kCalcPercentLength, kCalcPercentLength, kCalcPercentLength,
kCalcPercentLengthNumber, kCalcPercentLength, kCalcOther, kCalcOther, kCalcOther, kCalcOther, kCalcOther},
kCalcOther, kCalcOther, kCalcPercentLengthNumber},
/* CalcAngle */ /* CalcAngle */
{kCalcOther, kCalcOther, kCalcOther, kCalcOther, kCalcOther, kCalcAngle, {kCalcOther, kCalcOther, kCalcOther, kCalcOther, kCalcAngle, kCalcOther,
kCalcOther, kCalcOther, kCalcOther, kCalcOther}, kCalcOther},
/* CalcTime */ /* CalcTime */
{kCalcOther, kCalcOther, kCalcOther, kCalcOther, kCalcOther, kCalcOther, {kCalcOther, kCalcOther, kCalcOther, kCalcOther, kCalcOther, kCalcTime,
kCalcTime, kCalcOther, kCalcOther, kCalcOther}, kCalcOther},
/* CalcFrequency */ /* CalcFrequency */
{kCalcOther, kCalcOther, kCalcOther, kCalcOther, kCalcOther, kCalcOther, {kCalcOther, kCalcOther, kCalcOther, kCalcOther, kCalcOther, kCalcOther,
kCalcOther, kCalcFrequency, kCalcOther, kCalcOther}, kCalcFrequency}};
/* CalcLengthNumber */
{kCalcLengthNumber, kCalcLengthNumber, kCalcPercentLengthNumber,
kCalcPercentLengthNumber, kCalcPercentLengthNumber, kCalcOther, kCalcOther,
kCalcOther, kCalcLengthNumber, kCalcPercentLengthNumber},
/* CalcPercentLengthNumber */
{kCalcPercentLengthNumber, kCalcPercentLengthNumber,
kCalcPercentLengthNumber, kCalcPercentLengthNumber,
kCalcPercentLengthNumber, kCalcOther, kCalcOther, kCalcOther,
kCalcPercentLengthNumber, kCalcPercentLengthNumber}};
static CalculationCategory DetermineCategory( static CalculationCategory DetermineCategory(
const CSSMathExpressionNode& left_side, const CSSMathExpressionNode& left_side,
...@@ -729,9 +709,6 @@ CSSPrimitiveValue::UnitType CSSMathExpressionBinaryOperation::ResolvedUnitType() ...@@ -729,9 +709,6 @@ CSSPrimitiveValue::UnitType CSSMathExpressionBinaryOperation::ResolvedUnitType()
case kCalcFrequency: case kCalcFrequency:
return CSSPrimitiveValue::UnitType::kHertz; return CSSPrimitiveValue::UnitType::kHertz;
case kCalcPercentLength: case kCalcPercentLength:
case kCalcPercentNumber:
case kCalcLengthNumber:
case kCalcPercentLengthNumber:
case kCalcOther: case kCalcOther:
return CSSPrimitiveValue::UnitType::kUnknown; return CSSPrimitiveValue::UnitType::kUnknown;
} }
......
...@@ -47,17 +47,14 @@ class CSSNumericLiteralValue; ...@@ -47,17 +47,14 @@ class CSSNumericLiteralValue;
// The order of this enum should not change since its elements are used as // The order of this enum should not change since its elements are used as
// indices in the addSubtractResult matrix. // indices in the addSubtractResult matrix.
enum CalculationCategory { enum CalculationCategory {
kCalcNumber = 0, kCalcNumber,
kCalcLength, kCalcLength,
kCalcPercent, kCalcPercent,
kCalcPercentNumber,
kCalcPercentLength, kCalcPercentLength,
kCalcAngle, kCalcAngle,
kCalcTime, kCalcTime,
kCalcFrequency, kCalcFrequency,
kCalcLengthNumber, kCalcOther,
kCalcPercentLengthNumber,
kCalcOther
}; };
class CORE_EXPORT CSSMathExpressionNode class CORE_EXPORT CSSMathExpressionNode
...@@ -121,9 +118,7 @@ class CORE_EXPORT CSSMathExpressionNode ...@@ -121,9 +118,7 @@ class CORE_EXPORT CSSMathExpressionNode
CalculationCategory Category() const { return category_; } CalculationCategory Category() const { return category_; }
bool HasPercentage() const { bool HasPercentage() const {
return category_ == kCalcPercent || category_ == kCalcPercentNumber || return category_ == kCalcPercent || category_ == kCalcPercentLength;
category_ == kCalcPercentLength ||
category_ == kCalcPercentLengthNumber;
} }
// Returns the unit type of the math expression *without doing any type // Returns the unit type of the math expression *without doing any type
......
...@@ -470,18 +470,9 @@ CSSPrimitiveValue* ConsumeAlphaValue(CSSParserTokenRange& range, ...@@ -470,18 +470,9 @@ CSSPrimitiveValue* ConsumeAlphaValue(CSSParserTokenRange& range,
bool CanConsumeCalcValue(CalculationCategory category, bool CanConsumeCalcValue(CalculationCategory category,
CSSParserMode css_parser_mode) { CSSParserMode css_parser_mode) {
if (category == kCalcLength || category == kCalcPercent || return category == kCalcLength || category == kCalcPercent ||
category == kCalcPercentLength) category == kCalcPercentLength ||
return true; (css_parser_mode == kSVGAttributeMode && category == kCalcNumber);
if (css_parser_mode != kSVGAttributeMode)
return false;
if (category == kCalcNumber || category == kCalcPercentNumber ||
category == kCalcLengthNumber || category == kCalcPercentLengthNumber)
return true;
return false;
} }
CSSPrimitiveValue* ConsumeLengthOrPercent(CSSParserTokenRange& range, CSSPrimitiveValue* ConsumeLengthOrPercent(CSSParserTokenRange& range,
...@@ -512,16 +503,7 @@ bool IsNonZeroUserUnitsValue(const CSSPrimitiveValue* value) { ...@@ -512,16 +503,7 @@ bool IsNonZeroUserUnitsValue(const CSSPrimitiveValue* value) {
value->GetDoubleValue() != 0; value->GetDoubleValue() != 0;
} }
const auto& math_value = To<CSSMathFunctionValue>(*value); const auto& math_value = To<CSSMathFunctionValue>(*value);
switch (math_value.Category()) { return math_value.Category() == kCalcNumber && math_value.DoubleValue() != 0;
case kCalcNumber:
return math_value.DoubleValue() != 0;
case kCalcPercentNumber:
case kCalcLengthNumber:
case kCalcPercentLengthNumber:
return true;
default:
return false;
}
} }
} // namespace } // namespace
......
...@@ -165,10 +165,7 @@ static bool IsSupportedCalculationCategory(CalculationCategory category) { ...@@ -165,10 +165,7 @@ static bool IsSupportedCalculationCategory(CalculationCategory category) {
case kCalcLength: case kCalcLength:
case kCalcNumber: case kCalcNumber:
case kCalcPercent: case kCalcPercent:
case kCalcPercentNumber:
case kCalcPercentLength: case kCalcPercentLength:
case kCalcLengthNumber:
case kCalcPercentLengthNumber:
return true; return true;
default: default:
return false; return false;
......
...@@ -40,13 +40,13 @@ assertAttributeInterpolation({ ...@@ -40,13 +40,13 @@ assertAttributeInterpolation({
from: '0%', from: '0%',
to: '100' to: '100'
}, [ }, [
{at: -0.25, is: 'calc(0% + -25)'}, {at: -0.25, is: 'calc(0% + -25px)'},
{at: 0, is: '0%'}, {at: 0, is: '0%'},
{at: 0.25, is: 'calc(0% + 25)'}, {at: 0.25, is: 'calc(0% + 25px)'},
{at: 0.5, is: 'calc(0% + 50)'}, {at: 0.5, is: 'calc(0% + 50px)'},
{at: 0.75, is: 'calc(0% + 75)'}, {at: 0.75, is: 'calc(0% + 75px)'},
{at: 1, is: '100px'}, {at: 1, is: '100px'},
{at: 1.25, is: 'calc(0% + 125)'} {at: 1.25, is: 'calc(0% + 125px)'}
]); ]);
assertAttributeInterpolation({ assertAttributeInterpolation({
property: 'cy', property: 'cy',
......
...@@ -13,8 +13,8 @@ ...@@ -13,8 +13,8 @@
<!-- an1: Change width from 10 to 50 in 4s --> <!-- an1: Change width from 10 to 50 in 4s -->
<!-- an2: Change width from 10 to 100 in 4s starting at 5s --> <!-- an2: Change width from 10 to 100 in 4s starting at 5s -->
<rect width="10" height="100" fill="green"> <rect width="10" height="100" fill="green">
<animate id="an1" attributeType="XML" attributeName="width" fill="remove" by="calc(4% + 8)" begin="0s" dur="4s"/> <animate id="an1" attributeType="XML" attributeName="width" fill="remove" by="calc(4% + 8px)" begin="0s" dur="4s"/>
<animate id="an2" attributeType="XML" attributeName="width" additive="replace" fill="freeze" by="calc(10% + 10)" begin="5s" dur="4s"/> <animate id="an2" attributeType="XML" attributeName="width" additive="replace" fill="freeze" by="calc(10% + 10px)" begin="5s" dur="4s"/>
</rect> </rect>
</svg> </svg>
...@@ -73,4 +73,4 @@ smil_async_test((t) => { ...@@ -73,4 +73,4 @@ smil_async_test((t) => {
window.animationStartsImmediately = true; window.animationStartsImmediately = true;
</script> </script>
\ No newline at end of file
...@@ -17,6 +17,9 @@ test_invalid_value("stroke-dasharray", "auto"); ...@@ -17,6 +17,9 @@ test_invalid_value("stroke-dasharray", "auto");
test_invalid_value("stroke-dasharray", "none 10px"); test_invalid_value("stroke-dasharray", "none 10px");
test_invalid_value("stroke-dasharray", "20px / 30px"); test_invalid_value("stroke-dasharray", "20px / 30px");
test_invalid_value("stroke-dasharray", "-40px"); test_invalid_value("stroke-dasharray", "-40px");
test_invalid_value("stroke-dasharray", "calc(2px + 3)");
test_invalid_value("stroke-dasharray", "calc(10% + 5)");
test_invalid_value("stroke-dasharray", "calc(40 + calc(3px + 6%))");
]]></script> ]]></script>
</svg> </svg>
...@@ -23,6 +23,9 @@ test_valid_value("stroke-dasharray", "calc(2em + 3ex)"); ...@@ -23,6 +23,9 @@ test_valid_value("stroke-dasharray", "calc(2em + 3ex)");
test_valid_value("stroke-dasharray", "10pt 20% 30pc 40in", "10pt, 20%, 30pc, 40in"); test_valid_value("stroke-dasharray", "10pt 20% 30pc 40in", "10pt, 20%, 30pc, 40in");
test_valid_value("stroke-dasharray", "10vmin, 20vmax, 30em, 40ex"); test_valid_value("stroke-dasharray", "10vmin, 20vmax, 30em, 40ex");
test_valid_value("stroke-dasharray", "0, 5", ["0, 5", "0px, 5px"]); // Edge/Safari serialize numbers as lengths. test_valid_value("stroke-dasharray", "0, 5", ["0, 5", "0px, 5px"]); // Edge/Safari serialize numbers as lengths.
test_valid_value("stroke-dasharray", "calc(3)");
test_valid_value("stroke-dasharray", "calc(2 + 1)", "calc(3)");
test_valid_value("stroke-dasharray", "calc(2 + (7 - 5))", "calc(4)");
]]></script> ]]></script>
</svg> </svg>
...@@ -17,6 +17,9 @@ test_invalid_value("stroke-dashoffset", "auto"); ...@@ -17,6 +17,9 @@ test_invalid_value("stroke-dashoffset", "auto");
test_invalid_value("stroke-dashoffset", "-10.px"); test_invalid_value("stroke-dashoffset", "-10.px");
test_invalid_value("stroke-dashoffset", "30deg"); test_invalid_value("stroke-dashoffset", "30deg");
test_invalid_value("stroke-dashoffset", "40px 50%"); test_invalid_value("stroke-dashoffset", "40px 50%");
test_invalid_value("stroke-dashoffset", "calc(2px + 3)");
test_invalid_value("stroke-dashoffset", "calc(10% + 5)");
test_invalid_value("stroke-dashoffset", "calc(40 + calc(3px + 6%))");
]]></script> ]]></script>
</svg> </svg>
...@@ -5,5 +5,8 @@ PASS e.style['stroke-dashoffset'] = "-20%" should set the property value ...@@ -5,5 +5,8 @@ PASS e.style['stroke-dashoffset'] = "-20%" should set the property value
FAIL e.style['stroke-dashoffset'] = "30" should set the property value assert_equals: serialization should be canonical expected "30px" but got "30" FAIL e.style['stroke-dashoffset'] = "30" should set the property value assert_equals: serialization should be canonical expected "30px" but got "30"
PASS e.style['stroke-dashoffset'] = "40Q" should set the property value PASS e.style['stroke-dashoffset'] = "40Q" should set the property value
PASS e.style['stroke-dashoffset'] = "calc(2em + 3ex)" should set the property value PASS e.style['stroke-dashoffset'] = "calc(2em + 3ex)" should set the property value
PASS e.style['stroke-dashoffset'] = "calc(3)" should set the property value
PASS e.style['stroke-dashoffset'] = "calc(2 + 1)" should set the property value
PASS e.style['stroke-dashoffset'] = "calc(2 + (7 - 5))" should set the property value
Harness: the test ran to completion. Harness: the test ran to completion.
...@@ -19,6 +19,9 @@ test_valid_value("stroke-dashoffset", "-20%"); ...@@ -19,6 +19,9 @@ test_valid_value("stroke-dashoffset", "-20%");
test_valid_value("stroke-dashoffset", "30", "30px"); test_valid_value("stroke-dashoffset", "30", "30px");
test_valid_value("stroke-dashoffset", "40Q", "40q"); test_valid_value("stroke-dashoffset", "40Q", "40q");
test_valid_value("stroke-dashoffset", "calc(2em + 3ex)"); test_valid_value("stroke-dashoffset", "calc(2em + 3ex)");
test_valid_value("stroke-dashoffset", "calc(3)");
test_valid_value("stroke-dashoffset", "calc(2 + 1)", "calc(3)");
test_valid_value("stroke-dashoffset", "calc(2 + (7 - 5))", "calc(4)");
]]></script> ]]></script>
</svg> </svg>
...@@ -17,6 +17,9 @@ test_invalid_value("stroke-width", "auto"); ...@@ -17,6 +17,9 @@ test_invalid_value("stroke-width", "auto");
test_invalid_value("stroke-width", "10px 20px"); test_invalid_value("stroke-width", "10px 20px");
test_invalid_value("stroke-width", "-1px"); test_invalid_value("stroke-width", "-1px");
test_invalid_value("stroke-width", "-10%"); test_invalid_value("stroke-width", "-10%");
test_invalid_value("stroke-width", "calc(2px + 3)");
test_invalid_value("stroke-width", "calc(10% + 5)");
test_invalid_value("stroke-width", "calc(40 + calc(3px + 6%))");
]]></script> ]]></script>
</svg> </svg>
...@@ -6,5 +6,8 @@ PASS e.style['stroke-width'] = "calc(2em + 3ex)" should set the property value ...@@ -6,5 +6,8 @@ PASS e.style['stroke-width'] = "calc(2em + 3ex)" should set the property value
PASS e.style['stroke-width'] = "4%" should set the property value PASS e.style['stroke-width'] = "4%" should set the property value
PASS e.style['stroke-width'] = "5vmin" should set the property value PASS e.style['stroke-width'] = "5vmin" should set the property value
PASS e.style['stroke-width'] = "calc(50% + 60px)" should set the property value PASS e.style['stroke-width'] = "calc(50% + 60px)" should set the property value
PASS e.style['stroke-width'] = "calc(3)" should set the property value
PASS e.style['stroke-width'] = "calc(2 + 1)" should set the property value
PASS e.style['stroke-width'] = "calc(2 + (7 - 5))" should set the property value
Harness: the test ran to completion. Harness: the test ran to completion.
...@@ -20,6 +20,9 @@ test_valid_value("stroke-width", "calc(2em + 3ex)"); ...@@ -20,6 +20,9 @@ test_valid_value("stroke-width", "calc(2em + 3ex)");
test_valid_value("stroke-width", "4%"); test_valid_value("stroke-width", "4%");
test_valid_value("stroke-width", "5vmin"); test_valid_value("stroke-width", "5vmin");
test_valid_value("stroke-width", "calc(50% + 60px)"); test_valid_value("stroke-width", "calc(50% + 60px)");
test_valid_value("stroke-width", "calc(3)");
test_valid_value("stroke-width", "calc(2 + 1)", "calc(3)");
test_valid_value("stroke-width", "calc(2 + (7 - 5))", "calc(4)");
]]></script> ]]></script>
</svg> </svg>
...@@ -3,6 +3,6 @@ ...@@ -3,6 +3,6 @@
body { zoom: 200%; } body { zoom: 200%; }
</style> </style>
<svg id="svg" width="500" height="500" viewBox='0 0 1000 1000'> <svg id="svg" width="500" height="500" viewBox='0 0 1000 1000'>
<rect width='calc(50px + 50)' height='100' fill='green'/> <rect width='calc(50px + 50px)' height='100' fill='green'/>
<rect x='110' width='calc(50 + 50)' height='100' fill='green'/> <rect x='110' width='calc(50 + 50)' height='100' fill='green'/>
</svg> </svg>
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500"> <svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
<rect x="50" y="50" width="400" height="400" rx="calc(-50)" ry="calc(-50)" fill="#00ff00" /> <rect x="50" y="50" width="400" height="400" rx="calc(-50)" ry="calc(-50)" fill="#00ff00" />
<rect x="150" y="100" width="200" height="300" rx="calc(20% - 50)" ry="50" fill="#0000ff" /> <rect x="150" y="100" width="200" height="300" rx="calc(20% - 50px)" ry="50" fill="#0000ff" />
<rect x="200" y="150" width="100" height="200" rx="50" ry="-100" fill="#ff0000" /> <rect x="200" y="150" width="100" height="200" rx="50" ry="-100" fill="#ff0000" />
</svg> </svg>
...@@ -82,10 +82,10 @@ test(function() { ...@@ -82,10 +82,10 @@ test(function() {
assert_calc_expression("calc(10mm + 10mm)", (20 * cssPixelsPerMillimeter)); assert_calc_expression("calc(10mm + 10mm)", (20 * cssPixelsPerMillimeter));
assert_calc_expression("calc(20mm)", (20 * cssPixelsPerMillimeter)); assert_calc_expression("calc(20mm)", (20 * cssPixelsPerMillimeter));
assert_calc_expression("calc(10 + 10)", 20); assert_calc_expression("calc(10 + 10)", 20);
assert_calc_expression("calc(10mm + 10)", (10 * cssPixelsPerMillimeter) + 10); assert_calc_expression("calc(10mm + 10px)", (10 * cssPixelsPerMillimeter) + 10);
assert_calc_expression("calc(10% + 10)", (10 * viewportWidthPercent()) + 10); assert_calc_expression("calc(10% + 10px)", (10 * viewportWidthPercent()) + 10);
assert_calc_expression("calc(1cm + 2in + 1cm + 2)", (2 * cssPixelsPerInch) + (2 * cssPixelsPerCentimeter) + 2); assert_calc_expression("calc(1cm + 2in + 1cm + 2px)", (2 * cssPixelsPerInch) + (2 * cssPixelsPerCentimeter) + 2);
assert_calc_expression("calc(1cm + 2 + 1cm + 2in)", (2 * cssPixelsPerInch) + (2 * cssPixelsPerCentimeter) + 2); assert_calc_expression("calc(1cm + 2px + 1cm + 2in)", (2 * cssPixelsPerInch) + (2 * cssPixelsPerCentimeter) + 2);
assert_calc_expression("calc(10% + 10 + 2% + 10pc)", (12 * viewportWidthPercent()) + 10 + (10 * cssPixelsPerPica)); assert_calc_expression("calc(10% + 10px + 2% + 10pc)", (12 * viewportWidthPercent()) + 10 + (10 * cssPixelsPerPica));
}, "Tests calc() on presentation and non-presentation attr in svgLength"); }, "Tests calc() on presentation and non-presentation attr in svgLength");
</script> </script>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment