Commit ab911df3 authored by Darren Shen's avatar Darren Shen Committed by Commit Bot

[css-typed-om] Implement CSSNumericValue.toSum.

CSSNumericValue.toSum converts a CSSNumericValue into the sum of terms
with different units. For example,

   new CSSMathSum(CSS.em(1), CSS.cm(1)).toSum('mm', 'em')

returns

   new CSSMathSum(CSS.mm(10), CSS.em(1))

Spec: https://drafts.css-houdini.org/css-typed-om-1/#dom-cssnumericvalue-tosum

Bug: 776173
Change-Id: I073c9c6d0b3246ff2a085092547ba6dfb2b5a873
Reviewed-on: https://chromium-review.googlesource.com/798932
Commit-Queue: Darren Shen <shend@chromium.org>
Reviewed-by: default avatarmeade_UTC10 <meade@chromium.org>
Cr-Commit-Position: refs/heads/master@{#521280}
parent de6781ce
......@@ -55,6 +55,7 @@ CONSOLE MESSAGE: line 147: method min
CONSOLE MESSAGE: line 147: method mul
CONSOLE MESSAGE: line 147: method sub
CONSOLE MESSAGE: line 147: method to
CONSOLE MESSAGE: line 147: method toSum
CONSOLE MESSAGE: line 147: interface CSSPerspective : CSSTransformComponent
CONSOLE MESSAGE: line 147: getter length
CONSOLE MESSAGE: line 147: method constructor
......@@ -349,6 +350,7 @@ CONSOLE MESSAGE: line 147: method min
CONSOLE MESSAGE: line 147: method mul
CONSOLE MESSAGE: line 147: method sub
CONSOLE MESSAGE: line 147: method to
CONSOLE MESSAGE: line 147: method toSum
CONSOLE MESSAGE: line 147: interface CSSPerspective : CSSTransformComponent
CONSOLE MESSAGE: line 147: getter length
CONSOLE MESSAGE: line 147: method constructor
......
<meta charset="utf-8">
<title>CSSNumericValue.toSum tests</title>
<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-cssnumericvalue-tosum">
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script src="../../resources/testhelper.js"></script>
<script>
'use strict';
test(() => {
assert_throws(new SyntaxError(), () => CSS.px(1).toSum('px', 'lemon'));
}, 'Converting a CSSNumericValue to a sum with invalid units throws SyntaxError');
test(() => {
assert_throws(new TypeError(), () => new CSSMathMax(1, CSS.px(1)).toSum('number'));
}, 'Converting a CSSNumericValue with an invalid sum value to a sum throws TypeError');
test(() => {
assert_throws(new TypeError(), () => new CSSMathProduct(CSS.px(1), CSS.px(1)).to('px'));
}, 'Converting a CSSNumericValue with compound units to a sum throws TypeError');
test(() => {
assert_throws(new TypeError(), () => CSS.px(1).toSum('number'));
}, 'Converting a CSSNumericValue to a sum with an incompatible unit throws TypeError');
test(() => {
assert_throws(new TypeError(), () => CSS.px(1).toSum('px', 's'));
}, 'Converting a CSSNumericValue to a sum with units that are not addable throws TypeError');
test(() => {
assert_throws(new TypeError(), () => new CSSMathSum(CSS.px(1), CSS.em(1)).toSum('px'));
}, 'Converting a CSSNumericValue with leftover units to a sum throws TypeError');
test(() => {
assert_style_value_equals(CSS.number(1).toSum('number'), new CSSMathSum(CSS.number(1)));
assert_style_value_equals(CSS.px(1).toSum('px'), new CSSMathSum(CSS.px(1)));
}, 'Converting CSSNumericValue to a sum with its own unit returns itself');
test(() => {
assert_style_value_equals(
new CSSMathSum(CSS.px(1), CSS.em(1), CSS.vw(1), CSS.rem(1)).toSum(),
new CSSMathSum(CSS.em(1), CSS.px(1), CSS.rem(1), CSS.vw(1))
);
}, 'Converting CSSNumericValue to a sum with no arguments returns all the units in sorted order');
// TODO(776173): cssUnitValue_toMethod.html has more comprehensive tests of converting
// within the same base type. Merge those tests into here.
test(() => {
assert_style_value_equals(CSS.cm(2).toSum('mm'), new CSSMathSum(CSS.mm(20)));
}, 'Converting CSSNumericValue to a sum with a relative unit converts correctly');
test(() => {
assert_style_value_equals(
CSS.px(1).toSum('em', 'px', 'vw'),
new CSSMathSum(CSS.em(0), CSS.px(1), CSS.vw(0))
);
}, 'Converting CSSNumericValue to a sum containing extra units returns zero for those units');
test(() => {
assert_style_value_equals(
new CSSMathSum(CSS.cm(1), CSS.mm(10)).toSum('cm', 'mm'),
new CSSMathSum(CSS.cm(2), CSS.mm(0))
);
assert_style_value_equals(
new CSSMathSum(CSS.cm(1), CSS.mm(10)).toSum('mm', 'cm'),
new CSSMathSum(CSS.mm(20), CSS.cm(0))
);
}, 'CSSNumericValue.toSum converts greedily');
</script>
......@@ -706,6 +706,7 @@ interface CSSNumericValue : CSSStyleValue
method mul
method sub
method to
method toSum
interface CSSPageRule : CSSRule
attribute @@toStringTag
getter selectorText
......
......@@ -17,6 +17,8 @@
#include "core/css/parser/CSSParserTokenStream.h"
#include "core/css/parser/CSSTokenizer.h"
#include <numeric>
namespace blink {
namespace {
......@@ -141,6 +143,15 @@ CSSNumericValue* CalcToNumericValue(const CSSCalcExpressionNode& root) {
return CSSMathProduct::Create(std::move(values));
}
CSSUnitValue* CSSNumericSumValueEntryToUnitValue(
const CSSNumericSumValue::Term& term) {
if (term.units.size() == 0)
return CSSUnitValue::Create(term.value);
if (term.units.size() == 1 && term.units.begin()->value == 1)
return CSSUnitValue::Create(term.value, term.units.begin()->key);
return nullptr;
}
} // namespace
bool CSSNumericValue::IsValidUnit(CSSPrimitiveValue::UnitType unit) {
......@@ -241,14 +252,87 @@ CSSUnitValue* CSSNumericValue::to(CSSPrimitiveValue::UnitType unit) const {
if (!sum || sum->terms.size() != 1)
return nullptr;
const auto& term = sum->terms[0];
if (term.units.size() == 0)
return CSSUnitValue::Create(term.value)->ConvertTo(unit);
if (term.units.size() == 1 && term.units.begin()->value == 1) {
return CSSUnitValue::Create(term.value, term.units.begin()->key)
->ConvertTo(unit);
CSSUnitValue* value = CSSNumericSumValueEntryToUnitValue(sum->terms[0]);
if (!value)
return nullptr;
return value->ConvertTo(unit);
}
CSSMathSum* CSSNumericValue::toSum(const Vector<String>& unit_strings,
ExceptionState& exception_state) {
for (const auto& unit_string : unit_strings) {
if (!IsValidUnit(UnitFromName(unit_string))) {
exception_state.ThrowDOMException(kSyntaxError,
"Invalid unit for conversion");
return nullptr;
}
}
return nullptr;
const WTF::Optional<CSSNumericSumValue> sum = SumValue();
if (!sum) {
exception_state.ThrowTypeError("Invalid value for conversion");
return nullptr;
}
CSSNumericValueVector values;
for (const auto& term : sum->terms) {
CSSUnitValue* value = CSSNumericSumValueEntryToUnitValue(term);
if (!value) {
exception_state.ThrowTypeError("Invalid value for conversion");
return nullptr;
}
values.push_back(value);
}
if (unit_strings.size() == 0) {
std::sort(values.begin(), values.end(), [](const auto& a, const auto& b) {
return WTF::CodePointCompareLessThan(ToCSSUnitValue(a)->unit(),
ToCSSUnitValue(b)->unit());
});
// We got 'values' from a sum value, so it must be a valid CSSMathSum.
CSSMathSum* result = CSSMathSum::Create(values);
DCHECK(result);
return result;
}
CSSNumericValueVector result;
for (const auto& unit_string : unit_strings) {
CSSPrimitiveValue::UnitType target_unit = UnitFromName(unit_string);
DCHECK(IsValidUnit(target_unit));
// Collect all the terms that are compatible with this unit.
// We mark used terms as null so we don't use them again.
double total_value =
std::accumulate(values.begin(), values.end(), 0.0,
[target_unit](double cur_sum, auto& value) {
if (value) {
auto& unit_value = ToCSSUnitValue(*value);
if (const auto* converted_value =
unit_value.ConvertTo(target_unit)) {
cur_sum += converted_value->value();
value = nullptr;
}
}
return cur_sum;
});
result.push_back(CSSUnitValue::Create(total_value, target_unit));
}
if (std::any_of(values.begin(), values.end(),
[](const auto& v) { return v; })) {
exception_state.ThrowTypeError(
"There were leftover terms that were not converted");
return nullptr;
}
CSSMathSum* value = CSSMathSum::Create(result);
if (!value) {
exception_state.ThrowTypeError("Can't create CSSMathSum");
return nullptr;
}
return value;
}
CSSNumericValue* CSSNumericValue::add(
......
......@@ -21,6 +21,7 @@ class CSSUnitValue;
class ExceptionState;
class CSSNumericValue;
class CSSMathSum;
using CSSNumberish = DoubleOrCSSNumericValue;
using CSSNumericValueVector = HeapVector<Member<CSSNumericValue>>;
......@@ -45,6 +46,7 @@ class CORE_EXPORT CSSNumericValue : public CSSStyleValue {
// Converts between compatible types, as defined in the IDL.
CSSNumericValue* to(const String&, ExceptionState&);
CSSMathSum* toSum(const Vector<String>&, ExceptionState&);
// Internal methods.
// Converts between compatible types.
......
......@@ -19,6 +19,7 @@ typedef (double or CSSNumericValue) CSSNumberish;
boolean equals(CSSNumberish... values);
[RaisesException, NewObject] CSSNumericValue to(DOMString unit);
[RaisesException, NewObject] CSSMathSum toSum(DOMString... units);
// Putting Exposed=Window in the next line makes |parse| not exposed to PaintWorklet.
[RaisesException, NewObject, Exposed=Window] static CSSNumericValue parse(DOMString cssText);
......
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