f1699e79e1f51f8aa95e61e12480eb1d4404ded6
[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     auto 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         auto radius = consumeNumber(args, ValueRangeNonNegative);
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         auto radius = consumeNumber(args, ValueRangeNonNegative);
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
1076         gradient.addStop(stop);
1077
1078         if (!stop.m_color || !stop.m_position)
1079             continue;
1080
1081         CSSGradientColorStop secondStop;
1082         if (isConicGradient)
1083             secondStop.m_position = consumeAngleOrPercent(range, cssParserMode, ValueRangeAll, UnitlessQuirk::Forbid);
1084         else
1085             secondStop.m_position = consumeLengthOrPercent(range, cssParserMode, ValueRangeAll);
1086         
1087         if (secondStop.m_position)
1088             gradient.addStop(secondStop);
1089         
1090     } while (consumeCommaIncludingWhitespace(range));
1091
1092     gradient.doneAddingStops();
1093
1094     // The last color stop cannot be a color hint.
1095     if (previousStopWasColorHint)
1096         return false;
1097
1098     // Must have 2 or more stops to be valid.
1099     return gradient.stopCount() >= 2;
1100 }
1101
1102 static RefPtr<CSSValue> consumeDeprecatedRadialGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode, CSSGradientRepeat repeating)
1103 {
1104     RefPtr<CSSRadialGradientValue> result = CSSRadialGradientValue::create(repeating, CSSPrefixedRadialGradient);
1105     RefPtr<CSSPrimitiveValue> centerX;
1106     RefPtr<CSSPrimitiveValue> centerY;
1107     consumeOneOrTwoValuedPosition(args, cssParserMode, UnitlessQuirk::Forbid, centerX, centerY);
1108     if ((centerX || centerY) && !consumeCommaIncludingWhitespace(args))
1109         return nullptr;
1110
1111     result->setFirstX(centerX.copyRef());
1112     result->setFirstY(centerY.copyRef());
1113     result->setSecondX(centerX.copyRef());
1114     result->setSecondY(centerY.copyRef());
1115
1116     auto shape = consumeIdent<CSSValueCircle, CSSValueEllipse>(args);
1117     auto sizeKeyword = consumeIdent<CSSValueClosestSide, CSSValueClosestCorner, CSSValueFarthestSide, CSSValueFarthestCorner, CSSValueContain, CSSValueCover>(args);
1118     if (!shape)
1119         shape = consumeIdent<CSSValueCircle, CSSValueEllipse>(args);
1120     result->setShape(shape.copyRef());
1121     result->setSizingBehavior(sizeKeyword.copyRef());
1122
1123     // Or, two lengths or percentages
1124     if (!shape && !sizeKeyword) {
1125         auto horizontalSize = consumeLengthOrPercent(args, cssParserMode, ValueRangeNonNegative);
1126         RefPtr<CSSPrimitiveValue> verticalSize;
1127         if (horizontalSize) {
1128             verticalSize = consumeLengthOrPercent(args, cssParserMode, ValueRangeNonNegative);
1129             if (!verticalSize)
1130                 return nullptr;
1131             consumeCommaIncludingWhitespace(args);
1132             result->setEndHorizontalSize(horizontalSize.copyRef());
1133             result->setEndVerticalSize(verticalSize.copyRef());
1134         }
1135     } else {
1136         consumeCommaIncludingWhitespace(args);
1137     }
1138     if (!consumeGradientColorStops(args, cssParserMode, *result))
1139         return nullptr;
1140
1141     return result;
1142 }
1143
1144 static RefPtr<CSSValue> consumeRadialGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode, CSSGradientRepeat repeating)
1145 {
1146     RefPtr<CSSRadialGradientValue> result = CSSRadialGradientValue::create(repeating, CSSRadialGradient);
1147
1148     RefPtr<CSSPrimitiveValue> shape;
1149     RefPtr<CSSPrimitiveValue> sizeKeyword;
1150     RefPtr<CSSPrimitiveValue> horizontalSize;
1151     RefPtr<CSSPrimitiveValue> verticalSize;
1152
1153     // First part of grammar, the size/shape clause:
1154     // [ circle || <length> ] |
1155     // [ ellipse || [ <length> | <percentage> ]{2} ] |
1156     // [ [ circle | ellipse] || <size-keyword> ]
1157     for (int i = 0; i < 3; ++i) {
1158         if (args.peek().type() == IdentToken) {
1159             CSSValueID id = args.peek().id();
1160             if (id == CSSValueCircle || id == CSSValueEllipse) {
1161                 if (shape)
1162                     return nullptr;
1163                 shape = consumeIdent(args);
1164             } else if (id == CSSValueClosestSide || id == CSSValueClosestCorner || id == CSSValueFarthestSide || id == CSSValueFarthestCorner) {
1165                 if (sizeKeyword)
1166                     return nullptr;
1167                 sizeKeyword = consumeIdent(args);
1168             } else {
1169                 break;
1170             }
1171         } else {
1172             auto center = consumeLengthOrPercent(args, cssParserMode, ValueRangeNonNegative);
1173             if (!center)
1174                 break;
1175             if (horizontalSize)
1176                 return nullptr;
1177             horizontalSize = center;
1178             center = consumeLengthOrPercent(args, cssParserMode, ValueRangeNonNegative);
1179             if (center) {
1180                 verticalSize = center;
1181                 ++i;
1182             }
1183         }
1184     }
1185
1186     // You can specify size as a keyword or a length/percentage, not both.
1187     if (sizeKeyword && horizontalSize)
1188         return nullptr;
1189     // Circles must have 0 or 1 lengths.
1190     if (shape && shape->valueID() == CSSValueCircle && verticalSize)
1191         return nullptr;
1192     // Ellipses must have 0 or 2 length/percentages.
1193     if (shape && shape->valueID() == CSSValueEllipse && horizontalSize && !verticalSize)
1194         return nullptr;
1195     // If there's only one size, it must be a length.
1196     if (!verticalSize && horizontalSize && horizontalSize->isPercentage())
1197         return nullptr;
1198     if ((horizontalSize && horizontalSize->isCalculatedPercentageWithLength())
1199         || (verticalSize && verticalSize->isCalculatedPercentageWithLength()))
1200         return nullptr;
1201
1202     result->setShape(shape.copyRef());
1203     result->setSizingBehavior(sizeKeyword.copyRef());
1204     result->setEndHorizontalSize(horizontalSize.copyRef());
1205     result->setEndVerticalSize(verticalSize.copyRef());
1206
1207     RefPtr<CSSPrimitiveValue> centerX;
1208     RefPtr<CSSPrimitiveValue> centerY;
1209     if (args.peek().id() == CSSValueAt) {
1210         args.consumeIncludingWhitespace();
1211         consumePosition(args, cssParserMode, UnitlessQuirk::Forbid, centerX, centerY);
1212         if (!(centerX && centerY))
1213             return nullptr;
1214         
1215         result->setFirstX(centerX.copyRef());
1216         result->setFirstY(centerY.copyRef());
1217         
1218         // Right now, CSS radial gradients have the same start and end centers.
1219         result->setSecondX(centerX.copyRef());
1220         result->setSecondY(centerY.copyRef());
1221     }
1222
1223     if ((shape || sizeKeyword || horizontalSize || centerX || centerY) && !consumeCommaIncludingWhitespace(args))
1224         return nullptr;
1225     if (!consumeGradientColorStops(args, cssParserMode, *result))
1226         return nullptr;
1227     return result;
1228 }
1229
1230 static RefPtr<CSSValue> consumeLinearGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode, CSSGradientRepeat repeating, CSSGradientType gradientType)
1231 {
1232     RefPtr<CSSLinearGradientValue> result = CSSLinearGradientValue::create(repeating, gradientType);
1233
1234     bool expectComma = true;
1235     RefPtr<CSSPrimitiveValue> angle = consumeAngle(args, cssParserMode, UnitlessQuirk::Forbid);
1236     if (angle)
1237         result->setAngle(angle.releaseNonNull());
1238     else if (gradientType == CSSPrefixedLinearGradient || consumeIdent<CSSValueTo>(args)) {
1239         RefPtr<CSSPrimitiveValue> endX = consumeIdent<CSSValueLeft, CSSValueRight>(args);
1240         RefPtr<CSSPrimitiveValue> endY = consumeIdent<CSSValueBottom, CSSValueTop>(args);
1241         if (!endX && !endY) {
1242             if (gradientType == CSSLinearGradient)
1243                 return nullptr;
1244             endY = CSSPrimitiveValue::createIdentifier(CSSValueTop);
1245             expectComma = false;
1246         } else if (!endX) {
1247             endX = consumeIdent<CSSValueLeft, CSSValueRight>(args);
1248         }
1249
1250         result->setFirstX(endX.copyRef());
1251         result->setFirstY(endY.copyRef());
1252     } else {
1253         expectComma = false;
1254     }
1255
1256     if (expectComma && !consumeCommaIncludingWhitespace(args))
1257         return nullptr;
1258     if (!consumeGradientColorStops(args, cssParserMode, *result))
1259         return nullptr;
1260     return result;
1261 }
1262
1263 static RefPtr<CSSValue> consumeConicGradient(CSSParserTokenRange& args, CSSParserContext context, CSSGradientRepeat repeating)
1264 {
1265 #if ENABLE(CSS_CONIC_GRADIENTS)
1266     RefPtr<CSSConicGradientValue> result = CSSConicGradientValue::create(repeating);
1267
1268     bool expectComma = false;
1269     if (args.peek().type() == IdentToken) {
1270         if (consumeIdent<CSSValueFrom>(args)) {
1271             auto angle = consumeAngle(args, context.mode, UnitlessQuirk::Forbid);
1272             if (!angle)
1273                 return nullptr;
1274             result->setAngle(angle.releaseNonNull());
1275             expectComma = true;
1276         }
1277         
1278         if (consumeIdent<CSSValueAt>(args)) {
1279             RefPtr<CSSPrimitiveValue> centerX;
1280             RefPtr<CSSPrimitiveValue> centerY;
1281             consumePosition(args, context.mode, UnitlessQuirk::Forbid, centerX, centerY);
1282             if (!(centerX && centerY))
1283                 return nullptr;
1284             
1285             result->setFirstX(centerX.copyRef());
1286             result->setFirstY(centerY.copyRef());
1287             
1288             // Right now, conic gradients have the same start and end centers.
1289             result->setSecondX(centerX.copyRef());
1290             result->setSecondY(centerY.copyRef());
1291     
1292             expectComma = true;
1293         }
1294     }
1295
1296     if (expectComma && !consumeCommaIncludingWhitespace(args))
1297         return nullptr;
1298     if (!consumeGradientColorStops(args, context.mode, *result))
1299         return nullptr;
1300     return result;
1301 #else
1302     UNUSED_PARAM(args);
1303     UNUSED_PARAM(context);
1304     UNUSED_PARAM(repeating);
1305     return nullptr;
1306 #endif
1307 }
1308
1309 RefPtr<CSSValue> consumeImageOrNone(CSSParserTokenRange& range, CSSParserContext context)
1310 {
1311     if (range.peek().id() == CSSValueNone)
1312         return consumeIdent(range);
1313     return consumeImage(range, context);
1314 }
1315
1316 static RefPtr<CSSValue> consumeCrossFade(CSSParserTokenRange& args, CSSParserContext context, bool prefixed)
1317 {
1318     RefPtr<CSSValue> fromImageValue = consumeImageOrNone(args, context);
1319     if (!fromImageValue || !consumeCommaIncludingWhitespace(args))
1320         return nullptr;
1321     RefPtr<CSSValue> toImageValue = consumeImageOrNone(args, context);
1322     if (!toImageValue || !consumeCommaIncludingWhitespace(args))
1323         return nullptr;
1324
1325     RefPtr<CSSPrimitiveValue> percentage;
1326     if (auto percentValue = consumePercent(args, ValueRangeAll))
1327         percentage = CSSValuePool::singleton().createValue(clampTo<double>(percentValue->doubleValue() / 100.0, 0, 1), CSSPrimitiveValue::UnitType::CSS_NUMBER);
1328     else if (auto numberValue = consumeNumber(args, ValueRangeAll))
1329         percentage = CSSValuePool::singleton().createValue(clampTo<double>(numberValue->doubleValue(), 0, 1), CSSPrimitiveValue::UnitType::CSS_NUMBER);
1330
1331     if (!percentage)
1332         return nullptr;
1333     return CSSCrossfadeValue::create(fromImageValue.releaseNonNull(), toImageValue.releaseNonNull(), percentage.releaseNonNull(), prefixed);
1334 }
1335
1336 static RefPtr<CSSValue> consumeWebkitCanvas(CSSParserTokenRange& args)
1337 {
1338     if (args.peek().type() != IdentToken)
1339         return nullptr;
1340     auto canvasName = args.consumeIncludingWhitespace().value().toString();
1341     if (!args.atEnd())
1342         return nullptr;
1343     return CSSCanvasValue::create(canvasName);
1344 }
1345
1346 static RefPtr<CSSValue> consumeWebkitNamedImage(CSSParserTokenRange& args)
1347 {
1348     if (args.peek().type() != IdentToken)
1349         return nullptr;
1350     auto imageName = args.consumeIncludingWhitespace().value().toString();
1351     if (!args.atEnd())
1352         return nullptr;
1353     return CSSNamedImageValue::create(imageName);
1354 }
1355
1356 static RefPtr<CSSValue> consumeFilterImage(CSSParserTokenRange& args, const CSSParserContext& context)
1357 {
1358     auto imageValue = consumeImageOrNone(args, context);
1359     if (!imageValue || !consumeCommaIncludingWhitespace(args))
1360         return nullptr;
1361
1362     auto filterValue = consumeFilter(args, context, AllowedFilterFunctions::PixelFilters);
1363
1364     if (!filterValue)
1365         return nullptr;
1366
1367     if (!args.atEnd())
1368         return nullptr;
1369
1370     return CSSFilterImageValue::create(imageValue.releaseNonNull(), filterValue.releaseNonNull());
1371 }
1372
1373 #if ENABLE(CSS_PAINTING_API)
1374 static RefPtr<CSSValue> consumeCustomPaint(CSSParserTokenRange& args)
1375 {
1376     if (!RuntimeEnabledFeatures::sharedFeatures().cssPaintingAPIEnabled())
1377         return nullptr;
1378     if (args.peek().type() != IdentToken)
1379         return nullptr;
1380     auto name = args.consumeIncludingWhitespace().value().toString();
1381
1382     if (!args.atEnd() && args.peek() != CommaToken)
1383         return nullptr;
1384     if (!args.atEnd())
1385         args.consume();
1386
1387     auto argumentList = CSSVariableData::create(args);
1388
1389     while (!args.atEnd())
1390         args.consume();
1391
1392     return CSSPaintImageValue::create(name, WTFMove(argumentList));
1393 }
1394 #endif
1395
1396 static RefPtr<CSSValue> consumeGeneratedImage(CSSParserTokenRange& range, CSSParserContext context)
1397 {
1398     CSSValueID id = range.peek().functionId();
1399     CSSParserTokenRange rangeCopy = range;
1400     CSSParserTokenRange args = consumeFunction(rangeCopy);
1401     RefPtr<CSSValue> result;
1402     if (id == CSSValueRadialGradient)
1403         result = consumeRadialGradient(args, context.mode, NonRepeating);
1404     else if (id == CSSValueRepeatingRadialGradient)
1405         result = consumeRadialGradient(args, context.mode, Repeating);
1406     else if (id == CSSValueWebkitLinearGradient)
1407         result = consumeLinearGradient(args, context.mode, NonRepeating, CSSPrefixedLinearGradient);
1408     else if (id == CSSValueWebkitRepeatingLinearGradient)
1409         result = consumeLinearGradient(args, context.mode, Repeating, CSSPrefixedLinearGradient);
1410     else if (id == CSSValueRepeatingLinearGradient)
1411         result = consumeLinearGradient(args, context.mode, Repeating, CSSLinearGradient);
1412     else if (id == CSSValueLinearGradient)
1413         result = consumeLinearGradient(args, context.mode, NonRepeating, CSSLinearGradient);
1414     else if (id == CSSValueWebkitGradient)
1415         result = consumeDeprecatedGradient(args, context.mode);
1416     else if (id == CSSValueWebkitRadialGradient)
1417         result = consumeDeprecatedRadialGradient(args, context.mode, NonRepeating);
1418     else if (id == CSSValueWebkitRepeatingRadialGradient)
1419         result = consumeDeprecatedRadialGradient(args, context.mode, Repeating);
1420     else if (id == CSSValueConicGradient)
1421         result = consumeConicGradient(args, context, NonRepeating);
1422     else if (id == CSSValueRepeatingConicGradient)
1423         result = consumeConicGradient(args, context, Repeating);
1424     else if (id == CSSValueWebkitCrossFade || id == CSSValueCrossFade)
1425         result = consumeCrossFade(args, context, id == CSSValueWebkitCrossFade);
1426     else if (id == CSSValueWebkitCanvas)
1427         result = consumeWebkitCanvas(args);
1428     else if (id == CSSValueWebkitNamedImage)
1429         result = consumeWebkitNamedImage(args);
1430     else if (id == CSSValueWebkitFilter || id == CSSValueFilter)
1431         result = consumeFilterImage(args, context);
1432 #if ENABLE(CSS_PAINTING_API)
1433     else if (id == CSSValuePaint)
1434         result = consumeCustomPaint(args);
1435 #endif
1436     if (!result || !args.atEnd())
1437         return nullptr;
1438     range = rangeCopy;
1439     return result;
1440 }
1441
1442 static RefPtr<CSSValue> consumeImageSet(CSSParserTokenRange& range, const CSSParserContext& context)
1443 {
1444     CSSParserTokenRange rangeCopy = range;
1445     CSSParserTokenRange args = consumeFunction(rangeCopy);
1446     RefPtr<CSSImageSetValue> imageSet = CSSImageSetValue::create(context.isContentOpaque ? LoadedFromOpaqueSource::Yes : LoadedFromOpaqueSource::No);
1447     do {
1448         AtomString urlValue = consumeUrlAsStringView(args).toAtomString();
1449         if (urlValue.isNull())
1450             return nullptr;
1451
1452         RefPtr<CSSValue> image = CSSImageValue::create(completeURL(context, urlValue), context.isContentOpaque ? LoadedFromOpaqueSource::Yes : LoadedFromOpaqueSource::No);
1453         imageSet->append(image.releaseNonNull());
1454
1455         const CSSParserToken& token = args.consumeIncludingWhitespace();
1456         if (token.type() != DimensionToken)
1457             return nullptr;
1458         if (token.value() != "x")
1459             return nullptr;
1460         ASSERT(token.unitType() == CSSPrimitiveValue::UnitType::CSS_UNKNOWN);
1461         double imageScaleFactor = token.numericValue();
1462         if (imageScaleFactor <= 0)
1463             return nullptr;
1464         imageSet->append(CSSValuePool::singleton().createValue(imageScaleFactor, CSSPrimitiveValue::UnitType::CSS_NUMBER));
1465     } while (consumeCommaIncludingWhitespace(args));
1466     if (!args.atEnd())
1467         return nullptr;
1468     range = rangeCopy;
1469     return imageSet;
1470 }
1471
1472 static bool isGeneratedImage(CSSValueID id)
1473 {
1474     return id == CSSValueLinearGradient
1475         || id == CSSValueRadialGradient
1476         || id == CSSValueConicGradient
1477         || id == CSSValueRepeatingLinearGradient
1478         || id == CSSValueRepeatingRadialGradient
1479         || id == CSSValueRepeatingConicGradient
1480         || id == CSSValueWebkitLinearGradient
1481         || id == CSSValueWebkitRadialGradient
1482         || id == CSSValueWebkitRepeatingLinearGradient
1483         || id == CSSValueWebkitRepeatingRadialGradient
1484         || id == CSSValueWebkitGradient
1485         || id == CSSValueWebkitCrossFade
1486         || id == CSSValueWebkitCanvas
1487         || id == CSSValueCrossFade
1488         || id == CSSValueWebkitNamedImage
1489         || id == CSSValueWebkitFilter
1490 #if ENABLE(CSS_PAINTING_API)
1491         || id == CSSValuePaint
1492 #endif
1493         || id == CSSValueFilter;
1494 }
1495     
1496 static bool isPixelFilterFunction(CSSValueID filterFunction)
1497 {
1498     switch (filterFunction) {
1499     case CSSValueBlur:
1500     case CSSValueBrightness:
1501     case CSSValueContrast:
1502     case CSSValueDropShadow:
1503     case CSSValueGrayscale:
1504     case CSSValueHueRotate:
1505     case CSSValueInvert:
1506     case CSSValueOpacity:
1507     case CSSValueSaturate:
1508     case CSSValueSepia:
1509         return true;
1510     default:
1511         return false;
1512     }
1513 }
1514
1515 static bool isColorFilterFunction(CSSValueID filterFunction)
1516 {
1517     switch (filterFunction) {
1518     case CSSValueBrightness:
1519     case CSSValueContrast:
1520     case CSSValueGrayscale:
1521     case CSSValueHueRotate:
1522     case CSSValueInvert:
1523     case CSSValueOpacity:
1524     case CSSValueSaturate:
1525     case CSSValueSepia:
1526     case CSSValueAppleInvertLightness:
1527         return true;
1528     default:
1529         return false;
1530     }
1531 }
1532
1533 static bool allowsValuesGreaterThanOne(CSSValueID filterFunction)
1534 {
1535     switch (filterFunction) {
1536     case CSSValueBrightness:
1537     case CSSValueContrast:
1538     case CSSValueSaturate:
1539         return true;
1540     default:
1541         return false;
1542     }
1543 }
1544
1545 static RefPtr<CSSFunctionValue> consumeFilterFunction(CSSParserTokenRange& range, const CSSParserContext& context, AllowedFilterFunctions allowedFunctions)
1546 {
1547     CSSValueID filterType = range.peek().functionId();
1548     switch (allowedFunctions) {
1549     case AllowedFilterFunctions::PixelFilters:
1550         if (!isPixelFilterFunction(filterType))
1551             return nullptr;
1552         break;
1553     case AllowedFilterFunctions::ColorFilters:
1554         if (!isColorFilterFunction(filterType))
1555             return nullptr;
1556         break;
1557     }
1558
1559     CSSParserTokenRange args = consumeFunction(range);
1560     RefPtr<CSSFunctionValue> filterValue = CSSFunctionValue::create(filterType);
1561
1562     if (filterType == CSSValueAppleInvertLightness) {
1563         if (!args.atEnd())
1564             return nullptr;
1565         return filterValue;
1566     }
1567
1568     RefPtr<CSSValue> parsedValue;
1569
1570     if (filterType == CSSValueDropShadow)
1571         parsedValue = consumeSingleShadow(args, context.mode, false, false);
1572     else {
1573         if (args.atEnd())
1574             return filterValue;
1575
1576         if (filterType == CSSValueHueRotate)
1577             parsedValue = consumeAngle(args, context.mode, UnitlessQuirk::Forbid);
1578         else if (filterType == CSSValueBlur)
1579             parsedValue = consumeLength(args, HTMLStandardMode, ValueRangeNonNegative);
1580         else {
1581             parsedValue = consumePercent(args, ValueRangeNonNegative);
1582             if (!parsedValue)
1583                 parsedValue = consumeNumber(args, ValueRangeNonNegative);
1584             if (parsedValue && !allowsValuesGreaterThanOne(filterType)) {
1585                 bool isPercentage = downcast<CSSPrimitiveValue>(*parsedValue).isPercentage();
1586                 double maxAllowed = isPercentage ? 100.0 : 1.0;
1587                 if (downcast<CSSPrimitiveValue>(*parsedValue).doubleValue() > maxAllowed)
1588                     parsedValue = CSSPrimitiveValue::create(maxAllowed, isPercentage ? CSSPrimitiveValue::UnitType::CSS_PERCENTAGE : CSSPrimitiveValue::UnitType::CSS_NUMBER);
1589             }
1590         }
1591     }
1592     if (!parsedValue || !args.atEnd())
1593         return nullptr;
1594     filterValue->append(parsedValue.releaseNonNull());
1595     return filterValue;
1596 }
1597
1598 RefPtr<CSSValue> consumeFilter(CSSParserTokenRange& range, const CSSParserContext& context, AllowedFilterFunctions allowedFunctions)
1599 {
1600     if (range.peek().id() == CSSValueNone)
1601         return consumeIdent(range);
1602
1603     bool referenceFiltersAllowed = allowedFunctions == AllowedFilterFunctions::PixelFilters;
1604     auto list = CSSValueList::createSpaceSeparated();
1605     do {
1606         RefPtr<CSSValue> filterValue = referenceFiltersAllowed ? consumeUrl(range) : nullptr;
1607         if (!filterValue) {
1608             filterValue = consumeFilterFunction(range, context, allowedFunctions);
1609             if (!filterValue)
1610                 return nullptr;
1611         }
1612         list->append(filterValue.releaseNonNull());
1613     } while (!range.atEnd());
1614
1615     return list.ptr();
1616 }
1617
1618 RefPtr<CSSShadowValue> consumeSingleShadow(CSSParserTokenRange& range, CSSParserMode cssParserMode, bool allowInset, bool allowSpread)
1619 {
1620     RefPtr<CSSPrimitiveValue> style;
1621     RefPtr<CSSPrimitiveValue> color;
1622
1623     if (range.atEnd())
1624         return nullptr;
1625     if (range.peek().id() == CSSValueInset) {
1626         if (!allowInset)
1627             return nullptr;
1628         style = consumeIdent(range);
1629     }
1630     color = consumeColor(range, cssParserMode);
1631
1632     auto horizontalOffset = consumeLength(range, cssParserMode, ValueRangeAll);
1633     if (!horizontalOffset)
1634         return nullptr;
1635
1636     auto verticalOffset = consumeLength(range, cssParserMode, ValueRangeAll);
1637     if (!verticalOffset)
1638         return nullptr;
1639
1640     auto blurRadius = consumeLength(range, cssParserMode, ValueRangeAll);
1641     RefPtr<CSSPrimitiveValue> spreadDistance;
1642     if (blurRadius) {
1643         // Blur radius must be non-negative.
1644         if (blurRadius->doubleValue() < 0)
1645             return nullptr;
1646         if (allowSpread)
1647             spreadDistance = consumeLength(range, cssParserMode, ValueRangeAll);
1648     }
1649
1650     if (!range.atEnd()) {
1651         if (!color)
1652             color = consumeColor(range, cssParserMode);
1653         if (range.peek().id() == CSSValueInset) {
1654             if (!allowInset || style)
1655                 return nullptr;
1656             style = consumeIdent(range);
1657         }
1658     }
1659
1660     return CSSShadowValue::create(WTFMove(horizontalOffset), WTFMove(verticalOffset), WTFMove(blurRadius), WTFMove(spreadDistance), WTFMove(style), WTFMove(color));
1661 }
1662
1663 RefPtr<CSSValue> consumeImage(CSSParserTokenRange& range, CSSParserContext context, ConsumeGeneratedImage generatedImage)
1664 {
1665     AtomString uri = consumeUrlAsStringView(range).toAtomString();
1666     if (!uri.isNull())
1667         return CSSImageValue::create(completeURL(context, uri), context.isContentOpaque ? LoadedFromOpaqueSource::Yes : LoadedFromOpaqueSource::No);
1668
1669     if (range.peek().type() == FunctionToken) {
1670         CSSValueID id = range.peek().functionId();
1671         if (id == CSSValueWebkitImageSet || id == CSSValueImageSet)
1672             return consumeImageSet(range, context);
1673         if (generatedImage == ConsumeGeneratedImage::Allow && isGeneratedImage(id))
1674             return consumeGeneratedImage(range, context);
1675     }
1676     return nullptr;
1677 }
1678
1679 } // namespace CSSPropertyParserHelpers
1680
1681 } // namespace WebCore