summaryrefslogtreecommitdiff
path: root/Source/WebCore/css/parser/CSSPropertyParserHelpers.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/css/parser/CSSPropertyParserHelpers.cpp')
-rw-r--r--Source/WebCore/css/parser/CSSPropertyParserHelpers.cpp1348
1 files changed, 1348 insertions, 0 deletions
diff --git a/Source/WebCore/css/parser/CSSPropertyParserHelpers.cpp b/Source/WebCore/css/parser/CSSPropertyParserHelpers.cpp
new file mode 100644
index 000000000..a917ed28b
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSPropertyParserHelpers.cpp
@@ -0,0 +1,1348 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "config.h"
+#include "CSSPropertyParserHelpers.h"
+
+#include "CSSCalculationValue.h"
+#include "CSSCanvasValue.h"
+#include "CSSCrossfadeValue.h"
+#include "CSSFilterImageValue.h"
+#include "CSSGradientValue.h"
+#include "CSSImageSetValue.h"
+#include "CSSImageValue.h"
+#include "CSSNamedImageValue.h"
+#include "CSSParserIdioms.h"
+#include "CSSValuePool.h"
+#include "Pair.h"
+#include "StyleColor.h"
+
+namespace WebCore {
+
+namespace CSSPropertyParserHelpers {
+
+bool consumeCommaIncludingWhitespace(CSSParserTokenRange& range)
+{
+ CSSParserToken value = range.peek();
+ if (value.type() != CommaToken)
+ return false;
+ range.consumeIncludingWhitespace();
+ return true;
+}
+
+bool consumeSlashIncludingWhitespace(CSSParserTokenRange& range)
+{
+ CSSParserToken value = range.peek();
+ if (value.type() != DelimiterToken || value.delimiter() != '/')
+ return false;
+ range.consumeIncludingWhitespace();
+ return true;
+}
+
+CSSParserTokenRange consumeFunction(CSSParserTokenRange& range)
+{
+ ASSERT(range.peek().type() == FunctionToken);
+ CSSParserTokenRange contents = range.consumeBlock();
+ range.consumeWhitespace();
+ contents.consumeWhitespace();
+ return contents;
+}
+
+// FIXME: consider pulling in the parsing logic from CSSCalculationValue.cpp.
+class CalcParser {
+
+public:
+ explicit CalcParser(CSSParserTokenRange& range, ValueRange valueRange = ValueRangeAll)
+ : m_sourceRange(range)
+ , m_range(range)
+ {
+ const CSSParserToken& token = range.peek();
+ if (token.functionId() == CSSValueCalc || token.functionId() == CSSValueWebkitCalc)
+ m_calcValue = CSSCalcValue::create(consumeFunction(m_range), valueRange);
+ }
+
+ const CSSCalcValue* value() const { return m_calcValue.get(); }
+ RefPtr<CSSPrimitiveValue> consumeValue()
+ {
+ if (!m_calcValue)
+ return nullptr;
+ m_sourceRange = m_range;
+ return CSSValuePool::singleton().createValue(m_calcValue.release());
+ }
+ RefPtr<CSSPrimitiveValue> consumeNumber()
+ {
+ if (!m_calcValue)
+ return nullptr;
+ m_sourceRange = m_range;
+ return CSSValuePool::singleton().createValue(m_calcValue->doubleValue(), CSSPrimitiveValue::UnitType::CSS_NUMBER);
+ }
+
+ bool consumeNumberRaw(double& result)
+ {
+ if (!m_calcValue || m_calcValue->category() != CalcNumber)
+ return false;
+ m_sourceRange = m_range;
+ result = m_calcValue->doubleValue();
+ return true;
+ }
+
+ bool consumePositiveIntegerRaw(int& result)
+ {
+ if (!m_calcValue || m_calcValue->category() != CalcNumber || !m_calcValue->isInt())
+ return false;
+ result = static_cast<int>(m_calcValue->doubleValue());
+ if (result < 1)
+ return false;
+ m_sourceRange = m_range;
+ return true;
+ }
+
+private:
+ CSSParserTokenRange& m_sourceRange;
+ CSSParserTokenRange m_range;
+ RefPtr<CSSCalcValue> m_calcValue;
+};
+
+RefPtr<CSSPrimitiveValue> consumeInteger(CSSParserTokenRange& range, double minimumValue)
+{
+ const CSSParserToken& token = range.peek();
+ if (token.type() == NumberToken) {
+ if (token.numericValueType() == NumberValueType || token.numericValue() < minimumValue)
+ return nullptr;
+ return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), CSSPrimitiveValue::UnitType::CSS_NUMBER);
+ }
+ CalcParser calcParser(range);
+ if (const CSSCalcValue* calculation = calcParser.value()) {
+ if (calculation->category() != CalcNumber || !calculation->isInt())
+ return nullptr;
+ double value = calculation->doubleValue();
+ if (value < minimumValue)
+ return nullptr;
+ return calcParser.consumeNumber();
+ }
+ return nullptr;
+}
+
+RefPtr<CSSPrimitiveValue> consumePositiveInteger(CSSParserTokenRange& range)
+{
+ return consumeInteger(range, 1);
+}
+
+bool consumePositiveIntegerRaw(CSSParserTokenRange& range, int& result)
+{
+ const CSSParserToken& token = range.peek();
+ if (token.type() == NumberToken) {
+ if (token.numericValueType() == NumberValueType || token.numericValue() < 1)
+ return false;
+ result = range.consumeIncludingWhitespace().numericValue();
+ return true;
+ }
+ CalcParser calcParser(range);
+ return calcParser.consumePositiveIntegerRaw(result);
+}
+
+bool consumeNumberRaw(CSSParserTokenRange& range, double& result)
+{
+ if (range.peek().type() == NumberToken) {
+ result = range.consumeIncludingWhitespace().numericValue();
+ return true;
+ }
+ CalcParser calcParser(range, ValueRangeAll);
+ return calcParser.consumeNumberRaw(result);
+}
+
+// FIXME: Work out if this can just call consumeNumberRaw
+RefPtr<CSSPrimitiveValue> consumeNumber(CSSParserTokenRange& range, ValueRange valueRange)
+{
+ const CSSParserToken& token = range.peek();
+ if (token.type() == NumberToken) {
+ if (valueRange == ValueRangeNonNegative && token.numericValue() < 0)
+ return nullptr;
+ return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), token.unitType());
+ }
+ CalcParser calcParser(range, ValueRangeAll);
+ if (const CSSCalcValue* calculation = calcParser.value()) {
+ // FIXME: Calcs should not be subject to parse time range checks.
+ // spec: https://drafts.csswg.org/css-values-3/#calc-range
+ if (calculation->category() != CalcNumber || (valueRange == ValueRangeNonNegative && calculation->isNegative()))
+ return nullptr;
+ return calcParser.consumeNumber();
+ }
+ return nullptr;
+}
+
+inline bool shouldAcceptUnitlessValue(double value, CSSParserMode cssParserMode, UnitlessQuirk unitless)
+{
+ // FIXME: Presentational HTML attributes shouldn't use the CSS parser for lengths
+ return value == 0
+ || isUnitLessValueParsingEnabledForMode(cssParserMode)
+ || (cssParserMode == HTMLQuirksMode && unitless == UnitlessQuirk::Allow);
+}
+
+RefPtr<CSSPrimitiveValue> consumeLength(CSSParserTokenRange& range, CSSParserMode cssParserMode, ValueRange valueRange, UnitlessQuirk unitless)
+{
+ const CSSParserToken& token = range.peek();
+ if (token.type() == DimensionToken) {
+ switch (token.unitType()) {
+ case CSSPrimitiveValue::UnitType::CSS_QUIRKY_EMS:
+ if (cssParserMode != UASheetMode)
+ return nullptr;
+ FALLTHROUGH;
+ case CSSPrimitiveValue::UnitType::CSS_EMS:
+ case CSSPrimitiveValue::UnitType::CSS_REMS:
+ case CSSPrimitiveValue::UnitType::CSS_CHS:
+ case CSSPrimitiveValue::UnitType::CSS_EXS:
+ case CSSPrimitiveValue::UnitType::CSS_PX:
+ case CSSPrimitiveValue::UnitType::CSS_CM:
+ case CSSPrimitiveValue::UnitType::CSS_MM:
+ case CSSPrimitiveValue::UnitType::CSS_IN:
+ case CSSPrimitiveValue::UnitType::CSS_PT:
+ case CSSPrimitiveValue::UnitType::CSS_PC:
+ case CSSPrimitiveValue::UnitType::CSS_VW:
+ case CSSPrimitiveValue::UnitType::CSS_VH:
+ case CSSPrimitiveValue::UnitType::CSS_VMIN:
+ case CSSPrimitiveValue::UnitType::CSS_VMAX:
+ break;
+ default:
+ return nullptr;
+ }
+ if ((valueRange == ValueRangeNonNegative && token.numericValue() < 0) || std::isinf(token.numericValue()))
+ return nullptr;
+ return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), token.unitType());
+ }
+ if (token.type() == NumberToken) {
+ if (!shouldAcceptUnitlessValue(token.numericValue(), cssParserMode, unitless)
+ || (valueRange == ValueRangeNonNegative && token.numericValue() < 0))
+ return nullptr;
+ if (std::isinf(token.numericValue()))
+ return nullptr;
+ CSSPrimitiveValue::UnitType unitType = CSSPrimitiveValue::UnitType::CSS_PX;
+ return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), unitType);
+ }
+ CalcParser calcParser(range, valueRange);
+ if (calcParser.value() && calcParser.value()->category() == CalcLength)
+ return calcParser.consumeValue();
+ return nullptr;
+}
+
+RefPtr<CSSPrimitiveValue> consumePercent(CSSParserTokenRange& range, ValueRange valueRange)
+{
+ const CSSParserToken& token = range.peek();
+ if (token.type() == PercentageToken) {
+ if ((valueRange == ValueRangeNonNegative && token.numericValue() < 0) || std::isinf(token.numericValue()))
+ return nullptr;
+ return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), CSSPrimitiveValue::UnitType::CSS_PERCENTAGE);
+ }
+ CalcParser calcParser(range, valueRange);
+ if (const CSSCalcValue* calculation = calcParser.value()) {
+ if (calculation->category() == CalcPercent)
+ return calcParser.consumeValue();
+ }
+ return nullptr;
+}
+
+static bool canConsumeCalcValue(CalculationCategory category, CSSParserMode cssParserMode)
+{
+ if (category == CalcLength || category == CalcPercent || category == CalcPercentLength)
+ return true;
+
+ if (cssParserMode != SVGAttributeMode)
+ return false;
+
+ if (category == CalcNumber || category == CalcPercentNumber)
+ return true;
+
+ return false;
+}
+
+RefPtr<CSSPrimitiveValue> consumeLengthOrPercent(CSSParserTokenRange& range, CSSParserMode cssParserMode, ValueRange valueRange, UnitlessQuirk unitless)
+{
+ const CSSParserToken& token = range.peek();
+ if (token.type() == DimensionToken || token.type() == NumberToken)
+ return consumeLength(range, cssParserMode, valueRange, unitless);
+ if (token.type() == PercentageToken)
+ return consumePercent(range, valueRange);
+ CalcParser calcParser(range, valueRange);
+ if (const CSSCalcValue* calculation = calcParser.value()) {
+ if (canConsumeCalcValue(calculation->category(), cssParserMode))
+ return calcParser.consumeValue();
+ }
+ return nullptr;
+}
+
+RefPtr<CSSPrimitiveValue> consumeAngle(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless)
+{
+ const CSSParserToken& token = range.peek();
+ if (token.type() == DimensionToken) {
+ switch (token.unitType()) {
+ case CSSPrimitiveValue::UnitType::CSS_DEG:
+ case CSSPrimitiveValue::UnitType::CSS_RAD:
+ case CSSPrimitiveValue::UnitType::CSS_GRAD:
+ case CSSPrimitiveValue::UnitType::CSS_TURN:
+ return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), token.unitType());
+ default:
+ return nullptr;
+ }
+ }
+ if (token.type() == NumberToken && shouldAcceptUnitlessValue(token.numericValue(), cssParserMode, unitless)) {
+ return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), CSSPrimitiveValue::UnitType::CSS_DEG);
+ }
+
+ CalcParser calcParser(range, ValueRangeAll);
+ if (const CSSCalcValue* calculation = calcParser.value()) {
+ if (calculation->category() == CalcAngle)
+ return calcParser.consumeValue();
+ }
+ return nullptr;
+}
+
+RefPtr<CSSPrimitiveValue> consumeTime(CSSParserTokenRange& range, CSSParserMode cssParserMode, ValueRange valueRange, UnitlessQuirk unitless)
+{
+ const CSSParserToken& token = range.peek();
+ CSSPrimitiveValue::UnitType unit = token.unitType();
+ bool acceptUnitless = token.type() == NumberToken && shouldAcceptUnitlessValue(token.numericValue(), cssParserMode, unitless);
+ if (acceptUnitless)
+ unit = CSSPrimitiveValue::UnitType::CSS_MS;
+ if (token.type() == DimensionToken || acceptUnitless) {
+ if (valueRange == ValueRangeNonNegative && token.numericValue() < 0)
+ return nullptr;
+ if (unit == CSSPrimitiveValue::UnitType::CSS_MS || unit == CSSPrimitiveValue::UnitType::CSS_S)
+ return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), unit);
+ return nullptr;
+ }
+ CalcParser calcParser(range, valueRange);
+ if (const CSSCalcValue* calculation = calcParser.value()) {
+ if (calculation->category() == CalcTime)
+ return calcParser.consumeValue();
+ }
+ return nullptr;
+}
+
+RefPtr<CSSPrimitiveValue> consumeIdent(CSSParserTokenRange& range)
+{
+ if (range.peek().type() != IdentToken)
+ return nullptr;
+ return CSSValuePool::singleton().createIdentifierValue(range.consumeIncludingWhitespace().id());
+}
+
+RefPtr<CSSPrimitiveValue> consumeIdentRange(CSSParserTokenRange& range, CSSValueID lower, CSSValueID upper)
+{
+ if (range.peek().id() < lower || range.peek().id() > upper)
+ return nullptr;
+ return consumeIdent(range);
+}
+
+// FIXME-NEWPARSER: Eventually we'd like this to use CSSCustomIdentValue, but we need
+// to do other plumbing work first (like changing Pair to CSSValuePair and make it not
+// use only primitive values).
+RefPtr<CSSPrimitiveValue> consumeCustomIdent(CSSParserTokenRange& range)
+{
+ if (range.peek().type() != IdentToken || isCSSWideKeyword(range.peek().id()))
+ return nullptr;
+ return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().value().toString(), CSSPrimitiveValue::UnitType::CSS_STRING);
+}
+
+RefPtr<CSSPrimitiveValue> consumeString(CSSParserTokenRange& range)
+{
+ if (range.peek().type() != StringToken)
+ return nullptr;
+ return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().value().toString(), CSSPrimitiveValue::UnitType::CSS_STRING);
+}
+
+StringView consumeUrlAsStringView(CSSParserTokenRange& range)
+{
+ const CSSParserToken& token = range.peek();
+ if (token.type() == UrlToken) {
+ range.consumeIncludingWhitespace();
+ return token.value();
+ }
+ if (token.functionId() == CSSValueUrl) {
+ CSSParserTokenRange urlRange = range;
+ CSSParserTokenRange urlArgs = urlRange.consumeBlock();
+ const CSSParserToken& next = urlArgs.consumeIncludingWhitespace();
+ if (next.type() == BadStringToken || !urlArgs.atEnd())
+ return StringView();
+ ASSERT(next.type() == StringToken);
+ range = urlRange;
+ range.consumeWhitespace();
+ return next.value();
+ }
+
+ return StringView();
+}
+
+RefPtr<CSSPrimitiveValue> consumeUrl(CSSParserTokenRange& range)
+{
+ StringView url = consumeUrlAsStringView(range);
+ if (url.isNull())
+ return nullptr;
+ return CSSValuePool::singleton().createValue(url.toString(), CSSPrimitiveValue::UnitType::CSS_URI);
+}
+
+static int clampRGBComponent(const CSSPrimitiveValue& value)
+{
+ double result = value.doubleValue();
+ // FIXME: Multiply by 2.55 and round instead of floor.
+ if (value.isPercentage())
+ result *= 2.56;
+ return clampTo<int>(result, 0, 255);
+}
+
+static Color parseRGBParameters(CSSParserTokenRange& range, bool parseAlpha)
+{
+ ASSERT(range.peek().functionId() == CSSValueRgb || range.peek().functionId() == CSSValueRgba);
+ Color result;
+ CSSParserTokenRange args = consumeFunction(range);
+ RefPtr<CSSPrimitiveValue> colorParameter = consumeInteger(args);
+ if (!colorParameter)
+ colorParameter = consumePercent(args, ValueRangeAll);
+ if (!colorParameter)
+ return Color();
+ const bool isPercent = colorParameter->isPercentage();
+ int colorArray[3];
+ colorArray[0] = clampRGBComponent(*colorParameter);
+ for (int i = 1; i < 3; i++) {
+ if (!consumeCommaIncludingWhitespace(args))
+ return Color();
+ colorParameter = isPercent ? consumePercent(args, ValueRangeAll) : consumeInteger(args);
+ if (!colorParameter)
+ return Color();
+ colorArray[i] = clampRGBComponent(*colorParameter);
+ }
+ if (parseAlpha) {
+ if (!consumeCommaIncludingWhitespace(args))
+ return Color();
+ double alpha;
+ if (!consumeNumberRaw(args, alpha))
+ return Color();
+ // Convert the floating pointer number of alpha to an integer in the range [0, 256),
+ // with an equal distribution across all 256 values.
+ int alphaComponent = static_cast<int>(clampTo<double>(alpha, 0.0, 1.0) * nextafter(256.0, 0.0));
+ result = Color(makeRGBA(colorArray[0], colorArray[1], colorArray[2], alphaComponent));
+ } else {
+ result = Color(makeRGB(colorArray[0], colorArray[1], colorArray[2]));
+ }
+
+ if (!args.atEnd())
+ return Color();
+
+ return result;
+}
+
+static Color parseHSLParameters(CSSParserTokenRange& range, bool parseAlpha)
+{
+ ASSERT(range.peek().functionId() == CSSValueHsl || range.peek().functionId() == CSSValueHsla);
+ CSSParserTokenRange args = consumeFunction(range);
+ RefPtr<CSSPrimitiveValue> hslValue = consumeNumber(args, ValueRangeAll);
+ if (!hslValue)
+ return Color();
+ double colorArray[3];
+ colorArray[0] = (((hslValue->intValue() % 360) + 360) % 360) / 360.0;
+ for (int i = 1; i < 3; i++) {
+ if (!consumeCommaIncludingWhitespace(args))
+ return Color();
+ hslValue = consumePercent(args, ValueRangeAll);
+ if (!hslValue)
+ return Color();
+ double doubleValue = hslValue->doubleValue();
+ colorArray[i] = clampTo<double>(doubleValue, 0.0, 100.0) / 100.0; // Needs to be value between 0 and 1.0.
+ }
+ double alpha = 1.0;
+ if (parseAlpha) {
+ if (!consumeCommaIncludingWhitespace(args))
+ return Color();
+ if (!consumeNumberRaw(args, alpha))
+ return Color();
+ alpha = clampTo<double>(alpha, 0.0, 1.0);
+ }
+
+ if (!args.atEnd())
+ return Color();
+
+ return Color(makeRGBAFromHSLA(colorArray[0], colorArray[1], colorArray[2], alpha));
+}
+
+static Color parseColorFunctionParameters(CSSParserTokenRange& range)
+{
+ ASSERT(range.peek().functionId() == CSSValueColor);
+ CSSParserTokenRange args = consumeFunction(range);
+
+ ColorSpace colorSpace;
+ switch (args.peek().id()) {
+ case CSSValueSRGB:
+ colorSpace = ColorSpaceSRGB;
+ break;
+ case CSSValueDisplayP3:
+ colorSpace = ColorSpaceDisplayP3;
+ break;
+ default:
+ return Color();
+ }
+ consumeIdent(args);
+
+ double colorChannels[4] = { 0, 0, 0, 1 };
+ for (int i = 0; i < 3; ++i) {
+ double value;
+ if (consumeNumberRaw(args, value))
+ colorChannels[i] = std::max(0.0, std::min(1.0, value));
+ else
+ break;
+ }
+
+ if (consumeSlashIncludingWhitespace(args)) {
+ auto alphaParameter = consumePercent(args, ValueRangeAll);
+ if (!alphaParameter)
+ alphaParameter = consumeNumber(args, ValueRangeAll);
+ if (!alphaParameter)
+ return Color();
+
+ colorChannels[3] = std::max(0.0, std::min(1.0, alphaParameter->isPercentage() ? (alphaParameter->doubleValue() / 100) : alphaParameter->doubleValue()));
+ }
+
+ // FIXME: Support the comma-separated list of fallback color values.
+
+ if (!args.atEnd())
+ return Color();
+
+ return Color(colorChannels[0], colorChannels[1], colorChannels[2], colorChannels[3], colorSpace);
+}
+
+static Color parseHexColor(CSSParserTokenRange& range, bool acceptQuirkyColors)
+{
+ RGBA32 result;
+ const CSSParserToken& token = range.peek();
+ if (token.type() == HashToken) {
+ if (!Color::parseHexColor(token.value(), result))
+ return Color();
+ } else if (acceptQuirkyColors) {
+ String color;
+ if (token.type() == NumberToken || token.type() == DimensionToken) {
+ if (token.numericValueType() != IntegerValueType
+ || token.numericValue() < 0. || token.numericValue() >= 1000000.)
+ return Color();
+ if (token.type() == NumberToken) // e.g. 112233
+ color = String::format("%d", static_cast<int>(token.numericValue()));
+ else // e.g. 0001FF
+ color = String::number(static_cast<int>(token.numericValue())) + token.value().toString();
+ while (color.length() < 6)
+ color = "0" + color;
+ } else if (token.type() == IdentToken) { // e.g. FF0000
+ color = token.value().toString();
+ }
+ unsigned length = color.length();
+ if (length != 3 && length != 6)
+ return Color();
+ if (!Color::parseHexColor(color, result))
+ return Color();
+ } else {
+ return Color();
+ }
+ range.consumeIncludingWhitespace();
+ return Color(result);
+}
+
+static Color parseColorFunction(CSSParserTokenRange& range)
+{
+ CSSParserTokenRange colorRange = range;
+ CSSValueID functionId = range.peek().functionId();
+ Color color;
+ switch (functionId) {
+ case CSSValueRgb:
+ case CSSValueRgba:
+ color = parseRGBParameters(colorRange, functionId == CSSValueRgba);
+ break;
+ case CSSValueHsl:
+ case CSSValueHsla:
+ color = parseHSLParameters(colorRange, functionId == CSSValueHsla);
+ break;
+ case CSSValueColor:
+ color = parseColorFunctionParameters(colorRange);
+ break;
+ default:
+ return Color();
+ }
+ range = colorRange;
+ return color;
+}
+
+RefPtr<CSSPrimitiveValue> consumeColor(CSSParserTokenRange& range, CSSParserMode cssParserMode, bool acceptQuirkyColors)
+{
+ CSSValueID id = range.peek().id();
+ if (StyleColor::isColorKeyword(id)) {
+ if (!isValueAllowedInMode(id, cssParserMode))
+ return nullptr;
+ return consumeIdent(range);
+ }
+ Color color = parseHexColor(range, acceptQuirkyColors);
+ if (!color.isValid())
+ color = parseColorFunction(range);
+ if (!color.isValid())
+ return nullptr;
+ return CSSValuePool::singleton().createValue(color);
+}
+
+static RefPtr<CSSPrimitiveValue> consumePositionComponent(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless)
+{
+ if (range.peek().type() == IdentToken)
+ return consumeIdent<CSSValueLeft, CSSValueTop, CSSValueBottom, CSSValueRight, CSSValueCenter>(range);
+ return consumeLengthOrPercent(range, cssParserMode, ValueRangeAll, unitless);
+}
+
+static bool isHorizontalPositionKeywordOnly(const CSSPrimitiveValue& value)
+{
+ return value.isValueID() && (value.valueID() == CSSValueLeft || value.valueID() == CSSValueRight);
+}
+
+static bool isVerticalPositionKeywordOnly(const CSSPrimitiveValue& value)
+{
+ return value.isValueID() && (value.valueID() == CSSValueTop || value.valueID() == CSSValueBottom);
+}
+
+static void positionFromOneValue(CSSPrimitiveValue& value, RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
+{
+ bool valueAppliesToYAxisOnly = isVerticalPositionKeywordOnly(value);
+ resultX = &value;
+ resultY = CSSPrimitiveValue::createIdentifier(CSSValueCenter);
+ if (valueAppliesToYAxisOnly)
+ std::swap(resultX, resultY);
+}
+
+static bool positionFromTwoValues(CSSPrimitiveValue& value1, CSSPrimitiveValue& value2,
+ RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
+{
+ bool mustOrderAsXY = isHorizontalPositionKeywordOnly(value1) || isVerticalPositionKeywordOnly(value2)
+ || !value1.isValueID() || !value2.isValueID();
+ bool mustOrderAsYX = isVerticalPositionKeywordOnly(value1) || isHorizontalPositionKeywordOnly(value2);
+ if (mustOrderAsXY && mustOrderAsYX)
+ return false;
+ resultX = &value1;
+ resultY = &value2;
+ if (mustOrderAsYX)
+ std::swap(resultX, resultY);
+ return true;
+}
+
+
+template<typename... Args>
+static Ref<CSSPrimitiveValue> createPrimitiveValuePair(Args&&... args)
+{
+ return CSSValuePool::singleton().createValue(Pair::create(std::forward<Args>(args)...));
+}
+
+static bool positionFromThreeOrFourValues(CSSPrimitiveValue** values, RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
+{
+ CSSPrimitiveValue* center = nullptr;
+ for (int i = 0; values[i]; i++) {
+ CSSPrimitiveValue* currentValue = values[i];
+ if (!currentValue->isValueID())
+ return false;
+ CSSValueID id = currentValue->valueID();
+
+ if (id == CSSValueCenter) {
+ if (center)
+ return false;
+ center = currentValue;
+ continue;
+ }
+
+ RefPtr<CSSPrimitiveValue> result;
+ if (values[i + 1] && !values[i + 1]->isValueID())
+ result = createPrimitiveValuePair(currentValue, values[++i]);
+ else
+ result = currentValue;
+
+ if (id == CSSValueLeft || id == CSSValueRight) {
+ if (resultX)
+ return false;
+ resultX = result;
+ } else {
+ ASSERT(id == CSSValueTop || id == CSSValueBottom);
+ if (resultY)
+ return false;
+ resultY = result;
+ }
+ }
+
+ if (center) {
+ ASSERT(resultX || resultY);
+ if (resultX && resultY)
+ return false;
+ if (!resultX)
+ resultX = center;
+ else
+ resultY = center;
+ }
+
+ ASSERT(resultX && resultY);
+ return true;
+}
+
+// FIXME: This may consume from the range upon failure. The background
+// shorthand works around it, but we should just fix it here.
+bool consumePosition(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless, RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
+{
+ RefPtr<CSSPrimitiveValue> value1 = consumePositionComponent(range, cssParserMode, unitless);
+ if (!value1)
+ return false;
+
+ RefPtr<CSSPrimitiveValue> value2 = consumePositionComponent(range, cssParserMode, unitless);
+ if (!value2) {
+ positionFromOneValue(*value1, resultX, resultY);
+ return true;
+ }
+
+ RefPtr<CSSPrimitiveValue> value3 = consumePositionComponent(range, cssParserMode, unitless);
+ if (!value3)
+ return positionFromTwoValues(*value1, *value2, resultX, resultY);
+
+ RefPtr<CSSPrimitiveValue> value4 = consumePositionComponent(range, cssParserMode, unitless);
+ CSSPrimitiveValue* values[5];
+ values[0] = value1.get();
+ values[1] = value2.get();
+ values[2] = value3.get();
+ values[3] = value4.get();
+ values[4] = nullptr;
+ return positionFromThreeOrFourValues(values, resultX, resultY);
+}
+
+RefPtr<CSSPrimitiveValue> consumePosition(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless)
+{
+ RefPtr<CSSPrimitiveValue> resultX;
+ RefPtr<CSSPrimitiveValue> resultY;
+ if (consumePosition(range, cssParserMode, unitless, resultX, resultY))
+ return createPrimitiveValuePair(resultX.releaseNonNull(), resultY.releaseNonNull());
+ return nullptr;
+}
+
+bool consumeOneOrTwoValuedPosition(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless, RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
+{
+ RefPtr<CSSPrimitiveValue> value1 = consumePositionComponent(range, cssParserMode, unitless);
+ if (!value1)
+ return false;
+ RefPtr<CSSPrimitiveValue> value2 = consumePositionComponent(range, cssParserMode, unitless);
+ if (!value2) {
+ positionFromOneValue(*value1, resultX, resultY);
+ return true;
+ }
+ return positionFromTwoValues(*value1, *value2, resultX, resultY);
+}
+
+// This should go away once we drop support for -webkit-gradient
+static RefPtr<CSSPrimitiveValue> consumeDeprecatedGradientPoint(CSSParserTokenRange& args, bool horizontal)
+{
+ if (args.peek().type() == IdentToken) {
+ if ((horizontal && consumeIdent<CSSValueLeft>(args)) || (!horizontal && consumeIdent<CSSValueTop>(args)))
+ return CSSValuePool::singleton().createValue(0., CSSPrimitiveValue::UnitType::CSS_PERCENTAGE);
+ if ((horizontal && consumeIdent<CSSValueRight>(args)) || (!horizontal && consumeIdent<CSSValueBottom>(args)))
+ return CSSValuePool::singleton().createValue(100., CSSPrimitiveValue::UnitType::CSS_PERCENTAGE);
+ if (consumeIdent<CSSValueCenter>(args))
+ return CSSValuePool::singleton().createValue(50., CSSPrimitiveValue::UnitType::CSS_PERCENTAGE);
+ return nullptr;
+ }
+ RefPtr<CSSPrimitiveValue> result = consumePercent(args, ValueRangeAll);
+ if (!result)
+ result = consumeNumber(args, ValueRangeAll);
+ return result;
+}
+
+// Used to parse colors for -webkit-gradient(...).
+static RefPtr<CSSPrimitiveValue> consumeDeprecatedGradientStopColor(CSSParserTokenRange& args, CSSParserMode cssParserMode)
+{
+ if (args.peek().id() == CSSValueCurrentcolor)
+ return nullptr;
+ return consumeColor(args, cssParserMode);
+}
+
+static bool consumeDeprecatedGradientColorStop(CSSParserTokenRange& range, CSSGradientColorStop& stop, CSSParserMode cssParserMode)
+{
+ CSSValueID id = range.peek().functionId();
+ if (id != CSSValueFrom && id != CSSValueTo && id != CSSValueColorStop)
+ return false;
+
+ CSSParserTokenRange args = consumeFunction(range);
+ double position;
+ if (id == CSSValueFrom || id == CSSValueTo) {
+ position = (id == CSSValueFrom) ? 0 : 1;
+ } else {
+ ASSERT(id == CSSValueColorStop);
+ const CSSParserToken& arg = args.consumeIncludingWhitespace();
+ if (arg.type() == PercentageToken)
+ position = arg.numericValue() / 100.0;
+ else if (arg.type() == NumberToken)
+ position = arg.numericValue();
+ else
+ return false;
+
+ if (!consumeCommaIncludingWhitespace(args))
+ return false;
+ }
+
+ stop.m_position = CSSValuePool::singleton().createValue(position, CSSPrimitiveValue::UnitType::CSS_NUMBER);
+ stop.m_color = consumeDeprecatedGradientStopColor(args, cssParserMode);
+ return stop.m_color && args.atEnd();
+}
+
+static RefPtr<CSSValue> consumeDeprecatedGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode)
+{
+ RefPtr<CSSGradientValue> result;
+ CSSValueID id = args.consumeIncludingWhitespace().id();
+ bool isDeprecatedRadialGradient = (id == CSSValueRadial);
+ if (isDeprecatedRadialGradient)
+ result = CSSRadialGradientValue::create(NonRepeating, CSSDeprecatedRadialGradient);
+ else if (id == CSSValueLinear)
+ result = CSSLinearGradientValue::create(NonRepeating, CSSDeprecatedLinearGradient);
+ if (!result || !consumeCommaIncludingWhitespace(args))
+ return nullptr;
+
+ RefPtr<CSSPrimitiveValue> point = consumeDeprecatedGradientPoint(args, true);
+ if (!point)
+ return nullptr;
+ result->setFirstX(point.copyRef());
+ point = consumeDeprecatedGradientPoint(args, false);
+ if (!point)
+ return nullptr;
+ result->setFirstY(point.copyRef());
+
+ if (!consumeCommaIncludingWhitespace(args))
+ return nullptr;
+
+ // For radial gradients only, we now expect a numeric radius.
+ if (isDeprecatedRadialGradient) {
+ RefPtr<CSSPrimitiveValue> radius = consumeNumber(args, ValueRangeAll);
+ if (!radius || !consumeCommaIncludingWhitespace(args))
+ return nullptr;
+ downcast<CSSRadialGradientValue>(result.get())->setFirstRadius(radius.copyRef());
+ }
+
+ point = consumeDeprecatedGradientPoint(args, true);
+ if (!point)
+ return nullptr;
+ result->setSecondX(point.copyRef());
+ point = consumeDeprecatedGradientPoint(args, false);
+ if (!point)
+ return nullptr;
+ result->setSecondY(point.copyRef());
+
+ // For radial gradients only, we now expect the second radius.
+ if (isDeprecatedRadialGradient) {
+ if (!consumeCommaIncludingWhitespace(args))
+ return nullptr;
+ RefPtr<CSSPrimitiveValue> radius = consumeNumber(args, ValueRangeAll);
+ if (!radius)
+ return nullptr;
+ downcast<CSSRadialGradientValue>(result.get())->setSecondRadius(radius.copyRef());
+ }
+
+ CSSGradientColorStop stop;
+ while (consumeCommaIncludingWhitespace(args)) {
+ if (!consumeDeprecatedGradientColorStop(args, stop, cssParserMode))
+ return nullptr;
+ result->addStop(stop);
+ }
+
+ return result;
+}
+
+static bool consumeGradientColorStops(CSSParserTokenRange& range, CSSParserMode cssParserMode, CSSGradientValue* gradient)
+{
+ bool supportsColorHints = gradient->gradientType() == CSSLinearGradient || gradient->gradientType() == CSSRadialGradient;
+
+ // The first color stop cannot be a color hint.
+ bool previousStopWasColorHint = true;
+ do {
+ CSSGradientColorStop stop;
+ stop.m_color = consumeColor(range, cssParserMode);
+ // Two hints in a row are not allowed.
+ if (!stop.m_color && (!supportsColorHints || previousStopWasColorHint))
+ return false;
+
+ previousStopWasColorHint = !stop.m_color;
+
+ // FIXME-NEWPARSER: This boolean could be removed. Null checking color would be sufficient.
+ stop.isMidpoint = !stop.m_color;
+
+ stop.m_position = consumeLengthOrPercent(range, cssParserMode, ValueRangeAll);
+ if (!stop.m_color && !stop.m_position)
+ return false;
+ gradient->addStop(stop);
+ } while (consumeCommaIncludingWhitespace(range));
+
+ // The last color stop cannot be a color hint.
+ if (previousStopWasColorHint)
+ return false;
+
+ // Must have 2 or more stops to be valid.
+ return gradient->stopCount() >= 2;
+}
+
+static RefPtr<CSSValue> consumeDeprecatedRadialGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode, CSSGradientRepeat repeating)
+{
+ RefPtr<CSSRadialGradientValue> result = CSSRadialGradientValue::create(repeating, CSSPrefixedRadialGradient);
+ RefPtr<CSSPrimitiveValue> centerX;
+ RefPtr<CSSPrimitiveValue> centerY;
+ consumeOneOrTwoValuedPosition(args, cssParserMode, UnitlessQuirk::Forbid, centerX, centerY);
+ if ((centerX || centerY) && !consumeCommaIncludingWhitespace(args))
+ return nullptr;
+
+ result->setFirstX(centerX.copyRef());
+ result->setFirstY(centerY.copyRef());
+ result->setSecondX(centerX.copyRef());
+ result->setSecondY(centerY.copyRef());
+
+ RefPtr<CSSPrimitiveValue> shape = consumeIdent<CSSValueCircle, CSSValueEllipse>(args);
+ RefPtr<CSSPrimitiveValue> sizeKeyword = consumeIdent<CSSValueClosestSide, CSSValueClosestCorner, CSSValueFarthestSide, CSSValueFarthestCorner, CSSValueContain, CSSValueCover>(args);
+ if (!shape)
+ shape = consumeIdent<CSSValueCircle, CSSValueEllipse>(args);
+ result->setShape(shape.copyRef());
+ result->setSizingBehavior(sizeKeyword.copyRef());
+
+ // Or, two lengths or percentages
+ if (!shape && !sizeKeyword) {
+ RefPtr<CSSPrimitiveValue> horizontalSize = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll);
+ RefPtr<CSSPrimitiveValue> verticalSize;
+ if (horizontalSize) {
+ verticalSize = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll);
+ if (!verticalSize)
+ return nullptr;
+ consumeCommaIncludingWhitespace(args);
+ result->setEndHorizontalSize(horizontalSize.copyRef());
+ result->setEndVerticalSize(verticalSize.copyRef());
+ }
+ } else {
+ consumeCommaIncludingWhitespace(args);
+ }
+ if (!consumeGradientColorStops(args, cssParserMode, result.get()))
+ return nullptr;
+
+ return result;
+}
+
+static RefPtr<CSSValue> consumeRadialGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode, CSSGradientRepeat repeating)
+{
+ RefPtr<CSSRadialGradientValue> result = CSSRadialGradientValue::create(repeating, CSSRadialGradient);
+
+ RefPtr<CSSPrimitiveValue> shape;
+ RefPtr<CSSPrimitiveValue> sizeKeyword;
+ RefPtr<CSSPrimitiveValue> horizontalSize;
+ RefPtr<CSSPrimitiveValue> verticalSize;
+
+ // First part of grammar, the size/shape clause:
+ // [ circle || <length> ] |
+ // [ ellipse || [ <length> | <percentage> ]{2} ] |
+ // [ [ circle | ellipse] || <size-keyword> ]
+ for (int i = 0; i < 3; ++i) {
+ if (args.peek().type() == IdentToken) {
+ CSSValueID id = args.peek().id();
+ if (id == CSSValueCircle || id == CSSValueEllipse) {
+ if (shape)
+ return nullptr;
+ shape = consumeIdent(args);
+ } else if (id == CSSValueClosestSide || id == CSSValueClosestCorner || id == CSSValueFarthestSide || id == CSSValueFarthestCorner) {
+ if (sizeKeyword)
+ return nullptr;
+ sizeKeyword = consumeIdent(args);
+ } else {
+ break;
+ }
+ } else {
+ RefPtr<CSSPrimitiveValue> center = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll);
+ if (!center)
+ break;
+ if (horizontalSize)
+ return nullptr;
+ horizontalSize = center;
+ center = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll);
+ if (center) {
+ verticalSize = center;
+ ++i;
+ }
+ }
+ }
+
+ // You can specify size as a keyword or a length/percentage, not both.
+ if (sizeKeyword && horizontalSize)
+ return nullptr;
+ // Circles must have 0 or 1 lengths.
+ if (shape && shape->valueID() == CSSValueCircle && verticalSize)
+ return nullptr;
+ // Ellipses must have 0 or 2 length/percentages.
+ if (shape && shape->valueID() == CSSValueEllipse && horizontalSize && !verticalSize)
+ return nullptr;
+ // If there's only one size, it must be a length.
+ if (!verticalSize && horizontalSize && horizontalSize->isPercentage())
+ return nullptr;
+ if ((horizontalSize && horizontalSize->isCalculatedPercentageWithLength())
+ || (verticalSize && verticalSize->isCalculatedPercentageWithLength()))
+ return nullptr;
+
+ result->setShape(shape.copyRef());
+ result->setSizingBehavior(sizeKeyword.copyRef());
+ result->setEndHorizontalSize(horizontalSize.copyRef());
+ result->setEndVerticalSize(verticalSize.copyRef());
+
+ RefPtr<CSSPrimitiveValue> centerX;
+ RefPtr<CSSPrimitiveValue> centerY;
+ if (args.peek().id() == CSSValueAt) {
+ args.consumeIncludingWhitespace();
+ consumePosition(args, cssParserMode, UnitlessQuirk::Forbid, centerX, centerY);
+ if (!(centerX && centerY))
+ return nullptr;
+
+ result->setFirstX(centerX.copyRef());
+ result->setFirstY(centerY.copyRef());
+
+ // Right now, CSS radial gradients have the same start and end centers.
+ result->setSecondX(centerX.copyRef());
+ result->setSecondY(centerY.copyRef());
+ }
+
+ if ((shape || sizeKeyword || horizontalSize || centerX || centerY) && !consumeCommaIncludingWhitespace(args))
+ return nullptr;
+ if (!consumeGradientColorStops(args, cssParserMode, result.get()))
+ return nullptr;
+ return result;
+}
+
+static RefPtr<CSSValue> consumeLinearGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode, CSSGradientRepeat repeating, CSSGradientType gradientType)
+{
+ RefPtr<CSSLinearGradientValue> result = CSSLinearGradientValue::create(repeating, gradientType);
+
+ bool expectComma = true;
+ RefPtr<CSSPrimitiveValue> angle = consumeAngle(args, cssParserMode, UnitlessQuirk::Forbid);
+ if (angle)
+ result->setAngle(angle.releaseNonNull());
+ else if (gradientType == CSSPrefixedLinearGradient || consumeIdent<CSSValueTo>(args)) {
+ RefPtr<CSSPrimitiveValue> endX = consumeIdent<CSSValueLeft, CSSValueRight>(args);
+ RefPtr<CSSPrimitiveValue> endY = consumeIdent<CSSValueBottom, CSSValueTop>(args);
+ if (!endX && !endY) {
+ if (gradientType == CSSLinearGradient)
+ return nullptr;
+ endY = CSSPrimitiveValue::createIdentifier(CSSValueTop);
+ expectComma = false;
+ } else if (!endX) {
+ endX = consumeIdent<CSSValueLeft, CSSValueRight>(args);
+ }
+
+ result->setFirstX(endX.copyRef());
+ result->setFirstY(endY.copyRef());
+ } else {
+ expectComma = false;
+ }
+
+ if (expectComma && !consumeCommaIncludingWhitespace(args))
+ return nullptr;
+ if (!consumeGradientColorStops(args, cssParserMode, result.get()))
+ return nullptr;
+ return result;
+}
+
+RefPtr<CSSValue> consumeImageOrNone(CSSParserTokenRange& range, CSSParserContext context)
+{
+ if (range.peek().id() == CSSValueNone)
+ return consumeIdent(range);
+ return consumeImage(range, context);
+}
+
+static RefPtr<CSSValue> consumeCrossFade(CSSParserTokenRange& args, CSSParserContext context, bool prefixed)
+{
+ RefPtr<CSSValue> fromImageValue = consumeImageOrNone(args, context);
+ if (!fromImageValue || !consumeCommaIncludingWhitespace(args))
+ return nullptr;
+ RefPtr<CSSValue> toImageValue = consumeImageOrNone(args, context);
+ if (!toImageValue || !consumeCommaIncludingWhitespace(args))
+ return nullptr;
+
+ RefPtr<CSSPrimitiveValue> percentage;
+ const CSSParserToken& percentageArg = args.consumeIncludingWhitespace();
+ if (percentageArg.type() == PercentageToken)
+ percentage = CSSValuePool::singleton().createValue(clampTo<double>(percentageArg.numericValue() / 100, 0, 1), CSSPrimitiveValue::UnitType::CSS_NUMBER);
+ else if (percentageArg.type() == NumberToken)
+ percentage = CSSValuePool::singleton().createValue(clampTo<double>(percentageArg.numericValue(), 0, 1), CSSPrimitiveValue::UnitType::CSS_NUMBER);
+
+ if (!percentage)
+ return nullptr;
+ return CSSCrossfadeValue::create(fromImageValue.releaseNonNull(), toImageValue.releaseNonNull(), percentage.releaseNonNull(), prefixed);
+}
+
+static RefPtr<CSSValue> consumeWebkitCanvas(CSSParserTokenRange& args)
+{
+ if (args.peek().type() != IdentToken)
+ return nullptr;
+ auto canvasName = args.consumeIncludingWhitespace().value().toString();
+ if (!args.atEnd())
+ return nullptr;
+ return CSSCanvasValue::create(canvasName);
+}
+
+static RefPtr<CSSValue> consumeWebkitNamedImage(CSSParserTokenRange& args)
+{
+ if (args.peek().type() != IdentToken)
+ return nullptr;
+ auto imageName = args.consumeIncludingWhitespace().value().toString();
+ if (!args.atEnd())
+ return nullptr;
+ return CSSNamedImageValue::create(imageName);
+}
+
+static RefPtr<CSSValue> consumeFilterImage(CSSParserTokenRange& args, const CSSParserContext& context)
+{
+ auto imageValue = consumeImageOrNone(args, context);
+ if (!imageValue || !consumeCommaIncludingWhitespace(args))
+ return nullptr;
+
+ auto filterValue = consumeFilter(args, context);
+
+ if (!filterValue)
+ return nullptr;
+
+ if (!args.atEnd())
+ return nullptr;
+
+ return CSSFilterImageValue::create(imageValue.releaseNonNull(), filterValue.releaseNonNull());
+}
+
+static RefPtr<CSSValue> consumeGeneratedImage(CSSParserTokenRange& range, CSSParserContext context)
+{
+ CSSValueID id = range.peek().functionId();
+ CSSParserTokenRange rangeCopy = range;
+ CSSParserTokenRange args = consumeFunction(rangeCopy);
+ RefPtr<CSSValue> result;
+ if (id == CSSValueRadialGradient)
+ result = consumeRadialGradient(args, context.mode, NonRepeating);
+ else if (id == CSSValueRepeatingRadialGradient)
+ result = consumeRadialGradient(args, context.mode, Repeating);
+ else if (id == CSSValueWebkitLinearGradient)
+ result = consumeLinearGradient(args, context.mode, NonRepeating, CSSPrefixedLinearGradient);
+ else if (id == CSSValueWebkitRepeatingLinearGradient)
+ result = consumeLinearGradient(args, context.mode, Repeating, CSSPrefixedLinearGradient);
+ else if (id == CSSValueRepeatingLinearGradient)
+ result = consumeLinearGradient(args, context.mode, Repeating, CSSLinearGradient);
+ else if (id == CSSValueLinearGradient)
+ result = consumeLinearGradient(args, context.mode, NonRepeating, CSSLinearGradient);
+ else if (id == CSSValueWebkitGradient)
+ result = consumeDeprecatedGradient(args, context.mode);
+ else if (id == CSSValueWebkitRadialGradient)
+ result = consumeDeprecatedRadialGradient(args, context.mode, NonRepeating);
+ else if (id == CSSValueWebkitRepeatingRadialGradient)
+ result = consumeDeprecatedRadialGradient(args, context.mode, Repeating);
+ else if (id == CSSValueWebkitCrossFade || id == CSSValueCrossFade)
+ result = consumeCrossFade(args, context, id == CSSValueWebkitCrossFade);
+ else if (id == CSSValueWebkitCanvas)
+ result = consumeWebkitCanvas(args);
+ else if (id == CSSValueWebkitNamedImage)
+ result = consumeWebkitNamedImage(args);
+ else if (id == CSSValueWebkitFilter || id == CSSValueFilter)
+ result = consumeFilterImage(args, context);
+ if (!result || !args.atEnd())
+ return nullptr;
+ range = rangeCopy;
+ return result;
+}
+
+static RefPtr<CSSValue> consumeImageSet(CSSParserTokenRange& range, const CSSParserContext& context)
+{
+ CSSParserTokenRange rangeCopy = range;
+ CSSParserTokenRange args = consumeFunction(rangeCopy);
+ RefPtr<CSSImageSetValue> imageSet = CSSImageSetValue::create();
+ do {
+ AtomicString urlValue = consumeUrlAsStringView(args).toAtomicString();
+ if (urlValue.isNull())
+ return nullptr;
+
+ RefPtr<CSSValue> image = CSSImageValue::create(completeURL(context, urlValue));
+ imageSet->append(image.releaseNonNull());
+
+ const CSSParserToken& token = args.consumeIncludingWhitespace();
+ if (token.type() != DimensionToken)
+ return nullptr;
+ if (token.value() != "x")
+ return nullptr;
+ ASSERT(token.unitType() == CSSPrimitiveValue::UnitType::CSS_UNKNOWN);
+ double imageScaleFactor = token.numericValue();
+ if (imageScaleFactor <= 0)
+ return nullptr;
+ imageSet->append(CSSValuePool::singleton().createValue(imageScaleFactor, CSSPrimitiveValue::UnitType::CSS_NUMBER));
+ } while (consumeCommaIncludingWhitespace(args));
+ if (!args.atEnd())
+ return nullptr;
+ range = rangeCopy;
+ return imageSet;
+}
+
+static bool isGeneratedImage(CSSValueID id)
+{
+ return id == CSSValueLinearGradient || id == CSSValueRadialGradient
+ || id == CSSValueRepeatingLinearGradient || id == CSSValueRepeatingRadialGradient
+ || id == CSSValueWebkitLinearGradient || id == CSSValueWebkitRadialGradient
+ || id == CSSValueWebkitRepeatingLinearGradient || id == CSSValueWebkitRepeatingRadialGradient
+ || id == CSSValueWebkitGradient || id == CSSValueWebkitCrossFade || id == CSSValueWebkitCanvas
+ || id == CSSValueCrossFade || id == CSSValueWebkitNamedImage || id == CSSValueWebkitFilter || id == CSSValueFilter;
+}
+
+static bool isValidPrimitiveFilterFunction(CSSValueID filterFunction)
+{
+ switch (filterFunction) {
+ case CSSValueBlur:
+ case CSSValueBrightness:
+ case CSSValueContrast:
+ case CSSValueDropShadow:
+ case CSSValueGrayscale:
+ case CSSValueHueRotate:
+ case CSSValueInvert:
+ case CSSValueOpacity:
+ case CSSValueSaturate:
+ case CSSValueSepia:
+ return true;
+ default:
+ return false;
+ }
+}
+
+RefPtr<CSSFunctionValue> consumeFilterFunction(CSSParserTokenRange& range, const CSSParserContext& context)
+{
+ CSSValueID filterType = range.peek().functionId();
+ if (!isValidPrimitiveFilterFunction(filterType))
+ return nullptr;
+ CSSParserTokenRange args = consumeFunction(range);
+ RefPtr<CSSFunctionValue> filterValue = CSSFunctionValue::create(filterType);
+ RefPtr<CSSValue> parsedValue;
+
+ if (filterType == CSSValueDropShadow)
+ parsedValue = consumeSingleShadow(args, context.mode, false, false);
+ else {
+ if (args.atEnd())
+ return filterValue;
+ if (filterType == CSSValueBrightness) {
+ parsedValue = consumePercent(args, ValueRangeAll);
+ if (!parsedValue)
+ parsedValue = consumeNumber(args, ValueRangeAll);
+ } else if (filterType == CSSValueHueRotate)
+ parsedValue = consumeAngle(args, context.mode, UnitlessQuirk::Forbid);
+ else if (filterType == CSSValueBlur)
+ parsedValue = consumeLength(args, HTMLStandardMode, ValueRangeNonNegative);
+ else {
+ parsedValue = consumePercent(args, ValueRangeNonNegative);
+ if (!parsedValue)
+ parsedValue = consumeNumber(args, ValueRangeNonNegative);
+ if (parsedValue && filterType != CSSValueSaturate && filterType != CSSValueContrast) {
+ bool isPercentage = downcast<CSSPrimitiveValue>(*parsedValue).isPercentage();
+ double maxAllowed = isPercentage ? 100.0 : 1.0;
+ if (downcast<CSSPrimitiveValue>(*parsedValue).doubleValue() > maxAllowed)
+ parsedValue = CSSPrimitiveValue::create(maxAllowed, isPercentage ? CSSPrimitiveValue::UnitType::CSS_PERCENTAGE : CSSPrimitiveValue::UnitType::CSS_NUMBER);
+ }
+ }
+ }
+ if (!parsedValue || !args.atEnd())
+ return nullptr;
+ filterValue->append(parsedValue.releaseNonNull());
+ return filterValue;
+}
+
+RefPtr<CSSValue> consumeFilter(CSSParserTokenRange& range, const CSSParserContext& context)
+{
+ if (range.peek().id() == CSSValueNone)
+ return consumeIdent(range);
+
+ auto list = CSSValueList::createSpaceSeparated();
+ do {
+ RefPtr<CSSValue> filterValue = consumeUrl(range);
+ if (!filterValue) {
+ filterValue = consumeFilterFunction(range, context);
+ if (!filterValue)
+ return nullptr;
+ }
+ list->append(filterValue.releaseNonNull());
+ } while (!range.atEnd());
+
+ return list.ptr();
+}
+
+RefPtr<CSSShadowValue> consumeSingleShadow(CSSParserTokenRange& range, CSSParserMode cssParserMode, bool allowInset, bool allowSpread)
+{
+ RefPtr<CSSPrimitiveValue> style;
+ RefPtr<CSSPrimitiveValue> color;
+
+ if (range.atEnd())
+ return nullptr;
+ if (range.peek().id() == CSSValueInset) {
+ if (!allowInset)
+ return nullptr;
+ style = consumeIdent(range);
+ }
+ color = consumeColor(range, cssParserMode);
+
+ auto horizontalOffset = consumeLength(range, cssParserMode, ValueRangeAll);
+ if (!horizontalOffset)
+ return nullptr;
+
+ auto verticalOffset = consumeLength(range, cssParserMode, ValueRangeAll);
+ if (!verticalOffset)
+ return nullptr;
+
+ auto blurRadius = consumeLength(range, cssParserMode, ValueRangeAll);
+ RefPtr<CSSPrimitiveValue> spreadDistance;
+ if (blurRadius) {
+ // Blur radius must be non-negative.
+ if (blurRadius->doubleValue() < 0)
+ return nullptr;
+ if (allowSpread)
+ spreadDistance = consumeLength(range, cssParserMode, ValueRangeAll);
+ }
+
+ if (!range.atEnd()) {
+ if (!color)
+ color = consumeColor(range, cssParserMode);
+ if (range.peek().id() == CSSValueInset) {
+ if (!allowInset || style)
+ return nullptr;
+ style = consumeIdent(range);
+ }
+ }
+
+ return CSSShadowValue::create(WTFMove(horizontalOffset), WTFMove(verticalOffset), WTFMove(blurRadius), WTFMove(spreadDistance), WTFMove(style), WTFMove(color));
+}
+
+RefPtr<CSSValue> consumeImage(CSSParserTokenRange& range, CSSParserContext context, ConsumeGeneratedImage generatedImage)
+{
+ AtomicString uri = consumeUrlAsStringView(range).toAtomicString();
+ if (!uri.isNull())
+ return CSSImageValue::create(completeURL(context, uri));
+ if (range.peek().type() == FunctionToken) {
+ CSSValueID id = range.peek().functionId();
+ if (id == CSSValueWebkitImageSet || id == CSSValueImageSet)
+ return consumeImageSet(range, context);
+ if (generatedImage == ConsumeGeneratedImage::Allow && isGeneratedImage(id))
+ return consumeGeneratedImage(range, context);
+ }
+ return nullptr;
+}
+
+} // namespace CSSPropertyParserHelpers
+
+} // namespace WebCore