Commit 458cc06f authored by Xiaocheng Hu's avatar Xiaocheng Hu Committed by Commit Bot

Fix type conversion in calculation constructed by CSS typed OM

Current implementation of CSSMathFunctionValue assumes a simplified
expression node tree, where some units are already canonicalized (e.g.,
angle units are canonicalized into 'deg'). This doesn't hold if the
tree is constructed by CSS typed OM.

This patch fixes the issue by introducing a safe API
CSSMathExpressionNode::ComputeValueInCanonicalUnit(), which evaluates
the expression and handles type conversions, and in case of failure,
returns nullopt.

Bug: 983702
Change-Id: Ib8d5be3731b4b6585913223f11cdd3656eefb486
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1700356Reviewed-by: default avatarAnders Hartvoll Ruud <andruud@chromium.org>
Commit-Queue: Xiaocheng Hu <xiaochengh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#678594}
parent 91d1ba16
...@@ -191,6 +191,27 @@ double CSSMathExpressionNumericLiteral::DoubleValue() const { ...@@ -191,6 +191,27 @@ double CSSMathExpressionNumericLiteral::DoubleValue() const {
return 0; 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( double CSSMathExpressionNumericLiteral::ComputeLengthPx(
const CSSToLengthConversionData& conversion_data) const { const CSSToLengthConversionData& conversion_data) const {
switch (category_) { switch (category_) {
...@@ -482,6 +503,29 @@ double CSSMathExpressionBinaryOperation::DoubleValue() const { ...@@ -482,6 +503,29 @@ double CSSMathExpressionBinaryOperation::DoubleValue() const {
return Evaluate(left_side_->DoubleValue(), right_side_->DoubleValue()); 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( double CSSMathExpressionBinaryOperation::ComputeLengthPx(
const CSSToLengthConversionData& conversion_data) const { const CSSToLengthConversionData& conversion_data) const {
const double left_value = left_side_->ComputeLengthPx(conversion_data); const double left_value = left_side_->ComputeLengthPx(conversion_data);
......
...@@ -81,6 +81,19 @@ class CORE_EXPORT CSSMathExpressionNode ...@@ -81,6 +81,19 @@ class CORE_EXPORT CSSMathExpressionNode
virtual void AccumulatePixelsAndPercent(const CSSToLengthConversionData&, virtual void AccumulatePixelsAndPercent(const CSSToLengthConversionData&,
PixelsAndPercent&, PixelsAndPercent&,
float multiplier = 1) const = 0; float multiplier = 1) const = 0;
// Evaluates the expression with type conversion (e.g., cm -> px) handled, and
// returns the result value in the canonical unit of the corresponding
// category (see https://www.w3.org/TR/css3-values/#canonical-unit).
// TODO(crbug.com/984372): We currently use 'ms' as the canonical unit of
// <time>. Switch to 's' to follow the spec.
// Returns |nullopt| on evaluation failures due to the following reasons:
// - The category doesn't have a canonical unit (e.g., |kCalcPercentLength|).
// - A type conversion that doesn't have a fixed conversion ratio is needed
// (e.g., between 'px' and 'em').
// - There's an unsupported calculation, e.g., dividing two lengths.
virtual base::Optional<double> ComputeValueInCanonicalUnit() const = 0;
virtual String CustomCSSText() const = 0; virtual String CustomCSSText() const = 0;
virtual bool operator==(const CSSMathExpressionNode& other) const { virtual bool operator==(const CSSMathExpressionNode& other) const {
return category_ == other.category_ && is_integer_ == other.is_integer_; return category_ == other.category_ && is_integer_ == other.is_integer_;
...@@ -131,6 +144,7 @@ class CORE_EXPORT CSSMathExpressionNumericLiteral final ...@@ -131,6 +144,7 @@ class CORE_EXPORT CSSMathExpressionNumericLiteral final
PixelsAndPercent& value, PixelsAndPercent& value,
float multiplier) const final; float multiplier) const final;
double DoubleValue() const final; double DoubleValue() const final;
base::Optional<double> ComputeValueInCanonicalUnit() const final;
double ComputeLengthPx( double ComputeLengthPx(
const CSSToLengthConversionData& conversion_data) const final; const CSSToLengthConversionData& conversion_data) const final;
void AccumulateLengthArray(CSSLengthArray& length_array, void AccumulateLengthArray(CSSLengthArray& length_array,
...@@ -180,6 +194,7 @@ class CORE_EXPORT CSSMathExpressionBinaryOperation final ...@@ -180,6 +194,7 @@ class CORE_EXPORT CSSMathExpressionBinaryOperation final
PixelsAndPercent& value, PixelsAndPercent& value,
float multiplier) const final; float multiplier) const final;
double DoubleValue() const final; double DoubleValue() const final;
base::Optional<double> ComputeValueInCanonicalUnit() const final;
double ComputeLengthPx( double ComputeLengthPx(
const CSSToLengthConversionData& conversion_data) const final; const CSSToLengthConversionData& conversion_data) const final;
void AccumulateLengthArray(CSSLengthArray& length_array, void AccumulateLengthArray(CSSLengthArray& length_array,
......
...@@ -56,32 +56,14 @@ double CSSMathFunctionValue::DoubleValue() const { ...@@ -56,32 +56,14 @@ double CSSMathFunctionValue::DoubleValue() const {
double CSSMathFunctionValue::ComputeSeconds() const { double CSSMathFunctionValue::ComputeSeconds() const {
DCHECK_EQ(kCalcTime, expression_->Category()); DCHECK_EQ(kCalcTime, expression_->Category());
UnitType expression_type = expression_->ResolvedUnitType(); // TODO(crbug.com/984372): We currently use 'ms' as the canonical unit of
if (expression_type == UnitType::kSeconds) // <time>. Switch to 's' to follow the spec.
return DoubleValue(); return *expression_->ComputeValueInCanonicalUnit() / 1000;
if (expression_type == UnitType::kMilliseconds)
return DoubleValue() / 1000;
NOTREACHED();
return 0;
} }
double CSSMathFunctionValue::ComputeDegrees() const { double CSSMathFunctionValue::ComputeDegrees() const {
DCHECK_EQ(kCalcAngle, expression_->Category()); DCHECK_EQ(kCalcAngle, expression_->Category());
double expression_value = DoubleValue(); return *expression_->ComputeValueInCanonicalUnit();
UnitType expression_type = expression_->ResolvedUnitType();
switch (expression_type) {
case UnitType::kDegrees:
return expression_value;
case UnitType::kRadians:
return rad2deg(expression_value);
case UnitType::kGradians:
return grad2deg(expression_value);
case UnitType::kTurns:
return turn2deg(expression_value);
default:
NOTREACHED();
return 0;
}
} }
double CSSMathFunctionValue::ComputeLengthPx( double CSSMathFunctionValue::ComputeLengthPx(
......
<!DOCTYPE html>
<link rel="author" title="Xiaocheng Hu" href="mailto:xiaochengh@chromium.org">
<link rel="help" href="https://www.w3.org/TR/css-typed-om-1/#stylevalue-subclasses">
<meta name="assert" content="CSSUnitValue of different angle units can be added correctly.">
<style>
.ref {
width: 200px;
height: 100px;
position: absolute;
top: 100px;
left: 100px;
transform: rotate(90deg);
background-color: green;
}
</style>
<p>Test passes if there is a filled green rectangle with <strong>no red</strong>.</p>
<div class="ref"></div>
<!DOCTYPE html>
<link rel="author" title="Xiaocheng Hu" href="mailto:xiaochengh@chromium.org">
<link rel="help" href="https://www.w3.org/TR/css-typed-om-1/#stylevalue-subclasses">
<link rel="match" href="rotate-by-added-angle-ref.html">
<meta name="assert" content="CSSUnitValue of different angle units can be added correctly.">
<style>
.common {
width: 200px;
height: 100px;
position: absolute;
top: 100px;
left: 100px;
}
.ref {
transform: rotate(90deg);
background-color: red;
}
.test {
background-color: green;
z-index: 1;
}
</style>
<p>Test passes if there is a filled green rectangle with <strong>no red</strong>.</p>
<div class="common ref"></div>
<div class="common test"></div>
<script>
const angle = new CSSMathSum(CSS.deg(45), CSS.turn(0.125)); // 90 degrees
const transform = new CSSTransformValue([new CSSRotate(angle)]);
const target = document.querySelector('.test');
target.attributeStyleMap.set('transform', transform);
</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