CSSPropertyParserHelpers.cpp [plain text]
#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 "CSSPaintImageValue.h"
#include "CSSParserIdioms.h"
#include "CSSValuePool.h"
#include "ColorConversion.h"
#include "Pair.h"
#include "RuntimeEnabledFeatures.h"
#include "StyleColor.h"
#include <wtf/text/StringConcatenateNumbers.h>
namespace WebCore {
namespace CSSPropertyParserHelpers {
static inline bool isCSSWideKeyword(CSSValueID id)
{
return id == CSSValueInitial || id == CSSValueInherit || id == CSSValueUnset || id == CSSValueRevert || id == CSSValueDefault;
}
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;
}
class CalcParser {
public:
explicit CalcParser(CSSParserTokenRange& range, CalculationCategory destinationCategory, ValueRange valueRange = ValueRangeAll)
: m_sourceRange(range)
, m_range(range)
{
const CSSParserToken& token = range.peek();
auto functionId = token.functionId();
if (CSSCalcValue::isCalcFunction(functionId))
m_calcValue = CSSCalcValue::create(functionId, consumeFunction(m_range), destinationCategory, 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(WTFMove(m_calcValue));
}
RefPtr<CSSPrimitiveValue> consumeInteger(double minimumValue)
{
if (!m_calcValue)
return nullptr;
m_sourceRange = m_range;
double value = std::max(m_calcValue->doubleValue(), minimumValue);
value = std::round(value);
return CSSValuePool::singleton().createValue(value, CSSUnitType::CSS_NUMBER);
}
RefPtr<CSSPrimitiveValue> consumeNumber()
{
if (!m_calcValue)
return nullptr;
m_sourceRange = m_range;
return CSSValuePool::singleton().createValue(m_calcValue->doubleValue(), CSSUnitType::CSS_NUMBER);
}
bool consumeNumberRaw(double& result)
{
if (!m_calcValue || m_calcValue->category() != CalculationCategory::Number)
return false;
m_sourceRange = m_range;
result = m_calcValue->doubleValue();
return true;
}
Optional<double> consumePercentRaw()
{
if (!m_calcValue)
return WTF::nullopt;
auto category = m_calcValue->category();
if (category != CalculationCategory::Percent)
return WTF::nullopt;
m_sourceRange = m_range;
return m_calcValue->doubleValue();
}
Optional<AngleRaw> consumeAngleRaw()
{
if (!m_calcValue || m_calcValue->category() != CalculationCategory::Angle)
return WTF::nullopt;
m_sourceRange = m_range;
return { { m_calcValue->primitiveType(), m_calcValue->doubleValue() } };
}
Optional<LengthRaw> consumeLengthRaw()
{
if (!m_calcValue || m_calcValue->category() != CalculationCategory::Length)
return WTF::nullopt;
m_sourceRange = m_range;
return { { m_calcValue->primitiveType(), m_calcValue->doubleValue() } };
}
Optional<LengthOrPercentRaw> consumeLengthOrPercentRaw()
{
if (!m_calcValue)
return WTF::nullopt;
switch (m_calcValue->category()) {
case CalculationCategory::Length:
m_sourceRange = m_range;
return { LengthRaw({ m_calcValue->primitiveType(), m_calcValue->doubleValue() }) };
case CalculationCategory::Percent:
case CalculationCategory::PercentLength:
case CalculationCategory::PercentNumber:
m_sourceRange = m_range;
return { { m_calcValue->doubleValue() } };
default:
return WTF::nullopt;
}
}
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(), CSSUnitType::CSS_NUMBER);
}
if (token.type() != FunctionToken)
return nullptr;
CalcParser calcParser(range, CalculationCategory::Number);
if (const CSSCalcValue* calculation = calcParser.value()) {
if (calculation->category() != CalculationCategory::Number)
return nullptr;
return calcParser.consumeInteger(minimumValue);
}
return nullptr;
}
RefPtr<CSSPrimitiveValue> consumePositiveInteger(CSSParserTokenRange& range)
{
return consumeInteger(range, 1);
}
bool consumeNumberRaw(CSSParserTokenRange& range, double& result, ValueRange valueRange)
{
const CSSParserToken& token = range.peek();
if (token.type() == NumberToken) {
if (valueRange == ValueRangeNonNegative && token.numericValue() < 0)
return false;
result = range.consumeIncludingWhitespace().numericValue();
return true;
}
if (token.type() != FunctionToken)
return false;
CalcParser calcParser(range, CalculationCategory::Number, valueRange);
return calcParser.consumeNumberRaw(result);
}
RefPtr<CSSPrimitiveValue> consumeNumber(CSSParserTokenRange& range, ValueRange valueRange)
{
const CSSParserToken& token = range.peek();
if (token.type() == FunctionToken) {
CalcParser calcParser(range, CalculationCategory::Number, valueRange);
if (const auto* calcValue = calcParser.value()) {
if (calcValue->category() == CalculationCategory::Number)
return calcParser.consumeValue();
}
return nullptr;
}
double number;
if (consumeNumberRaw(range, number, valueRange))
return CSSValuePool::singleton().createValue(number, token.unitType());
return nullptr;
}
#if !ENABLE(VARIATION_FONTS)
static inline bool divisibleBy100(double value)
{
return static_cast<int>(value / 100) * 100 == value;
}
#endif
Optional<double> consumeFontWeightNumberRaw(CSSParserTokenRange& range)
{
auto& token = range.peek();
if (token.type() == NumberToken && token.numericValue() >= 1 && token.numericValue() <= 1000
#if !ENABLE(VARIATION_FONTS)
&& token.numericValueType() == IntegerValueType && divisibleBy100(token.numericValue())
#endif
) {
double result;
if (consumeNumberRaw(range, result))
return result;
return WTF::nullopt;
}
if (token.type() != FunctionToken)
return WTF::nullopt;
CalcParser calcParser(range, CalculationCategory::Number, ValueRangeAll);
double result;
if (calcParser.consumeNumberRaw(result)
#if !ENABLE(VARIATION_FONTS)
&& result > 0 && result < 1000 && divisibleBy100(result)
#endif
) {
result = std::min(std::max(result, std::nextafter(0., 1.)), std::nextafter(1000., 0.));
return result;
}
return WTF::nullopt;
}
RefPtr<CSSPrimitiveValue> consumeFontWeightNumber(CSSParserTokenRange& range)
{
if (auto result = consumeFontWeightNumberRaw(range))
return CSSValuePool::singleton().createValue(*result, CSSUnitType::CSS_NUMBER);
return nullptr;
}
inline bool shouldAcceptUnitlessValue(double value, CSSParserMode cssParserMode, UnitlessQuirk unitless)
{
return value == 0
|| isUnitLessValueParsingEnabledForMode(cssParserMode)
|| (cssParserMode == HTMLQuirksMode && unitless == UnitlessQuirk::Allow);
}
Optional<LengthRaw> consumeLengthRaw(CSSParserTokenRange& range, CSSParserMode cssParserMode, ValueRange valueRange, UnitlessQuirk unitless)
{
const CSSParserToken& token = range.peek();
if (token.type() == DimensionToken) {
switch (token.unitType()) {
case CSSUnitType::CSS_QUIRKY_EMS:
if (cssParserMode != UASheetMode)
return WTF::nullopt;
FALLTHROUGH;
case CSSUnitType::CSS_EMS:
case CSSUnitType::CSS_REMS:
case CSSUnitType::CSS_LHS:
case CSSUnitType::CSS_RLHS:
case CSSUnitType::CSS_CHS:
case CSSUnitType::CSS_EXS:
case CSSUnitType::CSS_PX:
case CSSUnitType::CSS_CM:
case CSSUnitType::CSS_MM:
case CSSUnitType::CSS_IN:
case CSSUnitType::CSS_PT:
case CSSUnitType::CSS_PC:
case CSSUnitType::CSS_VW:
case CSSUnitType::CSS_VH:
case CSSUnitType::CSS_VMIN:
case CSSUnitType::CSS_VMAX:
case CSSUnitType::CSS_Q:
break;
default:
return WTF::nullopt;
}
if ((valueRange == ValueRangeNonNegative && token.numericValue() < 0) || std::isinf(token.numericValue()))
return WTF::nullopt;
return { { token.unitType(), range.consumeIncludingWhitespace().numericValue() } };
}
if (token.type() == NumberToken) {
if (!shouldAcceptUnitlessValue(token.numericValue(), cssParserMode, unitless)
|| (valueRange == ValueRangeNonNegative && token.numericValue() < 0))
return WTF::nullopt;
if (std::isinf(token.numericValue()))
return WTF::nullopt;
return { { CSSUnitType::CSS_PX, range.consumeIncludingWhitespace().numericValue() } };
}
if (token.type() != FunctionToken)
return WTF::nullopt;
CalcParser calcParser(range, CalculationCategory::Length, valueRange);
return calcParser.consumeLengthRaw();
}
RefPtr<CSSPrimitiveValue> consumeLength(CSSParserTokenRange& range, CSSParserMode cssParserMode, ValueRange valueRange, UnitlessQuirk unitless)
{
const CSSParserToken& token = range.peek();
if (token.type() == FunctionToken) {
CalcParser calcParser(range, CalculationCategory::Length, valueRange);
if (calcParser.value() && calcParser.value()->category() == CalculationCategory::Length)
return calcParser.consumeValue();
}
if (auto result = consumeLengthRaw(range, cssParserMode, valueRange, unitless))
return CSSValuePool::singleton().createValue(result->value, result->type);
return nullptr;
}
Optional<double> consumePercentRaw(CSSParserTokenRange& range, ValueRange valueRange)
{
const CSSParserToken& token = range.peek();
if (token.type() == PercentageToken) {
if (std::isinf(token.numericValue()) || (valueRange == ValueRangeNonNegative && token.numericValue() < 0))
return WTF::nullopt;
return range.consumeIncludingWhitespace().numericValue();
}
if (token.type() != FunctionToken)
return WTF::nullopt;
CalcParser calcParser(range, CalculationCategory::Percent, valueRange);
return calcParser.consumePercentRaw();
}
RefPtr<CSSPrimitiveValue> consumePercent(CSSParserTokenRange& range, ValueRange valueRange)
{
const CSSParserToken& token = range.peek();
if (token.type() == FunctionToken) {
CalcParser calcParser(range, CalculationCategory::Percent, valueRange);
if (const CSSCalcValue* calculation = calcParser.value()) {
if (calculation->category() == CalculationCategory::Percent)
return calcParser.consumeValue();
}
return nullptr;
}
if (auto percent = consumePercentRaw(range, valueRange))
return CSSValuePool::singleton().createValue(*percent, CSSUnitType::CSS_PERCENTAGE);
return nullptr;
}
static bool canConsumeCalcValue(CalculationCategory category, CSSParserMode cssParserMode)
{
if (category == CalculationCategory::Length || category == CalculationCategory::Percent || category == CalculationCategory::PercentLength)
return true;
if (cssParserMode != SVGAttributeMode)
return false;
if (category == CalculationCategory::Number || category == CalculationCategory::PercentNumber)
return true;
return false;
}
Optional<LengthOrPercentRaw> consumeLengthOrPercentRaw(CSSParserTokenRange& range, CSSParserMode cssParserMode, ValueRange valueRange, UnitlessQuirk unitless)
{
const CSSParserToken& token = range.peek();
if (token.type() == DimensionToken || token.type() == NumberToken) {
if (auto result = consumeLengthRaw(range, cssParserMode, valueRange, unitless))
return { *result };
return WTF::nullopt;
}
if (token.type() == PercentageToken) {
if (auto result = consumePercentRaw(range, valueRange))
return { *result };
return WTF::nullopt;
}
if (token.type() != FunctionToken)
return WTF::nullopt;
CalcParser calcParser(range, CalculationCategory::Length, valueRange);
if (const CSSCalcValue* calculation = calcParser.value()) {
if (canConsumeCalcValue(calculation->category(), cssParserMode))
return calcParser.consumeLengthOrPercentRaw();
}
return WTF::nullopt;
}
RefPtr<CSSPrimitiveValue> consumeLengthOrPercent(CSSParserTokenRange& range, CSSParserMode cssParserMode, ValueRange valueRange, UnitlessQuirk unitless)
{
const CSSParserToken& token = range.peek();
if (token.type() == FunctionToken) {
CalcParser calcParser(range, CalculationCategory::Length, valueRange);
if (const CSSCalcValue* calculation = calcParser.value()) {
if (canConsumeCalcValue(calculation->category(), cssParserMode))
return calcParser.consumeValue();
}
return nullptr;
}
if (auto result = consumeLengthOrPercentRaw(range, cssParserMode, valueRange, unitless)) {
return switchOn(*result, [] (LengthRaw length) {
return CSSValuePool::singleton().createValue(length.value, length.type);
}, [] (double percentage) {
return CSSValuePool::singleton().createValue(percentage, CSSUnitType::CSS_PERCENTAGE);
});
}
return nullptr;
}
Optional<AngleRaw> consumeAngleRaw(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless)
{
const CSSParserToken& token = range.peek();
if (token.type() == DimensionToken) {
auto unitType = token.unitType();
switch (unitType) {
case CSSUnitType::CSS_DEG:
case CSSUnitType::CSS_RAD:
case CSSUnitType::CSS_GRAD:
case CSSUnitType::CSS_TURN:
return { { unitType, range.consumeIncludingWhitespace().numericValue() } };
default:
return WTF::nullopt;
}
}
if (token.type() == NumberToken && shouldAcceptUnitlessValue(token.numericValue(), cssParserMode, unitless))
return { { CSSUnitType::CSS_DEG, range.consumeIncludingWhitespace().numericValue() } };
if (token.type() != FunctionToken)
return WTF::nullopt;
CalcParser calcParser(range, CalculationCategory::Angle, ValueRangeAll);
return calcParser.consumeAngleRaw();
}
RefPtr<CSSPrimitiveValue> consumeAngle(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless)
{
const CSSParserToken& token = range.peek();
if (token.type() == FunctionToken) {
CalcParser calcParser(range, CalculationCategory::Angle, ValueRangeAll);
if (const CSSCalcValue* calculation = calcParser.value()) {
if (calculation->category() == CalculationCategory::Angle)
return calcParser.consumeValue();
}
return nullptr;
}
if (auto angle = consumeAngleRaw(range, cssParserMode, unitless))
return CSSValuePool::singleton().createValue(angle->value, angle->type);
return nullptr;
}
static RefPtr<CSSPrimitiveValue> consumeAngleOrPercent(CSSParserTokenRange& range, CSSParserMode cssParserMode, ValueRange valueRange, UnitlessQuirk unitless)
{
const CSSParserToken& token = range.peek();
if (token.type() == DimensionToken) {
switch (token.unitType()) {
case CSSUnitType::CSS_DEG:
case CSSUnitType::CSS_RAD:
case CSSUnitType::CSS_GRAD:
case CSSUnitType::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(), CSSUnitType::CSS_DEG);
if (token.type() == PercentageToken)
return consumePercent(range, valueRange);
if (token.type() != FunctionToken)
return nullptr;
CalcParser angleCalcParser(range, CalculationCategory::Angle, valueRange);
if (const CSSCalcValue* calculation = angleCalcParser.value()) {
if (calculation->category() == CalculationCategory::Angle)
return angleCalcParser.consumeValue();
}
CalcParser percentCalcParser(range, CalculationCategory::Percent, valueRange);
if (const CSSCalcValue* calculation = percentCalcParser.value()) {
if (calculation->category() == CalculationCategory::Percent)
return percentCalcParser.consumeValue();
}
return nullptr;
}
RefPtr<CSSPrimitiveValue> consumeTime(CSSParserTokenRange& range, CSSParserMode cssParserMode, ValueRange valueRange, UnitlessQuirk unitless)
{
const CSSParserToken& token = range.peek();
CSSUnitType unit = token.unitType();
bool acceptUnitless = token.type() == NumberToken && unitless == UnitlessQuirk::Allow && shouldAcceptUnitlessValue(token.numericValue(), cssParserMode, unitless);
if (acceptUnitless)
unit = CSSUnitType::CSS_MS;
if (token.type() == DimensionToken || acceptUnitless) {
if (valueRange == ValueRangeNonNegative && token.numericValue() < 0)
return nullptr;
if (unit == CSSUnitType::CSS_MS || unit == CSSUnitType::CSS_S)
return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), unit);
return nullptr;
}
if (token.type() != FunctionToken)
return nullptr;
CalcParser calcParser(range, CalculationCategory::Time, valueRange);
if (const CSSCalcValue* calculation = calcParser.value()) {
if (calculation->category() == CalculationCategory::Time)
return calcParser.consumeValue();
}
return nullptr;
}
RefPtr<CSSPrimitiveValue> consumeResolution(CSSParserTokenRange& range, AllowXResolutionUnit allowX)
{
const CSSParserToken& token = range.peek();
if (token.type() != DimensionToken)
return nullptr;
CSSUnitType unit = token.unitType();
if (unit == CSSUnitType::CSS_DPPX || unit == CSSUnitType::CSS_DPI || unit == CSSUnitType::CSS_DPCM)
return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), unit);
if (allowX == AllowXResolutionUnit::Allow && token.value() == "x")
return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), CSSUnitType::CSS_DPPX);
return nullptr;
}
Optional<CSSValueID> consumeIdentRaw(CSSParserTokenRange& range)
{
if (range.peek().type() != IdentToken)
return WTF::nullopt;
return range.consumeIncludingWhitespace().id();
}
RefPtr<CSSPrimitiveValue> consumeIdent(CSSParserTokenRange& range)
{
if (auto result = consumeIdentRaw(range))
return CSSValuePool::singleton().createIdentifierValue(*result);
return nullptr;
}
Optional<CSSValueID> consumeIdentRangeRaw(CSSParserTokenRange& range, CSSValueID lower, CSSValueID upper)
{
if (range.peek().id() < lower || range.peek().id() > upper)
return WTF::nullopt;
return consumeIdentRaw(range);
}
RefPtr<CSSPrimitiveValue> consumeIdentRange(CSSParserTokenRange& range, CSSValueID lower, CSSValueID upper)
{
if (range.peek().id() < lower || range.peek().id() > upper)
return nullptr;
return consumeIdent(range);
}
RefPtr<CSSPrimitiveValue> consumeCustomIdent(CSSParserTokenRange& range)
{
if (range.peek().type() != IdentToken || isCSSWideKeyword(range.peek().id()))
return nullptr;
return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().value().toString(), CSSUnitType::CSS_STRING);
}
RefPtr<CSSPrimitiveValue> consumeString(CSSParserTokenRange& range)
{
if (range.peek().type() != StringToken)
return nullptr;
return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().value().toString(), CSSUnitType::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(), CSSUnitType::CSS_URI);
}
static uint8_t clampRGBComponent(double value, bool isPercentage)
{
if (isPercentage)
value = value / 100.0 * 255.0;
return convertPrescaledToComponentByte(value);
}
static Color parseRGBParameters(CSSParserTokenRange& range)
{
ASSERT(range.peek().functionId() == CSSValueRgb || range.peek().functionId() == CSSValueRgba);
CSSParserTokenRange args = consumeFunction(range);
bool isPercentage = false;
double colorParameter;
if (!consumeNumberRaw(args, colorParameter)) {
if (auto percent = consumePercentRaw(args)) {
colorParameter = *percent;
isPercentage = true;
} else
return Color();
}
enum class ColorSyntax {
Commas,
WhitespaceSlash,
};
ColorSyntax syntax = ColorSyntax::Commas;
auto consumeSeparator = [&] {
if (syntax == ColorSyntax::Commas)
return consumeCommaIncludingWhitespace(args);
return true;
};
uint8_t colorArray[3];
colorArray[0] = clampRGBComponent(colorParameter, isPercentage);
for (int i = 1; i < 3; i++) {
if (i == 1)
syntax = consumeCommaIncludingWhitespace(args) ? ColorSyntax::Commas : ColorSyntax::WhitespaceSlash;
else if (!consumeSeparator())
return Color();
if (isPercentage) {
if (auto percent = consumePercentRaw(args))
colorParameter = *percent;
else
return Color();
} else {
if (!consumeNumberRaw(args, colorParameter))
return Color();
}
colorArray[i] = clampRGBComponent(colorParameter, isPercentage);
}
auto consumeAlphaSeparator = [&] {
if (syntax == ColorSyntax::Commas)
return consumeCommaIncludingWhitespace(args);
return consumeSlashIncludingWhitespace(args);
};
uint8_t alphaComponent = 255;
if (consumeAlphaSeparator()) {
double alpha;
if (!consumeNumberRaw(args, alpha)) {
if (auto percent = consumePercentRaw(args))
alpha = *percent / 100.0;
else
return Color();
}
alphaComponent = convertToComponentByte(alpha);
};
if (!args.atEnd())
return Color();
return SRGBA<uint8_t> { colorArray[0], colorArray[1], colorArray[2], alphaComponent };
}
static Color parseHSLParameters(CSSParserTokenRange& range, CSSParserMode cssParserMode)
{
ASSERT(range.peek().functionId() == CSSValueHsl || range.peek().functionId() == CSSValueHsla);
CSSParserTokenRange args = consumeFunction(range);
double angleInDegrees;
if (auto angle = consumeAngleRaw(args, cssParserMode, UnitlessQuirk::Forbid))
angleInDegrees = CSSPrimitiveValue::computeDegrees(angle->type, angle->value);
else {
if (!consumeNumberRaw(args, angleInDegrees))
return Color();
}
double colorArray[3];
colorArray[0] = fmod(fmod(angleInDegrees, 360.0) + 360.0, 360.0) / 360.0;
bool requiresCommas = false;
for (int i = 1; i < 3; i++) {
if (consumeCommaIncludingWhitespace(args)) {
if (i != 1 && !requiresCommas)
return Color();
requiresCommas = true;
} else if (requiresCommas || args.atEnd() || (&args.peek() - 1)->type() != WhitespaceToken)
return Color();
auto percent = consumePercentRaw(args);
if (!percent)
return Color();
colorArray[i] = clampTo<double>(*percent, 0.0, 100.0) / 100.0; }
double alpha = 1.0;
bool commaConsumed = consumeCommaIncludingWhitespace(args);
bool slashConsumed = consumeSlashIncludingWhitespace(args);
if ((commaConsumed && !requiresCommas) || (slashConsumed && requiresCommas))
return Color();
if (commaConsumed || slashConsumed) {
if (!consumeNumberRaw(args, alpha)) {
if (auto percent = consumePercentRaw(args))
alpha = *percent / 100.0f;
else
return Color();
}
alpha = clampTo<double>(alpha, 0.0, 1.0);
}
if (!args.atEnd())
return Color();
return convertToComponentBytes(toSRGBA(HSLA<float> { static_cast<float>(colorArray[0]), static_cast<float>(colorArray[1]), static_cast<float>(colorArray[2]), static_cast<float>(alpha) }));
}
static Color parseColorFunctionParameters(CSSParserTokenRange& range)
{
ASSERT(range.peek().functionId() == CSSValueColor);
CSSParserTokenRange args = consumeFunction(range);
ColorSpace colorSpace;
switch (args.peek().id()) {
case CSSValueSRGB:
colorSpace = ColorSpace::SRGB;
break;
case CSSValueDisplayP3:
colorSpace = ColorSpace::DisplayP3;
break;
default:
return { };
}
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 { };
colorChannels[3] = std::max(0.0, std::min(1.0, alphaParameter->isPercentage() ? (alphaParameter->doubleValue() / 100) : alphaParameter->doubleValue()));
}
if (!args.atEnd())
return { };
return Color { ColorComponents { static_cast<float>(colorChannels[0]), static_cast<float>(colorChannels[1]), static_cast<float>(colorChannels[2]), static_cast<float>(colorChannels[3]) }, colorSpace };
}
static Optional<SRGBA<uint8_t>> parseHexColor(CSSParserTokenRange& range, bool acceptQuirkyColors)
{
String string;
StringView view;
auto& token = range.peek();
if (token.type() == HashToken)
view = token.value();
else {
if (!acceptQuirkyColors)
return WTF::nullopt;
if (token.type() == IdentToken)
string = token.value().toString(); else if (token.type() == NumberToken || token.type() == DimensionToken) {
if (token.numericValueType() != IntegerValueType)
return WTF::nullopt;
auto numericValue = token.numericValue();
if (!(numericValue >= 0 && numericValue < 1000000))
return WTF::nullopt;
auto integerValue = static_cast<int>(token.numericValue());
if (token.type() == NumberToken)
string = String::number(integerValue); else
string = makeString(integerValue, token.value()); if (string.length() < 6)
string = makeString(&"000000"[string.length()], string);
}
if (string.length() != 3 && string.length() != 6)
return WTF::nullopt;
view = string;
}
auto result = CSSParser::parseHexColor(view);
if (!result)
return WTF::nullopt;
range.consumeIncludingWhitespace();
return *result;
}
static Color parseColorFunction(CSSParserTokenRange& range, CSSParserMode cssParserMode)
{
CSSParserTokenRange colorRange = range;
CSSValueID functionId = range.peek().functionId();
Color color;
switch (functionId) {
case CSSValueRgb:
case CSSValueRgba:
color = parseRGBParameters(colorRange);
break;
case CSSValueHsl:
case CSSValueHsla:
color = parseHSLParameters(colorRange, cssParserMode);
break;
case CSSValueColor:
color = parseColorFunctionParameters(colorRange);
break;
default:
return Color();
}
if (color.isValid())
range = colorRange;
return color;
}
Color consumeColorWorkerSafe(CSSParserTokenRange& range, CSSParserMode cssParserMode)
{
Color result;
auto keyword = range.peek().id();
if (StyleColor::isColorKeyword(keyword)) {
if (StyleColor::isSystemColor(keyword))
return Color();
if (!isValueAllowedInMode(keyword, cssParserMode))
return Color();
result = StyleColor::colorFromKeyword(keyword, { });
range.consumeIncludingWhitespace();
}
if (auto parsedColor = parseHexColor(range, false))
result = *parsedColor;
else
result = parseColorFunction(range, cssParserMode);
if (!range.atEnd())
return Color();
return result;
}
RefPtr<CSSPrimitiveValue> consumeColor(CSSParserTokenRange& range, CSSParserMode cssParserMode, bool acceptQuirkyColors)
{
auto keyword = range.peek().id();
if (StyleColor::isColorKeyword(keyword)) {
if (!isValueAllowedInMode(keyword, cssParserMode))
return nullptr;
return consumeIdent(range);
}
Color color;
if (auto parsedColor = parseHexColor(range, acceptQuirkyColors))
color = *parsedColor;
else {
color = parseColorFunction(range, cssParserMode);
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;
}
namespace CSSPropertyParserHelpersInternal {
template<typename... Args>
static Ref<CSSPrimitiveValue> createPrimitiveValuePair(Args&&... args)
{
return CSSValuePool::singleton().createValue(Pair::create(std::forward<Args>(args)...));
}
}
static bool backgroundPositionFromThreeValues(const std::array<CSSPrimitiveValue*, 5>& 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 = CSSPropertyParserHelpersInternal::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;
}
static bool positionFromFourValues(const std::array<CSSPrimitiveValue*, 5>& values, RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
{
for (int i = 0; values[i]; i++) {
CSSPrimitiveValue* currentValue = values[i];
if (!currentValue->isValueID())
return false;
CSSValueID id = currentValue->valueID();
if (id == CSSValueCenter)
return false;
RefPtr<CSSPrimitiveValue> result;
if (values[i + 1] && !values[i + 1]->isValueID())
result = CSSPropertyParserHelpersInternal::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;
}
}
ASSERT(resultX && resultY);
return true;
}
bool consumePosition(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless, PositionSyntax positionSyntax, 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);
std::array<CSSPrimitiveValue*, 5> values;
values[0] = value1.get();
values[1] = value2.get();
values[2] = value3.get();
values[3] = value4.get();
values[4] = nullptr;
if (value4)
return positionFromFourValues(values, resultX, resultY);
if (positionSyntax != PositionSyntax::BackgroundPosition)
return false;
return backgroundPositionFromThreeValues(values, resultX, resultY);
}
RefPtr<CSSPrimitiveValue> consumePosition(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless, PositionSyntax positionSyntax)
{
RefPtr<CSSPrimitiveValue> resultX;
RefPtr<CSSPrimitiveValue> resultY;
if (consumePosition(range, cssParserMode, unitless, positionSyntax, resultX, resultY))
return CSSPropertyParserHelpersInternal::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);
}
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., CSSUnitType::CSS_PERCENTAGE);
if ((horizontal && consumeIdent<CSSValueRight>(args)) || (!horizontal && consumeIdent<CSSValueBottom>(args)))
return CSSValuePool::singleton().createValue(100., CSSUnitType::CSS_PERCENTAGE);
if (consumeIdent<CSSValueCenter>(args))
return CSSValuePool::singleton().createValue(50., CSSUnitType::CSS_PERCENTAGE);
return nullptr;
}
RefPtr<CSSPrimitiveValue> result = consumePercent(args, ValueRangeAll);
if (!result)
result = consumeNumber(args, ValueRangeAll);
return result;
}
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);
if (auto percentValue = consumePercent(args, ValueRangeAll))
position = percentValue->doubleValue() / 100.0;
else if (!consumeNumberRaw(args, position))
return false;
if (!consumeCommaIncludingWhitespace(args))
return false;
}
stop.position = CSSValuePool::singleton().createValue(position, CSSUnitType::CSS_NUMBER);
stop.color = consumeDeprecatedGradientStopColor(args, cssParserMode);
return stop.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;
auto point = consumeDeprecatedGradientPoint(args, true);
if (!point)
return nullptr;
result->setFirstX(WTFMove(point));
point = consumeDeprecatedGradientPoint(args, false);
if (!point)
return nullptr;
result->setFirstY(WTFMove(point));
if (!consumeCommaIncludingWhitespace(args))
return nullptr;
if (isDeprecatedRadialGradient) {
auto radius = consumeNumber(args, ValueRangeNonNegative);
if (!radius || !consumeCommaIncludingWhitespace(args))
return nullptr;
downcast<CSSRadialGradientValue>(result.get())->setFirstRadius(WTFMove(radius));
}
point = consumeDeprecatedGradientPoint(args, true);
if (!point)
return nullptr;
result->setSecondX(WTFMove(point));
point = consumeDeprecatedGradientPoint(args, false);
if (!point)
return nullptr;
result->setSecondY(WTFMove(point));
if (isDeprecatedRadialGradient) {
if (!consumeCommaIncludingWhitespace(args))
return nullptr;
auto radius = consumeNumber(args, ValueRangeNonNegative);
if (!radius)
return nullptr;
downcast<CSSRadialGradientValue>(result.get())->setSecondRadius(WTFMove(radius));
}
CSSGradientColorStop stop;
while (consumeCommaIncludingWhitespace(args)) {
if (!consumeDeprecatedGradientColorStop(args, stop, cssParserMode))
return nullptr;
result->addStop(WTFMove(stop));
}
result->doneAddingStops();
return result;
}
static bool consumeGradientColorStops(CSSParserTokenRange& range, CSSParserMode mode, CSSGradientValue& gradient)
{
bool supportsColorHints = gradient.gradientType() == CSSLinearGradient || gradient.gradientType() == CSSRadialGradient || gradient.gradientType() == CSSConicGradient;
auto consumeStopPosition = [&] {
return gradient.gradientType() == CSSConicGradient
? consumeAngleOrPercent(range, mode, ValueRangeAll, UnitlessQuirk::Forbid)
: consumeLengthOrPercent(range, mode, ValueRangeAll);
};
bool previousStopWasColorHint = true;
do {
CSSGradientColorStop stop { consumeColor(range, mode), consumeStopPosition(), { } };
if (!stop.color && !stop.position)
return false;
if (!stop.color && (!supportsColorHints || previousStopWasColorHint))
return false;
previousStopWasColorHint = !stop.color;
if (stop.color && stop.position) {
if (auto secondPosition = consumeStopPosition()) {
gradient.addStop(CSSGradientColorStop { stop });
stop.position = WTFMove(secondPosition);
}
}
gradient.addStop(WTFMove(stop));
} while (consumeCommaIncludingWhitespace(range));
if (previousStopWasColorHint)
return false;
if (!gradient.hasAtLeastTwoStops())
return false;
gradient.doneAddingStops();
return true;
}
static RefPtr<CSSValue> consumeDeprecatedRadialGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode, CSSGradientRepeat repeating)
{
auto 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;
auto shape = consumeIdent<CSSValueCircle, CSSValueEllipse>(args);
auto sizeKeyword = consumeIdent<CSSValueClosestSide, CSSValueClosestCorner, CSSValueFarthestSide, CSSValueFarthestCorner, CSSValueContain, CSSValueCover>(args);
if (!shape)
shape = consumeIdent<CSSValueCircle, CSSValueEllipse>(args);
if (!shape && !sizeKeyword) {
auto horizontalSize = consumeLengthOrPercent(args, cssParserMode, ValueRangeNonNegative);
RefPtr<CSSPrimitiveValue> verticalSize;
if (horizontalSize) {
verticalSize = consumeLengthOrPercent(args, cssParserMode, ValueRangeNonNegative);
if (!verticalSize)
return nullptr;
consumeCommaIncludingWhitespace(args);
result->setEndHorizontalSize(WTFMove(horizontalSize));
result->setEndVerticalSize(WTFMove(verticalSize));
}
} else {
consumeCommaIncludingWhitespace(args);
}
if (!consumeGradientColorStops(args, cssParserMode, result))
return nullptr;
result->setFirstX(centerX.copyRef());
result->setFirstY(centerY.copyRef());
result->setSecondX(WTFMove(centerX));
result->setSecondY(WTFMove(centerY));
result->setShape(WTFMove(shape));
result->setSizingBehavior(WTFMove(sizeKeyword));
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;
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 {
auto center = consumeLengthOrPercent(args, cssParserMode, ValueRangeNonNegative);
if (!center)
break;
if (horizontalSize)
return nullptr;
horizontalSize = center;
center = consumeLengthOrPercent(args, cssParserMode, ValueRangeNonNegative);
if (center) {
verticalSize = center;
++i;
}
}
}
if (sizeKeyword && horizontalSize)
return nullptr;
if (shape && shape->valueID() == CSSValueCircle && verticalSize)
return nullptr;
if (shape && shape->valueID() == CSSValueEllipse && horizontalSize && !verticalSize)
return nullptr;
if (!verticalSize && horizontalSize && horizontalSize->isPercentage())
return nullptr;
if ((horizontalSize && horizontalSize->isCalculatedPercentageWithLength())
|| (verticalSize && verticalSize->isCalculatedPercentageWithLength()))
return nullptr;
RefPtr<CSSPrimitiveValue> centerX;
RefPtr<CSSPrimitiveValue> centerY;
if (args.peek().id() == CSSValueAt) {
args.consumeIncludingWhitespace();
consumePosition(args, cssParserMode, UnitlessQuirk::Forbid, PositionSyntax::Position, centerX, centerY);
if (!(centerX && centerY))
return nullptr;
result->setFirstX(centerX.copyRef());
result->setFirstY(centerY.copyRef());
result->setSecondX(centerX.copyRef());
result->setSecondY(centerY.copyRef());
}
if ((shape || sizeKeyword || horizontalSize || centerX || centerY) && !consumeCommaIncludingWhitespace(args))
return nullptr;
if (!consumeGradientColorStops(args, cssParserMode, *result))
return nullptr;
result->setShape(WTFMove(shape));
result->setSizingBehavior(WTFMove(sizeKeyword));
result->setEndHorizontalSize(WTFMove(horizontalSize));
result->setEndVerticalSize(WTFMove(verticalSize));
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(WTFMove(endX));
result->setFirstY(WTFMove(endY));
} else {
expectComma = false;
}
if (expectComma && !consumeCommaIncludingWhitespace(args))
return nullptr;
if (!consumeGradientColorStops(args, cssParserMode, *result))
return nullptr;
return result;
}
static RefPtr<CSSValue> consumeConicGradient(CSSParserTokenRange& args, CSSParserContext context, CSSGradientRepeat repeating)
{
#if ENABLE(CSS_CONIC_GRADIENTS)
RefPtr<CSSConicGradientValue> result = CSSConicGradientValue::create(repeating);
bool expectComma = false;
if (args.peek().type() == IdentToken) {
if (consumeIdent<CSSValueFrom>(args)) {
auto angle = consumeAngle(args, context.mode, UnitlessQuirk::Forbid);
if (!angle)
return nullptr;
result->setAngle(WTFMove(angle));
expectComma = true;
}
if (consumeIdent<CSSValueAt>(args)) {
RefPtr<CSSPrimitiveValue> centerX;
RefPtr<CSSPrimitiveValue> centerY;
consumePosition(args, context.mode, UnitlessQuirk::Forbid, PositionSyntax::Position, centerX, centerY);
if (!(centerX && centerY))
return nullptr;
result->setFirstX(centerX.copyRef());
result->setFirstY(centerY.copyRef());
result->setSecondX(WTFMove(centerX));
result->setSecondY(WTFMove(centerY));
expectComma = true;
}
}
if (expectComma && !consumeCommaIncludingWhitespace(args))
return nullptr;
if (!consumeGradientColorStops(args, context.mode, *result))
return nullptr;
return result;
#else
UNUSED_PARAM(args);
UNUSED_PARAM(context);
UNUSED_PARAM(repeating);
return nullptr;
#endif
}
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;
if (auto percentValue = consumePercent(args, ValueRangeAll))
percentage = CSSValuePool::singleton().createValue(clampTo<double>(percentValue->doubleValue() / 100.0, 0, 1), CSSUnitType::CSS_NUMBER);
else if (auto numberValue = consumeNumber(args, ValueRangeAll))
percentage = CSSValuePool::singleton().createValue(clampTo<double>(numberValue->doubleValue(), 0, 1), CSSUnitType::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, AllowedFilterFunctions::PixelFilters);
if (!filterValue)
return nullptr;
if (!args.atEnd())
return nullptr;
return CSSFilterImageValue::create(imageValue.releaseNonNull(), filterValue.releaseNonNull());
}
#if ENABLE(CSS_PAINTING_API)
static RefPtr<CSSValue> consumeCustomPaint(CSSParserTokenRange& args)
{
if (!RuntimeEnabledFeatures::sharedFeatures().cssPaintingAPIEnabled())
return nullptr;
if (args.peek().type() != IdentToken)
return nullptr;
auto name = args.consumeIncludingWhitespace().value().toString();
if (!args.atEnd() && args.peek() != CommaToken)
return nullptr;
if (!args.atEnd())
args.consume();
auto argumentList = CSSVariableData::create(args);
while (!args.atEnd())
args.consume();
return CSSPaintImageValue::create(name, WTFMove(argumentList));
}
#endif
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 == CSSValueConicGradient)
result = consumeConicGradient(args, context, NonRepeating);
else if (id == CSSValueRepeatingConicGradient)
result = consumeConicGradient(args, context, 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 ENABLE(CSS_PAINTING_API)
else if (id == CSSValuePaint)
result = consumeCustomPaint(args);
#endif
if (!result || !args.atEnd())
return nullptr;
range = rangeCopy;
return result;
}
static RefPtr<CSSValue> consumeImageSet(CSSParserTokenRange& range, const CSSParserContext& context, OptionSet<AllowedImageType> allowedImageTypes)
{
CSSParserTokenRange rangeCopy = range;
CSSParserTokenRange args = consumeFunction(rangeCopy);
RefPtr<CSSImageSetValue> imageSet = CSSImageSetValue::create();
do {
auto image = consumeImage(args, context, allowedImageTypes);
if (!image)
return nullptr;
imageSet->append(image.releaseNonNull());
auto resolution = consumeResolution(args, AllowXResolutionUnit::Allow);
if (!resolution || resolution->floatValue() <= 0)
return nullptr;
imageSet->append(resolution.releaseNonNull());
} while (consumeCommaIncludingWhitespace(args));
if (!args.atEnd())
return nullptr;
range = rangeCopy;
return imageSet;
}
static bool isGeneratedImage(CSSValueID id)
{
return id == CSSValueLinearGradient
|| id == CSSValueRadialGradient
|| id == CSSValueConicGradient
|| id == CSSValueRepeatingLinearGradient
|| id == CSSValueRepeatingRadialGradient
|| id == CSSValueRepeatingConicGradient
|| id == CSSValueWebkitLinearGradient
|| id == CSSValueWebkitRadialGradient
|| id == CSSValueWebkitRepeatingLinearGradient
|| id == CSSValueWebkitRepeatingRadialGradient
|| id == CSSValueWebkitGradient
|| id == CSSValueWebkitCrossFade
|| id == CSSValueWebkitCanvas
|| id == CSSValueCrossFade
|| id == CSSValueWebkitNamedImage
|| id == CSSValueWebkitFilter
#if ENABLE(CSS_PAINTING_API)
|| id == CSSValuePaint
#endif
|| id == CSSValueFilter;
}
static bool isPixelFilterFunction(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;
}
}
static bool isColorFilterFunction(CSSValueID filterFunction)
{
switch (filterFunction) {
case CSSValueBrightness:
case CSSValueContrast:
case CSSValueGrayscale:
case CSSValueHueRotate:
case CSSValueInvert:
case CSSValueOpacity:
case CSSValueSaturate:
case CSSValueSepia:
case CSSValueAppleInvertLightness:
return true;
default:
return false;
}
}
static bool allowsValuesGreaterThanOne(CSSValueID filterFunction)
{
switch (filterFunction) {
case CSSValueBrightness:
case CSSValueContrast:
case CSSValueSaturate:
return true;
default:
return false;
}
}
static RefPtr<CSSFunctionValue> consumeFilterFunction(CSSParserTokenRange& range, const CSSParserContext& context, AllowedFilterFunctions allowedFunctions)
{
CSSValueID filterType = range.peek().functionId();
switch (allowedFunctions) {
case AllowedFilterFunctions::PixelFilters:
if (!isPixelFilterFunction(filterType))
return nullptr;
break;
case AllowedFilterFunctions::ColorFilters:
if (!isColorFilterFunction(filterType))
return nullptr;
break;
}
CSSParserTokenRange args = consumeFunction(range);
RefPtr<CSSFunctionValue> filterValue = CSSFunctionValue::create(filterType);
if (filterType == CSSValueAppleInvertLightness) {
if (!args.atEnd())
return nullptr;
return filterValue;
}
RefPtr<CSSValue> parsedValue;
if (filterType == CSSValueDropShadow)
parsedValue = consumeSingleShadow(args, context.mode, false, false);
else {
if (args.atEnd())
return filterValue;
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 && !allowsValuesGreaterThanOne(filterType)) {
bool isPercentage = downcast<CSSPrimitiveValue>(*parsedValue).isPercentage();
double maxAllowed = isPercentage ? 100.0 : 1.0;
if (downcast<CSSPrimitiveValue>(*parsedValue).doubleValue() > maxAllowed)
parsedValue = CSSPrimitiveValue::create(maxAllowed, isPercentage ? CSSUnitType::CSS_PERCENTAGE : CSSUnitType::CSS_NUMBER);
}
}
}
if (!parsedValue || !args.atEnd())
return nullptr;
filterValue->append(parsedValue.releaseNonNull());
return filterValue;
}
RefPtr<CSSValue> consumeFilter(CSSParserTokenRange& range, const CSSParserContext& context, AllowedFilterFunctions allowedFunctions)
{
if (range.peek().id() == CSSValueNone)
return consumeIdent(range);
bool referenceFiltersAllowed = allowedFunctions == AllowedFilterFunctions::PixelFilters;
auto list = CSSValueList::createSpaceSeparated();
do {
RefPtr<CSSValue> filterValue = referenceFiltersAllowed ? consumeUrl(range) : nullptr;
if (!filterValue) {
filterValue = consumeFilterFunction(range, context, allowedFunctions);
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;
RefPtr<CSSPrimitiveValue> horizontalOffset;
RefPtr<CSSPrimitiveValue> verticalOffset;
RefPtr<CSSPrimitiveValue> blurRadius;
RefPtr<CSSPrimitiveValue> spreadDistance;
for (size_t i = 0; i < 3; i++) {
if (range.atEnd())
break;
const CSSParserToken& nextToken = range.peek();
if (nextToken.type() == CommaToken)
break;
if (nextToken.id() == CSSValueInset) {
if (!allowInset || style)
return nullptr;
style = consumeIdent(range);
continue;
}
auto maybeColor = consumeColor(range, cssParserMode);
if (maybeColor) {
if (color)
return nullptr;
color = maybeColor;
continue;
}
if (horizontalOffset || verticalOffset || blurRadius || spreadDistance) {
return nullptr;
}
horizontalOffset = consumeLength(range, cssParserMode, ValueRangeAll);
if (!horizontalOffset)
return nullptr;
verticalOffset = consumeLength(range, cssParserMode, ValueRangeAll);
if (!verticalOffset)
return nullptr;
const CSSParserToken& token = range.peek();
if (token.type() == DimensionToken || token.type() == NumberToken || (token.type() == FunctionToken && CSSCalcValue::isCalcFunction(token.functionId()))) {
blurRadius = consumeLength(range, cssParserMode, ValueRangeNonNegative);
if (!blurRadius)
return nullptr;
}
if (blurRadius && allowSpread)
spreadDistance = consumeLength(range, cssParserMode, ValueRangeAll);
}
if (!horizontalOffset || !verticalOffset)
return nullptr;
return CSSShadowValue::create(WTFMove(horizontalOffset), WTFMove(verticalOffset), WTFMove(blurRadius), WTFMove(spreadDistance), WTFMove(style), WTFMove(color));
}
RefPtr<CSSValue> consumeImage(CSSParserTokenRange& range, CSSParserContext context, OptionSet<AllowedImageType> allowedImageTypes)
{
if ((range.peek().type() == StringToken) && (allowedImageTypes.contains(AllowedImageType::RawStringAsURL))) {
auto urlStringView = range.consumeIncludingWhitespace().value();
return CSSImageValue::create(completeURL(context, urlStringView.toAtomString()), context.isContentOpaque ? LoadedFromOpaqueSource::Yes : LoadedFromOpaqueSource::No);
}
if (range.peek().type() == FunctionToken) {
CSSValueID functionId = range.peek().functionId();
if ((allowedImageTypes.contains(AllowedImageType::GeneratedImage)) && isGeneratedImage(functionId))
return consumeGeneratedImage(range, context);
if (allowedImageTypes.contains(AllowedImageType::ImageSet)) {
if (functionId == CSSValueImageSet)
return consumeImageSet(range, context, (allowedImageTypes | AllowedImageType::RawStringAsURL) - AllowedImageType::ImageSet);
if (functionId == CSSValueWebkitImageSet)
return consumeImageSet(range, context, AllowedImageType::URLFunction);
}
}
if (allowedImageTypes.contains(AllowedImageType::URLFunction)) {
auto uri = consumeUrlAsStringView(range);
if (!uri.isNull())
return CSSImageValue::create(completeURL(context, uri.toAtomString()), context.isContentOpaque ? LoadedFromOpaqueSource::Yes : LoadedFromOpaqueSource::No);
}
return nullptr;
}
Optional<CSSValueID> consumeFontVariantCSS21Raw(CSSParserTokenRange& range)
{
return consumeIdentRaw<CSSValueNormal, CSSValueSmallCaps>(range);
}
Optional<CSSValueID> consumeFontWeightKeywordValueRaw(CSSParserTokenRange& range)
{
return consumeIdentRaw<CSSValueNormal, CSSValueBold, CSSValueBolder, CSSValueLighter>(range);
}
Optional<FontWeightRaw> consumeFontWeightRaw(CSSParserTokenRange& range)
{
if (auto result = consumeFontWeightKeywordValueRaw(range))
return { *result };
if (auto result = consumeFontWeightNumberRaw(range))
return { *result };
return WTF::nullopt;
}
Optional<CSSValueID> consumeFontStretchKeywordValueRaw(CSSParserTokenRange& range)
{
return consumeIdentRaw<CSSValueUltraCondensed, CSSValueExtraCondensed, CSSValueCondensed, CSSValueSemiCondensed, CSSValueNormal, CSSValueSemiExpanded, CSSValueExpanded, CSSValueExtraExpanded, CSSValueUltraExpanded>(range);
}
Optional<CSSValueID> consumeFontStyleKeywordValueRaw(CSSParserTokenRange& range)
{
return consumeIdentRaw<CSSValueNormal, CSSValueItalic, CSSValueOblique>(range);
}
Optional<FontStyleRaw> consumeFontStyleRaw(CSSParserTokenRange& range, CSSParserMode cssParserMode)
{
auto result = consumeFontStyleKeywordValueRaw(range);
if (!result)
return WTF::nullopt;
auto ident = *result;
if (ident == CSSValueNormal || ident == CSSValueItalic)
return { { ident, WTF::nullopt } };
ASSERT(ident == CSSValueOblique);
#if ENABLE(VARIATION_FONTS)
if (!range.atEnd()) {
if (auto angle = consumeAngleRaw(range, cssParserMode)) {
if (isFontStyleAngleInRange(CSSPrimitiveValue::computeDegrees(angle->type, angle->value)))
return { { CSSValueOblique, WTFMove(angle) } };
return WTF::nullopt;
}
}
#else
UNUSED_PARAM(cssParserMode);
#endif
return { { CSSValueOblique, WTF::nullopt } };
}
String concatenateFamilyName(CSSParserTokenRange& range)
{
StringBuilder builder;
bool addedSpace = false;
const CSSParserToken& firstToken = range.peek();
while (range.peek().type() == IdentToken) {
if (!builder.isEmpty()) {
builder.append(' ');
addedSpace = true;
}
builder.append(range.consumeIncludingWhitespace().value());
}
if (!addedSpace && isCSSWideKeyword(firstToken.id()))
return String();
return builder.toString();
}
String consumeFamilyNameRaw(CSSParserTokenRange& range)
{
if (range.peek().type() == StringToken)
return range.consumeIncludingWhitespace().value().toString();
if (range.peek().type() != IdentToken)
return String();
return concatenateFamilyName(range);
}
Optional<CSSValueID> consumeGenericFamilyRaw(CSSParserTokenRange& range)
{
return consumeIdentRangeRaw(range, CSSValueSerif, CSSValueWebkitBody);
}
Optional<WTF::Vector<FontFamilyRaw>> consumeFontFamilyRaw(CSSParserTokenRange& range)
{
WTF::Vector<FontFamilyRaw> list;
do {
if (auto ident = consumeGenericFamilyRaw(range))
list.append({ *ident });
else {
auto familyName = consumeFamilyNameRaw(range);
if (familyName.isNull())
return WTF::nullopt;
list.append({ familyName });
}
} while (consumeCommaIncludingWhitespace(range));
return list;
}
Optional<FontSizeRaw> consumeFontSizeRaw(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless)
{
if (range.peek().id() >= CSSValueXxSmall && range.peek().id() <= CSSValueLarger) {
if (auto ident = consumeIdentRaw(range))
return { *ident };
return WTF::nullopt;
}
if (auto result = consumeLengthOrPercentRaw(range, cssParserMode, ValueRangeNonNegative, unitless))
return { *result };
return WTF::nullopt;
}
Optional<LineHeightRaw> consumeLineHeightRaw(CSSParserTokenRange& range, CSSParserMode cssParserMode)
{
if (range.peek().id() == CSSValueNormal) {
if (auto ident = consumeIdentRaw(range))
return { *ident };
return WTF::nullopt;
}
double number;
if (consumeNumberRaw(range, number, ValueRangeNonNegative))
return { number };
if (auto lengthOrPercent = consumeLengthOrPercentRaw(range, cssParserMode, ValueRangeNonNegative))
return { *lengthOrPercent };
return WTF::nullopt;
}
Optional<FontRaw> consumeFontWorkerSafe(CSSParserTokenRange& range, CSSParserMode cssParserMode)
{
CSSParserTokenRange rangeCopy = range;
while (!rangeCopy.atEnd()) {
CSSValueID id = rangeCopy.consumeIncludingWhitespace().id();
if (id == CSSValueInherit || id == CSSValueInitial)
return WTF::nullopt;
}
FontRaw result;
while (!range.atEnd()) {
CSSValueID id = range.peek().id();
if (!result.style) {
if ((result.style = consumeFontStyleRaw(range, cssParserMode)))
continue;
}
if (!result.variantCaps && (id == CSSValueNormal || id == CSSValueSmallCaps)) {
if ((result.variantCaps = consumeFontVariantCSS21Raw(range)))
continue;
}
if (!result.weight) {
if ((result.weight = consumeFontWeightRaw(range)))
continue;
}
if (!result.stretch) {
if ((result.stretch = consumeFontStretchKeywordValueRaw(range)))
continue;
}
break;
}
if (range.atEnd())
return WTF::nullopt;
if (auto size = consumeFontSizeRaw(range, cssParserMode))
result.size = *size;
else
return WTF::nullopt;
if (range.atEnd())
return WTF::nullopt;
if (consumeSlashIncludingWhitespace(range)) {
if (!(result.lineHeight = consumeLineHeightRaw(range, cssParserMode)))
return WTF::nullopt;
}
if (auto family = consumeFontFamilyRaw(range))
result.family = *family;
else
return WTF::nullopt;
if (!range.atEnd())
return WTF::nullopt;
return result;
}
const AtomString& genericFontFamilyFromValueID(CSSValueID ident)
{
switch (ident) {
case CSSValueSerif:
return serifFamily.get();
case CSSValueSansSerif:
return sansSerifFamily.get();
case CSSValueCursive:
return cursiveFamily.get();
case CSSValueFantasy:
return fantasyFamily.get();
case CSSValueMonospace:
return monospaceFamily.get();
case CSSValueWebkitPictograph:
return pictographFamily.get();
case CSSValueSystemUi:
return systemUiFamily.get();
default:
return emptyAtom();
}
}
}
}