1/*
2 * Copyright (C) 2011, 2012 Google Inc. All rights reserved.
3 * Copyright (C) 2014 Apple Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 * * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#include "config.h"
33#include "CSSCalculationValue.h"
34
35#include "CSSParser.h"
36#include "CSSParserTokenRange.h"
37#include "CSSPrimitiveValueMappings.h"
38#include "StyleResolver.h"
39#include <wtf/MathExtras.h>
40#include <wtf/text/StringBuilder.h>
41
42static const int maxExpressionDepth = 100;
43
44enum ParseState {
45 OK,
46 TooDeep,
47 NoMoreTokens
48};
49
50namespace WebCore {
51
52static RefPtr<CSSCalcExpressionNode> createCSS(const CalcExpressionNode&, const RenderStyle&);
53static RefPtr<CSSCalcExpressionNode> createCSS(const Length&, const RenderStyle&);
54
55static CalculationCategory unitCategory(CSSPrimitiveValue::UnitType type)
56{
57 switch (type) {
58 case CSSPrimitiveValue::CSS_NUMBER:
59 return CalculationCategory::Number;
60 case CSSPrimitiveValue::CSS_EMS:
61 case CSSPrimitiveValue::CSS_EXS:
62 case CSSPrimitiveValue::CSS_PX:
63 case CSSPrimitiveValue::CSS_CM:
64 case CSSPrimitiveValue::CSS_MM:
65 case CSSPrimitiveValue::CSS_IN:
66 case CSSPrimitiveValue::CSS_PT:
67 case CSSPrimitiveValue::CSS_PC:
68 case CSSPrimitiveValue::CSS_REMS:
69 case CSSPrimitiveValue::CSS_CHS:
70 case CSSPrimitiveValue::CSS_VW:
71 case CSSPrimitiveValue::CSS_VH:
72 case CSSPrimitiveValue::CSS_VMIN:
73 case CSSPrimitiveValue::CSS_VMAX:
74 return CalculationCategory::Length;
75 case CSSPrimitiveValue::CSS_PERCENTAGE:
76 return CalculationCategory::Percent;
77 case CSSPrimitiveValue::CSS_DEG:
78 case CSSPrimitiveValue::CSS_RAD:
79 case CSSPrimitiveValue::CSS_GRAD:
80 case CSSPrimitiveValue::CSS_TURN:
81 return CalculationCategory::Angle;
82 case CSSPrimitiveValue::CSS_MS:
83 case CSSPrimitiveValue::CSS_S:
84 return CalculationCategory::Time;
85 case CSSPrimitiveValue::CSS_HZ:
86 case CSSPrimitiveValue::CSS_KHZ:
87 return CalculationCategory::Frequency;
88 default:
89 return CalculationCategory::Other;
90 }
91}
92
93static bool hasDoubleValue(CSSPrimitiveValue::UnitType type)
94{
95 switch (type) {
96 case CSSPrimitiveValue::CSS_FR:
97 case CSSPrimitiveValue::CSS_NUMBER:
98 case CSSPrimitiveValue::CSS_PERCENTAGE:
99 case CSSPrimitiveValue::CSS_EMS:
100 case CSSPrimitiveValue::CSS_EXS:
101 case CSSPrimitiveValue::CSS_CHS:
102 case CSSPrimitiveValue::CSS_REMS:
103 case CSSPrimitiveValue::CSS_PX:
104 case CSSPrimitiveValue::CSS_CM:
105 case CSSPrimitiveValue::CSS_MM:
106 case CSSPrimitiveValue::CSS_IN:
107 case CSSPrimitiveValue::CSS_PT:
108 case CSSPrimitiveValue::CSS_PC:
109 case CSSPrimitiveValue::CSS_DEG:
110 case CSSPrimitiveValue::CSS_RAD:
111 case CSSPrimitiveValue::CSS_GRAD:
112 case CSSPrimitiveValue::CSS_TURN:
113 case CSSPrimitiveValue::CSS_MS:
114 case CSSPrimitiveValue::CSS_S:
115 case CSSPrimitiveValue::CSS_HZ:
116 case CSSPrimitiveValue::CSS_KHZ:
117 case CSSPrimitiveValue::CSS_DIMENSION:
118 case CSSPrimitiveValue::CSS_VW:
119 case CSSPrimitiveValue::CSS_VH:
120 case CSSPrimitiveValue::CSS_VMIN:
121 case CSSPrimitiveValue::CSS_VMAX:
122 case CSSPrimitiveValue::CSS_DPPX:
123 case CSSPrimitiveValue::CSS_DPI:
124 case CSSPrimitiveValue::CSS_DPCM:
125 return true;
126 case CSSPrimitiveValue::CSS_UNKNOWN:
127 case CSSPrimitiveValue::CSS_STRING:
128 case CSSPrimitiveValue::CSS_FONT_FAMILY:
129 case CSSPrimitiveValue::CSS_URI:
130 case CSSPrimitiveValue::CSS_IDENT:
131 case CSSPrimitiveValue::CSS_ATTR:
132 case CSSPrimitiveValue::CSS_COUNTER:
133 case CSSPrimitiveValue::CSS_RECT:
134 case CSSPrimitiveValue::CSS_RGBCOLOR:
135 case CSSPrimitiveValue::CSS_PAIR:
136 case CSSPrimitiveValue::CSS_UNICODE_RANGE:
137 case CSSPrimitiveValue::CSS_COUNTER_NAME:
138 case CSSPrimitiveValue::CSS_SHAPE:
139 case CSSPrimitiveValue::CSS_QUAD:
140 case CSSPrimitiveValue::CSS_QUIRKY_EMS:
141 case CSSPrimitiveValue::CSS_CALC:
142 case CSSPrimitiveValue::CSS_CALC_PERCENTAGE_WITH_NUMBER:
143 case CSSPrimitiveValue::CSS_CALC_PERCENTAGE_WITH_LENGTH:
144 case CSSPrimitiveValue::CSS_PROPERTY_ID:
145 case CSSPrimitiveValue::CSS_VALUE_ID:
146#if ENABLE(DASHBOARD_SUPPORT)
147 case CSSPrimitiveValue::CSS_DASHBOARD_REGION:
148#endif
149 return false;
150 };
151 ASSERT_NOT_REACHED();
152 return false;
153}
154
155static String buildCssText(const String& expression)
156{
157 StringBuilder result;
158 result.appendLiteral("calc");
159 bool expressionHasSingleTerm = expression[0] != '(';
160 if (expressionHasSingleTerm)
161 result.append('(');
162 result.append(expression);
163 if (expressionHasSingleTerm)
164 result.append(')');
165 return result.toString();
166}
167
168String CSSCalcValue::customCSSText() const
169{
170 return buildCssText(m_expression->customCSSText());
171}
172
173bool CSSCalcValue::equals(const CSSCalcValue& other) const
174{
175 return compareCSSValue(m_expression, other.m_expression);
176}
177
178inline double CSSCalcValue::clampToPermittedRange(double value) const
179{
180 return m_shouldClampToNonNegative && value < 0 ? 0 : value;
181}
182
183double CSSCalcValue::doubleValue() const
184{
185 return clampToPermittedRange(m_expression->doubleValue());
186}
187
188double CSSCalcValue::computeLengthPx(const CSSToLengthConversionData& conversionData) const
189{
190 return clampToPermittedRange(m_expression->computeLengthPx(conversionData));
191}
192
193class CSSCalcPrimitiveValue final : public CSSCalcExpressionNode {
194 WTF_MAKE_FAST_ALLOCATED;
195public:
196 static Ref<CSSCalcPrimitiveValue> create(Ref<CSSPrimitiveValue>&& value, bool isInteger)
197 {
198 return adoptRef(*new CSSCalcPrimitiveValue(WTFMove(value), isInteger));
199 }
200
201 static RefPtr<CSSCalcPrimitiveValue> create(double value, CSSPrimitiveValue::UnitType type, bool isInteger)
202 {
203 if (!std::isfinite(value))
204 return nullptr;
205 return adoptRef(new CSSCalcPrimitiveValue(CSSPrimitiveValue::create(value, type), isInteger));
206 }
207
208private:
209 bool isZero() const final
210 {
211 return !m_value->doubleValue();
212 }
213
214 String customCSSText() const final
215 {
216 return m_value->cssText();
217 }
218
219 std::unique_ptr<CalcExpressionNode> createCalcExpression(const CSSToLengthConversionData& conversionData) const final
220 {
221 switch (category()) {
222 case CalculationCategory::Number:
223 return std::make_unique<CalcExpressionNumber>(m_value->floatValue());
224 case CalculationCategory::Length:
225 return std::make_unique<CalcExpressionLength>(Length(m_value->computeLength<float>(conversionData), WebCore::Fixed));
226 case CalculationCategory::Percent:
227 case CalculationCategory::PercentLength: {
228 return std::make_unique<CalcExpressionLength>(m_value->convertToLength<FixedFloatConversion | PercentConversion>(conversionData));
229 }
230 // Only types that could be part of a Length expression can be converted
231 // to a CalcExpressionNode. CalculationCategory::PercentNumber makes no sense as a Length.
232 case CalculationCategory::PercentNumber:
233 case CalculationCategory::Angle:
234 case CalculationCategory::Time:
235 case CalculationCategory::Frequency:
236 case CalculationCategory::Other:
237 ASSERT_NOT_REACHED();
238 }
239 ASSERT_NOT_REACHED();
240 return nullptr;
241 }
242
243 double doubleValue() const final
244 {
245 if (hasDoubleValue(primitiveType()))
246 return m_value->doubleValue();
247 ASSERT_NOT_REACHED();
248 return 0;
249 }
250
251 double computeLengthPx(const CSSToLengthConversionData& conversionData) const final
252 {
253 switch (category()) {
254 case CalculationCategory::Length:
255 return m_value->computeLength<double>(conversionData);
256 case CalculationCategory::Percent:
257 case CalculationCategory::Number:
258 return m_value->doubleValue();
259 case CalculationCategory::PercentLength:
260 case CalculationCategory::PercentNumber:
261 case CalculationCategory::Angle:
262 case CalculationCategory::Time:
263 case CalculationCategory::Frequency:
264 case CalculationCategory::Other:
265 ASSERT_NOT_REACHED();
266 break;
267 }
268 ASSERT_NOT_REACHED();
269 return 0;
270 }
271
272 void collectDirectComputationalDependencies(HashSet<CSSPropertyID>& values) const final
273 {
274 m_value->collectDirectComputationalDependencies(values);
275 }
276
277 void collectDirectRootComputationalDependencies(HashSet<CSSPropertyID>& values) const final
278 {
279 m_value->collectDirectRootComputationalDependencies(values);
280 }
281
282 bool equals(const CSSCalcExpressionNode& other) const final
283 {
284 if (type() != other.type())
285 return false;
286
287 return compareCSSValue(m_value, static_cast<const CSSCalcPrimitiveValue&>(other).m_value);
288 }
289
290 Type type() const final { return CssCalcPrimitiveValue; }
291 CSSPrimitiveValue::UnitType primitiveType() const final
292 {
293 return CSSPrimitiveValue::UnitType(m_value->primitiveType());
294 }
295
296private:
297 explicit CSSCalcPrimitiveValue(Ref<CSSPrimitiveValue>&& value, bool isInteger)
298 : CSSCalcExpressionNode(unitCategory((CSSPrimitiveValue::UnitType)value->primitiveType()), isInteger)
299 , m_value(WTFMove(value))
300 {
301 }
302
303 Ref<CSSPrimitiveValue> m_value;
304};
305
306static const CalculationCategory addSubtractResult[static_cast<unsigned>(CalculationCategory::Angle)][static_cast<unsigned>(CalculationCategory::Angle)] = {
307// CalculationCategory::Number CalculationCategory::Length CalculationCategory::Percent CalculationCategory::PercentNumber CalculationCategory::PercentLength
308 { CalculationCategory::Number, CalculationCategory::Other, CalculationCategory::PercentNumber, CalculationCategory::PercentNumber, CalculationCategory::Other }, // CalculationCategory::Number
309 { CalculationCategory::Other, CalculationCategory::Length, CalculationCategory::PercentLength, CalculationCategory::Other, CalculationCategory::PercentLength }, // CalculationCategory::Length
310 { CalculationCategory::PercentNumber, CalculationCategory::PercentLength, CalculationCategory::Percent, CalculationCategory::PercentNumber, CalculationCategory::PercentLength }, // CalculationCategory::Percent
311 { CalculationCategory::PercentNumber, CalculationCategory::Other, CalculationCategory::PercentNumber, CalculationCategory::PercentNumber, CalculationCategory::Other }, // CalculationCategory::PercentNumber
312 { CalculationCategory::Other, CalculationCategory::PercentLength, CalculationCategory::PercentLength, CalculationCategory::Other, CalculationCategory::PercentLength }, // CalculationCategory::PercentLength
313};
314
315static CalculationCategory determineCategory(const CSSCalcExpressionNode& leftSide, const CSSCalcExpressionNode& rightSide, CalcOperator op)
316{
317 CalculationCategory leftCategory = leftSide.category();
318 CalculationCategory rightCategory = rightSide.category();
319 ASSERT(leftCategory < CalculationCategory::Other);
320 ASSERT(rightCategory < CalculationCategory::Other);
321
322 switch (op) {
323 case CalcOperator::Add:
324 case CalcOperator::Subtract:
325 if (leftCategory < CalculationCategory::Angle && rightCategory < CalculationCategory::Angle)
326 return addSubtractResult[static_cast<unsigned>(leftCategory)][static_cast<unsigned>(rightCategory)];
327 if (leftCategory == rightCategory)
328 return leftCategory;
329 return CalculationCategory::Other;
330 case CalcOperator::Multiply:
331 if (leftCategory != CalculationCategory::Number && rightCategory != CalculationCategory::Number)
332 return CalculationCategory::Other;
333 return leftCategory == CalculationCategory::Number ? rightCategory : leftCategory;
334 case CalcOperator::Divide:
335 if (rightCategory != CalculationCategory::Number || rightSide.isZero())
336 return CalculationCategory::Other;
337 return leftCategory;
338 case CalcOperator::Min:
339 case CalcOperator::Max:
340 ASSERT_NOT_REACHED();
341 return CalculationCategory::Other;
342 }
343
344 ASSERT_NOT_REACHED();
345 return CalculationCategory::Other;
346}
347
348static CalculationCategory resolvedTypeForMinOrMax(CalculationCategory category, CalculationCategory destinationCategory)
349{
350 switch (category) {
351 case CalculationCategory::Number:
352 case CalculationCategory::Length:
353 case CalculationCategory::PercentNumber:
354 case CalculationCategory::PercentLength:
355 case CalculationCategory::Angle:
356 case CalculationCategory::Time:
357 case CalculationCategory::Frequency:
358 case CalculationCategory::Other:
359 return category;
360
361 case CalculationCategory::Percent:
362 if (destinationCategory == CalculationCategory::Length)
363 return CalculationCategory::PercentLength;
364 if (destinationCategory == CalculationCategory::Number)
365 return CalculationCategory::PercentNumber;
366 return category;
367 }
368
369 return CalculationCategory::Other;
370}
371
372static inline bool isIntegerResult(CalcOperator op, const CSSCalcExpressionNode& leftSide, const CSSCalcExpressionNode& rightSide)
373{
374 // Performs W3C spec's type checking for calc integers.
375 // http://www.w3.org/TR/css3-values/#calc-type-checking
376 return op != CalcOperator::Divide && leftSide.isInteger() && rightSide.isInteger();
377}
378
379static inline bool isIntegerResult(CalcOperator op, const Vector<Ref<CSSCalcExpressionNode>>& nodes)
380{
381 // Performs W3C spec's type checking for calc integers.
382 // http://www.w3.org/TR/css3-values/#calc-type-checking
383 if (op == CalcOperator::Divide)
384 return false;
385
386 for (auto& node : nodes) {
387 if (!node->isInteger())
388 return false;
389 }
390
391 return true;
392}
393
394static bool isSamePair(CalculationCategory a, CalculationCategory b, CalculationCategory x, CalculationCategory y)
395{
396 return (a == x && b == y) || (a == y && b == x);
397}
398
399class CSSCalcOperation final : public CSSCalcExpressionNode {
400 WTF_MAKE_FAST_ALLOCATED;
401public:
402 static RefPtr<CSSCalcOperation> create(CalcOperator op, RefPtr<CSSCalcExpressionNode>&& leftSide, RefPtr<CSSCalcExpressionNode>&& rightSide)
403 {
404 if (!leftSide || !rightSide)
405 return nullptr;
406
407 ASSERT(leftSide->category() < CalculationCategory::Other);
408 ASSERT(rightSide->category() < CalculationCategory::Other);
409
410 auto newCategory = determineCategory(*leftSide, *rightSide, op);
411 if (newCategory == CalculationCategory::Other)
412 return nullptr;
413
414 return adoptRef(new CSSCalcOperation(newCategory, op, leftSide.releaseNonNull(), rightSide.releaseNonNull()));
415 }
416
417 static RefPtr<CSSCalcOperation> createMinOrMax(CalcOperator op, Vector<Ref<CSSCalcExpressionNode>>&& values, CalculationCategory destinationCategory)
418 {
419 ASSERT(op == CalcOperator::Min || op == CalcOperator::Max);
420
421 Optional<CalculationCategory> category = WTF::nullopt;
422 for (auto& value : values) {
423 auto valueCategory = resolvedTypeForMinOrMax(value->category(), destinationCategory);
424
425 ASSERT(valueCategory < CalculationCategory::Other);
426 if (!category) {
427 if (valueCategory == CalculationCategory::Other)
428 return nullptr;
429 category = valueCategory;
430 }
431
432 if (category != valueCategory) {
433 if (isSamePair(category.value(), valueCategory, CalculationCategory::Length, CalculationCategory::PercentLength)) {
434 category = CalculationCategory::PercentLength;
435 continue;
436 }
437 if (isSamePair(category.value(), valueCategory, CalculationCategory::Number, CalculationCategory::PercentNumber)) {
438 category = CalculationCategory::PercentNumber;
439 continue;
440 }
441 return nullptr;
442 }
443 }
444
445 return adoptRef(new CSSCalcOperation(category.value(), op, WTFMove(values)));
446 }
447
448 static RefPtr<CSSCalcExpressionNode> createSimplified(CalcOperator op, RefPtr<CSSCalcExpressionNode>&& leftSide, RefPtr<CSSCalcExpressionNode>&& rightSide)
449 {
450 if (!leftSide || !rightSide)
451 return nullptr;
452
453 auto leftCategory = leftSide->category();
454 auto rightCategory = rightSide->category();
455 ASSERT(leftCategory < CalculationCategory::Other);
456 ASSERT(rightCategory < CalculationCategory::Other);
457
458 bool isInteger = isIntegerResult(op, *leftSide, *rightSide);
459
460 // Simplify numbers.
461 if (leftCategory == CalculationCategory::Number && rightCategory == CalculationCategory::Number) {
462 CSSPrimitiveValue::UnitType evaluationType = CSSPrimitiveValue::CSS_NUMBER;
463 return CSSCalcPrimitiveValue::create(evaluateOperator(op, { leftSide->doubleValue(), rightSide->doubleValue() }), evaluationType, isInteger);
464 }
465
466 // Simplify addition and subtraction between same types.
467 if (op == CalcOperator::Add || op == CalcOperator::Subtract) {
468 if (leftCategory == rightSide->category()) {
469 CSSPrimitiveValue::UnitType leftType = leftSide->primitiveType();
470 if (hasDoubleValue(leftType)) {
471 CSSPrimitiveValue::UnitType rightType = rightSide->primitiveType();
472 if (leftType == rightType)
473 return CSSCalcPrimitiveValue::create(evaluateOperator(op, { leftSide->doubleValue(), rightSide->doubleValue() }), leftType, isInteger);
474 CSSPrimitiveValue::UnitCategory leftUnitCategory = CSSPrimitiveValue::unitCategory(leftType);
475 if (leftUnitCategory != CSSPrimitiveValue::UOther && leftUnitCategory == CSSPrimitiveValue::unitCategory(rightType)) {
476 CSSPrimitiveValue::UnitType canonicalType = CSSPrimitiveValue::canonicalUnitTypeForCategory(leftUnitCategory);
477 if (canonicalType != CSSPrimitiveValue::CSS_UNKNOWN) {
478 double leftValue = leftSide->doubleValue() * CSSPrimitiveValue::conversionToCanonicalUnitsScaleFactor(leftType);
479 double rightValue = rightSide->doubleValue() * CSSPrimitiveValue::conversionToCanonicalUnitsScaleFactor(rightType);
480 return CSSCalcPrimitiveValue::create(evaluateOperator(op, { leftValue, rightValue }), canonicalType, isInteger);
481 }
482 }
483 }
484 }
485 } else {
486 // Simplify multiplying or dividing by a number for simplifiable types.
487 ASSERT(op == CalcOperator::Multiply || op == CalcOperator::Divide);
488 auto* numberSide = getNumberSide(*leftSide, *rightSide);
489 if (!numberSide)
490 return create(op, leftSide.releaseNonNull(), rightSide.releaseNonNull());
491 if (numberSide == leftSide && op == CalcOperator::Divide)
492 return nullptr;
493 auto& otherSide = leftSide == numberSide ? *rightSide : *leftSide;
494
495 double number = numberSide->doubleValue();
496 if (!std::isfinite(number))
497 return nullptr;
498 if (op == CalcOperator::Divide && !number)
499 return nullptr;
500
501 auto otherType = otherSide.primitiveType();
502 if (hasDoubleValue(otherType))
503 return CSSCalcPrimitiveValue::create(evaluateOperator(op, { otherSide.doubleValue(), number }), otherType, isInteger);
504 }
505
506 return create(op, leftSide.releaseNonNull(), rightSide.releaseNonNull());
507 }
508
509private:
510 bool isZero() const final
511 {
512 return !doubleValue();
513 }
514
515 std::unique_ptr<CalcExpressionNode> createCalcExpression(const CSSToLengthConversionData& conversionData) const final
516 {
517 Vector<std::unique_ptr<CalcExpressionNode>> nodes;
518 nodes.reserveInitialCapacity(m_children.size());
519
520 for (auto& child : m_children) {
521 auto node = child->createCalcExpression(conversionData);
522 if (!node)
523 return nullptr;
524 nodes.uncheckedAppend(WTFMove(node));
525 }
526 return std::make_unique<CalcExpressionOperation>(WTFMove(nodes), m_operator);
527 }
528
529 double doubleValue() const final
530 {
531 Vector<double> doubleValues;
532 for (auto& child : m_children)
533 doubleValues.append(child->doubleValue());
534 return evaluate(doubleValues);
535 }
536
537 double computeLengthPx(const CSSToLengthConversionData& conversionData) const final
538 {
539 Vector<double> doubleValues;
540 for (auto& child : m_children)
541 doubleValues.append(child->computeLengthPx(conversionData));
542 return evaluate(doubleValues);
543 }
544
545 void collectDirectComputationalDependencies(HashSet<CSSPropertyID>& values) const final
546 {
547 for (auto& child : m_children)
548 child->collectDirectComputationalDependencies(values);
549 }
550
551 void collectDirectRootComputationalDependencies(HashSet<CSSPropertyID>& values) const final
552 {
553 for (auto& child : m_children)
554 child->collectDirectRootComputationalDependencies(values);
555 }
556
557 static String buildCssText(Vector<String> childExpressions, CalcOperator op)
558 {
559 StringBuilder result;
560 result.append('(');
561 switch (op) {
562 case CalcOperator::Add:
563 case CalcOperator::Subtract:
564 case CalcOperator::Multiply:
565 case CalcOperator::Divide:
566 ASSERT(childExpressions.size() == 2);
567 result.append(childExpressions[0]);
568 result.append(' ');
569 result.append(static_cast<char>(op));
570 result.append(' ');
571 result.append(childExpressions[1]);
572 break;
573 case CalcOperator::Min:
574 case CalcOperator::Max:
575 ASSERT(!childExpressions.isEmpty());
576 const char* functionName = op == CalcOperator::Min ? "min(" : "max(";
577 result.append(functionName);
578 result.append(childExpressions[0]);
579 for (size_t i = 1; i < childExpressions.size(); ++i) {
580 result.append(',');
581 result.append(' ');
582 result.append(childExpressions[i]);
583 }
584 result.append(')');
585 }
586 result.append(')');
587
588 return result.toString();
589 }
590
591 String customCSSText() const final
592 {
593 Vector<String> cssTexts;
594 for (auto& child : m_children)
595 cssTexts.append(child->customCSSText());
596 return buildCssText(cssTexts, m_operator);
597 }
598
599 bool equals(const CSSCalcExpressionNode& exp) const final
600 {
601 if (type() != exp.type())
602 return false;
603
604 const CSSCalcOperation& other = static_cast<const CSSCalcOperation&>(exp);
605
606 if (m_children.size() != other.m_children.size() || m_operator != other.m_operator)
607 return false;
608
609 for (size_t i = 0; i < m_children.size(); ++i) {
610 if (!compareCSSValue(m_children[i], other.m_children[i]))
611 return false;
612 }
613 return true;
614 }
615
616 Type type() const final { return CssCalcOperation; }
617
618 CSSPrimitiveValue::UnitType primitiveType() const final
619 {
620 switch (category()) {
621 case CalculationCategory::Number:
622#if !ASSERT_DISABLED
623 for (auto& child : m_children)
624 ASSERT(child->category() == CalculationCategory::Number);
625#endif
626 return CSSPrimitiveValue::CSS_NUMBER;
627 case CalculationCategory::Length:
628 case CalculationCategory::Percent: {
629 if (m_children.isEmpty())
630 return CSSPrimitiveValue::CSS_UNKNOWN;
631 if (m_children.size() == 2) {
632 if (m_children[0]->category() == CalculationCategory::Number)
633 return m_children[1]->primitiveType();
634 if (m_children[1]->category() == CalculationCategory::Number)
635 return m_children[0]->primitiveType();
636 }
637 CSSPrimitiveValue::UnitType firstType = m_children[0]->primitiveType();
638 for (auto& child : m_children) {
639 if (firstType != child->primitiveType())
640 return CSSPrimitiveValue::CSS_UNKNOWN;
641 }
642 return firstType;
643 }
644 case CalculationCategory::Angle:
645 return CSSPrimitiveValue::CSS_DEG;
646 case CalculationCategory::Time:
647 return CSSPrimitiveValue::CSS_MS;
648 case CalculationCategory::Frequency:
649 return CSSPrimitiveValue::CSS_HZ;
650 case CalculationCategory::PercentLength:
651 case CalculationCategory::PercentNumber:
652 case CalculationCategory::Other:
653 return CSSPrimitiveValue::CSS_UNKNOWN;
654 }
655 ASSERT_NOT_REACHED();
656 return CSSPrimitiveValue::CSS_UNKNOWN;
657 }
658
659 CSSCalcOperation(CalculationCategory category, CalcOperator op, Ref<CSSCalcExpressionNode>&& leftSide, Ref<CSSCalcExpressionNode>&& rightSide)
660 : CSSCalcExpressionNode(category, isIntegerResult(op, leftSide.get(), rightSide.get()))
661 , m_operator(op)
662 {
663 m_children.reserveInitialCapacity(2);
664 m_children.uncheckedAppend(WTFMove(leftSide));
665 m_children.uncheckedAppend(WTFMove(rightSide));
666 }
667
668 CSSCalcOperation(CalculationCategory category, CalcOperator op, Vector<Ref<CSSCalcExpressionNode>>&& children)
669 : CSSCalcExpressionNode(category, isIntegerResult(op, children))
670 , m_operator(op)
671 , m_children(WTFMove(children))
672 {
673 }
674
675 static CSSCalcExpressionNode* getNumberSide(CSSCalcExpressionNode& leftSide, CSSCalcExpressionNode& rightSide)
676 {
677 if (leftSide.category() == CalculationCategory::Number)
678 return &leftSide;
679 if (rightSide.category() == CalculationCategory::Number)
680 return &rightSide;
681 return nullptr;
682 }
683
684 double evaluate(const Vector<double>& children) const
685 {
686 return evaluateOperator(m_operator, children);
687 }
688
689 static double evaluateOperator(CalcOperator op, const Vector<double>& children)
690 {
691 switch (op) {
692 case CalcOperator::Add:
693 ASSERT(children.size() == 2);
694 return children[0] + children[1];
695 case CalcOperator::Subtract:
696 ASSERT(children.size() == 2);
697 return children[0] - children[1];
698 case CalcOperator::Multiply:
699 ASSERT(children.size() == 2);
700 return children[0] * children[1];
701 case CalcOperator::Divide:
702 ASSERT(children.size() == 1 || children.size() == 2);
703 if (children.size() == 1)
704 return std::numeric_limits<double>::quiet_NaN();
705 return children[0] / children[1];
706 case CalcOperator::Min: {
707 if (children.isEmpty())
708 return std::numeric_limits<double>::quiet_NaN();
709 double minimum = children[0];
710 for (auto child : children)
711 minimum = std::min(minimum, child);
712 return minimum;
713 }
714 case CalcOperator::Max: {
715 if (children.isEmpty())
716 return std::numeric_limits<double>::quiet_NaN();
717 double maximum = children[0];
718 for (auto child : children)
719 maximum = std::max(maximum, child);
720 return maximum;
721 }
722 }
723 ASSERT_NOT_REACHED();
724 return 0;
725 }
726
727 const CalcOperator m_operator;
728 Vector<Ref<CSSCalcExpressionNode>> m_children;
729};
730
731static ParseState checkDepthAndIndex(int* depth, CSSParserTokenRange tokens)
732{
733 (*depth)++;
734 if (tokens.atEnd())
735 return NoMoreTokens;
736 if (*depth > maxExpressionDepth)
737 return TooDeep;
738 return OK;
739}
740
741class CSSCalcExpressionNodeParser {
742public:
743 explicit CSSCalcExpressionNodeParser(CalculationCategory destinationCategory)
744 : m_destinationCategory(destinationCategory)
745 { }
746
747 RefPtr<CSSCalcExpressionNode> parseCalc(CSSParserTokenRange tokens, CSSValueID function)
748 {
749 Value result;
750 tokens.consumeWhitespace();
751 bool ok = false;
752 if (function == CSSValueCalc || function == CSSValueWebkitCalc)
753 ok = parseValueExpression(tokens, 0, &result);
754 else if (function == CSSValueMin || function == CSSValueMax)
755 ok = parseMinMaxExpression(tokens, function, 0, &result);
756 if (!ok || !tokens.atEnd())
757 return nullptr;
758 return result.value;
759 }
760
761private:
762 struct Value {
763 RefPtr<CSSCalcExpressionNode> value;
764 };
765
766 char operatorValue(const CSSParserToken& token)
767 {
768 if (token.type() == DelimiterToken)
769 return token.delimiter();
770 return 0;
771 }
772
773 bool parseValue(CSSParserTokenRange& tokens, Value* result)
774 {
775 CSSParserToken token = tokens.consumeIncludingWhitespace();
776 if (!(token.type() == NumberToken || token.type() == PercentageToken || token.type() == DimensionToken))
777 return false;
778
779 CSSPrimitiveValue::UnitType type = token.unitType();
780 if (unitCategory(type) == CalculationCategory::Other)
781 return false;
782
783 bool isInteger = token.numericValueType() == IntegerValueType || (token.numericValueType() == NumberValueType && token.numericValue() == trunc(token.numericValue()));
784 result->value = CSSCalcPrimitiveValue::create(CSSPrimitiveValue::create(token.numericValue(), type), isInteger);
785
786 return true;
787 }
788
789 bool parseValueTerm(CSSParserTokenRange& tokens, int depth, Value* result)
790 {
791 if (checkDepthAndIndex(&depth, tokens) != OK)
792 return false;
793
794 auto functionId = tokens.peek().functionId();
795
796 if (tokens.peek().type() == LeftParenthesisToken || functionId == CSSValueCalc) {
797 CSSParserTokenRange innerRange = tokens.consumeBlock();
798 tokens.consumeWhitespace();
799 innerRange.consumeWhitespace();
800 return parseValueExpression(innerRange, depth, result);
801 }
802
803 if (functionId == CSSValueMax || functionId == CSSValueMin) {
804 CSSParserTokenRange innerRange = tokens.consumeBlock();
805 tokens.consumeWhitespace();
806 innerRange.consumeWhitespace();
807 return parseMinMaxExpression(innerRange, functionId, depth, result);
808 }
809
810 return parseValue(tokens, result);
811 }
812
813 bool parseValueMultiplicativeExpression(CSSParserTokenRange& tokens, int depth, Value* result)
814 {
815 if (checkDepthAndIndex(&depth, tokens) != OK)
816 return false;
817
818 if (!parseValueTerm(tokens, depth, result))
819 return false;
820
821 while (!tokens.atEnd()) {
822 char operatorCharacter = operatorValue(tokens.peek());
823 if (operatorCharacter != static_cast<char>(CalcOperator::Multiply) && operatorCharacter != static_cast<char>(CalcOperator::Divide))
824 break;
825 tokens.consumeIncludingWhitespace();
826
827 Value rhs;
828 if (!parseValueTerm(tokens, depth, &rhs))
829 return false;
830
831 result->value = CSSCalcOperation::createSimplified(static_cast<CalcOperator>(operatorCharacter), WTFMove(result->value), WTFMove(rhs.value));
832
833 if (!result->value)
834 return false;
835 }
836
837 return true;
838 }
839
840 bool parseAdditiveValueExpression(CSSParserTokenRange& tokens, int depth, Value* result)
841 {
842 if (checkDepthAndIndex(&depth, tokens) != OK)
843 return false;
844
845 if (!parseValueMultiplicativeExpression(tokens, depth, result))
846 return false;
847
848 while (!tokens.atEnd()) {
849 char operatorCharacter = operatorValue(tokens.peek());
850 if (operatorCharacter != static_cast<char>(CalcOperator::Add) && operatorCharacter != static_cast<char>(CalcOperator::Subtract))
851 break;
852 if ((&tokens.peek() - 1)->type() != WhitespaceToken)
853 return false; // calc(1px+ 2px) is invalid
854 tokens.consume();
855 if (tokens.peek().type() != WhitespaceToken)
856 return false; // calc(1px +2px) is invalid
857 tokens.consumeIncludingWhitespace();
858
859 Value rhs;
860 if (!parseValueMultiplicativeExpression(tokens, depth, &rhs))
861 return false;
862
863 result->value = CSSCalcOperation::createSimplified(static_cast<CalcOperator>(operatorCharacter), WTFMove(result->value), WTFMove(rhs.value));
864 if (!result->value)
865 return false;
866 }
867
868 return true;
869 }
870
871 bool parseMinMaxExpression(CSSParserTokenRange& tokens, CSSValueID minMaxFunction, int depth, Value* result)
872 {
873 if (checkDepthAndIndex(&depth, tokens) != OK)
874 return false;
875
876 CalcOperator op = (minMaxFunction == CSSValueMin) ? CalcOperator::Min : CalcOperator::Max;
877
878 Value value;
879 if (!parseValueExpression(tokens, depth, &value))
880 return false;
881
882 Vector<Ref<CSSCalcExpressionNode>> nodes;
883 nodes.append(value.value.releaseNonNull());
884
885 while (!tokens.atEnd()) {
886 tokens.consumeWhitespace();
887 if (tokens.consume().type() != CommaToken)
888 return false;
889 tokens.consumeWhitespace();
890
891 if (!parseValueExpression(tokens, depth, &value))
892 return false;
893
894 nodes.append(value.value.releaseNonNull());
895 }
896
897 result->value = CSSCalcOperation::createMinOrMax(op, WTFMove(nodes), m_destinationCategory);
898 return result->value;
899 }
900
901 bool parseValueExpression(CSSParserTokenRange& tokens, int depth, Value* result)
902 {
903 return parseAdditiveValueExpression(tokens, depth, result);
904 }
905
906 CalculationCategory m_destinationCategory;
907};
908
909static inline RefPtr<CSSCalcOperation> createBlendHalf(const Length& length, const RenderStyle& style, float progress)
910{
911 return CSSCalcOperation::create(CalcOperator::Multiply, createCSS(length, style),
912 CSSCalcPrimitiveValue::create(CSSPrimitiveValue::create(progress, CSSPrimitiveValue::CSS_NUMBER), !progress || progress == 1));
913}
914
915static RefPtr<CSSCalcExpressionNode> createCSS(const CalcExpressionNode& node, const RenderStyle& style)
916{
917 switch (node.type()) {
918 case CalcExpressionNodeType::Number: {
919 float value = toCalcExpressionNumber(node).value();
920 return CSSCalcPrimitiveValue::create(CSSPrimitiveValue::create(value, CSSPrimitiveValue::CSS_NUMBER), value == std::trunc(value));
921 }
922 case CalcExpressionNodeType::Length:
923 return createCSS(toCalcExpressionLength(node).length(), style);
924 case CalcExpressionNodeType::Operation: {
925 auto& operationNode = toCalcExpressionOperation(node);
926 auto& operationChildren = operationNode.children();
927 CalcOperator op = operationNode.getOperator();
928 if (op == CalcOperator::Min || op == CalcOperator::Max) {
929 Vector<Ref<CSSCalcExpressionNode>> values;
930 values.reserveInitialCapacity(operationChildren.size());
931 for (auto& child : operationChildren) {
932 auto cssNode = createCSS(*child, style);
933 if (!cssNode)
934 return nullptr;
935 values.uncheckedAppend(*cssNode);
936 }
937 return CSSCalcOperation::createMinOrMax(operationNode.getOperator(), WTFMove(values), CalculationCategory::Other);
938 }
939
940 if (operationChildren.size() == 2)
941 return CSSCalcOperation::create(operationNode.getOperator(), createCSS(*operationChildren[0], style), createCSS(*operationChildren[1], style));
942
943 return nullptr;
944 }
945 case CalcExpressionNodeType::BlendLength: {
946 // FIXME: (http://webkit.org/b/122036) Create a CSSCalcExpressionNode equivalent of CalcExpressionBlendLength.
947 auto& blend = toCalcExpressionBlendLength(node);
948 float progress = blend.progress();
949 return CSSCalcOperation::create(CalcOperator::Add, createBlendHalf(blend.from(), style, 1 - progress), createBlendHalf(blend.to(), style, progress));
950 }
951 case CalcExpressionNodeType::Undefined:
952 ASSERT_NOT_REACHED();
953 }
954 return nullptr;
955}
956
957static RefPtr<CSSCalcExpressionNode> createCSS(const Length& length, const RenderStyle& style)
958{
959 switch (length.type()) {
960 case Percent:
961 case Fixed:
962 return CSSCalcPrimitiveValue::create(CSSPrimitiveValue::create(length, style), length.value() == trunc(length.value()));
963 case Calculated:
964 return createCSS(length.calculationValue().expression(), style);
965 case Auto:
966 case Intrinsic:
967 case MinIntrinsic:
968 case MinContent:
969 case MaxContent:
970 case FillAvailable:
971 case FitContent:
972 case Relative:
973 case Undefined:
974 ASSERT_NOT_REACHED();
975 }
976 return nullptr;
977}
978
979RefPtr<CSSCalcValue> CSSCalcValue::create(CSSValueID function, const CSSParserTokenRange& tokens, CalculationCategory destinationCategory, ValueRange range)
980{
981 CSSCalcExpressionNodeParser parser(destinationCategory);
982 auto expression = parser.parseCalc(tokens, function);
983 if (!expression)
984 return nullptr;
985 return adoptRef(new CSSCalcValue(expression.releaseNonNull(), range != ValueRangeAll));
986}
987
988RefPtr<CSSCalcValue> CSSCalcValue::create(const CalculationValue& value, const RenderStyle& style)
989{
990 auto expression = createCSS(value.expression(), style);
991 if (!expression)
992 return nullptr;
993 return adoptRef(new CSSCalcValue(expression.releaseNonNull(), value.shouldClampToNonNegative()));
994}
995
996} // namespace WebCore
997