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