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 | |
42 | static const int maxExpressionDepth = 100; |
43 | |
44 | enum ParseState { |
45 | OK, |
46 | TooDeep, |
47 | NoMoreTokens |
48 | }; |
49 | |
50 | namespace WebCore { |
51 | |
52 | static RefPtr<CSSCalcExpressionNode> createCSS(const CalcExpressionNode&, const RenderStyle&); |
53 | static RefPtr<CSSCalcExpressionNode> createCSS(const Length&, const RenderStyle&); |
54 | |
55 | static 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 | |
93 | static 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 | |
155 | static 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 | |
168 | String CSSCalcValue::customCSSText() const |
169 | { |
170 | return buildCssText(m_expression->customCSSText()); |
171 | } |
172 | |
173 | bool CSSCalcValue::equals(const CSSCalcValue& other) const |
174 | { |
175 | return compareCSSValue(m_expression, other.m_expression); |
176 | } |
177 | |
178 | inline double CSSCalcValue::clampToPermittedRange(double value) const |
179 | { |
180 | return m_shouldClampToNonNegative && value < 0 ? 0 : value; |
181 | } |
182 | |
183 | double CSSCalcValue::doubleValue() const |
184 | { |
185 | return clampToPermittedRange(m_expression->doubleValue()); |
186 | } |
187 | |
188 | double CSSCalcValue::computeLengthPx(const CSSToLengthConversionData& conversionData) const |
189 | { |
190 | return clampToPermittedRange(m_expression->computeLengthPx(conversionData)); |
191 | } |
192 | |
193 | class CSSCalcPrimitiveValue final : public CSSCalcExpressionNode { |
194 | WTF_MAKE_FAST_ALLOCATED; |
195 | public: |
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 | |
208 | private: |
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 | |
296 | private: |
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 | |
306 | static 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 | |
315 | static 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 | |
348 | static 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 | |
372 | static 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 | |
379 | static 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 | |
394 | static bool isSamePair(CalculationCategory a, CalculationCategory b, CalculationCategory x, CalculationCategory y) |
395 | { |
396 | return (a == x && b == y) || (a == y && b == x); |
397 | } |
398 | |
399 | class CSSCalcOperation final : public CSSCalcExpressionNode { |
400 | WTF_MAKE_FAST_ALLOCATED; |
401 | public: |
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 | |
509 | private: |
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 | |
731 | static 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 | |
741 | class CSSCalcExpressionNodeParser { |
742 | public: |
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 | |
761 | private: |
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 | |
909 | static 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 | |
915 | static 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 | |
957 | static 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 | |
979 | RefPtr<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 | |
988 | RefPtr<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 | |