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 {
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(
const CSSToLengthConversionData& conversion_data) const {
switch (category_) {
......@@ -482,6 +503,29 @@ double CSSMathExpressionBinaryOperation::DoubleValue() const {
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(
const CSSToLengthConversionData& conversion_data) const {
const double left_value = left_side_->ComputeLengthPx(conversion_data);
......
......@@ -81,6 +81,19 @@ class CORE_EXPORT CSSMathExpressionNode
virtual void AccumulatePixelsAndPercent(const CSSToLengthConversionData&,
PixelsAndPercent&,
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 bool operator==(const CSSMathExpressionNode& other) const {
return category_ == other.category_ && is_integer_ == other.is_integer_;
......@@ -131,6 +144,7 @@ class CORE_EXPORT CSSMathExpressionNumericLiteral final
PixelsAndPercent& value,
float multiplier) const final;
double DoubleValue() const final;
base::Optional<double> ComputeValueInCanonicalUnit() const final;
double ComputeLengthPx(
const CSSToLengthConversionData& conversion_data) const final;
void AccumulateLengthArray(CSSLengthArray& length_array,
......@@ -180,6 +194,7 @@ class CORE_EXPORT CSSMathExpressionBinaryOperation final
PixelsAndPercent& value,
float multiplier) const final;
double DoubleValue() const final;
base::Optional<double> ComputeValueInCanonicalUnit() const final;
double ComputeLengthPx(
const CSSToLengthConversionData& conversion_data) const final;
void AccumulateLengthArray(CSSLengthArray& length_array,
......
......@@ -56,32 +56,14 @@ double CSSMathFunctionValue::DoubleValue() const {
double CSSMathFunctionValue::ComputeSeconds() const {
DCHECK_EQ(kCalcTime, expression_->Category());
UnitType expression_type = expression_->ResolvedUnitType();
if (expression_type == UnitType::kSeconds)
return DoubleValue();
if (expression_type == UnitType::kMilliseconds)
return DoubleValue() / 1000;
NOTREACHED();
return 0;
// TODO(crbug.com/984372): We currently use 'ms' as the canonical unit of
// <time>. Switch to 's' to follow the spec.
return *expression_->ComputeValueInCanonicalUnit() / 1000;
}
double CSSMathFunctionValue::ComputeDegrees() const {
DCHECK_EQ(kCalcAngle, expression_->Category());
double expression_value = DoubleValue();
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;
}
return *expression_->ComputeValueInCanonicalUnit();
}
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