Replace many uses of String::format with more type-safe alternatives
[WebKit-https.git] / Source / WebCore / css / parser / CSSPropertyParserHelpers.cpp
1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Copyright (C) 2016 Apple Inc. All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 //    * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 //    * Redistributions in binary form must reproduce the above
11 // copyright notice, this list of conditions and the following disclaimer
12 // in the documentation and/or other materials provided with the
13 // distribution.
14 //    * Neither the name of Google Inc. nor the names of its
15 // contributors may be used to endorse or promote products derived from
16 // this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 #include "config.h"
31 #include "CSSPropertyParserHelpers.h"
32
33 #include "CSSCalculationValue.h"
34 #include "CSSCanvasValue.h"
35 #include "CSSCrossfadeValue.h"
36 #include "CSSFilterImageValue.h"
37 #include "CSSGradientValue.h"
38 #include "CSSImageSetValue.h"
39 #include "CSSImageValue.h"
40 #include "CSSNamedImageValue.h"
41 #include "CSSPaintImageValue.h"
42 #include "CSSParserIdioms.h"
43 #include "CSSValuePool.h"
44 #include "Pair.h"
45 #include "StyleColor.h"
46 #include <wtf/text/StringConcatenateNumbers.h>
47
48 namespace WebCore {
49
50 namespace CSSPropertyParserHelpers {
51
52 bool consumeCommaIncludingWhitespace(CSSParserTokenRange& range)
53 {
54     CSSParserToken value = range.peek();
55     if (value.type() != CommaToken)
56         return false;
57     range.consumeIncludingWhitespace();
58     return true;
59 }
60
61 bool consumeSlashIncludingWhitespace(CSSParserTokenRange& range)
62 {
63     CSSParserToken value = range.peek();
64     if (value.type() != DelimiterToken || value.delimiter() != '/')
65         return false;
66     range.consumeIncludingWhitespace();
67     return true;
68 }
69
70 CSSParserTokenRange consumeFunction(CSSParserTokenRange& range)
71 {
72     ASSERT(range.peek().type() == FunctionToken);
73     CSSParserTokenRange contents = range.consumeBlock();
74     range.consumeWhitespace();
75     contents.consumeWhitespace();
76     return contents;
77 }
78
79 // FIXME: consider pulling in the parsing logic from CSSCalculationValue.cpp.
80 class CalcParser {
81 public:
82     explicit CalcParser(CSSParserTokenRange& range, CalculationCategory destinationCategory, ValueRange valueRange = ValueRangeAll)
83         : m_sourceRange(range)
84         , m_range(range)
85     {
86         const CSSParserToken& token = range.peek();
87         auto functionId = token.functionId();
88         if (functionId == CSSValueCalc || functionId == CSSValueWebkitCalc || functionId == CSSValueMin || functionId == CSSValueMax)
89             m_calcValue = CSSCalcValue::create(functionId, consumeFunction(m_range), destinationCategory, valueRange);
90     }
91
92     const CSSCalcValue* value() const { return m_calcValue.get(); }
93     RefPtr<CSSPrimitiveValue> consumeValue()
94     {
95         if (!m_calcValue)
96             return nullptr;
97         m_sourceRange = m_range;
98         return CSSValuePool::singleton().createValue(WTFMove(m_calcValue));
99     }
100     RefPtr<CSSPrimitiveValue> consumeNumber()
101     {
102         if (!m_calcValue)
103             return nullptr;
104         m_sourceRange = m_range;
105         return CSSValuePool::singleton().createValue(m_calcValue->doubleValue(), CSSPrimitiveValue::UnitType::CSS_NUMBER);
106     }
107
108     bool consumeNumberRaw(double& result)
109     {
110         if (!m_calcValue || m_calcValue->category() != CalculationCategory::Number)
111             return false;
112         m_sourceRange = m_range;
113         result = m_calcValue->doubleValue();
114         return true;
115     }
116     
117     bool consumePositiveIntegerRaw(int& result)
118     {
119         if (!m_calcValue || m_calcValue->category() != CalculationCategory::Number || !m_calcValue->isInt())
120             return false;
121         result = static_cast<int>(m_calcValue->doubleValue());
122         if (result < 1)
123             return false;
124         m_sourceRange = m_range;
125         return true;
126     }
127
128 private:
129     CSSParserTokenRange& m_sourceRange;
130     CSSParserTokenRange m_range;
131     RefPtr<CSSCalcValue> m_calcValue;
132 };
133
134 RefPtr<CSSPrimitiveValue> consumeInteger(CSSParserTokenRange& range, double minimumValue)
135 {
136     const CSSParserToken& token = range.peek();
137     if (token.type() == NumberToken) {
138         if (token.numericValueType() == NumberValueType || token.numericValue() < minimumValue)
139             return nullptr;
140         return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), CSSPrimitiveValue::UnitType::CSS_NUMBER);
141     }
142
143     if (token.type() != FunctionToken)
144         return nullptr;
145
146     CalcParser calcParser(range, CalculationCategory::Number);
147     if (const CSSCalcValue* calculation = calcParser.value()) {
148         if (calculation->category() != CalculationCategory::Number || !calculation->isInt())
149             return nullptr;
150         double value = calculation->doubleValue();
151         if (value < minimumValue)
152             return nullptr;
153         return calcParser.consumeNumber();
154     }
155
156     return nullptr;
157 }
158
159 RefPtr<CSSPrimitiveValue> consumePositiveInteger(CSSParserTokenRange& range)
160 {
161     return consumeInteger(range, 1);
162 }
163
164 bool consumePositiveIntegerRaw(CSSParserTokenRange& range, int& result)
165 {
166     const CSSParserToken& token = range.peek();
167     if (token.type() == NumberToken) {
168         if (token.numericValueType() == NumberValueType || token.numericValue() < 1)
169             return false;
170         result = range.consumeIncludingWhitespace().numericValue();
171         return true;
172     }
173
174     if (token.type() != FunctionToken)
175         return false;
176
177     CalcParser calcParser(range, CalculationCategory::Number);
178     return calcParser.consumePositiveIntegerRaw(result);
179 }
180     
181 bool consumeNumberRaw(CSSParserTokenRange& range, double& result)
182 {
183     const CSSParserToken& token = range.peek();
184     if (token.type() == NumberToken) {
185         result = range.consumeIncludingWhitespace().numericValue();
186         return true;
187     }
188
189     if (token.type() != FunctionToken)
190         return false;
191
192     CalcParser calcParser(range, CalculationCategory::Number, ValueRangeAll);
193     return calcParser.consumeNumberRaw(result);
194 }
195
196 // FIXME: Work out if this can just call consumeNumberRaw
197 RefPtr<CSSPrimitiveValue> consumeNumber(CSSParserTokenRange& range, ValueRange valueRange)
198 {
199     const CSSParserToken& token = range.peek();
200     if (token.type() == NumberToken) {
201         if (valueRange == ValueRangeNonNegative && token.numericValue() < 0)
202             return nullptr;
203         return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), token.unitType());
204     }
205
206     if (token.type() != FunctionToken)
207         return nullptr;
208
209     CalcParser calcParser(range, CalculationCategory::Number, ValueRangeAll);
210     if (const CSSCalcValue* calculation = calcParser.value()) {
211         // FIXME: Calcs should not be subject to parse time range checks.
212         // spec: https://drafts.csswg.org/css-values-3/#calc-range
213         if (calculation->category() != CalculationCategory::Number || (valueRange == ValueRangeNonNegative && calculation->isNegative()))
214             return nullptr;
215         return calcParser.consumeNumber();
216     }
217
218     return nullptr;
219 }
220
221 #if !ENABLE(VARIATION_FONTS)
222 static inline bool divisibleBy100(double value)
223 {
224     return static_cast<int>(value / 100) * 100 == value;
225 }
226 #endif
227
228 RefPtr<CSSPrimitiveValue> consumeFontWeightNumber(CSSParserTokenRange& range)
229 {
230     // Values less than or equal to 0 or greater than or equal to 1000 are parse errors.
231     auto& token = range.peek();
232     if (token.type() == NumberToken && token.numericValue() > 0 && token.numericValue() < 1000
233 #if !ENABLE(VARIATION_FONTS)
234         && token.numericValueType() == IntegerValueType && divisibleBy100(token.numericValue())
235 #endif
236     )
237         return consumeNumber(range, ValueRangeAll);
238
239     if (token.type() != FunctionToken)
240         return nullptr;
241
242     // "[For calc()], the used value resulting from an expression must be clamped to the range allowed in the target context."
243     CalcParser calcParser(range, CalculationCategory::Number, ValueRangeAll);
244     double result;
245     if (calcParser.consumeNumberRaw(result)
246 #if !ENABLE(VARIATION_FONTS)
247         && result > 0 && result < 1000 && divisibleBy100(result)
248 #endif
249     ) {
250         result = std::min(std::max(result, std::nextafter(0., 1.)), std::nextafter(1000., 0.));
251         return CSSValuePool::singleton().createValue(result, CSSPrimitiveValue::UnitType::CSS_NUMBER);
252     }
253
254     return nullptr;
255 }
256
257 inline bool shouldAcceptUnitlessValue(double value, CSSParserMode cssParserMode, UnitlessQuirk unitless)
258 {
259     // FIXME: Presentational HTML attributes shouldn't use the CSS parser for lengths
260     return value == 0
261         || isUnitLessValueParsingEnabledForMode(cssParserMode)
262         || (cssParserMode == HTMLQuirksMode && unitless == UnitlessQuirk::Allow);
263 }
264
265 RefPtr<CSSPrimitiveValue> consumeLength(CSSParserTokenRange& range, CSSParserMode cssParserMode, ValueRange valueRange, UnitlessQuirk unitless)
266 {
267     const CSSParserToken& token = range.peek();
268     if (token.type() == DimensionToken) {
269         switch (token.unitType()) {
270         case CSSPrimitiveValue::UnitType::CSS_QUIRKY_EMS:
271             if (cssParserMode != UASheetMode)
272                 return nullptr;
273             FALLTHROUGH;
274         case CSSPrimitiveValue::UnitType::CSS_EMS:
275         case CSSPrimitiveValue::UnitType::CSS_REMS:
276         case CSSPrimitiveValue::UnitType::CSS_CHS:
277         case CSSPrimitiveValue::UnitType::CSS_EXS:
278         case CSSPrimitiveValue::UnitType::CSS_PX:
279         case CSSPrimitiveValue::UnitType::CSS_CM:
280         case CSSPrimitiveValue::UnitType::CSS_MM:
281         case CSSPrimitiveValue::UnitType::CSS_IN:
282         case CSSPrimitiveValue::UnitType::CSS_PT:
283         case CSSPrimitiveValue::UnitType::CSS_PC:
284         case CSSPrimitiveValue::UnitType::CSS_VW:
285         case CSSPrimitiveValue::UnitType::CSS_VH:
286         case CSSPrimitiveValue::UnitType::CSS_VMIN:
287         case CSSPrimitiveValue::UnitType::CSS_VMAX:
288             break;
289         default:
290             return nullptr;
291         }
292         if ((valueRange == ValueRangeNonNegative && token.numericValue() < 0) || std::isinf(token.numericValue()))
293             return nullptr;
294         return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), token.unitType());
295     }
296     if (token.type() == NumberToken) {
297         if (!shouldAcceptUnitlessValue(token.numericValue(), cssParserMode, unitless)
298             || (valueRange == ValueRangeNonNegative && token.numericValue() < 0))
299             return nullptr;
300         if (std::isinf(token.numericValue()))
301             return nullptr;
302         CSSPrimitiveValue::UnitType unitType = CSSPrimitiveValue::UnitType::CSS_PX;
303         return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), unitType);
304     }
305
306     if (token.type() != FunctionToken)
307         return nullptr;
308
309     CalcParser calcParser(range, CalculationCategory::Length, valueRange);
310     if (calcParser.value() && calcParser.value()->category() == CalculationCategory::Length)
311         return calcParser.consumeValue();
312
313     return nullptr;
314 }
315
316 RefPtr<CSSPrimitiveValue> consumePercent(CSSParserTokenRange& range, ValueRange valueRange)
317 {
318     const CSSParserToken& token = range.peek();
319     if (token.type() == PercentageToken) {
320         if ((valueRange == ValueRangeNonNegative && token.numericValue() < 0) || std::isinf(token.numericValue()))
321             return nullptr;
322         return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), CSSPrimitiveValue::UnitType::CSS_PERCENTAGE);
323     }
324
325     if (token.type() != FunctionToken)
326         return nullptr;
327
328     CalcParser calcParser(range, CalculationCategory::Percent, valueRange);
329     if (const CSSCalcValue* calculation = calcParser.value()) {
330         if (calculation->category() == CalculationCategory::Percent)
331             return calcParser.consumeValue();
332     }
333     return nullptr;
334 }
335
336 static bool canConsumeCalcValue(CalculationCategory category, CSSParserMode cssParserMode)
337 {
338     if (category == CalculationCategory::Length || category == CalculationCategory::Percent || category == CalculationCategory::PercentLength)
339         return true;
340
341     if (cssParserMode != SVGAttributeMode)
342         return false;
343
344     if (category == CalculationCategory::Number || category == CalculationCategory::PercentNumber)
345         return true;
346
347     return false;
348 }
349
350 RefPtr<CSSPrimitiveValue> consumeLengthOrPercent(CSSParserTokenRange& range, CSSParserMode cssParserMode, ValueRange valueRange, UnitlessQuirk unitless)
351 {
352     const CSSParserToken& token = range.peek();
353     if (token.type() == DimensionToken || token.type() == NumberToken)
354         return consumeLength(range, cssParserMode, valueRange, unitless);
355     if (token.type() == PercentageToken)
356         return consumePercent(range, valueRange);
357
358     if (token.type() != FunctionToken)
359         return nullptr;
360
361     CalcParser calcParser(range, CalculationCategory::Length, valueRange);
362     if (const CSSCalcValue* calculation = calcParser.value()) {
363         if (canConsumeCalcValue(calculation->category(), cssParserMode))
364             return calcParser.consumeValue();
365     }
366     return nullptr;
367 }
368
369 RefPtr<CSSPrimitiveValue> consumeAngle(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless)
370 {
371     const CSSParserToken& token = range.peek();
372     if (token.type() == DimensionToken) {
373         switch (token.unitType()) {
374         case CSSPrimitiveValue::UnitType::CSS_DEG:
375         case CSSPrimitiveValue::UnitType::CSS_RAD:
376         case CSSPrimitiveValue::UnitType::CSS_GRAD:
377         case CSSPrimitiveValue::UnitType::CSS_TURN:
378             return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), token.unitType());
379         default:
380             return nullptr;
381         }
382     }
383
384     if (token.type() == NumberToken && shouldAcceptUnitlessValue(token.numericValue(), cssParserMode, unitless))
385         return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), CSSPrimitiveValue::UnitType::CSS_DEG);
386
387     if (token.type() != FunctionToken)
388         return nullptr;
389
390     CalcParser calcParser(range, CalculationCategory::Angle, ValueRangeAll);
391     if (const CSSCalcValue* calculation = calcParser.value()) {
392         if (calculation->category() == CalculationCategory::Angle)
393             return calcParser.consumeValue();
394     }
395     return nullptr;
396 }
397
398 static RefPtr<CSSPrimitiveValue> consumeAngleOrPercent(CSSParserTokenRange& range, CSSParserMode cssParserMode, ValueRange valueRange, UnitlessQuirk unitless)
399 {
400     const CSSParserToken& token = range.peek();
401     if (token.type() == DimensionToken) {
402         switch (token.unitType()) {
403         case CSSPrimitiveValue::UnitType::CSS_DEG:
404         case CSSPrimitiveValue::UnitType::CSS_RAD:
405         case CSSPrimitiveValue::UnitType::CSS_GRAD:
406         case CSSPrimitiveValue::UnitType::CSS_TURN:
407             return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), token.unitType());
408         default:
409             return nullptr;
410         }
411     }
412     if (token.type() == NumberToken && shouldAcceptUnitlessValue(token.numericValue(), cssParserMode, unitless))
413         return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), CSSPrimitiveValue::UnitType::CSS_DEG);
414
415     if (token.type() == PercentageToken)
416         return consumePercent(range, valueRange);
417
418      if (token.type() != FunctionToken)
419          return nullptr;
420
421     CalcParser angleCalcParser(range, CalculationCategory::Angle, valueRange);
422     if (const CSSCalcValue* calculation = angleCalcParser.value()) {
423         if (calculation->category() == CalculationCategory::Angle)
424             return angleCalcParser.consumeValue();
425     }
426
427     CalcParser percentCalcParser(range, CalculationCategory::Percent, valueRange);
428     if (const CSSCalcValue* calculation = percentCalcParser.value()) {
429         if (calculation->category() == CalculationCategory::Percent)
430             return percentCalcParser.consumeValue();
431     }
432     return nullptr;
433 }
434
435 RefPtr<CSSPrimitiveValue> consumeTime(CSSParserTokenRange& range, CSSParserMode cssParserMode, ValueRange valueRange, UnitlessQuirk unitless)
436 {
437     const CSSParserToken& token = range.peek();
438     CSSPrimitiveValue::UnitType unit = token.unitType();
439     bool acceptUnitless = token.type() == NumberToken && shouldAcceptUnitlessValue(token.numericValue(), cssParserMode, unitless);
440     if (acceptUnitless)
441         unit = CSSPrimitiveValue::UnitType::CSS_MS;
442     if (token.type() == DimensionToken || acceptUnitless) {
443         if (valueRange == ValueRangeNonNegative && token.numericValue() < 0)
444             return nullptr;
445         if (unit == CSSPrimitiveValue::UnitType::CSS_MS || unit == CSSPrimitiveValue::UnitType::CSS_S)
446             return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), unit);
447         return nullptr;
448     }
449
450     if (token.type() != FunctionToken)
451         return nullptr;
452
453     CalcParser calcParser(range, CalculationCategory::Time, valueRange);
454     if (const CSSCalcValue* calculation = calcParser.value()) {
455         if (calculation->category() == CalculationCategory::Time)
456             return calcParser.consumeValue();
457     }
458     return nullptr;
459 }
460
461 RefPtr<CSSPrimitiveValue> consumeResolution(CSSParserTokenRange& range)
462 {
463     const CSSParserToken& token = range.peek();
464     // Unlike the other types, calc() does not work with <resolution>.
465     if (token.type() != DimensionToken)
466         return nullptr;
467     CSSPrimitiveValue::UnitType unit = token.unitType();
468     if (unit == CSSPrimitiveValue::UnitType::CSS_DPPX || unit == CSSPrimitiveValue::UnitType::CSS_DPI || unit == CSSPrimitiveValue::UnitType::CSS_DPCM)
469         return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), unit);
470     return nullptr;
471 }
472
473 RefPtr<CSSPrimitiveValue> consumeIdent(CSSParserTokenRange& range)
474 {
475     if (range.peek().type() != IdentToken)
476         return nullptr;
477     return CSSValuePool::singleton().createIdentifierValue(range.consumeIncludingWhitespace().id());
478 }
479
480 RefPtr<CSSPrimitiveValue> consumeIdentRange(CSSParserTokenRange& range, CSSValueID lower, CSSValueID upper)
481 {
482     if (range.peek().id() < lower || range.peek().id() > upper)
483         return nullptr;
484     return consumeIdent(range);
485 }
486
487 // FIXME-NEWPARSER: Eventually we'd like this to use CSSCustomIdentValue, but we need
488 // to do other plumbing work first (like changing Pair to CSSValuePair and make it not
489 // use only primitive values).
490 RefPtr<CSSPrimitiveValue> consumeCustomIdent(CSSParserTokenRange& range)
491 {
492     if (range.peek().type() != IdentToken || isCSSWideKeyword(range.peek().id()))
493         return nullptr;
494     return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().value().toString(), CSSPrimitiveValue::UnitType::CSS_STRING);
495 }
496
497 RefPtr<CSSPrimitiveValue> consumeString(CSSParserTokenRange& range)
498 {
499     if (range.peek().type() != StringToken)
500         return nullptr;
501     return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().value().toString(), CSSPrimitiveValue::UnitType::CSS_STRING);
502 }
503
504 StringView consumeUrlAsStringView(CSSParserTokenRange& range)
505 {
506     const CSSParserToken& token = range.peek();
507     if (token.type() == UrlToken) {
508         range.consumeIncludingWhitespace();
509         return token.value();
510     }
511     if (token.functionId() == CSSValueUrl) {
512         CSSParserTokenRange urlRange = range;
513         CSSParserTokenRange urlArgs = urlRange.consumeBlock();
514         const CSSParserToken& next = urlArgs.consumeIncludingWhitespace();
515         if (next.type() == BadStringToken || !urlArgs.atEnd())
516             return StringView();
517         ASSERT(next.type() == StringToken);
518         range = urlRange;
519         range.consumeWhitespace();
520         return next.value();
521     }
522
523     return StringView();
524 }
525
526 RefPtr<CSSPrimitiveValue> consumeUrl(CSSParserTokenRange& range)
527 {
528     StringView url = consumeUrlAsStringView(range);
529     if (url.isNull())
530         return nullptr;
531     return CSSValuePool::singleton().createValue(url.toString(), CSSPrimitiveValue::UnitType::CSS_URI);
532 }
533
534 static int clampRGBComponent(const CSSPrimitiveValue& value)
535 {
536     double result = value.doubleValue();
537     if (value.isPercentage())
538         result = result / 100.0 * 255.0;
539
540     return clampTo<int>(round(result), 0, 255);
541 }
542
543 static Color parseRGBParameters(CSSParserTokenRange& range)
544 {
545     ASSERT(range.peek().functionId() == CSSValueRgb || range.peek().functionId() == CSSValueRgba);
546     Color result;
547     CSSParserTokenRange args = consumeFunction(range);
548     RefPtr<CSSPrimitiveValue> colorParameter = consumeNumber(args, ValueRangeAll);
549     if (!colorParameter)
550         colorParameter = consumePercent(args, ValueRangeAll);
551     if (!colorParameter)
552         return Color();
553
554     const bool isPercent = colorParameter->isPercentage();
555
556     enum class ColorSyntax {
557         Commas,
558         WhitespaceSlash,
559     };
560
561     ColorSyntax syntax = ColorSyntax::Commas;
562     auto consumeSeparator = [&] {
563         if (syntax == ColorSyntax::Commas)
564             return consumeCommaIncludingWhitespace(args);
565         
566         return true;
567     };
568
569     int colorArray[3];
570     colorArray[0] = clampRGBComponent(*colorParameter);
571     for (int i = 1; i < 3; i++) {
572         if (i == 1)
573             syntax = consumeCommaIncludingWhitespace(args) ? ColorSyntax::Commas : ColorSyntax::WhitespaceSlash;
574         else if (!consumeSeparator())
575             return Color();
576
577         colorParameter = isPercent ? consumePercent(args, ValueRangeAll) : consumeNumber(args, ValueRangeAll);
578         if (!colorParameter)
579             return Color();
580         colorArray[i] = clampRGBComponent(*colorParameter);
581     }
582
583     // Historically, alpha was only parsed for rgba(), but css-color-4 specifies that rgba() is a simple alias for rgb().
584     auto consumeAlphaSeparator = [&] {
585         if (syntax == ColorSyntax::Commas)
586             return consumeCommaIncludingWhitespace(args);
587         
588         return consumeSlashIncludingWhitespace(args);
589     };
590
591     int alphaComponent = 255;
592     if (consumeAlphaSeparator()) {
593         double alpha;
594         if (!consumeNumberRaw(args, alpha)) {
595             auto alphaPercent = consumePercent(args, ValueRangeAll);
596             if (!alphaPercent)
597                 return Color();
598             alpha = alphaPercent->doubleValue() / 100.0;
599         }
600
601         // Convert the floating pointer number of alpha to an integer in the range [0, 256),
602         // with an equal distribution across all 256 values.
603         alphaComponent = static_cast<int>(clampTo<double>(alpha, 0.0, 1.0) * nextafter(256.0, 0.0));
604     };
605
606     result = Color(makeRGBA(colorArray[0], colorArray[1], colorArray[2], alphaComponent));
607
608     if (!args.atEnd())
609         return Color();
610
611     return result;
612 }
613
614 static Color parseHSLParameters(CSSParserTokenRange& range, CSSParserMode cssParserMode)
615 {
616     ASSERT(range.peek().functionId() == CSSValueHsl || range.peek().functionId() == CSSValueHsla);
617     CSSParserTokenRange args = consumeFunction(range);
618     auto hslValue = consumeAngle(args, cssParserMode, UnitlessQuirk::Forbid);
619     double angleInDegrees;
620     if (!hslValue) {
621         hslValue = consumeNumber(args, ValueRangeAll);
622         if (!hslValue)
623             return Color();
624         angleInDegrees = hslValue->doubleValue();
625     } else
626         angleInDegrees = hslValue->computeDegrees();
627     double colorArray[3];
628     // The hue needs to be in the range [0.0, 6.0) for calcHue()
629     colorArray[0] = fmod(fmod(angleInDegrees, 360.0) + 360.0, 360.0) / 60.0;
630     bool requiresCommas = false;
631     for (int i = 1; i < 3; i++) {
632         if (consumeCommaIncludingWhitespace(args)) {
633             if (i != 1 && !requiresCommas)
634                 return Color();
635             requiresCommas = true;
636         } else if (requiresCommas || args.atEnd() || (&args.peek() - 1)->type() != WhitespaceToken)
637             return Color();
638         hslValue = consumePercent(args, ValueRangeAll);
639         if (!hslValue)
640             return Color();
641         double doubleValue = hslValue->doubleValue();
642         colorArray[i] = clampTo<double>(doubleValue, 0.0, 100.0) / 100.0; // Needs to be value between 0 and 1.0.
643     }
644
645     double alpha = 1.0;
646     bool commaConsumed = consumeCommaIncludingWhitespace(args);
647     bool slashConsumed = consumeSlashIncludingWhitespace(args);
648     if ((commaConsumed && !requiresCommas) || (slashConsumed && requiresCommas))
649         return Color();
650     if (commaConsumed || slashConsumed) {
651         if (!consumeNumberRaw(args, alpha)) {
652             auto alphaPercent = consumePercent(args, ValueRangeAll);
653             if (!alphaPercent)
654                 return Color();
655             alpha = alphaPercent->doubleValue() / 100.0f;
656         }
657         alpha = clampTo<double>(alpha, 0.0, 1.0);
658     }
659
660     if (!args.atEnd())
661         return Color();
662
663     return Color(makeRGBAFromHSLA(colorArray[0], colorArray[1], colorArray[2], alpha));
664 }
665
666 static Color parseColorFunctionParameters(CSSParserTokenRange& range)
667 {
668     ASSERT(range.peek().functionId() == CSSValueColor);
669     CSSParserTokenRange args = consumeFunction(range);
670
671     ColorSpace colorSpace;
672     switch (args.peek().id()) {
673     case CSSValueSRGB:
674         colorSpace = ColorSpaceSRGB;
675         break;
676     case CSSValueDisplayP3:
677         colorSpace = ColorSpaceDisplayP3;
678         break;
679     default:
680         return Color();
681     }
682     consumeIdent(args);
683
684     double colorChannels[4] = { 0, 0, 0, 1 };
685     for (int i = 0; i < 3; ++i) {
686         double value;
687         if (consumeNumberRaw(args, value))
688             colorChannels[i] = std::max(0.0, std::min(1.0, value));
689         else
690             break;
691     }
692
693     if (consumeSlashIncludingWhitespace(args)) {
694         auto alphaParameter = consumePercent(args, ValueRangeAll);
695         if (!alphaParameter)
696             alphaParameter = consumeNumber(args, ValueRangeAll);
697         if (!alphaParameter)
698             return Color();
699
700         colorChannels[3] = std::max(0.0, std::min(1.0, alphaParameter->isPercentage() ? (alphaParameter->doubleValue() / 100) : alphaParameter->doubleValue()));
701     }
702
703     // FIXME: Support the comma-separated list of fallback color values.
704
705     if (!args.atEnd())
706         return Color();
707     
708     return Color(colorChannels[0], colorChannels[1], colorChannels[2], colorChannels[3], colorSpace);
709 }
710
711 static Color parseHexColor(CSSParserTokenRange& range, bool acceptQuirkyColors)
712 {
713     RGBA32 result;
714     const CSSParserToken& token = range.peek();
715     if (token.type() == HashToken) {
716         if (!Color::parseHexColor(token.value(), result))
717             return Color();
718     } else if (acceptQuirkyColors) {
719         String color;
720         if (token.type() == NumberToken || token.type() == DimensionToken) {
721             if (token.numericValueType() != IntegerValueType
722                 || token.numericValue() < 0. || token.numericValue() >= 1000000.)
723                 return Color();
724             if (token.type() == NumberToken) // e.g. 112233
725                 color = String::number(static_cast<int>(token.numericValue()));
726             else // e.g. 0001FF
727                 color = makeString(static_cast<int>(token.numericValue()), token.value().toString());
728             while (color.length() < 6)
729                 color = "0" + color;
730         } else if (token.type() == IdentToken) { // e.g. FF0000
731             color = token.value().toString();
732         }
733         unsigned length = color.length();
734         if (length != 3 && length != 6)
735             return Color();
736         if (!Color::parseHexColor(color, result))
737             return Color();
738     } else {
739         return Color();
740     }
741     range.consumeIncludingWhitespace();
742     return Color(result);
743 }
744
745 static Color parseColorFunction(CSSParserTokenRange& range, CSSParserMode cssParserMode)
746 {
747     CSSParserTokenRange colorRange = range;
748     CSSValueID functionId = range.peek().functionId();
749     Color color;
750     switch (functionId) {
751     case CSSValueRgb:
752     case CSSValueRgba:
753         color = parseRGBParameters(colorRange);
754         break;
755     case CSSValueHsl:
756     case CSSValueHsla:
757         color = parseHSLParameters(colorRange, cssParserMode);
758         break;
759     case CSSValueColor:
760         color = parseColorFunctionParameters(colorRange);
761         break;
762     default:
763         return Color();
764     }
765     if (color.isValid())
766         range = colorRange;
767     return color;
768 }
769
770 RefPtr<CSSPrimitiveValue> consumeColor(CSSParserTokenRange& range, CSSParserMode cssParserMode, bool acceptQuirkyColors)
771 {
772     CSSValueID id = range.peek().id();
773     if (StyleColor::isColorKeyword(id)) {
774         if (!isValueAllowedInMode(id, cssParserMode))
775             return nullptr;
776         return consumeIdent(range);
777     }
778     Color color = parseHexColor(range, acceptQuirkyColors);
779     if (!color.isValid())
780         color = parseColorFunction(range, cssParserMode);
781     if (!color.isValid())
782         return nullptr;
783     return CSSValuePool::singleton().createValue(color);
784 }
785
786 static RefPtr<CSSPrimitiveValue> consumePositionComponent(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless)
787 {
788     if (range.peek().type() == IdentToken)
789         return consumeIdent<CSSValueLeft, CSSValueTop, CSSValueBottom, CSSValueRight, CSSValueCenter>(range);
790     return consumeLengthOrPercent(range, cssParserMode, ValueRangeAll, unitless);
791 }
792
793 static bool isHorizontalPositionKeywordOnly(const CSSPrimitiveValue& value)
794 {
795     return value.isValueID() && (value.valueID() == CSSValueLeft || value.valueID() == CSSValueRight);
796 }
797
798 static bool isVerticalPositionKeywordOnly(const CSSPrimitiveValue& value)
799 {
800     return value.isValueID() && (value.valueID() == CSSValueTop || value.valueID() == CSSValueBottom);
801 }
802
803 static void positionFromOneValue(CSSPrimitiveValue& value, RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
804 {
805     bool valueAppliesToYAxisOnly = isVerticalPositionKeywordOnly(value);
806     resultX = &value;
807     resultY = CSSPrimitiveValue::createIdentifier(CSSValueCenter);
808     if (valueAppliesToYAxisOnly)
809         std::swap(resultX, resultY);
810 }
811
812 static bool positionFromTwoValues(CSSPrimitiveValue& value1, CSSPrimitiveValue& value2,
813     RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
814 {
815     bool mustOrderAsXY = isHorizontalPositionKeywordOnly(value1) || isVerticalPositionKeywordOnly(value2)
816         || !value1.isValueID() || !value2.isValueID();
817     bool mustOrderAsYX = isVerticalPositionKeywordOnly(value1) || isHorizontalPositionKeywordOnly(value2);
818     if (mustOrderAsXY && mustOrderAsYX)
819         return false;
820     resultX = &value1;
821     resultY = &value2;
822     if (mustOrderAsYX)
823         std::swap(resultX, resultY);
824     return true;
825 }
826
827 namespace CSSPropertyParserHelpersInternal {
828 template<typename... Args>
829 static Ref<CSSPrimitiveValue> createPrimitiveValuePair(Args&&... args)
830 {
831     return CSSValuePool::singleton().createValue(Pair::create(std::forward<Args>(args)...));
832 }
833 }
834
835 static bool positionFromThreeOrFourValues(CSSPrimitiveValue** values, RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
836 {
837     CSSPrimitiveValue* center = nullptr;
838     for (int i = 0; values[i]; i++) {
839         CSSPrimitiveValue* currentValue = values[i];
840         if (!currentValue->isValueID())
841             return false;
842         CSSValueID id = currentValue->valueID();
843
844         if (id == CSSValueCenter) {
845             if (center)
846                 return false;
847             center = currentValue;
848             continue;
849         }
850
851         RefPtr<CSSPrimitiveValue> result;
852         if (values[i + 1] && !values[i + 1]->isValueID())
853             result = CSSPropertyParserHelpersInternal::createPrimitiveValuePair(currentValue, values[++i]);
854         else
855             result = currentValue;
856
857         if (id == CSSValueLeft || id == CSSValueRight) {
858             if (resultX)
859                 return false;
860             resultX = result;
861         } else {
862             ASSERT(id == CSSValueTop || id == CSSValueBottom);
863             if (resultY)
864                 return false;
865             resultY = result;
866         }
867     }
868
869     if (center) {
870         ASSERT(resultX || resultY);
871         if (resultX && resultY)
872             return false;
873         if (!resultX)
874             resultX = center;
875         else
876             resultY = center;
877     }
878
879     ASSERT(resultX && resultY);
880     return true;
881 }
882
883 // FIXME: This may consume from the range upon failure. The background
884 // shorthand works around it, but we should just fix it here.
885 bool consumePosition(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless, RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
886 {
887     RefPtr<CSSPrimitiveValue> value1 = consumePositionComponent(range, cssParserMode, unitless);
888     if (!value1)
889         return false;
890
891     RefPtr<CSSPrimitiveValue> value2 = consumePositionComponent(range, cssParserMode, unitless);
892     if (!value2) {
893         positionFromOneValue(*value1, resultX, resultY);
894         return true;
895     }
896
897     RefPtr<CSSPrimitiveValue> value3 = consumePositionComponent(range, cssParserMode, unitless);
898     if (!value3)
899         return positionFromTwoValues(*value1, *value2, resultX, resultY);
900
901     RefPtr<CSSPrimitiveValue> value4 = consumePositionComponent(range, cssParserMode, unitless);
902     CSSPrimitiveValue* values[5];
903     values[0] = value1.get();
904     values[1] = value2.get();
905     values[2] = value3.get();
906     values[3] = value4.get();
907     values[4] = nullptr;
908     return positionFromThreeOrFourValues(values, resultX, resultY);
909 }
910
911 RefPtr<CSSPrimitiveValue> consumePosition(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless)
912 {
913     RefPtr<CSSPrimitiveValue> resultX;
914     RefPtr<CSSPrimitiveValue> resultY;
915     if (consumePosition(range, cssParserMode, unitless, resultX, resultY))
916         return CSSPropertyParserHelpersInternal::createPrimitiveValuePair(resultX.releaseNonNull(), resultY.releaseNonNull());
917     return nullptr;
918 }
919
920 bool consumeOneOrTwoValuedPosition(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless, RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
921 {
922     RefPtr<CSSPrimitiveValue> value1 = consumePositionComponent(range, cssParserMode, unitless);
923     if (!value1)
924         return false;
925     RefPtr<CSSPrimitiveValue> value2 = consumePositionComponent(range, cssParserMode, unitless);
926     if (!value2) {
927         positionFromOneValue(*value1, resultX, resultY);
928         return true;
929     }
930     return positionFromTwoValues(*value1, *value2, resultX, resultY);
931 }
932
933 // This should go away once we drop support for -webkit-gradient
934 static RefPtr<CSSPrimitiveValue> consumeDeprecatedGradientPoint(CSSParserTokenRange& args, bool horizontal)
935 {
936     if (args.peek().type() == IdentToken) {
937         if ((horizontal && consumeIdent<CSSValueLeft>(args)) || (!horizontal && consumeIdent<CSSValueTop>(args)))
938             return CSSValuePool::singleton().createValue(0., CSSPrimitiveValue::UnitType::CSS_PERCENTAGE);
939         if ((horizontal && consumeIdent<CSSValueRight>(args)) || (!horizontal && consumeIdent<CSSValueBottom>(args)))
940             return CSSValuePool::singleton().createValue(100., CSSPrimitiveValue::UnitType::CSS_PERCENTAGE);
941         if (consumeIdent<CSSValueCenter>(args))
942             return CSSValuePool::singleton().createValue(50., CSSPrimitiveValue::UnitType::CSS_PERCENTAGE);
943         return nullptr;
944     }
945     RefPtr<CSSPrimitiveValue> result = consumePercent(args, ValueRangeAll);
946     if (!result)
947         result = consumeNumber(args, ValueRangeAll);
948     return result;
949 }
950
951 // Used to parse colors for -webkit-gradient(...).
952 static RefPtr<CSSPrimitiveValue> consumeDeprecatedGradientStopColor(CSSParserTokenRange& args, CSSParserMode cssParserMode)
953 {
954     if (args.peek().id() == CSSValueCurrentcolor)
955         return nullptr;
956     return consumeColor(args, cssParserMode);
957 }
958
959 static bool consumeDeprecatedGradientColorStop(CSSParserTokenRange& range, CSSGradientColorStop& stop, CSSParserMode cssParserMode)
960 {
961     CSSValueID id = range.peek().functionId();
962     if (id != CSSValueFrom && id != CSSValueTo && id != CSSValueColorStop)
963         return false;
964
965     CSSParserTokenRange args = consumeFunction(range);
966     double position;
967     if (id == CSSValueFrom || id == CSSValueTo) {
968         position = (id == CSSValueFrom) ? 0 : 1;
969     } else {
970         ASSERT(id == CSSValueColorStop);
971         if (auto percentValue = consumePercent(args, ValueRangeAll))
972             position = percentValue->doubleValue() / 100.0;
973         else if (!consumeNumberRaw(args, position))
974             return false;
975
976         if (!consumeCommaIncludingWhitespace(args))
977             return false;
978     }
979
980     stop.m_position = CSSValuePool::singleton().createValue(position, CSSPrimitiveValue::UnitType::CSS_NUMBER);
981     stop.m_color = consumeDeprecatedGradientStopColor(args, cssParserMode);
982     return stop.m_color && args.atEnd();
983 }
984
985 static RefPtr<CSSValue> consumeDeprecatedGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode)
986 {
987     RefPtr<CSSGradientValue> result;
988     CSSValueID id = args.consumeIncludingWhitespace().id();
989     bool isDeprecatedRadialGradient = (id == CSSValueRadial);
990     if (isDeprecatedRadialGradient)
991         result = CSSRadialGradientValue::create(NonRepeating, CSSDeprecatedRadialGradient);
992     else if (id == CSSValueLinear)
993         result = CSSLinearGradientValue::create(NonRepeating, CSSDeprecatedLinearGradient);
994     if (!result || !consumeCommaIncludingWhitespace(args))
995         return nullptr;
996
997     RefPtr<CSSPrimitiveValue> point = consumeDeprecatedGradientPoint(args, true);
998     if (!point)
999         return nullptr;
1000     result->setFirstX(point.copyRef());
1001     point = consumeDeprecatedGradientPoint(args, false);
1002     if (!point)
1003         return nullptr;
1004     result->setFirstY(point.copyRef());
1005
1006     if (!consumeCommaIncludingWhitespace(args))
1007         return nullptr;
1008
1009     // For radial gradients only, we now expect a numeric radius.
1010     if (isDeprecatedRadialGradient) {
1011         RefPtr<CSSPrimitiveValue> radius = consumeNumber(args, ValueRangeAll);
1012         if (!radius || !consumeCommaIncludingWhitespace(args))
1013             return nullptr;
1014         downcast<CSSRadialGradientValue>(result.get())->setFirstRadius(radius.copyRef());
1015     }
1016
1017     point = consumeDeprecatedGradientPoint(args, true);
1018     if (!point)
1019         return nullptr;
1020     result->setSecondX(point.copyRef());
1021     point = consumeDeprecatedGradientPoint(args, false);
1022     if (!point)
1023         return nullptr;
1024     result->setSecondY(point.copyRef());
1025
1026     // For radial gradients only, we now expect the second radius.
1027     if (isDeprecatedRadialGradient) {
1028         if (!consumeCommaIncludingWhitespace(args))
1029             return nullptr;
1030         RefPtr<CSSPrimitiveValue> radius = consumeNumber(args, ValueRangeAll);
1031         if (!radius)
1032             return nullptr;
1033         downcast<CSSRadialGradientValue>(result.get())->setSecondRadius(radius.copyRef());
1034     }
1035
1036     CSSGradientColorStop stop;
1037     while (consumeCommaIncludingWhitespace(args)) {
1038         if (!consumeDeprecatedGradientColorStop(args, stop, cssParserMode))
1039             return nullptr;
1040         result->addStop(stop);
1041     }
1042
1043     result->doneAddingStops();
1044     return result;
1045 }
1046
1047 static bool consumeGradientColorStops(CSSParserTokenRange& range, CSSParserMode cssParserMode, CSSGradientValue& gradient)
1048 {
1049     bool supportsColorHints = gradient.gradientType() == CSSLinearGradient || gradient.gradientType() == CSSRadialGradient || gradient.gradientType() == CSSConicGradient;
1050     
1051     bool isConicGradient = gradient.gradientType() == CSSConicGradient;
1052
1053     // The first color stop cannot be a color hint.
1054     bool previousStopWasColorHint = true;
1055     do {
1056         CSSGradientColorStop stop;
1057         stop.m_color = consumeColor(range, cssParserMode);
1058         // Two hints in a row are not allowed.
1059         if (!stop.m_color && (!supportsColorHints || previousStopWasColorHint))
1060             return false;
1061         
1062         previousStopWasColorHint = !stop.m_color;
1063         
1064         // FIXME-NEWPARSER: This boolean could be removed. Null checking color would be sufficient.
1065         stop.isMidpoint = !stop.m_color;
1066
1067         if (isConicGradient)
1068             stop.m_position = consumeAngleOrPercent(range, cssParserMode, ValueRangeAll, UnitlessQuirk::Forbid);
1069         else
1070             stop.m_position = consumeLengthOrPercent(range, cssParserMode, ValueRangeAll);
1071         
1072         if (!stop.m_color && !stop.m_position)
1073             return false;
1074         gradient.addStop(stop);
1075         
1076         // See if there is a second color hint, which is optional.
1077         CSSGradientColorStop secondStop;
1078         if (isConicGradient)
1079             secondStop.m_position = consumeAngleOrPercent(range, cssParserMode, ValueRangeAll, UnitlessQuirk::Forbid);
1080         else
1081             secondStop.m_position = consumeLengthOrPercent(range, cssParserMode, ValueRangeAll);
1082         
1083         if (secondStop.m_position) {
1084             secondStop.m_color = stop.m_color;
1085             gradient.addStop(secondStop);
1086         }
1087         
1088     } while (consumeCommaIncludingWhitespace(range));
1089
1090     gradient.doneAddingStops();
1091
1092     // The last color stop cannot be a color hint.
1093     if (previousStopWasColorHint)
1094         return false;
1095
1096     // Must have 2 or more stops to be valid.
1097     return gradient.stopCount() >= 2;
1098 }
1099
1100 static RefPtr<CSSValue> consumeDeprecatedRadialGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode, CSSGradientRepeat repeating)
1101 {
1102     RefPtr<CSSRadialGradientValue> result = CSSRadialGradientValue::create(repeating, CSSPrefixedRadialGradient);
1103     RefPtr<CSSPrimitiveValue> centerX;
1104     RefPtr<CSSPrimitiveValue> centerY;
1105     consumeOneOrTwoValuedPosition(args, cssParserMode, UnitlessQuirk::Forbid, centerX, centerY);
1106     if ((centerX || centerY) && !consumeCommaIncludingWhitespace(args))
1107         return nullptr;
1108
1109     result->setFirstX(centerX.copyRef());
1110     result->setFirstY(centerY.copyRef());
1111     result->setSecondX(centerX.copyRef());
1112     result->setSecondY(centerY.copyRef());
1113
1114     RefPtr<CSSPrimitiveValue> shape = consumeIdent<CSSValueCircle, CSSValueEllipse>(args);
1115     RefPtr<CSSPrimitiveValue> sizeKeyword = consumeIdent<CSSValueClosestSide, CSSValueClosestCorner, CSSValueFarthestSide, CSSValueFarthestCorner, CSSValueContain, CSSValueCover>(args);
1116     if (!shape)
1117         shape = consumeIdent<CSSValueCircle, CSSValueEllipse>(args);
1118     result->setShape(shape.copyRef());
1119     result->setSizingBehavior(sizeKeyword.copyRef());
1120
1121     // Or, two lengths or percentages
1122     if (!shape && !sizeKeyword) {
1123         RefPtr<CSSPrimitiveValue> horizontalSize = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll);
1124         RefPtr<CSSPrimitiveValue> verticalSize;
1125         if (horizontalSize) {
1126             verticalSize = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll);
1127             if (!verticalSize)
1128                 return nullptr;
1129             consumeCommaIncludingWhitespace(args);
1130             result->setEndHorizontalSize(horizontalSize.copyRef());
1131             result->setEndVerticalSize(verticalSize.copyRef());
1132         }
1133     } else {
1134         consumeCommaIncludingWhitespace(args);
1135     }
1136     if (!consumeGradientColorStops(args, cssParserMode, *result))
1137         return nullptr;
1138
1139     return result;
1140 }
1141
1142 static RefPtr<CSSValue> consumeRadialGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode, CSSGradientRepeat repeating)
1143 {
1144     RefPtr<CSSRadialGradientValue> result = CSSRadialGradientValue::create(repeating, CSSRadialGradient);
1145
1146     RefPtr<CSSPrimitiveValue> shape;
1147     RefPtr<CSSPrimitiveValue> sizeKeyword;
1148     RefPtr<CSSPrimitiveValue> horizontalSize;
1149     RefPtr<CSSPrimitiveValue> verticalSize;
1150
1151     // First part of grammar, the size/shape clause:
1152     // [ circle || <length> ] |
1153     // [ ellipse || [ <length> | <percentage> ]{2} ] |
1154     // [ [ circle | ellipse] || <size-keyword> ]
1155     for (int i = 0; i < 3; ++i) {
1156         if (args.peek().type() == IdentToken) {
1157             CSSValueID id = args.peek().id();
1158             if (id == CSSValueCircle || id == CSSValueEllipse) {
1159                 if (shape)
1160                     return nullptr;
1161                 shape = consumeIdent(args);
1162             } else if (id == CSSValueClosestSide || id == CSSValueClosestCorner || id == CSSValueFarthestSide || id == CSSValueFarthestCorner) {
1163                 if (sizeKeyword)
1164                     return nullptr;
1165                 sizeKeyword = consumeIdent(args);
1166             } else {
1167                 break;
1168             }
1169         } else {
1170             RefPtr<CSSPrimitiveValue> center = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll);
1171             if (!center)
1172                 break;
1173             if (horizontalSize)
1174                 return nullptr;
1175             horizontalSize = center;
1176             center = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll);
1177             if (center) {
1178                 verticalSize = center;
1179                 ++i;
1180             }
1181         }
1182     }
1183
1184     // You can specify size as a keyword or a length/percentage, not both.
1185     if (sizeKeyword && horizontalSize)
1186         return nullptr;
1187     // Circles must have 0 or 1 lengths.
1188     if (shape && shape->valueID() == CSSValueCircle && verticalSize)
1189         return nullptr;
1190     // Ellipses must have 0 or 2 length/percentages.
1191     if (shape && shape->valueID() == CSSValueEllipse && horizontalSize && !verticalSize)
1192         return nullptr;
1193     // If there's only one size, it must be a length.
1194     if (!verticalSize && horizontalSize && horizontalSize->isPercentage())
1195         return nullptr;
1196     if ((horizontalSize && horizontalSize->isCalculatedPercentageWithLength())
1197         || (verticalSize && verticalSize->isCalculatedPercentageWithLength()))
1198         return nullptr;
1199
1200     result->setShape(shape.copyRef());
1201     result->setSizingBehavior(sizeKeyword.copyRef());
1202     result->setEndHorizontalSize(horizontalSize.copyRef());
1203     result->setEndVerticalSize(verticalSize.copyRef());
1204
1205     RefPtr<CSSPrimitiveValue> centerX;
1206     RefPtr<CSSPrimitiveValue> centerY;
1207     if (args.peek().id() == CSSValueAt) {
1208         args.consumeIncludingWhitespace();
1209         consumePosition(args, cssParserMode, UnitlessQuirk::Forbid, centerX, centerY);
1210         if (!(centerX && centerY))
1211             return nullptr;
1212         
1213         result->setFirstX(centerX.copyRef());
1214         result->setFirstY(centerY.copyRef());
1215         
1216         // Right now, CSS radial gradients have the same start and end centers.
1217         result->setSecondX(centerX.copyRef());
1218         result->setSecondY(centerY.copyRef());
1219     }
1220
1221     if ((shape || sizeKeyword || horizontalSize || centerX || centerY) && !consumeCommaIncludingWhitespace(args))
1222         return nullptr;
1223     if (!consumeGradientColorStops(args, cssParserMode, *result))
1224         return nullptr;
1225     return result;
1226 }
1227
1228 static RefPtr<CSSValue> consumeLinearGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode, CSSGradientRepeat repeating, CSSGradientType gradientType)
1229 {
1230     RefPtr<CSSLinearGradientValue> result = CSSLinearGradientValue::create(repeating, gradientType);
1231
1232     bool expectComma = true;
1233     RefPtr<CSSPrimitiveValue> angle = consumeAngle(args, cssParserMode, UnitlessQuirk::Forbid);
1234     if (angle)
1235         result->setAngle(angle.releaseNonNull());
1236     else if (gradientType == CSSPrefixedLinearGradient || consumeIdent<CSSValueTo>(args)) {
1237         RefPtr<CSSPrimitiveValue> endX = consumeIdent<CSSValueLeft, CSSValueRight>(args);
1238         RefPtr<CSSPrimitiveValue> endY = consumeIdent<CSSValueBottom, CSSValueTop>(args);
1239         if (!endX && !endY) {
1240             if (gradientType == CSSLinearGradient)
1241                 return nullptr;
1242             endY = CSSPrimitiveValue::createIdentifier(CSSValueTop);
1243             expectComma = false;
1244         } else if (!endX) {
1245             endX = consumeIdent<CSSValueLeft, CSSValueRight>(args);
1246         }
1247
1248         result->setFirstX(endX.copyRef());
1249         result->setFirstY(endY.copyRef());
1250     } else {
1251         expectComma = false;
1252     }
1253
1254     if (expectComma && !consumeCommaIncludingWhitespace(args))
1255         return nullptr;
1256     if (!consumeGradientColorStops(args, cssParserMode, *result))
1257         return nullptr;
1258     return result;
1259 }
1260
1261 static RefPtr<CSSValue> consumeConicGradient(CSSParserTokenRange& args, CSSParserContext context, CSSGradientRepeat repeating)
1262 {
1263 #if ENABLE(CSS_CONIC_GRADIENTS)
1264     RefPtr<CSSConicGradientValue> result = CSSConicGradientValue::create(repeating);
1265
1266     bool expectComma = false;
1267     if (args.peek().type() == IdentToken) {
1268         if (consumeIdent<CSSValueFrom>(args)) {
1269             auto angle = consumeAngle(args, context.mode, UnitlessQuirk::Forbid);
1270             if (!angle)
1271                 return nullptr;
1272             result->setAngle(angle.releaseNonNull());
1273             expectComma = true;
1274         }
1275         
1276         if (consumeIdent<CSSValueAt>(args)) {
1277             RefPtr<CSSPrimitiveValue> centerX;
1278             RefPtr<CSSPrimitiveValue> centerY;
1279             consumePosition(args, context.mode, UnitlessQuirk::Forbid, centerX, centerY);
1280             if (!(centerX && centerY))
1281                 return nullptr;
1282             
1283             result->setFirstX(centerX.copyRef());
1284             result->setFirstY(centerY.copyRef());
1285             
1286             // Right now, conic gradients have the same start and end centers.
1287             result->setSecondX(centerX.copyRef());
1288             result->setSecondY(centerY.copyRef());
1289     
1290             expectComma = true;
1291         }
1292     }
1293
1294     if (expectComma && !consumeCommaIncludingWhitespace(args))
1295         return nullptr;
1296     if (!consumeGradientColorStops(args, context.mode, *result))
1297         return nullptr;
1298     return result;
1299 #else
1300     UNUSED_PARAM(args);
1301     UNUSED_PARAM(context);
1302     UNUSED_PARAM(repeating);
1303     return nullptr;
1304 #endif
1305 }
1306
1307 RefPtr<CSSValue> consumeImageOrNone(CSSParserTokenRange& range, CSSParserContext context)
1308 {
1309     if (range.peek().id() == CSSValueNone)
1310         return consumeIdent(range);
1311     return consumeImage(range, context);
1312 }
1313
1314 static RefPtr<CSSValue> consumeCrossFade(CSSParserTokenRange& args, CSSParserContext context, bool prefixed)
1315 {
1316     RefPtr<CSSValue> fromImageValue = consumeImageOrNone(args, context);
1317     if (!fromImageValue || !consumeCommaIncludingWhitespace(args))
1318         return nullptr;
1319     RefPtr<CSSValue> toImageValue = consumeImageOrNone(args, context);
1320     if (!toImageValue || !consumeCommaIncludingWhitespace(args))
1321         return nullptr;
1322
1323     RefPtr<CSSPrimitiveValue> percentage;
1324     if (auto percentValue = consumePercent(args, ValueRangeAll))
1325         percentage = CSSValuePool::singleton().createValue(clampTo<double>(percentValue->doubleValue() / 100.0, 0, 1), CSSPrimitiveValue::UnitType::CSS_NUMBER);
1326     else if (auto numberValue = consumeNumber(args, ValueRangeAll))
1327         percentage = CSSValuePool::singleton().createValue(clampTo<double>(numberValue->doubleValue(), 0, 1), CSSPrimitiveValue::UnitType::CSS_NUMBER);
1328
1329     if (!percentage)
1330         return nullptr;
1331     return CSSCrossfadeValue::create(fromImageValue.releaseNonNull(), toImageValue.releaseNonNull(), percentage.releaseNonNull(), prefixed);
1332 }
1333
1334 static RefPtr<CSSValue> consumeWebkitCanvas(CSSParserTokenRange& args)
1335 {
1336     if (args.peek().type() != IdentToken)
1337         return nullptr;
1338     auto canvasName = args.consumeIncludingWhitespace().value().toString();
1339     if (!args.atEnd())
1340         return nullptr;
1341     return CSSCanvasValue::create(canvasName);
1342 }
1343
1344 static RefPtr<CSSValue> consumeWebkitNamedImage(CSSParserTokenRange& args)
1345 {
1346     if (args.peek().type() != IdentToken)
1347         return nullptr;
1348     auto imageName = args.consumeIncludingWhitespace().value().toString();
1349     if (!args.atEnd())
1350         return nullptr;
1351     return CSSNamedImageValue::create(imageName);
1352 }
1353
1354 static RefPtr<CSSValue> consumeFilterImage(CSSParserTokenRange& args, const CSSParserContext& context)
1355 {
1356     auto imageValue = consumeImageOrNone(args, context);
1357     if (!imageValue || !consumeCommaIncludingWhitespace(args))
1358         return nullptr;
1359
1360     auto filterValue = consumeFilter(args, context, AllowedFilterFunctions::PixelFilters);
1361
1362     if (!filterValue)
1363         return nullptr;
1364
1365     if (!args.atEnd())
1366         return nullptr;
1367
1368     return CSSFilterImageValue::create(imageValue.releaseNonNull(), filterValue.releaseNonNull());
1369 }
1370
1371 #if ENABLE(CSS_PAINTING_API)
1372 static RefPtr<CSSValue> consumeCustomPaint(CSSParserTokenRange& args)
1373 {
1374     if (!RuntimeEnabledFeatures::sharedFeatures().cssPaintingAPIEnabled())
1375         return nullptr;
1376     if (args.peek().type() != IdentToken)
1377         return nullptr;
1378     auto name = args.consumeIncludingWhitespace().value().toString();
1379
1380     if (!args.atEnd() && args.peek() != CommaToken)
1381         return nullptr;
1382     if (!args.atEnd())
1383         args.consume();
1384
1385     auto argumentList = CSSVariableData::create(args);
1386
1387     while (!args.atEnd())
1388         args.consume();
1389
1390     return CSSPaintImageValue::create(name, WTFMove(argumentList));
1391 }
1392 #endif
1393
1394 static RefPtr<CSSValue> consumeGeneratedImage(CSSParserTokenRange& range, CSSParserContext context)
1395 {
1396     CSSValueID id = range.peek().functionId();
1397     CSSParserTokenRange rangeCopy = range;
1398     CSSParserTokenRange args = consumeFunction(rangeCopy);
1399     RefPtr<CSSValue> result;
1400     if (id == CSSValueRadialGradient)
1401         result = consumeRadialGradient(args, context.mode, NonRepeating);
1402     else if (id == CSSValueRepeatingRadialGradient)
1403         result = consumeRadialGradient(args, context.mode, Repeating);
1404     else if (id == CSSValueWebkitLinearGradient)
1405         result = consumeLinearGradient(args, context.mode, NonRepeating, CSSPrefixedLinearGradient);
1406     else if (id == CSSValueWebkitRepeatingLinearGradient)
1407         result = consumeLinearGradient(args, context.mode, Repeating, CSSPrefixedLinearGradient);
1408     else if (id == CSSValueRepeatingLinearGradient)
1409         result = consumeLinearGradient(args, context.mode, Repeating, CSSLinearGradient);
1410     else if (id == CSSValueLinearGradient)
1411         result = consumeLinearGradient(args, context.mode, NonRepeating, CSSLinearGradient);
1412     else if (id == CSSValueWebkitGradient)
1413         result = consumeDeprecatedGradient(args, context.mode);
1414     else if (id == CSSValueWebkitRadialGradient)
1415         result = consumeDeprecatedRadialGradient(args, context.mode, NonRepeating);
1416     else if (id == CSSValueWebkitRepeatingRadialGradient)
1417         result = consumeDeprecatedRadialGradient(args, context.mode, Repeating);
1418     else if (id == CSSValueConicGradient)
1419         result = consumeConicGradient(args, context, NonRepeating);
1420     else if (id == CSSValueRepeatingConicGradient)
1421         result = consumeConicGradient(args, context, Repeating);
1422     else if (id == CSSValueWebkitCrossFade || id == CSSValueCrossFade)
1423         result = consumeCrossFade(args, context, id == CSSValueWebkitCrossFade);
1424     else if (id == CSSValueWebkitCanvas)
1425         result = consumeWebkitCanvas(args);
1426     else if (id == CSSValueWebkitNamedImage)
1427         result = consumeWebkitNamedImage(args);
1428     else if (id == CSSValueWebkitFilter || id == CSSValueFilter)
1429         result = consumeFilterImage(args, context);
1430 #if ENABLE(CSS_PAINTING_API)
1431     else if (id == CSSValuePaint)
1432         result = consumeCustomPaint(args);
1433 #endif
1434     if (!result || !args.atEnd())
1435         return nullptr;
1436     range = rangeCopy;
1437     return result;
1438 }
1439
1440 static RefPtr<CSSValue> consumeImageSet(CSSParserTokenRange& range, const CSSParserContext& context)
1441 {
1442     CSSParserTokenRange rangeCopy = range;
1443     CSSParserTokenRange args = consumeFunction(rangeCopy);
1444     RefPtr<CSSImageSetValue> imageSet = CSSImageSetValue::create(context.isContentOpaque ? LoadedFromOpaqueSource::Yes : LoadedFromOpaqueSource::No);
1445     do {
1446         AtomicString urlValue = consumeUrlAsStringView(args).toAtomicString();
1447         if (urlValue.isNull())
1448             return nullptr;
1449
1450         RefPtr<CSSValue> image = CSSImageValue::create(completeURL(context, urlValue), context.isContentOpaque ? LoadedFromOpaqueSource::Yes : LoadedFromOpaqueSource::No);
1451         imageSet->append(image.releaseNonNull());
1452
1453         const CSSParserToken& token = args.consumeIncludingWhitespace();
1454         if (token.type() != DimensionToken)
1455             return nullptr;
1456         if (token.value() != "x")
1457             return nullptr;
1458         ASSERT(token.unitType() == CSSPrimitiveValue::UnitType::CSS_UNKNOWN);
1459         double imageScaleFactor = token.numericValue();
1460         if (imageScaleFactor <= 0)
1461             return nullptr;
1462         imageSet->append(CSSValuePool::singleton().createValue(imageScaleFactor, CSSPrimitiveValue::UnitType::CSS_NUMBER));
1463     } while (consumeCommaIncludingWhitespace(args));
1464     if (!args.atEnd())
1465         return nullptr;
1466     range = rangeCopy;
1467     return imageSet;
1468 }
1469
1470 static bool isGeneratedImage(CSSValueID id)
1471 {
1472     return id == CSSValueLinearGradient
1473         || id == CSSValueRadialGradient
1474         || id == CSSValueConicGradient
1475         || id == CSSValueRepeatingLinearGradient
1476         || id == CSSValueRepeatingRadialGradient
1477         || id == CSSValueRepeatingConicGradient
1478         || id == CSSValueWebkitLinearGradient
1479         || id == CSSValueWebkitRadialGradient
1480         || id == CSSValueWebkitRepeatingLinearGradient
1481         || id == CSSValueWebkitRepeatingRadialGradient
1482         || id == CSSValueWebkitGradient
1483         || id == CSSValueWebkitCrossFade
1484         || id == CSSValueWebkitCanvas
1485         || id == CSSValueCrossFade
1486         || id == CSSValueWebkitNamedImage
1487         || id == CSSValueWebkitFilter
1488 #if ENABLE(CSS_PAINTING_API)
1489         || id == CSSValuePaint
1490 #endif
1491         || id == CSSValueFilter;
1492 }
1493     
1494 static bool isPixelFilterFunction(CSSValueID filterFunction)
1495 {
1496     switch (filterFunction) {
1497     case CSSValueBlur:
1498     case CSSValueBrightness:
1499     case CSSValueContrast:
1500     case CSSValueDropShadow:
1501     case CSSValueGrayscale:
1502     case CSSValueHueRotate:
1503     case CSSValueInvert:
1504     case CSSValueOpacity:
1505     case CSSValueSaturate:
1506     case CSSValueSepia:
1507         return true;
1508     default:
1509         return false;
1510     }
1511 }
1512
1513 static bool isColorFilterFunction(CSSValueID filterFunction)
1514 {
1515     switch (filterFunction) {
1516     case CSSValueBrightness:
1517     case CSSValueContrast:
1518     case CSSValueGrayscale:
1519     case CSSValueHueRotate:
1520     case CSSValueInvert:
1521     case CSSValueOpacity:
1522     case CSSValueSaturate:
1523     case CSSValueSepia:
1524     case CSSValueAppleInvertLightness:
1525         return true;
1526     default:
1527         return false;
1528     }
1529 }
1530
1531 static bool allowsValuesGreaterThanOne(CSSValueID filterFunction)
1532 {
1533     switch (filterFunction) {
1534     case CSSValueBrightness:
1535     case CSSValueContrast:
1536     case CSSValueSaturate:
1537         return true;
1538     default:
1539         return false;
1540     }
1541 }
1542
1543 static RefPtr<CSSFunctionValue> consumeFilterFunction(CSSParserTokenRange& range, const CSSParserContext& context, AllowedFilterFunctions allowedFunctions)
1544 {
1545     CSSValueID filterType = range.peek().functionId();
1546     switch (allowedFunctions) {
1547     case AllowedFilterFunctions::PixelFilters:
1548         if (!isPixelFilterFunction(filterType))
1549             return nullptr;
1550         break;
1551     case AllowedFilterFunctions::ColorFilters:
1552         if (!isColorFilterFunction(filterType))
1553             return nullptr;
1554         break;
1555     }
1556
1557     CSSParserTokenRange args = consumeFunction(range);
1558     RefPtr<CSSFunctionValue> filterValue = CSSFunctionValue::create(filterType);
1559
1560     if (filterType == CSSValueAppleInvertLightness) {
1561         if (!args.atEnd())
1562             return nullptr;
1563         return filterValue;
1564     }
1565
1566     RefPtr<CSSValue> parsedValue;
1567
1568     if (filterType == CSSValueDropShadow)
1569         parsedValue = consumeSingleShadow(args, context.mode, false, false);
1570     else {
1571         if (args.atEnd())
1572             return filterValue;
1573
1574         if (filterType == CSSValueHueRotate)
1575             parsedValue = consumeAngle(args, context.mode, UnitlessQuirk::Forbid);
1576         else if (filterType == CSSValueBlur)
1577             parsedValue = consumeLength(args, HTMLStandardMode, ValueRangeNonNegative);
1578         else {
1579             parsedValue = consumePercent(args, ValueRangeNonNegative);
1580             if (!parsedValue)
1581                 parsedValue = consumeNumber(args, ValueRangeNonNegative);
1582             if (parsedValue && !allowsValuesGreaterThanOne(filterType)) {
1583                 bool isPercentage = downcast<CSSPrimitiveValue>(*parsedValue).isPercentage();
1584                 double maxAllowed = isPercentage ? 100.0 : 1.0;
1585                 if (downcast<CSSPrimitiveValue>(*parsedValue).doubleValue() > maxAllowed)
1586                     parsedValue = CSSPrimitiveValue::create(maxAllowed, isPercentage ? CSSPrimitiveValue::UnitType::CSS_PERCENTAGE : CSSPrimitiveValue::UnitType::CSS_NUMBER);
1587             }
1588         }
1589     }
1590     if (!parsedValue || !args.atEnd())
1591         return nullptr;
1592     filterValue->append(parsedValue.releaseNonNull());
1593     return filterValue;
1594 }
1595
1596 RefPtr<CSSValue> consumeFilter(CSSParserTokenRange& range, const CSSParserContext& context, AllowedFilterFunctions allowedFunctions)
1597 {
1598     if (range.peek().id() == CSSValueNone)
1599         return consumeIdent(range);
1600
1601     bool referenceFiltersAllowed = allowedFunctions == AllowedFilterFunctions::PixelFilters;
1602     auto list = CSSValueList::createSpaceSeparated();
1603     do {
1604         RefPtr<CSSValue> filterValue = referenceFiltersAllowed ? consumeUrl(range) : nullptr;
1605         if (!filterValue) {
1606             filterValue = consumeFilterFunction(range, context, allowedFunctions);
1607             if (!filterValue)
1608                 return nullptr;
1609         }
1610         list->append(filterValue.releaseNonNull());
1611     } while (!range.atEnd());
1612
1613     return list.ptr();
1614 }
1615
1616 RefPtr<CSSShadowValue> consumeSingleShadow(CSSParserTokenRange& range, CSSParserMode cssParserMode, bool allowInset, bool allowSpread)
1617 {
1618     RefPtr<CSSPrimitiveValue> style;
1619     RefPtr<CSSPrimitiveValue> color;
1620
1621     if (range.atEnd())
1622         return nullptr;
1623     if (range.peek().id() == CSSValueInset) {
1624         if (!allowInset)
1625             return nullptr;
1626         style = consumeIdent(range);
1627     }
1628     color = consumeColor(range, cssParserMode);
1629
1630     auto horizontalOffset = consumeLength(range, cssParserMode, ValueRangeAll);
1631     if (!horizontalOffset)
1632         return nullptr;
1633
1634     auto verticalOffset = consumeLength(range, cssParserMode, ValueRangeAll);
1635     if (!verticalOffset)
1636         return nullptr;
1637
1638     auto blurRadius = consumeLength(range, cssParserMode, ValueRangeAll);
1639     RefPtr<CSSPrimitiveValue> spreadDistance;
1640     if (blurRadius) {
1641         // Blur radius must be non-negative.
1642         if (blurRadius->doubleValue() < 0)
1643             return nullptr;
1644         if (allowSpread)
1645             spreadDistance = consumeLength(range, cssParserMode, ValueRangeAll);
1646     }
1647
1648     if (!range.atEnd()) {
1649         if (!color)
1650             color = consumeColor(range, cssParserMode);
1651         if (range.peek().id() == CSSValueInset) {
1652             if (!allowInset || style)
1653                 return nullptr;
1654             style = consumeIdent(range);
1655         }
1656     }
1657
1658     return CSSShadowValue::create(WTFMove(horizontalOffset), WTFMove(verticalOffset), WTFMove(blurRadius), WTFMove(spreadDistance), WTFMove(style), WTFMove(color));
1659 }
1660
1661 RefPtr<CSSValue> consumeImage(CSSParserTokenRange& range, CSSParserContext context, ConsumeGeneratedImage generatedImage)
1662 {
1663     AtomicString uri = consumeUrlAsStringView(range).toAtomicString();
1664     if (!uri.isNull())
1665         return CSSImageValue::create(completeURL(context, uri), context.isContentOpaque ? LoadedFromOpaqueSource::Yes : LoadedFromOpaqueSource::No);
1666
1667     if (range.peek().type() == FunctionToken) {
1668         CSSValueID id = range.peek().functionId();
1669         if (id == CSSValueWebkitImageSet || id == CSSValueImageSet)
1670             return consumeImageSet(range, context);
1671         if (generatedImage == ConsumeGeneratedImage::Allow && isGeneratedImage(id))
1672             return consumeGeneratedImage(range, context);
1673     }
1674     return nullptr;
1675 }
1676
1677 } // namespace CSSPropertyParserHelpers
1678
1679 } // namespace WebCore