// Copyright 2015 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 "CSSPropertyParser.h" #if ENABLE(CSS_ANIMATIONS_LEVEL_2) #include "CSSAnimationTriggerScrollValue.h" #endif #include "CSSAspectRatioValue.h" #include "CSSBasicShapes.h" #include "CSSBorderImage.h" #include "CSSBorderImageSliceValue.h" #include "CSSContentDistributionValue.h" #include "CSSCursorImageValue.h" #include "CSSCustomIdentValue.h" #include "CSSFontFaceSrcValue.h" #include "CSSFontFeatureValue.h" #if ENABLE(VARIATION_FONTS) #include "CSSFontVariationValue.h" #endif #include "CSSFunctionValue.h" #include "CSSGridAutoRepeatValue.h" #include "CSSGridLineNamesValue.h" #include "CSSGridTemplateAreasValue.h" #include "CSSInheritedValue.h" #include "CSSInitialValue.h" #include "CSSLineBoxContainValue.h" #include "CSSParserFastPaths.h" #include "CSSParserIdioms.h" #include "CSSPendingSubstitutionValue.h" #include "CSSPrimitiveValueMappings.h" #include "CSSPropertyParserHelpers.h" #include "CSSReflectValue.h" #include "CSSRevertValue.h" #include "CSSShadowValue.h" #include "CSSTimingFunctionValue.h" #include "CSSUnicodeRangeValue.h" #include "CSSUnsetValue.h" #include "CSSVariableParser.h" #include "CSSVariableReferenceValue.h" #include "Counter.h" #if ENABLE(DASHBOARD_SUPPORT) #include "DashboardRegion.h" #endif #include "FontFace.h" #include "HashTools.h" // FIXME-NEWPARSER: Replace Pair and Rect with actual CSSValue subclasses (CSSValuePair and CSSQuadValue). #include "Pair.h" #include "Rect.h" #include "RenderTheme.h" #include "RuntimeEnabledFeatures.h" #include "SVGPathByteStream.h" #include "SVGPathUtilities.h" #include "StylePropertyShorthand.h" #include "StylePropertyShorthandFunctions.h" #include #include #include using namespace WTF; namespace WebCore { bool isCustomPropertyName(const String& propertyName) { return propertyName.length() > 2 && propertyName.characterAt(0) == '-' && propertyName.characterAt(1) == '-'; } static bool hasPrefix(const char* string, unsigned length, const char* prefix) { for (unsigned i = 0; i < length; ++i) { if (!prefix[i]) return true; if (string[i] != prefix[i]) return false; } return false; } #if PLATFORM(IOS) void cssPropertyNameIOSAliasing(const char* propertyName, const char*& propertyNameAlias, unsigned& newLength) { if (!strcmp(propertyName, "-webkit-hyphenate-locale")) { // Worked in iOS 4.2. static const char webkitLocale[] = "-webkit-locale"; propertyNameAlias = webkitLocale; newLength = strlen(webkitLocale); } } #endif template static CSSPropertyID cssPropertyID(const CharacterType* propertyName, unsigned length) { char buffer[maxCSSPropertyNameLength + 1 + 1]; // 1 to turn "apple"/"khtml" into "webkit", 1 for null character for (unsigned i = 0; i != length; ++i) { CharacterType c = propertyName[i]; if (!c || c >= 0x7F) return CSSPropertyInvalid; // illegal character buffer[i] = toASCIILower(c); } buffer[length] = '\0'; const char* name = buffer; if (buffer[0] == '-') { #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES) // If the prefix is -apple- or -khtml-, change it to -webkit-. // This makes the string one character longer. if (RuntimeEnabledFeatures::sharedFeatures().legacyCSSVendorPrefixesEnabled() && (hasPrefix(buffer, length, "-apple-") || hasPrefix(buffer, length, "-khtml-"))) { memmove(buffer + 7, buffer + 6, length + 1 - 6); memcpy(buffer, "-webkit", 7); ++length; } #endif #if PLATFORM(IOS) cssPropertyNameIOSAliasing(buffer, name, length); #endif } const Property* hashTableEntry = findProperty(name, length); return hashTableEntry ? static_cast(hashTableEntry->id) : CSSPropertyInvalid; } static bool isAppleLegacyCssValueKeyword(const char* valueKeyword, unsigned length) { static const char applePrefix[] = "-apple-"; static const char appleSystemPrefix[] = "-apple-system"; static const char applePayPrefix[] = "-apple-pay"; static const char* appleWirelessPlaybackTargetActive = getValueName(CSSValueAppleWirelessPlaybackTargetActive); return hasPrefix(valueKeyword, length, applePrefix) && !hasPrefix(valueKeyword, length, appleSystemPrefix) && !hasPrefix(valueKeyword, length, applePayPrefix) && !WTF::equal(reinterpret_cast(valueKeyword), reinterpret_cast(appleWirelessPlaybackTargetActive), length); } template static CSSValueID cssValueKeywordID(const CharacterType* valueKeyword, unsigned length) { char buffer[maxCSSValueKeywordLength + 1 + 1]; // 1 to turn "apple"/"khtml" into "webkit", 1 for null character for (unsigned i = 0; i != length; ++i) { CharacterType c = valueKeyword[i]; if (!c || c >= 0x7F) return CSSValueInvalid; // illegal keyword. buffer[i] = WTF::toASCIILower(c); } buffer[length] = '\0'; if (buffer[0] == '-') { // If the prefix is -apple- or -khtml-, change it to -webkit-. // This makes the string one character longer. // On iOS we don't want to change values starting with -apple-system to -webkit-system. // FIXME: Remove this mangling without breaking the web. if (isAppleLegacyCssValueKeyword(buffer, length) || hasPrefix(buffer, length, "-khtml-")) { memmove(buffer + 7, buffer + 6, length + 1 - 6); memcpy(buffer, "-webkit", 7); ++length; } } const Value* hashTableEntry = findValue(buffer, length); return hashTableEntry ? static_cast(hashTableEntry->id) : CSSValueInvalid; } CSSValueID cssValueKeywordID(StringView string) { unsigned length = string.length(); if (!length) return CSSValueInvalid; if (length > maxCSSValueKeywordLength) return CSSValueInvalid; return string.is8Bit() ? cssValueKeywordID(string.characters8(), length) : cssValueKeywordID(string.characters16(), length); } CSSPropertyID cssPropertyID(StringView string) { unsigned length = string.length(); if (!length) return CSSPropertyInvalid; if (length > maxCSSPropertyNameLength) return CSSPropertyInvalid; return string.is8Bit() ? cssPropertyID(string.characters8(), length) : cssPropertyID(string.characters16(), length); } using namespace CSSPropertyParserHelpers; CSSPropertyParser::CSSPropertyParser(const CSSParserTokenRange& range, const CSSParserContext& context, StyleSheetContents* styleSheetContents, Vector* parsedProperties) : m_range(range) , m_context(context) , m_styleSheetContents(styleSheetContents) , m_parsedProperties(parsedProperties) { m_range.consumeWhitespace(); } void CSSPropertyParser::addProperty(CSSPropertyID property, CSSPropertyID currentShorthand, Ref&& value, bool important, bool implicit) { int shorthandIndex = 0; bool setFromShorthand = false; if (currentShorthand) { auto shorthands = matchingShorthandsForLonghand(property); setFromShorthand = true; if (shorthands.size() > 1) shorthandIndex = indexOfShorthandForLonghand(currentShorthand, shorthands); } m_parsedProperties->append(CSSProperty(property, WTFMove(value), important, setFromShorthand, shorthandIndex, implicit)); } void CSSPropertyParser::addExpandedPropertyForValue(CSSPropertyID property, Ref&& value, bool important) { const StylePropertyShorthand& shorthand = shorthandForProperty(property); unsigned shorthandLength = shorthand.length(); ASSERT(shorthandLength); const CSSPropertyID* longhands = shorthand.properties(); for (unsigned i = 0; i < shorthandLength; ++i) addProperty(longhands[i], property, value.copyRef(), important); } bool CSSPropertyParser::parseValue(CSSPropertyID propertyID, bool important, const CSSParserTokenRange& range, const CSSParserContext& context, StyleSheetContents* styleSheetContents, ParsedPropertyVector& parsedProperties, StyleRule::Type ruleType) { int parsedPropertiesSize = parsedProperties.size(); CSSPropertyParser parser(range, context, styleSheetContents, &parsedProperties); bool parseSuccess; #if ENABLE(CSS_DEVICE_ADAPTATION) if (ruleType == StyleRule::Viewport) parseSuccess = parser.parseViewportDescriptor(propertyID, important); else #endif if (ruleType == StyleRule::FontFace) parseSuccess = parser.parseFontFaceDescriptor(propertyID); else parseSuccess = parser.parseValueStart(propertyID, important); if (!parseSuccess) parsedProperties.shrink(parsedPropertiesSize); return parseSuccess; } RefPtr CSSPropertyParser::parseSingleValue(CSSPropertyID property, const CSSParserTokenRange& range, const CSSParserContext& context, StyleSheetContents* styleSheetContents) { CSSPropertyParser parser(range, context, styleSheetContents, nullptr); RefPtr value = parser.parseSingleValue(property); if (!value || !parser.m_range.atEnd()) return nullptr; return value; } static bool isLegacyBreakProperty(CSSPropertyID propertyID) { switch (propertyID) { case CSSPropertyPageBreakAfter: case CSSPropertyPageBreakBefore: case CSSPropertyPageBreakInside: case CSSPropertyWebkitColumnBreakAfter: case CSSPropertyWebkitColumnBreakBefore: case CSSPropertyWebkitColumnBreakInside: #if ENABLE(CSS_REGIONS) case CSSPropertyWebkitRegionBreakAfter: case CSSPropertyWebkitRegionBreakBefore: case CSSPropertyWebkitRegionBreakInside: #endif return true; default: break; } return false; } bool CSSPropertyParser::parseValueStart(CSSPropertyID propertyID, bool important) { if (consumeCSSWideKeyword(propertyID, important)) return true; CSSParserTokenRange originalRange = m_range; bool isShorthand = isShorthandCSSProperty(propertyID); if (isShorthand) { // Variable references will fail to parse here and will fall out to the variable ref parser below. if (parseShorthand(propertyID, important)) return true; } else if (isLegacyBreakProperty(propertyID)) { // FIXME-NEWPARSER: Can turn this into a shorthand once old parser is gone, and then // we don't need the special case. if (consumeLegacyBreakProperty(propertyID, important)) return true; } else { RefPtr parsedValue = parseSingleValue(propertyID); if (parsedValue && m_range.atEnd()) { addProperty(propertyID, CSSPropertyInvalid, *parsedValue, important); return true; } } if (CSSVariableParser::containsValidVariableReferences(originalRange)) { RefPtr variable = CSSVariableReferenceValue::create(CSSVariableData::create(originalRange)); if (isShorthand) { RefPtr pendingValue = CSSPendingSubstitutionValue::create(propertyID, variable.releaseNonNull()); addExpandedPropertyForValue(propertyID, pendingValue.releaseNonNull(), important); } else addProperty(propertyID, CSSPropertyInvalid, variable.releaseNonNull(), important); return true; } return false; } bool CSSPropertyParser::consumeCSSWideKeyword(CSSPropertyID propertyID, bool important) { CSSParserTokenRange rangeCopy = m_range; CSSValueID valueID = rangeCopy.consumeIncludingWhitespace().id(); if (!rangeCopy.atEnd()) return false; RefPtr value; if (valueID == CSSValueInherit) value = CSSValuePool::singleton().createInheritedValue(); else if (valueID == CSSValueInitial) value = CSSValuePool::singleton().createExplicitInitialValue(); else if (valueID == CSSValueUnset) value = CSSValuePool::singleton().createUnsetValue(); else if (valueID == CSSValueRevert) value = CSSValuePool::singleton().createRevertValue(); else return false; const StylePropertyShorthand& shorthand = shorthandForProperty(propertyID); if (!shorthand.length()) { if (CSSProperty::isDescriptorOnly(propertyID)) return false; addProperty(propertyID, CSSPropertyInvalid, value.releaseNonNull(), important); } else addExpandedPropertyForValue(propertyID, value.releaseNonNull(), important); m_range = rangeCopy; return true; } bool CSSPropertyParser::consumeTransformOrigin(bool important) { RefPtr resultX; RefPtr resultY; if (consumeOneOrTwoValuedPosition(m_range, m_context.mode, UnitlessQuirk::Forbid, resultX, resultY)) { m_range.consumeWhitespace(); bool atEnd = m_range.atEnd(); RefPtr resultZ = consumeLength(m_range, m_context.mode, ValueRangeAll); bool hasZ = resultZ; if (!hasZ && !atEnd) return false; addProperty(CSSPropertyTransformOriginX, CSSPropertyTransformOrigin, resultX.releaseNonNull(), important); addProperty(CSSPropertyTransformOriginY, CSSPropertyTransformOrigin, resultY.releaseNonNull(), important); addProperty(CSSPropertyTransformOriginZ, CSSPropertyTransformOrigin, resultZ ? resultZ.releaseNonNull() : CSSValuePool::singleton().createValue(0, CSSPrimitiveValue::UnitType::CSS_PX), important, !hasZ); return true; } return false; } bool CSSPropertyParser::consumePerspectiveOrigin(bool important) { RefPtr resultX; RefPtr resultY; if (consumePosition(m_range, m_context.mode, UnitlessQuirk::Forbid, resultX, resultY)) { addProperty(CSSPropertyPerspectiveOriginX, CSSPropertyPerspectiveOrigin, resultX.releaseNonNull(), important); addProperty(CSSPropertyPerspectiveOriginY, CSSPropertyPerspectiveOrigin, resultY.releaseNonNull(), important); return true; } return false; } // Methods for consuming non-shorthand properties starts here. static RefPtr consumeWillChange(CSSParserTokenRange& range) { if (range.peek().id() == CSSValueAuto) return consumeIdent(range); RefPtr values = CSSValueList::createCommaSeparated(); // Every comma-separated list of identifiers is a valid will-change value, // unless the list includes an explicitly disallowed identifier. while (true) { if (range.peek().type() != IdentToken) return nullptr; CSSPropertyID propertyID = cssPropertyID(range.peek().value()); if (propertyID != CSSPropertyInvalid) { // Now "all" is used by both CSSValue and CSSPropertyValue. // Need to return nullptr when currentValue is CSSPropertyAll. if (propertyID == CSSPropertyWillChange || propertyID == CSSPropertyAll) return nullptr; // FIXME-NEWPARSER: Use CSSCustomIdentValue someday. values->append(CSSValuePool::singleton().createIdentifierValue(propertyID)); range.consumeIncludingWhitespace(); } else { switch (range.peek().id()) { case CSSValueNone: case CSSValueAll: case CSSValueAuto: case CSSValueDefault: case CSSValueInitial: case CSSValueInherit: return nullptr; case CSSValueContents: case CSSValueScrollPosition: values->append(consumeIdent(range).releaseNonNull()); break; default: // Append properties we don't recognize, but that are legal, as strings. values->append(consumeCustomIdent(range).releaseNonNull()); break; } } if (range.atEnd()) break; if (!consumeCommaIncludingWhitespace(range)) return nullptr; } return values; } static RefPtr consumeFontFeatureTag(CSSParserTokenRange& range) { // Feature tag name consists of 4-letter characters. static const unsigned tagNameLength = 4; const CSSParserToken& token = range.consumeIncludingWhitespace(); // Feature tag name comes first if (token.type() != StringToken) return nullptr; if (token.value().length() != tagNameLength) return nullptr; FontTag tag; for (unsigned i = 0; i < tag.size(); ++i) { // Limits the range of characters to 0x20-0x7E, following the tag name rules defiend in the OpenType specification. UChar character = token.value()[i]; if (character < 0x20 || character > 0x7E) return nullptr; tag[i] = toASCIILower(character); } int tagValue = 1; // Feature tag values could follow: | on | off if (range.peek().type() == NumberToken && range.peek().numericValueType() == IntegerValueType && range.peek().numericValue() >= 0) { tagValue = clampTo(range.consumeIncludingWhitespace().numericValue()); if (tagValue < 0) return nullptr; } else if (range.peek().id() == CSSValueOn || range.peek().id() == CSSValueOff) { tagValue = range.consumeIncludingWhitespace().id() == CSSValueOn; } return CSSFontFeatureValue::create(WTFMove(tag), tagValue); } static RefPtr consumeFontFeatureSettings(CSSParserTokenRange& range) { if (range.peek().id() == CSSValueNormal) return consumeIdent(range); RefPtr settings = CSSValueList::createCommaSeparated(); do { RefPtr fontFeatureValue = consumeFontFeatureTag(range); if (!fontFeatureValue) return nullptr; settings->append(fontFeatureValue.releaseNonNull()); } while (consumeCommaIncludingWhitespace(range)); return settings; } #if ENABLE(VARIATION_FONTS) static RefPtr consumeFontVariationTag(CSSParserTokenRange& range) { if (range.peek().type() != StringToken) return nullptr; auto string = range.consumeIncludingWhitespace().value().toString(); FontTag tag; if (string.length() != tag.size()) return nullptr; for (unsigned i = 0; i < tag.size(); ++i) { // Limits the range of characters to 0x20-0x7E, following the tag name rules defiend in the OpenType specification. UChar character = string[i]; if (character < 0x20 || character > 0x7E) return nullptr; tag[i] = character; } if (range.atEnd() || range.peek().type() != NumberToken) return nullptr; float tagValue = range.consumeIncludingWhitespace().numericValue(); return CSSFontVariationValue::create(tag, tagValue); } static RefPtr consumeFontVariationSettings(CSSParserTokenRange& range) { if (range.peek().id() == CSSValueNormal) return consumeIdent(range); auto settings = CSSValueList::createCommaSeparated(); do { RefPtr variationValue = consumeFontVariationTag(range); if (!variationValue) return nullptr; settings->append(variationValue.releaseNonNull()); } while (consumeCommaIncludingWhitespace(range)); if (!settings->length()) return nullptr; return WTFMove(settings); } #endif // ENABLE(VARIATION_FONTS) static RefPtr consumePage(CSSParserTokenRange& range) { if (range.peek().id() == CSSValueAuto) return consumeIdent(range); return consumeCustomIdent(range); } static RefPtr consumeQuotes(CSSParserTokenRange& range) { if (range.peek().id() == CSSValueNone) return consumeIdent(range); RefPtr values = CSSValueList::createSpaceSeparated(); while (!range.atEnd()) { RefPtr parsedValue = consumeString(range); if (!parsedValue) return nullptr; values->append(parsedValue.releaseNonNull()); } if (values->length() && values->length() % 2 == 0) return values; return nullptr; } class FontVariantLigaturesParser { public: FontVariantLigaturesParser() : m_sawCommonLigaturesValue(false) , m_sawDiscretionaryLigaturesValue(false) , m_sawHistoricalLigaturesValue(false) , m_sawContextualLigaturesValue(false) , m_result(CSSValueList::createSpaceSeparated()) { } enum class ParseResult { ConsumedValue, DisallowedValue, UnknownValue }; ParseResult consumeLigature(CSSParserTokenRange& range) { CSSValueID valueID = range.peek().id(); switch (valueID) { case CSSValueNoCommonLigatures: case CSSValueCommonLigatures: if (m_sawCommonLigaturesValue) return ParseResult::DisallowedValue; m_sawCommonLigaturesValue = true; break; case CSSValueNoDiscretionaryLigatures: case CSSValueDiscretionaryLigatures: if (m_sawDiscretionaryLigaturesValue) return ParseResult::DisallowedValue; m_sawDiscretionaryLigaturesValue = true; break; case CSSValueNoHistoricalLigatures: case CSSValueHistoricalLigatures: if (m_sawHistoricalLigaturesValue) return ParseResult::DisallowedValue; m_sawHistoricalLigaturesValue = true; break; case CSSValueNoContextual: case CSSValueContextual: if (m_sawContextualLigaturesValue) return ParseResult::DisallowedValue; m_sawContextualLigaturesValue = true; break; default: return ParseResult::UnknownValue; } m_result->append(consumeIdent(range).releaseNonNull()); return ParseResult::ConsumedValue; } RefPtr finalizeValue() { if (!m_result->length()) return CSSValuePool::singleton().createIdentifierValue(CSSValueNormal); return m_result.release(); } private: bool m_sawCommonLigaturesValue; bool m_sawDiscretionaryLigaturesValue; bool m_sawHistoricalLigaturesValue; bool m_sawContextualLigaturesValue; RefPtr m_result; }; static RefPtr consumeFontVariantLigatures(CSSParserTokenRange& range) { if (range.peek().id() == CSSValueNormal || range.peek().id() == CSSValueNone) return consumeIdent(range); FontVariantLigaturesParser ligaturesParser; do { if (ligaturesParser.consumeLigature(range) != FontVariantLigaturesParser::ParseResult::ConsumedValue) return nullptr; } while (!range.atEnd()); return ligaturesParser.finalizeValue(); } static RefPtr consumeFontVariantEastAsian(CSSParserTokenRange& range) { if (range.peek().id() == CSSValueNormal) return consumeIdent(range); RefPtr values = CSSValueList::createSpaceSeparated(); FontVariantEastAsianVariant variant = FontVariantEastAsianVariant::Normal; FontVariantEastAsianWidth width = FontVariantEastAsianWidth::Normal; FontVariantEastAsianRuby ruby = FontVariantEastAsianRuby::Normal; while (!range.atEnd()) { if (range.peek().type() != IdentToken) return nullptr; auto id = range.peek().id(); switch (id) { case CSSValueJis78: variant = FontVariantEastAsianVariant::Jis78; break; case CSSValueJis83: variant = FontVariantEastAsianVariant::Jis83; break; case CSSValueJis90: variant = FontVariantEastAsianVariant::Jis90; break; case CSSValueJis04: variant = FontVariantEastAsianVariant::Jis04; break; case CSSValueSimplified: variant = FontVariantEastAsianVariant::Simplified; break; case CSSValueTraditional: variant = FontVariantEastAsianVariant::Traditional; break; case CSSValueFullWidth: width = FontVariantEastAsianWidth::Full; break; case CSSValueProportionalWidth: width = FontVariantEastAsianWidth::Proportional; break; case CSSValueRuby: ruby = FontVariantEastAsianRuby::Yes; break; default: return nullptr; } range.consumeIncludingWhitespace(); } switch (variant) { case FontVariantEastAsianVariant::Normal: break; case FontVariantEastAsianVariant::Jis78: values->append(CSSValuePool::singleton().createIdentifierValue(CSSValueJis78)); break; case FontVariantEastAsianVariant::Jis83: values->append(CSSValuePool::singleton().createIdentifierValue(CSSValueJis83)); break; case FontVariantEastAsianVariant::Jis90: values->append(CSSValuePool::singleton().createIdentifierValue(CSSValueJis90)); break; case FontVariantEastAsianVariant::Jis04: values->append(CSSValuePool::singleton().createIdentifierValue(CSSValueJis04)); break; case FontVariantEastAsianVariant::Simplified: values->append(CSSValuePool::singleton().createIdentifierValue(CSSValueSimplified)); break; case FontVariantEastAsianVariant::Traditional: values->append(CSSValuePool::singleton().createIdentifierValue(CSSValueTraditional)); break; } switch (width) { case FontVariantEastAsianWidth::Normal: break; case FontVariantEastAsianWidth::Full: values->append(CSSValuePool::singleton().createIdentifierValue(CSSValueFullWidth)); break; case FontVariantEastAsianWidth::Proportional: values->append(CSSValuePool::singleton().createIdentifierValue(CSSValueProportionalWidth)); break; } switch (ruby) { case FontVariantEastAsianRuby::Normal: break; case FontVariantEastAsianRuby::Yes: values->append(CSSValuePool::singleton().createIdentifierValue(CSSValueRuby)); } if (!values->length()) return nullptr; return values; } static RefPtr consumeFontVariantCaps(CSSParserTokenRange& range) { return consumeIdent(range); } static RefPtr consumeFontVariantAlternates(CSSParserTokenRange& range) { return consumeIdent(range); } static RefPtr consumeFontVariantPosition(CSSParserTokenRange& range) { return consumeIdent(range); } class FontVariantNumericParser { public: FontVariantNumericParser() : m_sawNumericFigureValue(false) , m_sawNumericSpacingValue(false) , m_sawNumericFractionValue(false) , m_sawOrdinalValue(false) , m_sawSlashedZeroValue(false) , m_result(CSSValueList::createSpaceSeparated()) { } enum class ParseResult { ConsumedValue, DisallowedValue, UnknownValue }; ParseResult consumeNumeric(CSSParserTokenRange& range) { CSSValueID valueID = range.peek().id(); switch (valueID) { case CSSValueLiningNums: case CSSValueOldstyleNums: if (m_sawNumericFigureValue) return ParseResult::DisallowedValue; m_sawNumericFigureValue = true; break; case CSSValueProportionalNums: case CSSValueTabularNums: if (m_sawNumericSpacingValue) return ParseResult::DisallowedValue; m_sawNumericSpacingValue = true; break; case CSSValueDiagonalFractions: case CSSValueStackedFractions: if (m_sawNumericFractionValue) return ParseResult::DisallowedValue; m_sawNumericFractionValue = true; break; case CSSValueOrdinal: if (m_sawOrdinalValue) return ParseResult::DisallowedValue; m_sawOrdinalValue = true; break; case CSSValueSlashedZero: if (m_sawSlashedZeroValue) return ParseResult::DisallowedValue; m_sawSlashedZeroValue = true; break; default: return ParseResult::UnknownValue; } m_result->append(consumeIdent(range).releaseNonNull()); return ParseResult::ConsumedValue; } RefPtr finalizeValue() { if (!m_result->length()) return CSSValuePool::singleton().createIdentifierValue(CSSValueNormal); return m_result.release(); } private: bool m_sawNumericFigureValue; bool m_sawNumericSpacingValue; bool m_sawNumericFractionValue; bool m_sawOrdinalValue; bool m_sawSlashedZeroValue; RefPtr m_result; }; static RefPtr consumeFontVariantNumeric(CSSParserTokenRange& range) { if (range.peek().id() == CSSValueNormal) return consumeIdent(range); FontVariantNumericParser numericParser; do { if (numericParser.consumeNumeric(range) != FontVariantNumericParser::ParseResult::ConsumedValue) return nullptr; } while (!range.atEnd()); return numericParser.finalizeValue(); } static RefPtr consumeFontVariantCSS21(CSSParserTokenRange& range) { return consumeIdent(range); } static RefPtr consumeFontWeight(CSSParserTokenRange& range) { const CSSParserToken& token = range.peek(); if (token.id() >= CSSValueNormal && token.id() <= CSSValueLighter) return consumeIdent(range); int weight; if (!consumePositiveIntegerRaw(range, weight)) return nullptr; if ((weight % 100) || weight < 100 || weight > 900) return nullptr; return CSSValuePool::singleton().createIdentifierValue(static_cast(CSSValue100 + weight / 100 - 1)); } static 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(); } static RefPtr consumeFamilyName(CSSParserTokenRange& range) { if (range.peek().type() == StringToken) return CSSValuePool::singleton().createFontFamilyValue(range.consumeIncludingWhitespace().value().toString()); if (range.peek().type() != IdentToken) return nullptr; String familyName = concatenateFamilyName(range); if (familyName.isNull()) return nullptr; return CSSValuePool::singleton().createFontFamilyValue(familyName); } static RefPtr consumeGenericFamily(CSSParserTokenRange& range) { return consumeIdentRange(range, CSSValueSerif, CSSValueWebkitBody); } static RefPtr consumeFontFamily(CSSParserTokenRange& range) { RefPtr list = CSSValueList::createCommaSeparated(); do { RefPtr parsedValue = consumeGenericFamily(range); if (parsedValue) { list->append(parsedValue.releaseNonNull()); } else { parsedValue = consumeFamilyName(range); if (parsedValue) { list->append(parsedValue.releaseNonNull()); } else { return nullptr; } } } while (consumeCommaIncludingWhitespace(range)); return list; } static RefPtr consumeFontFamilyDescriptor(CSSParserTokenRange& range) { // FIXME-NEWPARSER: For compatibility with the old parser, we have to make // a list here, even though the list always contains only a single family name. // Once the old parser is gone, we can delete this function, make the caller // use consumeFamilyName instead, and then patch the @font-face code to // not expect a list with a single name in it. RefPtr list = CSSValueList::createCommaSeparated(); RefPtr parsedValue = consumeFamilyName(range); if (parsedValue) list->append(parsedValue.releaseNonNull()); if (!range.atEnd() || !list->length()) return nullptr; return list; } static RefPtr consumeFontSynthesis(CSSParserTokenRange& range) { // none | [ weight || style || small-caps ] CSSValueID id = range.peek().id(); if (id == CSSValueNone) return consumeIdent(range); RefPtr list = CSSValueList::createSpaceSeparated(); while (true) { auto ident = consumeIdent(range); if (!ident) break; if (list->hasValue(ident.get())) return nullptr; list->append(ident.releaseNonNull()); } if (!list->length()) return nullptr; return list; } static RefPtr consumeLetterSpacing(CSSParserTokenRange& range, CSSParserMode cssParserMode) { if (range.peek().id() == CSSValueNormal) return consumeIdent(range); return consumeLength(range, cssParserMode, ValueRangeAll, UnitlessQuirk::Allow); } static RefPtr consumeWordSpacing(CSSParserTokenRange& range, CSSParserMode cssParserMode) { if (range.peek().id() == CSSValueNormal) return consumeIdent(range); return consumeLengthOrPercent(range, cssParserMode, ValueRangeAll, UnitlessQuirk::Allow); } static RefPtr consumeTabSize(CSSParserTokenRange& range, CSSParserMode cssParserMode) { RefPtr parsedValue = consumeInteger(range, 0); if (parsedValue) return parsedValue; return consumeLength(range, cssParserMode, ValueRangeNonNegative); } #if ENABLE(TEXT_AUTOSIZING) static RefPtr consumeTextSizeAdjust(CSSParserTokenRange& range, CSSParserMode /* cssParserMode */) { if (range.peek().id() == CSSValueAuto) return consumeIdent(range); if (range.peek().id() == CSSValueNone) return consumeIdent(range); return consumePercent(range, ValueRangeNonNegative); } #endif static RefPtr consumeFontSize(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless = UnitlessQuirk::Forbid) { if (range.peek().id() >= CSSValueXxSmall && range.peek().id() <= CSSValueLarger) return consumeIdent(range); return consumeLengthOrPercent(range, cssParserMode, ValueRangeNonNegative, unitless); } static RefPtr consumeLineHeight(CSSParserTokenRange& range, CSSParserMode cssParserMode) { if (range.peek().id() == CSSValueNormal) return consumeIdent(range); RefPtr lineHeight = consumeNumber(range, ValueRangeNonNegative); if (lineHeight) return lineHeight; return consumeLengthOrPercent(range, cssParserMode, ValueRangeNonNegative); } template static Ref createPrimitiveValuePair(Args&&... args) { return CSSValuePool::singleton().createValue(Pair::create(std::forward(args)...)); } static RefPtr consumeCounter(CSSParserTokenRange& range, int defaultValue) { if (range.peek().id() == CSSValueNone) return consumeIdent(range); RefPtr list = CSSValueList::createSpaceSeparated(); do { RefPtr counterName = consumeCustomIdent(range); if (!counterName) return nullptr; int i = defaultValue; if (RefPtr counterValue = consumeInteger(range)) i = counterValue->intValue(); list->append(createPrimitiveValuePair(counterName.releaseNonNull(), CSSPrimitiveValue::create(i, CSSPrimitiveValue::UnitType::CSS_NUMBER), Pair::IdenticalValueEncoding::Coalesce)); } while (!range.atEnd()); return list; } static RefPtr consumePageSize(CSSParserTokenRange& range) { return consumeIdent(range); } static RefPtr consumeSize(CSSParserTokenRange& range, CSSParserMode cssParserMode) { RefPtr result = CSSValueList::createSpaceSeparated(); if (range.peek().id() == CSSValueAuto) { result->append(consumeIdent(range).releaseNonNull()); return result; } if (RefPtr width = consumeLength(range, cssParserMode, ValueRangeNonNegative)) { RefPtr height = consumeLength(range, cssParserMode, ValueRangeNonNegative); result->append(width.releaseNonNull()); if (height) result->append(height.releaseNonNull()); return result; } RefPtr pageSize = consumePageSize(range); RefPtr orientation = consumeIdent(range); if (!pageSize) pageSize = consumePageSize(range); if (!orientation && !pageSize) return nullptr; if (pageSize) result->append(pageSize.releaseNonNull()); if (orientation) result->append(orientation.releaseNonNull()); return result; } static RefPtr consumeTextIndent(CSSParserTokenRange& range, CSSParserMode cssParserMode) { // [ | ] && hanging? && each-line? // Keywords only allowed when css3Text is enabled. RefPtr list = CSSValueList::createSpaceSeparated(); bool hasLengthOrPercentage = false; // bool hasEachLine = false; bool hasHanging = false; do { if (!hasLengthOrPercentage) { if (RefPtr textIndent = consumeLengthOrPercent(range, cssParserMode, ValueRangeAll, UnitlessQuirk::Allow)) { list->append(*textIndent); hasLengthOrPercentage = true; continue; } } CSSValueID id = range.peek().id(); /* FIXME-NEWPARSER: We don't support this yet. if (!hasEachLine && id == CSSValueEachLine) { list->append(*consumeIdent(range)); hasEachLine = true; continue; } */ if (!hasHanging && id == CSSValueHanging) { list->append(consumeIdent(range).releaseNonNull()); hasHanging = true; continue; } return nullptr; } while (!range.atEnd()); if (!hasLengthOrPercentage) return nullptr; return list; } // FIXME-NEWPARSER: Drop the prefix on min-content, max-content and fit-content. static bool validWidthOrHeightKeyword(CSSValueID id, const CSSParserContext& /*context*/) { if (id == CSSValueIntrinsic || id == CSSValueMinIntrinsic || id == CSSValueWebkitMinContent || id == CSSValueWebkitMaxContent || id == CSSValueWebkitFillAvailable || id == CSSValueWebkitFitContent) { return true; } return false; } static RefPtr consumeMaxWidthOrHeight(CSSParserTokenRange& range, const CSSParserContext& context, UnitlessQuirk unitless = UnitlessQuirk::Forbid) { if (range.peek().id() == CSSValueNone || validWidthOrHeightKeyword(range.peek().id(), context)) return consumeIdent(range); return consumeLengthOrPercent(range, context.mode, ValueRangeNonNegative, unitless); } static RefPtr consumeWidthOrHeight(CSSParserTokenRange& range, const CSSParserContext& context, UnitlessQuirk unitless = UnitlessQuirk::Forbid) { if (range.peek().id() == CSSValueAuto || validWidthOrHeightKeyword(range.peek().id(), context)) return consumeIdent(range); return consumeLengthOrPercent(range, context.mode, ValueRangeNonNegative, unitless); } static RefPtr consumeMarginOrOffset(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless) { if (range.peek().id() == CSSValueAuto) return consumeIdent(range); return consumeLengthOrPercent(range, cssParserMode, ValueRangeAll, unitless); } static RefPtr consumeClipComponent(CSSParserTokenRange& range, CSSParserMode cssParserMode) { if (range.peek().id() == CSSValueAuto) return consumeIdent(range); return consumeLength(range, cssParserMode, ValueRangeAll, UnitlessQuirk::Allow); } static RefPtr consumeClip(CSSParserTokenRange& range, CSSParserMode cssParserMode) { if (range.peek().id() == CSSValueAuto) return consumeIdent(range); if (range.peek().functionId() != CSSValueRect) return nullptr; CSSParserTokenRange args = consumeFunction(range); // rect(t, r, b, l) || rect(t r b l) RefPtr top = consumeClipComponent(args, cssParserMode); if (!top) return nullptr; bool needsComma = consumeCommaIncludingWhitespace(args); RefPtr right = consumeClipComponent(args, cssParserMode); if (!right || (needsComma && !consumeCommaIncludingWhitespace(args))) return nullptr; RefPtr bottom = consumeClipComponent(args, cssParserMode); if (!bottom || (needsComma && !consumeCommaIncludingWhitespace(args))) return nullptr; RefPtr left = consumeClipComponent(args, cssParserMode); if (!left || !args.atEnd()) return nullptr; auto rect = Rect::create(); rect->setLeft(left.releaseNonNull()); rect->setTop(top.releaseNonNull()); rect->setRight(right.releaseNonNull()); rect->setBottom(bottom.releaseNonNull()); return CSSValuePool::singleton().createValue(WTFMove(rect)); } #if ENABLE(TOUCH_EVENTS) static RefPtr consumeTouchAction(CSSParserTokenRange& range) { RefPtr list = CSSValueList::createSpaceSeparated(); CSSValueID id = range.peek().id(); if (id == CSSValueAuto || id == CSSValueNone || id == CSSValueManipulation) { list->append(consumeIdent(range).releaseNonNull()); return list; } // FIXME-NEWPARSER: Support pan. return nullptr; } #endif static RefPtr consumeLineClamp(CSSParserTokenRange& range) { RefPtr clampValue = consumePercent(range, ValueRangeNonNegative); if (clampValue) return clampValue; // When specifying number of lines, don't allow 0 as a valid value. return consumePositiveInteger(range); } static RefPtr consumeLocale(CSSParserTokenRange& range) { if (range.peek().id() == CSSValueAuto) return consumeIdent(range); return consumeString(range); } static RefPtr consumeHyphenateLimit(CSSParserTokenRange& range, CSSValueID valueID) { if (range.peek().id() == valueID) return consumeIdent(range); return consumeNumber(range, ValueRangeNonNegative); } static RefPtr consumeColumnWidth(CSSParserTokenRange& range) { if (range.peek().id() == CSSValueAuto) return consumeIdent(range); // Always parse lengths in strict mode here, since it would be ambiguous otherwise when used in // the 'columns' shorthand property. RefPtr columnWidth = consumeLength(range, HTMLStandardMode, ValueRangeNonNegative); if (!columnWidth || (!columnWidth->isCalculated() && !columnWidth->doubleValue()) || (columnWidth->cssCalcValue() && !columnWidth->cssCalcValue()->doubleValue())) return nullptr; return columnWidth; } static RefPtr consumeColumnCount(CSSParserTokenRange& range) { if (range.peek().id() == CSSValueAuto) return consumeIdent(range); return consumePositiveInteger(range); } static RefPtr consumeColumnGap(CSSParserTokenRange& range, CSSParserMode cssParserMode) { if (range.peek().id() == CSSValueNormal) return consumeIdent(range); return consumeLength(range, cssParserMode, ValueRangeNonNegative); } static RefPtr consumeColumnSpan(CSSParserTokenRange& range) { return consumeIdent(range); } static RefPtr consumeZoom(CSSParserTokenRange& range, const CSSParserContext& /*context*/) { const CSSParserToken& token = range.peek(); RefPtr zoom; if (token.type() == IdentToken) zoom = consumeIdent(range); else { zoom = consumePercent(range, ValueRangeNonNegative); if (!zoom) zoom = consumeNumber(range, ValueRangeNonNegative); } return zoom; } static RefPtr consumeAnimationIterationCount(CSSParserTokenRange& range) { if (range.peek().id() == CSSValueInfinite) return consumeIdent(range); return consumeNumber(range, ValueRangeNonNegative); } static RefPtr consumeAnimationName(CSSParserTokenRange& range) { if (range.peek().id() == CSSValueNone) return consumeIdent(range); if (range.peek().type() == StringToken) { const CSSParserToken& token = range.consumeIncludingWhitespace(); if (equalIgnoringASCIICase(token.value(), "none")) return CSSValuePool::singleton().createIdentifierValue(CSSValueNone); // FIXME-NEWPARSER: Want to use a CSSCustomIdentValue here eventually. return CSSValuePool::singleton().createValue(token.value().toString(), CSSPrimitiveValue::UnitType::CSS_STRING); } return consumeCustomIdent(range); } static RefPtr consumeTransitionProperty(CSSParserTokenRange& range) { const CSSParserToken& token = range.peek(); if (token.type() != IdentToken) return nullptr; if (token.id() == CSSValueNone) return consumeIdent(range); if (CSSPropertyID property = token.parseAsCSSPropertyID()) { range.consumeIncludingWhitespace(); // FIXME-NEWPARSER: No reason why we can't use the "all" property now that it exists. // The old parser used a value keyword for "all", though, since it predated support for // the property. if (property == CSSPropertyAll) return CSSValuePool::singleton().createIdentifierValue(CSSValueAll); // FIXME-NEWPARSER: Want to use a CSSCustomIdentValue here eventually. return CSSValuePool::singleton().createIdentifierValue(property); } return consumeCustomIdent(range); } static RefPtr consumeSteps(CSSParserTokenRange& range) { ASSERT(range.peek().functionId() == CSSValueSteps); CSSParserTokenRange rangeCopy = range; CSSParserTokenRange args = consumeFunction(rangeCopy); RefPtr steps = consumePositiveInteger(args); if (!steps) return nullptr; // FIXME-NEWPARSER: Support the middle value and change from a boolean to an enum. bool stepAtStart = false; if (consumeCommaIncludingWhitespace(args)) { switch (args.consumeIncludingWhitespace().id()) { case CSSValueStart: stepAtStart = true; break; case CSSValueEnd: stepAtStart = false; break; default: return nullptr; } } if (!args.atEnd()) return nullptr; range = rangeCopy; return CSSStepsTimingFunctionValue::create(steps->intValue(), stepAtStart); } static RefPtr consumeCubicBezier(CSSParserTokenRange& range) { ASSERT(range.peek().functionId() == CSSValueCubicBezier); CSSParserTokenRange rangeCopy = range; CSSParserTokenRange args = consumeFunction(rangeCopy); double x1, y1, x2, y2; if (consumeNumberRaw(args, x1) && x1 >= 0 && x1 <= 1 && consumeCommaIncludingWhitespace(args) && consumeNumberRaw(args, y1) && consumeCommaIncludingWhitespace(args) && consumeNumberRaw(args, x2) && x2 >= 0 && x2 <= 1 && consumeCommaIncludingWhitespace(args) && consumeNumberRaw(args, y2) && args.atEnd()) { range = rangeCopy; return CSSCubicBezierTimingFunctionValue::create(x1, y1, x2, y2); } return nullptr; } static RefPtr consumeSpringFunction(CSSParserTokenRange& range) { ASSERT(range.peek().functionId() == CSSValueSpring); CSSParserTokenRange rangeCopy = range; CSSParserTokenRange args = consumeFunction(rangeCopy); // Mass must be greater than 0. double mass; if (!consumeNumberRaw(args, mass) || mass <= 0) return nullptr; // Stiffness must be greater than 0. double stiffness; if (!consumeNumberRaw(args, stiffness) || stiffness <= 0) return nullptr; // Damping coefficient must be greater than or equal to 0. double damping; if (!consumeNumberRaw(args, damping) || damping < 0) return nullptr; // Initial velocity may have any value. double initialVelocity; if (!consumeNumberRaw(args, initialVelocity)) return nullptr; if (!args.atEnd()) return nullptr; range = rangeCopy; return CSSSpringTimingFunctionValue::create(mass, stiffness, damping, initialVelocity); } static RefPtr consumeAnimationTimingFunction(CSSParserTokenRange& range, const CSSParserContext& context) { CSSValueID id = range.peek().id(); if (id == CSSValueEase || id == CSSValueLinear || id == CSSValueEaseIn || id == CSSValueEaseOut || id == CSSValueEaseInOut || id == CSSValueStepStart || id == CSSValueStepEnd) return consumeIdent(range); CSSValueID function = range.peek().functionId(); if (function == CSSValueCubicBezier) return consumeCubicBezier(range); if (function == CSSValueSteps) return consumeSteps(range); if (context.springTimingFunctionEnabled && function == CSSValueSpring) return consumeSpringFunction(range); return nullptr; } #if ENABLE(CSS_ANIMATIONS_LEVEL_2) static RefPtr consumeWebkitAnimationTrigger(CSSParserTokenRange& range, CSSParserMode mode) { if (range.peek().id() == CSSValueAuto) return consumeIdent(range); if (range.peek().functionId() != CSSValueContainerScroll) return nullptr; CSSParserTokenRange rangeCopy = range; CSSParserTokenRange args = consumeFunction(rangeCopy); RefPtr startValue = consumeLength(args, mode, ValueRangeAll, UnitlessQuirk::Forbid); if (!startValue) return nullptr; if (args.atEnd()) { range = rangeCopy; return CSSAnimationTriggerScrollValue::create(startValue.releaseNonNull()); } if (!consumeCommaIncludingWhitespace(args)) return nullptr; RefPtr endValue = consumeLength(args, mode, ValueRangeAll, UnitlessQuirk::Forbid); if (!endValue || !args.atEnd()) return nullptr; range = rangeCopy; return CSSAnimationTriggerScrollValue::create(startValue.releaseNonNull(), endValue.releaseNonNull()); } #endif static RefPtr consumeAnimationValue(CSSPropertyID property, CSSParserTokenRange& range, const CSSParserContext& context) { switch (property) { case CSSPropertyAnimationDelay: case CSSPropertyTransitionDelay: return consumeTime(range, context.mode, ValueRangeAll, UnitlessQuirk::Forbid); case CSSPropertyAnimationDirection: return consumeIdent(range); case CSSPropertyAnimationDuration: case CSSPropertyTransitionDuration: return consumeTime(range, context.mode, ValueRangeNonNegative, UnitlessQuirk::Allow); case CSSPropertyAnimationFillMode: return consumeIdent(range); case CSSPropertyAnimationIterationCount: return consumeAnimationIterationCount(range); case CSSPropertyAnimationName: return consumeAnimationName(range); case CSSPropertyAnimationPlayState: return consumeIdent(range); case CSSPropertyTransitionProperty: return consumeTransitionProperty(range); case CSSPropertyAnimationTimingFunction: case CSSPropertyTransitionTimingFunction: return consumeAnimationTimingFunction(range, context); #if ENABLE(CSS_ANIMATIONS_LEVEL_2) case CSSPropertyWebkitAnimationTrigger: return consumeWebkitAnimationTrigger(range, context.mode); #endif default: ASSERT_NOT_REACHED(); return nullptr; } } static bool isValidAnimationPropertyList(CSSPropertyID property, const CSSValueList& valueList) { if (property != CSSPropertyTransitionProperty || valueList.length() < 2) return true; for (auto& value : valueList) { if (value->isPrimitiveValue() && downcast(value.get()).isValueID() && downcast(value.get()).valueID() == CSSValueNone) return false; } return true; } static RefPtr consumeAnimationPropertyList(CSSPropertyID property, CSSParserTokenRange& range, const CSSParserContext& context) { RefPtr list; RefPtr singleton; do { RefPtr currentValue = consumeAnimationValue(property, range, context); if (!currentValue) return nullptr; if (singleton && !list) { list = CSSValueList::createCommaSeparated(); list->append(singleton.releaseNonNull()); } if (list) list->append(currentValue.releaseNonNull()); else singleton = WTFMove(currentValue); } while (consumeCommaIncludingWhitespace(range)); if (list) { if (!isValidAnimationPropertyList(property, *list)) return nullptr; ASSERT(list->length()); return list; } return singleton; } bool CSSPropertyParser::consumeAnimationShorthand(const StylePropertyShorthand& shorthand, bool important) { const unsigned longhandCount = shorthand.length(); RefPtr longhands[8]; ASSERT(longhandCount <= 8); for (size_t i = 0; i < longhandCount; ++i) longhands[i] = CSSValueList::createCommaSeparated(); do { bool parsedLonghand[8] = { false }; do { bool foundProperty = false; for (size_t i = 0; i < longhandCount; ++i) { if (parsedLonghand[i]) continue; if (RefPtr value = consumeAnimationValue(shorthand.properties()[i], m_range, m_context)) { parsedLonghand[i] = true; foundProperty = true; longhands[i]->append(*value); break; } } if (!foundProperty) return false; } while (!m_range.atEnd() && m_range.peek().type() != CommaToken); // FIXME: This will make invalid longhands, see crbug.com/386459 for (size_t i = 0; i < longhandCount; ++i) { if (!parsedLonghand[i]) longhands[i]->append(CSSValuePool::singleton().createImplicitInitialValue()); parsedLonghand[i] = false; } } while (consumeCommaIncludingWhitespace(m_range)); for (size_t i = 0; i < longhandCount; ++i) { if (!isValidAnimationPropertyList(shorthand.properties()[i], *longhands[i])) return false; } for (size_t i = 0; i < longhandCount; ++i) addProperty(shorthand.properties()[i], shorthand.id(), *longhands[i], important); return m_range.atEnd(); } static RefPtr consumeZIndex(CSSParserTokenRange& range) { if (range.peek().id() == CSSValueAuto) return consumeIdent(range); return consumeInteger(range); } static RefPtr consumeShadow(CSSParserTokenRange& range, CSSParserMode cssParserMode, bool isBoxShadowProperty) { if (range.peek().id() == CSSValueNone) return consumeIdent(range); RefPtr shadowValueList = CSSValueList::createCommaSeparated(); do { if (RefPtr shadowValue = consumeSingleShadow(range, cssParserMode, isBoxShadowProperty, isBoxShadowProperty)) shadowValueList->append(*shadowValue); else return nullptr; } while (consumeCommaIncludingWhitespace(range)); return shadowValueList; } static RefPtr consumeTextDecorationLine(CSSParserTokenRange& range) { CSSValueID id = range.peek().id(); if (id == CSSValueNone) return consumeIdent(range); RefPtr list = CSSValueList::createSpaceSeparated(); while (true) { #if ENABLE(LETTERPRESS) RefPtr ident = consumeIdent(range); #else RefPtr ident = consumeIdent(range); #endif if (!ident) break; if (list->hasValue(ident.get())) return nullptr; list->append(ident.releaseNonNull()); } if (!list->length()) return nullptr; return list; } static RefPtr consumeTextDecorationSkip(CSSParserTokenRange& range) { CSSValueID id = range.peek().id(); if (id == CSSValueNone) return consumeIdent(range); RefPtr list = CSSValueList::createSpaceSeparated(); while (true) { auto ident = consumeIdent(range); if (!ident) break; if (list->hasValue(ident.get())) return nullptr; list->append(ident.releaseNonNull()); } if (!list->length()) return nullptr; return list; } static RefPtr consumeTextEmphasisStyle(CSSParserTokenRange& range) { CSSValueID id = range.peek().id(); if (id == CSSValueNone) return consumeIdent(range); if (RefPtr textEmphasisStyle = consumeString(range)) return textEmphasisStyle; RefPtr fill = consumeIdent(range); RefPtr shape = consumeIdent(range); if (!fill) fill = consumeIdent(range); if (fill && shape) { RefPtr parsedValues = CSSValueList::createSpaceSeparated(); parsedValues->append(fill.releaseNonNull()); parsedValues->append(shape.releaseNonNull()); return parsedValues; } if (fill) return fill; if (shape) return shape; return nullptr; } static RefPtr consumeOutlineColor(CSSParserTokenRange& range, CSSParserMode cssParserMode) { // Allow the special focus color even in HTML Standard parsing mode. if (range.peek().id() == CSSValueWebkitFocusRingColor) return consumeIdent(range); return consumeColor(range, cssParserMode); } static RefPtr consumeLineWidth(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless) { CSSValueID id = range.peek().id(); if (id == CSSValueThin || id == CSSValueMedium || id == CSSValueThick) return consumeIdent(range); return consumeLength(range, cssParserMode, ValueRangeNonNegative, unitless); } static RefPtr consumeBorderWidth(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless) { return consumeLineWidth(range, cssParserMode, unitless); } static RefPtr consumeTextStrokeWidth(CSSParserTokenRange& range, CSSParserMode cssParserMode) { return consumeLineWidth(range, cssParserMode, UnitlessQuirk::Forbid); } static RefPtr consumeColumnRuleWidth(CSSParserTokenRange& range, CSSParserMode cssParserMode) { return consumeLineWidth(range, cssParserMode, UnitlessQuirk::Forbid); } static bool consumeTranslate3d(CSSParserTokenRange& args, CSSParserMode cssParserMode, RefPtr& transformValue) { unsigned numberOfArguments = 2; RefPtr parsedValue; do { parsedValue = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll); if (!parsedValue) return false; transformValue->append(*parsedValue); if (!consumeCommaIncludingWhitespace(args)) return false; } while (--numberOfArguments); parsedValue = consumeLength(args, cssParserMode, ValueRangeAll); if (!parsedValue) return false; transformValue->append(*parsedValue); return true; } static bool consumeNumbers(CSSParserTokenRange& args, RefPtr& transformValue, unsigned numberOfArguments) { do { RefPtr parsedValue = consumeNumber(args, ValueRangeAll); if (!parsedValue) return false; transformValue->append(parsedValue.releaseNonNull()); if (--numberOfArguments && !consumeCommaIncludingWhitespace(args)) return false; } while (numberOfArguments); return true; } static bool consumePerspective(CSSParserTokenRange& args, CSSParserMode cssParserMode, RefPtr& transformValue) { RefPtr parsedValue = consumeLength(args, cssParserMode, ValueRangeNonNegative); if (!parsedValue) { double perspective; if (!consumeNumberRaw(args, perspective) || perspective < 0) return false; parsedValue = CSSPrimitiveValue::create(perspective, CSSPrimitiveValue::UnitType::CSS_PX); } if (!parsedValue) return false; transformValue->append(parsedValue.releaseNonNull()); return true; } static RefPtr consumeTransformValue(CSSParserTokenRange& range, CSSParserMode cssParserMode) { CSSValueID functionId = range.peek().functionId(); if (functionId == CSSValueInvalid) return nullptr; CSSParserTokenRange args = consumeFunction(range); if (args.atEnd()) return nullptr; RefPtr transformValue = CSSFunctionValue::create(functionId); RefPtr parsedValue; switch (functionId) { case CSSValueRotate: case CSSValueRotateX: case CSSValueRotateY: case CSSValueRotateZ: case CSSValueSkewX: case CSSValueSkewY: case CSSValueSkew: parsedValue = consumeAngle(args, cssParserMode, UnitlessQuirk::Forbid); if (!parsedValue) return nullptr; if (functionId == CSSValueSkew && consumeCommaIncludingWhitespace(args)) { transformValue->append(*parsedValue); parsedValue = consumeAngle(args, cssParserMode, UnitlessQuirk::Forbid); if (!parsedValue) return nullptr; } break; case CSSValueScaleX: case CSSValueScaleY: case CSSValueScaleZ: case CSSValueScale: parsedValue = consumeNumber(args, ValueRangeAll); if (!parsedValue) return nullptr; if (functionId == CSSValueScale && consumeCommaIncludingWhitespace(args)) { transformValue->append(*parsedValue); parsedValue = consumeNumber(args, ValueRangeAll); if (!parsedValue) return nullptr; } break; case CSSValuePerspective: if (!consumePerspective(args, cssParserMode, transformValue)) return nullptr; break; case CSSValueTranslateX: case CSSValueTranslateY: case CSSValueTranslate: parsedValue = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll); if (!parsedValue) return nullptr; if (functionId == CSSValueTranslate && consumeCommaIncludingWhitespace(args)) { transformValue->append(*parsedValue); parsedValue = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll); if (!parsedValue) return nullptr; } break; case CSSValueTranslateZ: parsedValue = consumeLength(args, cssParserMode, ValueRangeAll); break; case CSSValueMatrix: case CSSValueMatrix3d: if (!consumeNumbers(args, transformValue, (functionId == CSSValueMatrix3d) ? 16 : 6)) return nullptr; break; case CSSValueScale3d: if (!consumeNumbers(args, transformValue, 3)) return nullptr; break; case CSSValueRotate3d: if (!consumeNumbers(args, transformValue, 3) || !consumeCommaIncludingWhitespace(args)) return nullptr; parsedValue = consumeAngle(args, cssParserMode, UnitlessQuirk::Forbid); if (!parsedValue) return nullptr; break; case CSSValueTranslate3d: if (!consumeTranslate3d(args, cssParserMode, transformValue)) return nullptr; break; default: return nullptr; } if (parsedValue) transformValue->append(*parsedValue); if (!args.atEnd()) return nullptr; return transformValue; } static RefPtr consumeTransform(CSSParserTokenRange& range, CSSParserMode cssParserMode) { if (range.peek().id() == CSSValueNone) return consumeIdent(range); RefPtr list = CSSValueList::createSpaceSeparated(); do { RefPtr parsedTransformValue = consumeTransformValue(range, cssParserMode); if (!parsedTransformValue) return nullptr; list->append(parsedTransformValue.releaseNonNull()); } while (!range.atEnd()); return list; } template static RefPtr consumePositionLonghand(CSSParserTokenRange& range, CSSParserMode cssParserMode) { if (range.peek().type() == IdentToken) { CSSValueID id = range.peek().id(); int percent; if (id == start) percent = 0; else if (id == CSSValueCenter) percent = 50; else if (id == end) percent = 100; else return nullptr; range.consumeIncludingWhitespace(); return CSSPrimitiveValue::create(percent, CSSPrimitiveValue::UnitType::CSS_PERCENTAGE); } return consumeLengthOrPercent(range, cssParserMode, ValueRangeAll); } static RefPtr consumePositionX(CSSParserTokenRange& range, CSSParserMode cssParserMode) { return consumePositionLonghand(range, cssParserMode); } static RefPtr consumePositionY(CSSParserTokenRange& range, CSSParserMode cssParserMode) { return consumePositionLonghand(range, cssParserMode); } static RefPtr consumePaintStroke(CSSParserTokenRange& range, CSSParserMode cssParserMode) { if (range.peek().id() == CSSValueNone) return consumeIdent(range); RefPtr url = consumeUrl(range); if (url) { RefPtr parsedValue; if (range.peek().id() == CSSValueNone) parsedValue = consumeIdent(range); else parsedValue = consumeColor(range, cssParserMode); if (parsedValue) { RefPtr values = CSSValueList::createSpaceSeparated(); values->append(url.releaseNonNull()); values->append(parsedValue.releaseNonNull()); return values; } return url; } return consumeColor(range, cssParserMode); } static RefPtr consumeGlyphOrientation(CSSParserTokenRange& range, CSSParserMode mode, CSSPropertyID property) { if (range.peek().id() == CSSValueAuto) { if (property == CSSPropertyGlyphOrientationVertical) return consumeIdent(range); return nullptr; } return consumeAngle(range, mode, UnitlessQuirk::Allow); } static RefPtr consumePaintOrder(CSSParserTokenRange& range) { if (range.peek().id() == CSSValueNormal) return consumeIdent(range); Vector paintTypeList; RefPtr fill; RefPtr stroke; RefPtr markers; do { CSSValueID id = range.peek().id(); if (id == CSSValueFill && !fill) fill = consumeIdent(range); else if (id == CSSValueStroke && !stroke) stroke = consumeIdent(range); else if (id == CSSValueMarkers && !markers) markers = consumeIdent(range); else return nullptr; paintTypeList.append(id); } while (!range.atEnd()); // After parsing we serialize the paint-order list. Since it is not possible to // pop a last list items from CSSValueList without bigger cost, we create the // list after parsing. CSSValueID firstPaintOrderType = paintTypeList.at(0); RefPtr paintOrderList = CSSValueList::createSpaceSeparated(); switch (firstPaintOrderType) { case CSSValueFill: case CSSValueStroke: paintOrderList->append(firstPaintOrderType == CSSValueFill ? fill.releaseNonNull() : stroke.releaseNonNull()); if (paintTypeList.size() > 1) { if (paintTypeList.at(1) == CSSValueMarkers) paintOrderList->append(markers.releaseNonNull()); } break; case CSSValueMarkers: paintOrderList->append(markers.releaseNonNull()); if (paintTypeList.size() > 1) { if (paintTypeList.at(1) == CSSValueStroke) paintOrderList->append(stroke.releaseNonNull()); } break; default: ASSERT_NOT_REACHED(); } return paintOrderList; } static RefPtr consumeNoneOrURI(CSSParserTokenRange& range) { if (range.peek().id() == CSSValueNone) return consumeIdent(range); return consumeUrl(range); } static RefPtr consumeFlexBasis(CSSParserTokenRange& range, CSSParserMode cssParserMode) { // FIXME: Support intrinsic dimensions too. if (range.peek().id() == CSSValueAuto) return consumeIdent(range); return consumeLengthOrPercent(range, cssParserMode, ValueRangeNonNegative); } static RefPtr consumeKerning(CSSParserTokenRange& range, CSSParserMode mode) { RefPtr result = consumeIdent(range); if (result) return result; return consumeLength(range, mode, ValueRangeAll, UnitlessQuirk::Allow); } static RefPtr consumeStrokeDasharray(CSSParserTokenRange& range) { CSSValueID id = range.peek().id(); if (id == CSSValueNone) return consumeIdent(range); RefPtr dashes = CSSValueList::createCommaSeparated(); do { RefPtr dash = consumeLengthOrPercent(range, SVGAttributeMode, ValueRangeNonNegative); if (!dash || (consumeCommaIncludingWhitespace(range) && range.atEnd())) return nullptr; dashes->append(dash.releaseNonNull()); } while (!range.atEnd()); return dashes; } static RefPtr consumeBaselineShift(CSSParserTokenRange& range) { CSSValueID id = range.peek().id(); if (id == CSSValueBaseline || id == CSSValueSub || id == CSSValueSuper) return consumeIdent(range); return consumeLengthOrPercent(range, SVGAttributeMode, ValueRangeAll); } static RefPtr consumeRxOrRy(CSSParserTokenRange& range) { // FIXME-NEWPARSER: We don't support auto values when mapping, so for now turn this // off until we can figure out if we're even supposed to support it. // if (range.peek().id() == CSSValueAuto) // return consumeIdent(range); return consumeLengthOrPercent(range, SVGAttributeMode, ValueRangeAll, UnitlessQuirk::Forbid); } static RefPtr consumeCursor(CSSParserTokenRange& range, const CSSParserContext& context, bool inQuirksMode) { RefPtr list; while (RefPtr image = consumeImage(range, context, ConsumeGeneratedImage::Forbid)) { double num; IntPoint hotSpot(-1, -1); bool hotSpotSpecified = false; if (consumeNumberRaw(range, num)) { hotSpot.setX(int(num)); if (!consumeNumberRaw(range, num)) return nullptr; hotSpot.setY(int(num)); hotSpotSpecified = true; } if (!list) list = CSSValueList::createCommaSeparated(); list->append(CSSCursorImageValue::create(image.releaseNonNull(), hotSpotSpecified, hotSpot)); if (!consumeCommaIncludingWhitespace(range)) return nullptr; } CSSValueID id = range.peek().id(); RefPtr cursorType; if (id == CSSValueHand) { if (!inQuirksMode) // Non-standard behavior return nullptr; cursorType = CSSValuePool::singleton().createIdentifierValue(CSSValuePointer); range.consumeIncludingWhitespace(); } else if ((id >= CSSValueAuto && id <= CSSValueWebkitZoomOut) || id == CSSValueCopy || id == CSSValueNone) { cursorType = consumeIdent(range); } else { return nullptr; } if (!list) return cursorType; list->append(cursorType.releaseNonNull()); return list; } static RefPtr consumeAttr(CSSParserTokenRange args, CSSParserContext context) { if (args.peek().type() != IdentToken) return nullptr; CSSParserToken token = args.consumeIncludingWhitespace(); auto attrName = token.value().toAtomicString(); if (context.isHTMLDocument) attrName = attrName.convertToASCIILowercase(); if (!args.atEnd()) return nullptr; // FIXME-NEWPARSER: We want to use a CSSCustomIdentValue here eventually for the attrName. // FIXME-NEWPARSER: We want to use a CSSFunctionValue rather than relying on a custom // attr() primitive value. return CSSValuePool::singleton().createValue(attrName, CSSPrimitiveValue::CSS_ATTR); } static RefPtr consumeCounterContent(CSSParserTokenRange args, bool counters) { RefPtr identifier = consumeCustomIdent(args); if (!identifier) return nullptr; RefPtr separator; if (!counters) separator = CSSPrimitiveValue::create(String(), CSSPrimitiveValue::UnitType::CSS_STRING); else { if (!consumeCommaIncludingWhitespace(args) || args.peek().type() != StringToken) return nullptr; separator = CSSPrimitiveValue::create(args.consumeIncludingWhitespace().value().toString(), CSSPrimitiveValue::UnitType::CSS_STRING); } RefPtr listStyle; if (consumeCommaIncludingWhitespace(args)) { CSSValueID id = args.peek().id(); if ((id != CSSValueNone && (id < CSSValueDisc || id > CSSValueKatakanaIroha))) return nullptr; listStyle = consumeIdent(args); } else listStyle = CSSValuePool::singleton().createIdentifierValue(CSSValueDecimal); if (!args.atEnd()) return nullptr; // FIXME-NEWPARSER: Should just have a CSSCounterValue. return CSSValuePool::singleton().createValue(Counter::create(identifier.releaseNonNull(), listStyle.releaseNonNull(), separator.releaseNonNull())); } static RefPtr consumeContent(CSSParserTokenRange& range, CSSParserContext context) { if (identMatches(range.peek().id())) return consumeIdent(range); RefPtr values = CSSValueList::createSpaceSeparated(); do { RefPtr parsedValue = consumeImage(range, context); if (!parsedValue) parsedValue = consumeIdent(range); if (!parsedValue) parsedValue = consumeString(range); if (!parsedValue) { if (range.peek().functionId() == CSSValueAttr) parsedValue = consumeAttr(consumeFunction(range), context); else if (range.peek().functionId() == CSSValueCounter) parsedValue = consumeCounterContent(consumeFunction(range), false); else if (range.peek().functionId() == CSSValueCounters) parsedValue = consumeCounterContent(consumeFunction(range), true); if (!parsedValue) return nullptr; } values->append(parsedValue.releaseNonNull()); } while (!range.atEnd()); return values; } static RefPtr consumePerspective(CSSParserTokenRange& range, CSSParserMode cssParserMode) { if (range.peek().id() == CSSValueNone) return consumeIdent(range); RefPtr parsedValue = consumeLength(range, cssParserMode, ValueRangeAll); if (!parsedValue) { // FIXME: Make this quirk only apply to the webkit prefixed version of the property. double perspective; if (!consumeNumberRaw(range, perspective)) return nullptr; parsedValue = CSSPrimitiveValue::create(perspective, CSSPrimitiveValue::UnitType::CSS_PX); } if (parsedValue && (parsedValue->isCalculated() || parsedValue->doubleValue() > 0)) return parsedValue; return nullptr; } #if ENABLE(CSS_SCROLL_SNAP) static RefPtr consumeScrollSnapAlign(CSSParserTokenRange& range) { RefPtr alignmentValue = CSSValueList::createSpaceSeparated(); if (RefPtr firstValue = consumeIdent(range)) { alignmentValue->append(firstValue.releaseNonNull()); if (auto secondValue = consumeIdent(range)) alignmentValue->append(secondValue.releaseNonNull()); } return alignmentValue->length() ? alignmentValue : nullptr; } static RefPtr consumeScrollSnapType(CSSParserTokenRange& range) { RefPtr typeValue = CSSValueList::createSpaceSeparated(); RefPtr secondValue; auto firstValue = consumeIdent(range); if (firstValue) secondValue = consumeIdent(range); else firstValue = consumeIdent(range); if (!firstValue) return nullptr; typeValue->append(firstValue.releaseNonNull()); if (secondValue) typeValue->append(secondValue.releaseNonNull()); return typeValue; } #endif static RefPtr consumeBorderRadiusCorner(CSSParserTokenRange& range, CSSParserMode cssParserMode) { RefPtr parsedValue1 = consumeLengthOrPercent(range, cssParserMode, ValueRangeNonNegative); if (!parsedValue1) return nullptr; RefPtr parsedValue2 = consumeLengthOrPercent(range, cssParserMode, ValueRangeNonNegative); if (!parsedValue2) parsedValue2 = parsedValue1; return createPrimitiveValuePair(parsedValue1.releaseNonNull(), parsedValue2.releaseNonNull(), Pair::IdenticalValueEncoding::Coalesce); } static RefPtr consumeVerticalAlign(CSSParserTokenRange& range, CSSParserMode cssParserMode) { RefPtr parsedValue = consumeIdentRange(range, CSSValueBaseline, CSSValueWebkitBaselineMiddle); if (!parsedValue) parsedValue = consumeLengthOrPercent(range, cssParserMode, ValueRangeAll, UnitlessQuirk::Allow); return parsedValue; } static RefPtr consumeShapeRadius(CSSParserTokenRange& args, CSSParserMode cssParserMode) { if (identMatches(args.peek().id())) return consumeIdent(args); return consumeLengthOrPercent(args, cssParserMode, ValueRangeNonNegative); } static RefPtr consumeBasicShapeCircle(CSSParserTokenRange& args, const CSSParserContext& context) { // spec: https://drafts.csswg.org/css-shapes/#supported-basic-shapes // circle( []? [at ]? ) RefPtr shape = CSSBasicShapeCircle::create(); if (RefPtr radius = consumeShapeRadius(args, context.mode)) shape->setRadius(radius.releaseNonNull()); if (consumeIdent(args)) { RefPtr centerX; RefPtr centerY; if (!consumePosition(args, context.mode, UnitlessQuirk::Forbid, centerX, centerY)) return nullptr; shape->setCenterX(centerX.releaseNonNull()); shape->setCenterY(centerY.releaseNonNull()); } return shape; } static RefPtr consumeBasicShapeEllipse(CSSParserTokenRange& args, const CSSParserContext& context) { // spec: https://drafts.csswg.org/css-shapes/#supported-basic-shapes // ellipse( [{2}]? [at ]? ) RefPtr shape = CSSBasicShapeEllipse::create(); if (RefPtr radiusX = consumeShapeRadius(args, context.mode)) { shape->setRadiusX(radiusX.releaseNonNull()); if (RefPtr radiusY = consumeShapeRadius(args, context.mode)) shape->setRadiusY(radiusY.releaseNonNull()); } if (consumeIdent(args)) { RefPtr centerX; RefPtr centerY; if (!consumePosition(args, context.mode, UnitlessQuirk::Forbid, centerX, centerY)) return nullptr; shape->setCenterX(centerX.releaseNonNull()); shape->setCenterY(centerY.releaseNonNull()); } return shape; } static RefPtr consumeBasicShapePolygon(CSSParserTokenRange& args, const CSSParserContext& context) { RefPtr shape = CSSBasicShapePolygon::create(); if (identMatches(args.peek().id())) { shape->setWindRule(args.consumeIncludingWhitespace().id() == CSSValueEvenodd ? RULE_EVENODD : RULE_NONZERO); if (!consumeCommaIncludingWhitespace(args)) return nullptr; } do { RefPtr xLength = consumeLengthOrPercent(args, context.mode, ValueRangeAll); if (!xLength) return nullptr; RefPtr yLength = consumeLengthOrPercent(args, context.mode, ValueRangeAll); if (!yLength) return nullptr; shape->appendPoint(xLength.releaseNonNull(), yLength.releaseNonNull()); } while (consumeCommaIncludingWhitespace(args)); return shape; } static RefPtr consumeBasicShapePath(CSSParserTokenRange& args) { WindRule windRule = RULE_NONZERO; if (identMatches(args.peek().id())) { windRule = args.consumeIncludingWhitespace().id() == CSSValueEvenodd ? RULE_EVENODD : RULE_NONZERO; if (!consumeCommaIncludingWhitespace(args)) return nullptr; } if (args.peek().type() != StringToken) return nullptr; auto byteStream = std::make_unique(); if (!buildSVGPathByteStreamFromString(args.consumeIncludingWhitespace().value().toString(), *byteStream, UnalteredParsing)) return nullptr; auto shape = CSSBasicShapePath::create(WTFMove(byteStream)); shape->setWindRule(windRule); return WTFMove(shape); } static void complete4Sides(RefPtr side[4]) { if (side[3]) return; if (!side[2]) { if (!side[1]) side[1] = side[0]; side[2] = side[0]; } side[3] = side[1]; } static bool consumeRadii(RefPtr horizontalRadii[4], RefPtr verticalRadii[4], CSSParserTokenRange& range, CSSParserMode cssParserMode, bool useLegacyParsing) { unsigned i = 0; for (; i < 4 && !range.atEnd() && range.peek().type() != DelimiterToken; ++i) { horizontalRadii[i] = consumeLengthOrPercent(range, cssParserMode, ValueRangeNonNegative); if (!horizontalRadii[i]) return false; } if (!horizontalRadii[0]) return false; if (range.atEnd()) { // Legacy syntax: -webkit-border-radius: l1 l2; is equivalent to border-radius: l1 / l2; if (useLegacyParsing && i == 2) { verticalRadii[0] = horizontalRadii[1]; horizontalRadii[1] = nullptr; } else { complete4Sides(horizontalRadii); for (unsigned i = 0; i < 4; ++i) verticalRadii[i] = horizontalRadii[i]; return true; } } else { if (!consumeSlashIncludingWhitespace(range)) return false; for (i = 0; i < 4 && !range.atEnd(); ++i) { verticalRadii[i] = consumeLengthOrPercent(range, cssParserMode, ValueRangeNonNegative); if (!verticalRadii[i]) return false; } if (!verticalRadii[0] || !range.atEnd()) return false; } complete4Sides(horizontalRadii); complete4Sides(verticalRadii); return true; } static RefPtr consumeBasicShapeInset(CSSParserTokenRange& args, const CSSParserContext& context) { RefPtr shape = CSSBasicShapeInset::create(); RefPtr top = consumeLengthOrPercent(args, context.mode, ValueRangeAll); if (!top) return nullptr; RefPtr right = consumeLengthOrPercent(args, context.mode, ValueRangeAll); RefPtr bottom; RefPtr left; if (right) { bottom = consumeLengthOrPercent(args, context.mode, ValueRangeAll); if (bottom) left = consumeLengthOrPercent(args, context.mode, ValueRangeAll); } if (left) shape->updateShapeSize4Values(top.releaseNonNull(), right.releaseNonNull(), bottom.releaseNonNull(), left.releaseNonNull()); else if (bottom) shape->updateShapeSize3Values(top.releaseNonNull(), right.releaseNonNull(), bottom.releaseNonNull()); else if (right) shape->updateShapeSize2Values(top.releaseNonNull(), right.releaseNonNull()); else shape->updateShapeSize1Value(top.releaseNonNull()); if (consumeIdent(args)) { RefPtr horizontalRadii[4] = { 0 }; RefPtr verticalRadii[4] = { 0 }; if (!consumeRadii(horizontalRadii, verticalRadii, args, context.mode, false)) return nullptr; shape->setTopLeftRadius(createPrimitiveValuePair(horizontalRadii[0].releaseNonNull(), verticalRadii[0].releaseNonNull(), Pair::IdenticalValueEncoding::Coalesce)); shape->setTopRightRadius(createPrimitiveValuePair(horizontalRadii[1].releaseNonNull(), verticalRadii[1].releaseNonNull(), Pair::IdenticalValueEncoding::Coalesce)); shape->setBottomRightRadius(createPrimitiveValuePair(horizontalRadii[2].releaseNonNull(), verticalRadii[2].releaseNonNull(), Pair::IdenticalValueEncoding::Coalesce)); shape->setBottomLeftRadius(createPrimitiveValuePair(horizontalRadii[3].releaseNonNull(), verticalRadii[3].releaseNonNull(), Pair::IdenticalValueEncoding::Coalesce)); } return shape; } static RefPtr consumeBasicShape(CSSParserTokenRange& range, const CSSParserContext& context) { RefPtr result; if (range.peek().type() != FunctionToken) return nullptr; CSSValueID id = range.peek().functionId(); CSSParserTokenRange rangeCopy = range; CSSParserTokenRange args = consumeFunction(rangeCopy); // FIXME-NEWPARSER: CSSBasicShape should be a CSSValue, and shapes should not be primitive values. RefPtr shape; if (id == CSSValueCircle) shape = consumeBasicShapeCircle(args, context); else if (id == CSSValueEllipse) shape = consumeBasicShapeEllipse(args, context); else if (id == CSSValuePolygon) shape = consumeBasicShapePolygon(args, context); else if (id == CSSValueInset) shape = consumeBasicShapeInset(args, context); else if (id == CSSValuePath) shape = consumeBasicShapePath(args); if (!shape) return nullptr; range = rangeCopy; if (!args.atEnd()) return nullptr; return CSSValuePool::singleton().createValue(shape.releaseNonNull()); } static RefPtr consumeBasicShapeOrBox(CSSParserTokenRange& range, const CSSParserContext& context) { RefPtr list = CSSValueList::createSpaceSeparated(); bool shapeFound = false; bool boxFound = false; while (!range.atEnd() && !(shapeFound && boxFound)) { RefPtr componentValue; if (range.peek().type() == FunctionToken && !shapeFound) { componentValue = consumeBasicShape(range, context); shapeFound = true; } else if (range.peek().type() == IdentToken && !boxFound) { componentValue = consumeIdent(range); boxFound = true; } if (!componentValue) return nullptr; list->append(componentValue.releaseNonNull()); } if (!range.atEnd() || !list->length()) return nullptr; return list; } static RefPtr consumeWebkitClipPath(CSSParserTokenRange& range, const CSSParserContext& context) { if (range.peek().id() == CSSValueNone) return consumeIdent(range); if (RefPtr url = consumeUrl(range)) return url; return consumeBasicShapeOrBox(range, context); } static RefPtr consumeShapeOutside(CSSParserTokenRange& range, const CSSParserContext& context) { if (RefPtr imageValue = consumeImageOrNone(range, context)) return imageValue; RefPtr list = CSSValueList::createSpaceSeparated(); if (RefPtr boxValue = consumeIdent(range)) list->append(boxValue.releaseNonNull()); if (RefPtr shapeValue = consumeBasicShape(range, context)) { list->append(shapeValue.releaseNonNull()); if (list->length() < 2) { if (RefPtr boxValue = consumeIdent(range)) list->append(boxValue.releaseNonNull()); } } if (!list->length()) return nullptr; return list; } static RefPtr consumeContentDistributionOverflowPosition(CSSParserTokenRange& range) { if (identMatches(range.peek().id())) return CSSContentDistributionValue::create(CSSValueInvalid, range.consumeIncludingWhitespace().id(), CSSValueInvalid); CSSValueID distribution = CSSValueInvalid; CSSValueID position = CSSValueInvalid; CSSValueID overflow = CSSValueInvalid; do { CSSValueID id = range.peek().id(); if (identMatches(id)) { if (distribution != CSSValueInvalid) return nullptr; distribution = id; } else if (identMatches(id)) { if (position != CSSValueInvalid) return nullptr; position = id; } else if (identMatches(id)) { if (overflow != CSSValueInvalid) return nullptr; overflow = id; } else { return nullptr; } range.consumeIncludingWhitespace(); } while (!range.atEnd()); // The grammar states that we should have at least or . if (position == CSSValueInvalid && distribution == CSSValueInvalid) return nullptr; // The grammar states that must be associated to . if (overflow != CSSValueInvalid && position == CSSValueInvalid) return nullptr; return CSSContentDistributionValue::create(distribution, position, overflow); } static RefPtr consumeBorderImageRepeatKeyword(CSSParserTokenRange& range) { return consumeIdent(range); } static RefPtr consumeBorderImageRepeat(CSSParserTokenRange& range) { RefPtr horizontal = consumeBorderImageRepeatKeyword(range); if (!horizontal) return nullptr; RefPtr vertical = consumeBorderImageRepeatKeyword(range); if (!vertical) vertical = horizontal; return createPrimitiveValuePair(horizontal.releaseNonNull(), vertical.releaseNonNull(), Pair::IdenticalValueEncoding::Coalesce); } static RefPtr consumeBorderImageSlice(CSSPropertyID property, CSSParserTokenRange& range) { bool fill = consumeIdent(range); RefPtr slices[4] = { 0 }; for (size_t index = 0; index < 4; ++index) { RefPtr value = consumePercent(range, ValueRangeNonNegative); if (!value) value = consumeNumber(range, ValueRangeNonNegative); if (!value) break; slices[index] = value; } if (!slices[0]) return nullptr; if (consumeIdent(range)) { if (fill) return nullptr; fill = true; } complete4Sides(slices); // FIXME: For backwards compatibility, -webkit-border-image, -webkit-mask-box-image and -webkit-box-reflect have to do a fill by default. // FIXME: What do we do with -webkit-box-reflect and -webkit-mask-box-image? Probably just have to leave them filling... if (property == CSSPropertyWebkitBorderImage || property == CSSPropertyWebkitMaskBoxImage || property == CSSPropertyWebkitBoxReflect) fill = true; // Now build a rect value to hold all four of our primitive values. // FIXME-NEWPARSER: Should just have a CSSQuadValue. auto quad = Quad::create(); quad->setTop(slices[0].releaseNonNull()); quad->setRight(slices[1].releaseNonNull()); quad->setBottom(slices[2].releaseNonNull()); quad->setLeft(slices[3].releaseNonNull()); // Make our new border image value now. return CSSBorderImageSliceValue::create(CSSValuePool::singleton().createValue(WTFMove(quad)), fill); } static RefPtr consumeBorderImageOutset(CSSParserTokenRange& range) { RefPtr outsets[4] = { 0 }; RefPtr value; for (size_t index = 0; index < 4; ++index) { value = consumeNumber(range, ValueRangeNonNegative); if (!value) value = consumeLength(range, HTMLStandardMode, ValueRangeNonNegative); if (!value) break; outsets[index] = value; } if (!outsets[0]) return nullptr; complete4Sides(outsets); // FIXME-NEWPARSER: Should just have a CSSQuadValue. auto quad = Quad::create(); quad->setTop(outsets[0].releaseNonNull()); quad->setRight(outsets[1].releaseNonNull()); quad->setBottom(outsets[2].releaseNonNull()); quad->setLeft(outsets[3].releaseNonNull()); return CSSValuePool::singleton().createValue(WTFMove(quad)); } static RefPtr consumeBorderImageWidth(CSSParserTokenRange& range) { RefPtr widths[4]; RefPtr value; for (size_t index = 0; index < 4; ++index) { value = consumeNumber(range, ValueRangeNonNegative); if (!value) value = consumeLengthOrPercent(range, HTMLStandardMode, ValueRangeNonNegative, UnitlessQuirk::Forbid); if (!value) value = consumeIdent(range); if (!value) break; widths[index] = value; } if (!widths[0]) return nullptr; complete4Sides(widths); // FIXME-NEWPARSER: Should just have a CSSQuadValue. auto quad = Quad::create(); quad->setTop(widths[0].releaseNonNull()); quad->setRight(widths[1].releaseNonNull()); quad->setBottom(widths[2].releaseNonNull()); quad->setLeft(widths[3].releaseNonNull()); return CSSValuePool::singleton().createValue(WTFMove(quad)); } static bool consumeBorderImageComponents(CSSPropertyID property, CSSParserTokenRange& range, const CSSParserContext& context, RefPtr& source, RefPtr& slice, RefPtr& width, RefPtr& outset, RefPtr& repeat) { do { if (!source) { source = consumeImageOrNone(range, context); if (source) continue; } if (!repeat) { repeat = consumeBorderImageRepeat(range); if (repeat) continue; } if (!slice) { slice = consumeBorderImageSlice(property, range); if (slice) { ASSERT(!width && !outset); if (consumeSlashIncludingWhitespace(range)) { width = consumeBorderImageWidth(range); if (consumeSlashIncludingWhitespace(range)) { outset = consumeBorderImageOutset(range); if (!outset) return false; } else if (!width) { return false; } } } else { return false; } } else { return false; } } while (!range.atEnd()); return true; } static RefPtr consumeWebkitBorderImage(CSSPropertyID property, CSSParserTokenRange& range, const CSSParserContext& context) { RefPtr source; RefPtr slice; RefPtr width; RefPtr outset; RefPtr repeat; if (consumeBorderImageComponents(property, range, context, source, slice, width, outset, repeat)) return createBorderImageValue(WTFMove(source), WTFMove(slice), WTFMove(width), WTFMove(outset), WTFMove(repeat)); return nullptr; } static RefPtr consumeReflect(CSSParserTokenRange& range, const CSSParserContext& context) { if (range.peek().id() == CSSValueNone) return consumeIdent(range); RefPtr direction = consumeIdent(range); if (!direction) return nullptr; RefPtr offset; if (range.atEnd()) offset = CSSValuePool::singleton().createValue(0, CSSPrimitiveValue::UnitType::CSS_PX); else { offset = consumeLengthOrPercent(range, context.mode, ValueRangeAll, UnitlessQuirk::Forbid); if (!offset) return nullptr; } RefPtr mask; if (!range.atEnd()) { mask = consumeWebkitBorderImage(CSSPropertyWebkitBoxReflect, range, context); if (!mask) return nullptr; } return CSSReflectValue::create(direction.releaseNonNull(), offset.releaseNonNull(), WTFMove(mask)); } #if ENABLE(CSS_IMAGE_ORIENTATION) static RefPtr consumeImageOrientation(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless = UnitlessQuirk::Forbid) { if (range.peek().type() != NumberToken) { RefPtr angle = consumeAngle(range, cssParserMode, unitless); if (angle && angle->doubleValue() == 0) return angle; } return nullptr; } #endif static RefPtr consumeBackgroundBlendMode(CSSParserTokenRange& range) { CSSValueID id = range.peek().id(); if (id == CSSValueNormal || id == CSSValueOverlay || (id >= CSSValueMultiply && id <= CSSValueLuminosity)) return consumeIdent(range); return nullptr; } static RefPtr consumeBackgroundAttachment(CSSParserTokenRange& range) { return consumeIdent(range); } static RefPtr consumeBackgroundBox(CSSParserTokenRange& range) { return consumeIdent(range); } static RefPtr consumeBackgroundComposite(CSSParserTokenRange& range) { return consumeIdentRange(range, CSSValueClear, CSSValuePlusLighter); } static RefPtr consumeWebkitMaskSourceType(CSSParserTokenRange& range) { return consumeIdent(range); } static RefPtr consumePrefixedBackgroundBox(CSSPropertyID property, CSSParserTokenRange& range, const CSSParserContext& /*context*/) { // The values 'border', 'padding' and 'content' are deprecated and do not apply to the version of the property that has the -webkit- prefix removed. if (RefPtr value = consumeIdentRange(range, CSSValueBorder, CSSValuePaddingBox)) return value; if (range.peek().id() == CSSValueWebkitText || ((property == CSSPropertyWebkitBackgroundClip || property == CSSPropertyWebkitMaskClip) && range.peek().id() == CSSValueText)) return consumeIdent(range); return nullptr; } static RefPtr consumeBackgroundSize(CSSPropertyID property, CSSParserTokenRange& range, CSSParserMode cssParserMode) { if (identMatches(range.peek().id())) return consumeIdent(range); // FIXME: We're allowing the unitless quirk on this property because our // tests assume that. Other browser engines don't allow it though. RefPtr horizontal = consumeIdent(range); if (!horizontal) horizontal = consumeLengthOrPercent(range, cssParserMode, ValueRangeAll, UnitlessQuirk::Allow); RefPtr vertical; if (!range.atEnd()) { if (range.peek().id() == CSSValueAuto) // `auto' is the default range.consumeIncludingWhitespace(); else vertical = consumeLengthOrPercent(range, cssParserMode, ValueRangeAll, UnitlessQuirk::Allow); } else if (!vertical && property == CSSPropertyWebkitBackgroundSize) { // Legacy syntax: "-webkit-background-size: 10px" is equivalent to "background-size: 10px 10px". vertical = horizontal; } if (!vertical) return horizontal; return createPrimitiveValuePair(horizontal.releaseNonNull(), vertical.releaseNonNull(), property == CSSPropertyWebkitBackgroundSize ? Pair::IdenticalValueEncoding::Coalesce : Pair::IdenticalValueEncoding::DoNotCoalesce); } static RefPtr consumeGridAutoFlow(CSSParserTokenRange& range) { RefPtr rowOrColumnValue = consumeIdent(range); RefPtr denseAlgorithm = consumeIdent(range); if (!rowOrColumnValue) { rowOrColumnValue = consumeIdent(range); if (!rowOrColumnValue && !denseAlgorithm) return nullptr; } RefPtr parsedValues = CSSValueList::createSpaceSeparated(); if (rowOrColumnValue) parsedValues->append(rowOrColumnValue.releaseNonNull()); if (denseAlgorithm) parsedValues->append(denseAlgorithm.releaseNonNull()); return parsedValues; } static RefPtr consumeBackgroundComponent(CSSPropertyID property, CSSParserTokenRange& range, const CSSParserContext& context) { switch (property) { case CSSPropertyBackgroundClip: return consumeBackgroundBox(range); case CSSPropertyBackgroundBlendMode: return consumeBackgroundBlendMode(range); case CSSPropertyBackgroundAttachment: return consumeBackgroundAttachment(range); case CSSPropertyBackgroundOrigin: return consumeBackgroundBox(range); case CSSPropertyWebkitMaskComposite: case CSSPropertyWebkitBackgroundComposite: return consumeBackgroundComposite(range); case CSSPropertyWebkitBackgroundClip: case CSSPropertyWebkitBackgroundOrigin: case CSSPropertyWebkitMaskClip: case CSSPropertyWebkitMaskOrigin: return consumePrefixedBackgroundBox(property, range, context); case CSSPropertyBackgroundImage: case CSSPropertyWebkitMaskImage: return consumeImageOrNone(range, context); case CSSPropertyWebkitMaskSourceType: return consumeWebkitMaskSourceType(range); case CSSPropertyBackgroundPositionX: case CSSPropertyWebkitMaskPositionX: return consumePositionX(range, context.mode); case CSSPropertyBackgroundPositionY: case CSSPropertyWebkitMaskPositionY: return consumePositionY(range, context.mode); case CSSPropertyBackgroundSize: case CSSPropertyWebkitBackgroundSize: case CSSPropertyWebkitMaskSize: return consumeBackgroundSize(property, range, context.mode); case CSSPropertyBackgroundColor: return consumeColor(range, context.mode); default: break; }; return nullptr; } static void addBackgroundValue(RefPtr& list, Ref&& value) { if (list) { if (!list->isBaseValueList()) { RefPtr firstValue = list; list = CSSValueList::createCommaSeparated(); downcast(*list).append(firstValue.releaseNonNull()); } downcast(*list).append(WTFMove(value)); } else { // To conserve memory we don't actually wrap a single value in a list. list = WTFMove(value); } } static RefPtr consumeCommaSeparatedBackgroundComponent(CSSPropertyID property, CSSParserTokenRange& range, const CSSParserContext& context) { RefPtr result; do { RefPtr value = consumeBackgroundComponent(property, range, context); if (!value) return nullptr; addBackgroundValue(result, value.releaseNonNull()); } while (consumeCommaIncludingWhitespace(range)); return result; } static RefPtr consumeSelfPositionKeyword(CSSParserTokenRange& range) { CSSValueID id = range.peek().id(); if (id == CSSValueStart || id == CSSValueEnd || id == CSSValueCenter || id == CSSValueSelfStart || id == CSSValueSelfEnd || id == CSSValueFlexStart || id == CSSValueFlexEnd || id == CSSValueLeft || id == CSSValueRight) return consumeIdent(range); return nullptr; } static RefPtr consumeSelfPositionOverflowPosition(CSSParserTokenRange& range) { if (identMatches(range.peek().id())) return consumeIdent(range); RefPtr overflowPosition = consumeIdent(range); RefPtr selfPosition = consumeSelfPositionKeyword(range); if (!selfPosition) return nullptr; if (!overflowPosition) overflowPosition = consumeIdent(range); if (overflowPosition) return createPrimitiveValuePair(selfPosition.releaseNonNull(), overflowPosition.releaseNonNull(), Pair::IdenticalValueEncoding::Coalesce); return selfPosition; } static RefPtr consumeAlignItems(CSSParserTokenRange& range) { // align-items property does not allow the 'auto' value. if (identMatches(range.peek().id())) return nullptr; return consumeSelfPositionOverflowPosition(range); } static RefPtr consumeJustifyItems(CSSParserTokenRange& range) { CSSParserTokenRange rangeCopy = range; RefPtr legacy = consumeIdent(rangeCopy); RefPtr positionKeyword = consumeIdent(rangeCopy); if (!legacy) legacy = consumeIdent(rangeCopy); if (legacy && positionKeyword) { range = rangeCopy; return createPrimitiveValuePair(legacy.releaseNonNull(), positionKeyword.releaseNonNull(), Pair::IdenticalValueEncoding::Coalesce); } return consumeSelfPositionOverflowPosition(range); } static RefPtr consumeFitContent(CSSParserTokenRange& range, CSSParserMode cssParserMode) { CSSParserTokenRange rangeCopy = range; CSSParserTokenRange args = consumeFunction(rangeCopy); RefPtr length = consumeLengthOrPercent(args, cssParserMode, ValueRangeNonNegative, UnitlessQuirk::Allow); if (!length || !args.atEnd()) return nullptr; range = rangeCopy; RefPtr result = CSSFunctionValue::create(CSSValueFitContent); result->append(length.releaseNonNull()); return result; } static RefPtr consumeCustomIdentForGridLine(CSSParserTokenRange& range) { if (range.peek().id() == CSSValueAuto || range.peek().id() == CSSValueSpan) return nullptr; return consumeCustomIdent(range); } static RefPtr consumeGridLine(CSSParserTokenRange& range) { if (range.peek().id() == CSSValueAuto) return consumeIdent(range); RefPtr spanValue; RefPtr gridLineName; RefPtr numericValue = consumeInteger(range); if (numericValue) { gridLineName = consumeCustomIdentForGridLine(range); spanValue = consumeIdent(range); } else { spanValue = consumeIdent(range); if (spanValue) { numericValue = consumeInteger(range); gridLineName = consumeCustomIdentForGridLine(range); if (!numericValue) numericValue = consumeInteger(range); } else { gridLineName = consumeCustomIdentForGridLine(range); if (gridLineName) { numericValue = consumeInteger(range); spanValue = consumeIdent(range); if (!spanValue && !numericValue) return gridLineName; } else { return nullptr; } } } if (spanValue && !numericValue && !gridLineName) return nullptr; // "span" keyword alone is invalid. if (spanValue && numericValue && numericValue->intValue() < 0) return nullptr; // Negative numbers are not allowed for span. if (numericValue && numericValue->intValue() == 0) return nullptr; // An value of zero makes the declaration invalid. RefPtr values = CSSValueList::createSpaceSeparated(); if (spanValue) values->append(spanValue.releaseNonNull()); if (numericValue) values->append(numericValue.releaseNonNull()); if (gridLineName) values->append(gridLineName.releaseNonNull()); ASSERT(values->length()); return values; } static bool isGridTrackFixedSized(const CSSPrimitiveValue& primitiveValue) { CSSValueID valueID = primitiveValue.valueID(); if (valueID == CSSValueWebkitMinContent || valueID == CSSValueWebkitMaxContent || valueID == CSSValueAuto || primitiveValue.isFlex()) return false; return true; } static bool isGridTrackFixedSized(const CSSValue& value) { if (value.isPrimitiveValue()) return isGridTrackFixedSized(downcast(value)); ASSERT(value.isFunctionValue()); auto& function = downcast(value); if (function.name() == CSSValueFitContent || function.length() < 2) return false; const CSSValue* minPrimitiveValue = downcast(function.item(0)); const CSSValue* maxPrimitiveValue = downcast(function.item(1)); return isGridTrackFixedSized(*minPrimitiveValue) || isGridTrackFixedSized(*maxPrimitiveValue); } static Vector parseGridTemplateAreasColumnNames(const String& gridRowNames) { ASSERT(!gridRowNames.isEmpty()); Vector columnNames; // Using StringImpl to avoid checks and indirection in every call to String::operator[]. StringImpl& text = *gridRowNames.impl(); StringBuilder areaName; for (unsigned i = 0; i < text.length(); ++i) { if (isCSSSpace(text[i])) { if (!areaName.isEmpty()) { columnNames.append(areaName.toString()); areaName.clear(); } continue; } if (text[i] == '.') { if (areaName == ".") continue; if (!areaName.isEmpty()) { columnNames.append(areaName.toString()); areaName.clear(); } } else { if (!isNameCodePoint(text[i])) return Vector(); if (areaName == ".") { columnNames.append(areaName.toString()); areaName.clear(); } } areaName.append(text[i]); } if (!areaName.isEmpty()) columnNames.append(areaName.toString()); return columnNames; } static bool parseGridTemplateAreasRow(const String& gridRowNames, NamedGridAreaMap& gridAreaMap, const size_t rowCount, size_t& columnCount) { if (gridRowNames.isEmpty() || gridRowNames.containsOnlyWhitespace()) return false; Vector columnNames = parseGridTemplateAreasColumnNames(gridRowNames); if (rowCount == 0) { columnCount = columnNames.size(); if (columnCount == 0) return false; } else if (columnCount != columnNames.size()) { // The declaration is invalid if all the rows don't have the number of columns. return false; } for (size_t currentColumn = 0; currentColumn < columnCount; ++currentColumn) { const String& gridAreaName = columnNames[currentColumn]; // Unamed areas are always valid (we consider them to be 1x1). if (gridAreaName == ".") continue; size_t lookAheadColumn = currentColumn + 1; while (lookAheadColumn < columnCount && columnNames[lookAheadColumn] == gridAreaName) lookAheadColumn++; NamedGridAreaMap::iterator gridAreaIt = gridAreaMap.find(gridAreaName); if (gridAreaIt == gridAreaMap.end()) { gridAreaMap.add(gridAreaName, GridArea(GridSpan::translatedDefiniteGridSpan(rowCount, rowCount + 1), GridSpan::translatedDefiniteGridSpan(currentColumn, lookAheadColumn))); } else { GridArea& gridArea = gridAreaIt->value; // The following checks test that the grid area is a single filled-in rectangle. // 1. The new row is adjacent to the previously parsed row. if (rowCount != gridArea.rows.endLine()) return false; // 2. The new area starts at the same position as the previously parsed area. if (currentColumn != gridArea.columns.startLine()) return false; // 3. The new area ends at the same position as the previously parsed area. if (lookAheadColumn != gridArea.columns.endLine()) return false; gridArea.rows = GridSpan::translatedDefiniteGridSpan(gridArea.rows.startLine(), gridArea.rows.endLine() + 1); } currentColumn = lookAheadColumn - 1; } return true; } static RefPtr consumeGridBreadth(CSSParserTokenRange& range, CSSParserMode cssParserMode) { const CSSParserToken& token = range.peek(); if (identMatches(token.id())) return consumeIdent(range); if (token.type() == DimensionToken && token.unitType() == CSSPrimitiveValue::UnitType::CSS_FR) { if (range.peek().numericValue() < 0) return nullptr; return CSSPrimitiveValue::create(range.consumeIncludingWhitespace().numericValue(), CSSPrimitiveValue::UnitType::CSS_FR); } return consumeLengthOrPercent(range, cssParserMode, ValueRangeNonNegative, UnitlessQuirk::Allow); } static RefPtr consumeGridTrackSize(CSSParserTokenRange& range, CSSParserMode cssParserMode) { const CSSParserToken& token = range.peek(); if (identMatches(token.id())) return consumeIdent(range); if (token.functionId() == CSSValueMinmax) { CSSParserTokenRange rangeCopy = range; CSSParserTokenRange args = consumeFunction(rangeCopy); RefPtr minTrackBreadth = consumeGridBreadth(args, cssParserMode); if (!minTrackBreadth || minTrackBreadth->isFlex() || !consumeCommaIncludingWhitespace(args)) return nullptr; RefPtr maxTrackBreadth = consumeGridBreadth(args, cssParserMode); if (!maxTrackBreadth || !args.atEnd()) return nullptr; range = rangeCopy; RefPtr result = CSSFunctionValue::create(CSSValueMinmax); result->append(minTrackBreadth.releaseNonNull()); result->append(maxTrackBreadth.releaseNonNull()); return result; } if (token.functionId() == CSSValueFitContent) return consumeFitContent(range, cssParserMode); return consumeGridBreadth(range, cssParserMode); } // Appends to the passed in CSSGridLineNamesValue if any, otherwise creates a new one. static RefPtr consumeGridLineNames(CSSParserTokenRange& range, CSSGridLineNamesValue* lineNames = nullptr) { CSSParserTokenRange rangeCopy = range; if (rangeCopy.consumeIncludingWhitespace().type() != LeftBracketToken) return nullptr; RefPtr result = lineNames; if (!result) result = CSSGridLineNamesValue::create(); while (RefPtr lineName = consumeCustomIdentForGridLine(rangeCopy)) result->append(lineName.releaseNonNull()); if (rangeCopy.consumeIncludingWhitespace().type() != RightBracketToken) return nullptr; range = rangeCopy; return result; } static bool consumeGridTrackRepeatFunction(CSSParserTokenRange& range, CSSParserMode cssParserMode, CSSValueList& list, bool& isAutoRepeat, bool& allTracksAreFixedSized) { CSSParserTokenRange args = consumeFunction(range); // The number of repetitions for is not important at parsing level // because it will be computed later, let's set it to 1. size_t repetitions = 1; isAutoRepeat = identMatches(args.peek().id()); RefPtr repeatedValues; if (isAutoRepeat) repeatedValues = CSSGridAutoRepeatValue::create(args.consumeIncludingWhitespace().id()); else { // FIXME: a consumeIntegerRaw would be more efficient here. RefPtr repetition = consumePositiveInteger(args); if (!repetition) return false; repetitions = clampTo(repetition->doubleValue(), 0, kGridMaxTracks); repeatedValues = CSSValueList::createSpaceSeparated(); } if (!consumeCommaIncludingWhitespace(args)) return false; RefPtr lineNames = consumeGridLineNames(args); if (lineNames) repeatedValues->append(lineNames.releaseNonNull()); size_t numberOfTracks = 0; while (!args.atEnd()) { RefPtr trackSize = consumeGridTrackSize(args, cssParserMode); if (!trackSize) return false; if (allTracksAreFixedSized) allTracksAreFixedSized = isGridTrackFixedSized(*trackSize); repeatedValues->append(trackSize.releaseNonNull()); ++numberOfTracks; lineNames = consumeGridLineNames(args); if (lineNames) repeatedValues->append(lineNames.releaseNonNull()); } // We should have found at least one or else it is not a valid . if (!numberOfTracks) return false; if (isAutoRepeat) list.append(repeatedValues.releaseNonNull()); else { // We clamp the repetitions to a multiple of the repeat() track list's size, while staying below the max grid size. repetitions = std::min(repetitions, kGridMaxTracks / numberOfTracks); for (size_t i = 0; i < repetitions; ++i) { for (size_t j = 0; j < repeatedValues->length(); ++j) list.append(*repeatedValues->itemWithoutBoundsCheck(j)); } } return true; } enum TrackListType { GridTemplate, GridTemplateNoRepeat, GridAuto }; static RefPtr consumeGridTrackList(CSSParserTokenRange& range, CSSParserMode cssParserMode, TrackListType trackListType) { bool allowGridLineNames = trackListType != GridAuto; RefPtr values = CSSValueList::createSpaceSeparated(); RefPtr lineNames = consumeGridLineNames(range); if (lineNames) { if (!allowGridLineNames) return nullptr; values->append(lineNames.releaseNonNull()); } bool allowRepeat = trackListType == GridTemplate; bool seenAutoRepeat = false; bool allTracksAreFixedSized = true; do { bool isAutoRepeat; if (range.peek().functionId() == CSSValueRepeat) { if (!allowRepeat) return nullptr; if (!consumeGridTrackRepeatFunction(range, cssParserMode, *values, isAutoRepeat, allTracksAreFixedSized)) return nullptr; if (isAutoRepeat && seenAutoRepeat) return nullptr; seenAutoRepeat = seenAutoRepeat || isAutoRepeat; } else if (RefPtr value = consumeGridTrackSize(range, cssParserMode)) { if (allTracksAreFixedSized) allTracksAreFixedSized = isGridTrackFixedSized(*value); values->append(value.releaseNonNull()); } else { return nullptr; } if (seenAutoRepeat && !allTracksAreFixedSized) return nullptr; lineNames = consumeGridLineNames(range); if (lineNames) { if (!allowGridLineNames) return nullptr; values->append(lineNames.releaseNonNull()); } } while (!range.atEnd() && range.peek().type() != DelimiterToken); return values; } static RefPtr consumeGridTemplatesRowsOrColumns(CSSParserTokenRange& range, CSSParserMode cssParserMode) { if (range.peek().id() == CSSValueNone) return consumeIdent(range); return consumeGridTrackList(range, cssParserMode, GridTemplate); } static RefPtr consumeGridTemplateAreas(CSSParserTokenRange& range) { if (range.peek().id() == CSSValueNone) return consumeIdent(range); NamedGridAreaMap gridAreaMap; size_t rowCount = 0; size_t columnCount = 0; while (range.peek().type() == StringToken) { if (!parseGridTemplateAreasRow(range.consumeIncludingWhitespace().value().toString(), gridAreaMap, rowCount, columnCount)) return nullptr; ++rowCount; } if (rowCount == 0) return nullptr; ASSERT(columnCount); return CSSGridTemplateAreasValue::create(gridAreaMap, rowCount, columnCount); } #if ENABLE(CSS_REGIONS) static RefPtr consumeFlowProperty(CSSParserTokenRange& range) { RefPtr result; if (range.peek().id() == CSSValueNone) result = consumeIdent(range); else result = consumeCustomIdent(range); return result; } #endif static RefPtr consumeLineBoxContain(CSSParserTokenRange& range) { if (range.peek().id() == CSSValueNone) return consumeIdent(range); LineBoxContain lineBoxContain = LineBoxContainNone; while (range.peek().type() == IdentToken) { auto id = range.peek().id(); if (id == CSSValueBlock) { if (lineBoxContain & LineBoxContainBlock) return nullptr; lineBoxContain |= LineBoxContainBlock; } else if (id == CSSValueInline) { if (lineBoxContain & LineBoxContainInline) return nullptr; lineBoxContain |= LineBoxContainInline; } else if (id == CSSValueFont) { if (lineBoxContain & LineBoxContainFont) return nullptr; lineBoxContain |= LineBoxContainFont; } else if (id == CSSValueGlyphs) { if (lineBoxContain & LineBoxContainGlyphs) return nullptr; lineBoxContain |= LineBoxContainGlyphs; } else if (id == CSSValueReplaced) { if (lineBoxContain & LineBoxContainReplaced) return nullptr; lineBoxContain |= LineBoxContainReplaced; } else if (id == CSSValueInlineBox) { if (lineBoxContain & LineBoxContainInlineBox) return nullptr; lineBoxContain |= LineBoxContainInlineBox; } else if (id == CSSValueInitialLetter) { if (lineBoxContain & LineBoxContainInitialLetter) return nullptr; lineBoxContain |= LineBoxContainInitialLetter; } else return nullptr; range.consumeIncludingWhitespace(); } if (!lineBoxContain) return nullptr; return CSSLineBoxContainValue::create(lineBoxContain); } static RefPtr consumeLineGrid(CSSParserTokenRange& range) { if (range.peek().id() == CSSValueNone) return consumeIdent(range); return consumeCustomIdent(range); } static RefPtr consumeInitialLetter(CSSParserTokenRange& range) { RefPtr ident = consumeIdent(range); if (ident) return ident; RefPtr height = consumeNumber(range, ValueRangeNonNegative); if (!height) return nullptr; RefPtr position; if (!range.atEnd()) { position = consumeNumber(range, ValueRangeNonNegative); if (!position || !range.atEnd()) return nullptr; } else position = height.copyRef(); return createPrimitiveValuePair(position.releaseNonNull(), WTFMove(height)); } static RefPtr consumeHangingPunctuation(CSSParserTokenRange& range) { if (range.peek().id() == CSSValueNone) return consumeIdent(range); RefPtr list = CSSValueList::createSpaceSeparated(); std::bitset seenValues; bool seenForceEnd = false; bool seenAllowEnd = false; bool seenFirst = false; bool seenLast = false; while (!range.atEnd()) { CSSValueID valueID = range.peek().id(); if ((valueID == CSSValueFirst && seenFirst) || (valueID == CSSValueLast && seenLast) || (valueID == CSSValueAllowEnd && (seenAllowEnd || seenForceEnd)) || (valueID == CSSValueForceEnd && (seenAllowEnd || seenForceEnd))) return nullptr; RefPtr ident = consumeIdent(range); if (!ident) return nullptr; switch (valueID) { case CSSValueAllowEnd: seenAllowEnd = true; break; case CSSValueForceEnd: seenForceEnd = true; break; case CSSValueFirst: seenFirst = true; break; case CSSValueLast: seenLast = true; break; default: break; } list->append(ident.releaseNonNull()); } return list->length() ? list : nullptr; } static RefPtr consumeWebkitMarqueeIncrement(CSSParserTokenRange& range, CSSParserMode cssParserMode) { if (range.peek().type() == IdentToken) return consumeIdent(range); return consumeLengthOrPercent(range, cssParserMode, ValueRangeAll, UnitlessQuirk::Allow); } static RefPtr consumeWebkitMarqueeRepetition(CSSParserTokenRange& range) { if (range.peek().type() == IdentToken) return consumeIdent(range); return consumeNumber(range, ValueRangeNonNegative); } static RefPtr consumeWebkitMarqueeSpeed(CSSParserTokenRange& range, CSSParserMode cssParserMode) { if (range.peek().type() == IdentToken) return consumeIdent(range); return consumeTime(range, cssParserMode, ValueRangeNonNegative, UnitlessQuirk::Allow); } static RefPtr consumeAlt(CSSParserTokenRange& range, const CSSParserContext& context) { if (range.peek().type() == StringToken) return consumeString(range); if (range.peek().functionId() != CSSValueAttr) return nullptr; return consumeAttr(consumeFunction(range), context); } static RefPtr consumeWebkitAspectRatio(CSSParserTokenRange& range) { if (range.peek().type() == IdentToken) return consumeIdent(range); RefPtr leftValue = consumeNumber(range, ValueRangeNonNegative); if (!leftValue || !leftValue->floatValue() || range.atEnd() || !consumeSlashIncludingWhitespace(range)) return nullptr; RefPtr rightValue = consumeNumber(range, ValueRangeNonNegative); if (!rightValue || !rightValue->floatValue()) return nullptr; return CSSAspectRatioValue::create(leftValue->floatValue(), rightValue->floatValue()); } static RefPtr consumeTextEmphasisPosition(CSSParserTokenRange& range) { bool foundOverOrUnder = false; CSSValueID overUnderValueID = CSSValueOver; bool foundLeftOrRight = false; CSSValueID leftRightValueID = CSSValueRight; while (!range.atEnd()) { switch (range.peek().id()) { case CSSValueOver: if (foundOverOrUnder) return nullptr; foundOverOrUnder = true; overUnderValueID = CSSValueOver; break; case CSSValueUnder: if (foundOverOrUnder) return nullptr; foundOverOrUnder = true; overUnderValueID = CSSValueUnder; break; case CSSValueLeft: if (foundLeftOrRight) return nullptr; foundLeftOrRight = true; leftRightValueID = CSSValueLeft; break; case CSSValueRight: if (foundLeftOrRight) return nullptr; foundLeftOrRight = true; leftRightValueID = CSSValueRight; break; default: return nullptr; } range.consumeIncludingWhitespace(); } if (!foundOverOrUnder) return nullptr; RefPtr list = CSSValueList::createSpaceSeparated(); list->append(CSSValuePool::singleton().createIdentifierValue(overUnderValueID)); if (foundLeftOrRight) list->append(CSSValuePool::singleton().createIdentifierValue(leftRightValueID)); return list; } #if ENABLE(DASHBOARD_SUPPORT) static RefPtr consumeWebkitDashboardRegion(CSSParserTokenRange& range, CSSParserMode mode) { if (range.atEnd()) return nullptr; if (range.peek().id() == CSSValueNone) return consumeIdent(range); auto firstRegion = DashboardRegion::create(); DashboardRegion* region = nullptr; bool requireCommas = false; while (!range.atEnd()) { if (!region) region = firstRegion.ptr(); else { auto nextRegion = DashboardRegion::create(); region->m_next = nextRegion.copyRef(); region = nextRegion.ptr(); } if (range.peek().functionId() != CSSValueDashboardRegion) return nullptr; CSSParserTokenRange rangeCopy = range; CSSParserTokenRange args = consumeFunction(rangeCopy); if (rangeCopy.end() == args.end()) return nullptr; // No ) was found. Be strict about this, since tests are. // First arg is a label. if (args.peek().type() != IdentToken) return nullptr; region->m_label = args.consumeIncludingWhitespace().value().toString(); // Comma is optional, so don't fail if we can't consume one. requireCommas = consumeCommaIncludingWhitespace(args); // Second arg is a type. if (args.peek().type() != IdentToken) return nullptr; region->m_geometryType = args.consumeIncludingWhitespace().value().toString(); if (equalLettersIgnoringASCIICase(region->m_geometryType, "circle")) region->m_isCircle = true; else if (equalLettersIgnoringASCIICase(region->m_geometryType, "rectangle")) region->m_isRectangle = true; else return nullptr; if (args.atEnd()) { // This originally used CSSValueInvalid by accident. It might be more logical to use something else. RefPtr amount = CSSValuePool::singleton().createIdentifierValue(CSSValueInvalid); region->setTop(amount.copyRef()); region->setRight(amount.copyRef()); region->setBottom(amount.copyRef()); region->setLeft(WTFMove(amount)); range = rangeCopy; continue; } // Next four arguments must be offset numbers or auto. for (int i = 0; i < 4; ++i) { if (args.atEnd() || (requireCommas && !consumeCommaIncludingWhitespace(args))) return nullptr; if (args.atEnd()) return nullptr; RefPtr amount; if (args.peek().id() == CSSValueAuto) amount = consumeIdent(args); else amount = consumeLength(args, mode, ValueRangeAll); if (!i) region->setTop(WTFMove(amount)); else if (i == 1) region->setRight(WTFMove(amount)); else if (i == 2) region->setBottom(WTFMove(amount)); else region->setLeft(WTFMove(amount)); } if (!args.atEnd()) return nullptr; range = rangeCopy; } return CSSValuePool::singleton().createValue(RefPtr(WTFMove(firstRegion))); } #endif RefPtr CSSPropertyParser::parseSingleValue(CSSPropertyID property, CSSPropertyID currentShorthand) { if (CSSParserFastPaths::isKeywordPropertyID(property)) { if (!CSSParserFastPaths::isValidKeywordPropertyAndValue(property, m_range.peek().id(), m_context.mode)) return nullptr; return consumeIdent(m_range); } switch (property) { case CSSPropertyWillChange: return consumeWillChange(m_range); case CSSPropertyPage: return consumePage(m_range); case CSSPropertyQuotes: return consumeQuotes(m_range); case CSSPropertyFontVariantCaps: return consumeFontVariantCaps(m_range); case CSSPropertyFontVariantLigatures: return consumeFontVariantLigatures(m_range); case CSSPropertyFontVariantNumeric: return consumeFontVariantNumeric(m_range); case CSSPropertyFontVariantEastAsian: return consumeFontVariantEastAsian(m_range); case CSSPropertyFontFeatureSettings: return consumeFontFeatureSettings(m_range); case CSSPropertyFontFamily: return consumeFontFamily(m_range); case CSSPropertyFontWeight: return consumeFontWeight(m_range); case CSSPropertyFontSynthesis: return consumeFontSynthesis(m_range); #if ENABLE(VARIATION_FONTS) case CSSPropertyFontVariationSettings: return consumeFontVariationSettings(m_range); #endif case CSSPropertyLetterSpacing: return consumeLetterSpacing(m_range, m_context.mode); case CSSPropertyWordSpacing: return consumeWordSpacing(m_range, m_context.mode); case CSSPropertyTabSize: return consumeTabSize(m_range, m_context.mode); #if ENABLE(TEXT_AUTOSIZING) case CSSPropertyWebkitTextSizeAdjust: // FIXME: Support toggling the validation of this property via a runtime setting that is independent of // whether isTextAutosizingEnabled() is true. We want to enable this property on iOS, when simulating // a iOS device in Safari's responsive design mode and when optionally enabled in DRT/WTR. Otherwise, // this property should be disabled by default. #if !PLATFORM(IOS) if (!m_context.textAutosizingEnabled) return nullptr; #endif return consumeTextSizeAdjust(m_range, m_context.mode); #endif case CSSPropertyFontSize: return consumeFontSize(m_range, m_context.mode, UnitlessQuirk::Allow); case CSSPropertyLineHeight: return consumeLineHeight(m_range, m_context.mode); case CSSPropertyWebkitBorderHorizontalSpacing: case CSSPropertyWebkitBorderVerticalSpacing: return consumeLength(m_range, m_context.mode, ValueRangeNonNegative); case CSSPropertyCounterIncrement: case CSSPropertyCounterReset: return consumeCounter(m_range, property == CSSPropertyCounterIncrement ? 1 : 0); case CSSPropertySize: return consumeSize(m_range, m_context.mode); case CSSPropertyTextIndent: return consumeTextIndent(m_range, m_context.mode); case CSSPropertyMaxWidth: case CSSPropertyMaxHeight: return consumeMaxWidthOrHeight(m_range, m_context, UnitlessQuirk::Allow); case CSSPropertyWebkitMaxLogicalWidth: case CSSPropertyWebkitMaxLogicalHeight: return consumeMaxWidthOrHeight(m_range, m_context); case CSSPropertyMinWidth: case CSSPropertyMinHeight: case CSSPropertyWidth: case CSSPropertyHeight: return consumeWidthOrHeight(m_range, m_context, UnitlessQuirk::Allow); case CSSPropertyWebkitMinLogicalWidth: case CSSPropertyWebkitMinLogicalHeight: case CSSPropertyWebkitLogicalWidth: case CSSPropertyWebkitLogicalHeight: return consumeWidthOrHeight(m_range, m_context); case CSSPropertyMarginTop: case CSSPropertyMarginRight: case CSSPropertyMarginBottom: case CSSPropertyMarginLeft: case CSSPropertyBottom: case CSSPropertyLeft: case CSSPropertyRight: case CSSPropertyTop: return consumeMarginOrOffset(m_range, m_context.mode, UnitlessQuirk::Allow); case CSSPropertyWebkitMarginStart: case CSSPropertyWebkitMarginEnd: case CSSPropertyWebkitMarginBefore: case CSSPropertyWebkitMarginAfter: return consumeMarginOrOffset(m_range, m_context.mode, UnitlessQuirk::Forbid); case CSSPropertyPaddingTop: case CSSPropertyPaddingRight: case CSSPropertyPaddingBottom: case CSSPropertyPaddingLeft: return consumeLengthOrPercent(m_range, m_context.mode, ValueRangeNonNegative, UnitlessQuirk::Allow); case CSSPropertyWebkitPaddingStart: case CSSPropertyWebkitPaddingEnd: case CSSPropertyWebkitPaddingBefore: case CSSPropertyWebkitPaddingAfter: return consumeLengthOrPercent(m_range, m_context.mode, ValueRangeNonNegative, UnitlessQuirk::Forbid); #if ENABLE(CSS_SCROLL_SNAP) case CSSPropertyScrollSnapMarginBottom: case CSSPropertyScrollSnapMarginLeft: case CSSPropertyScrollSnapMarginRight: case CSSPropertyScrollSnapMarginTop: return consumeLength(m_range, m_context.mode, ValueRangeAll); case CSSPropertyScrollPaddingBottom: case CSSPropertyScrollPaddingLeft: case CSSPropertyScrollPaddingRight: case CSSPropertyScrollPaddingTop: return consumeLengthOrPercent(m_range, m_context.mode, ValueRangeAll); case CSSPropertyScrollSnapAlign: return consumeScrollSnapAlign(m_range); case CSSPropertyScrollSnapType: return consumeScrollSnapType(m_range); #endif case CSSPropertyClip: return consumeClip(m_range, m_context.mode); #if ENABLE(TOUCH_EVENTS) case CSSPropertyTouchAction: return consumeTouchAction(m_range); #endif case CSSPropertyObjectPosition: return consumePosition(m_range, m_context.mode, UnitlessQuirk::Forbid); case CSSPropertyWebkitLineClamp: return consumeLineClamp(m_range); case CSSPropertyWebkitFontSizeDelta: return consumeLength(m_range, m_context.mode, ValueRangeAll, UnitlessQuirk::Allow); case CSSPropertyWebkitHyphenateCharacter: case CSSPropertyWebkitLocale: return consumeLocale(m_range); case CSSPropertyWebkitHyphenateLimitBefore: case CSSPropertyWebkitHyphenateLimitAfter: return consumeHyphenateLimit(m_range, CSSValueAuto); case CSSPropertyWebkitHyphenateLimitLines: return consumeHyphenateLimit(m_range, CSSValueNoLimit); case CSSPropertyColumnWidth: return consumeColumnWidth(m_range); case CSSPropertyColumnCount: return consumeColumnCount(m_range); case CSSPropertyColumnGap: return consumeColumnGap(m_range, m_context.mode); case CSSPropertyColumnSpan: return consumeColumnSpan(m_range); case CSSPropertyZoom: return consumeZoom(m_range, m_context); case CSSPropertyAnimationDelay: case CSSPropertyTransitionDelay: case CSSPropertyAnimationDirection: case CSSPropertyAnimationDuration: case CSSPropertyTransitionDuration: case CSSPropertyAnimationFillMode: case CSSPropertyAnimationIterationCount: case CSSPropertyAnimationName: case CSSPropertyAnimationPlayState: case CSSPropertyTransitionProperty: case CSSPropertyAnimationTimingFunction: case CSSPropertyTransitionTimingFunction: #if ENABLE(CSS_ANIMATIONS_LEVEL_2) case CSSPropertyWebkitAnimationTrigger: #endif return consumeAnimationPropertyList(property, m_range, m_context); case CSSPropertyGridColumnGap: case CSSPropertyGridRowGap: return consumeLength(m_range, m_context.mode, ValueRangeNonNegative); case CSSPropertyShapeMargin: return consumeLengthOrPercent(m_range, m_context.mode, ValueRangeNonNegative); case CSSPropertyShapeImageThreshold: return consumeNumber(m_range, ValueRangeAll); case CSSPropertyWebkitBoxOrdinalGroup: case CSSPropertyOrphans: case CSSPropertyWidows: return consumePositiveInteger(m_range); case CSSPropertyWebkitTextDecorationColor: return consumeColor(m_range, m_context.mode); case CSSPropertyWebkitTextDecorationSkip: return consumeTextDecorationSkip(m_range); case CSSPropertyWebkitTextStrokeWidth: return consumeTextStrokeWidth(m_range, m_context.mode); case CSSPropertyWebkitTextFillColor: #if ENABLE(TOUCH_EVENTS) case CSSPropertyWebkitTapHighlightColor: #endif case CSSPropertyWebkitTextEmphasisColor: case CSSPropertyWebkitBorderStartColor: case CSSPropertyWebkitBorderEndColor: case CSSPropertyWebkitBorderBeforeColor: case CSSPropertyWebkitBorderAfterColor: case CSSPropertyWebkitTextStrokeColor: case CSSPropertyStopColor: case CSSPropertyFloodColor: case CSSPropertyLightingColor: case CSSPropertyColumnRuleColor: return consumeColor(m_range, m_context.mode); case CSSPropertyColor: case CSSPropertyBackgroundColor: return consumeColor(m_range, m_context.mode, inQuirksMode()); case CSSPropertyWebkitBorderStartWidth: case CSSPropertyWebkitBorderEndWidth: case CSSPropertyWebkitBorderBeforeWidth: case CSSPropertyWebkitBorderAfterWidth: return consumeBorderWidth(m_range, m_context.mode, UnitlessQuirk::Forbid); case CSSPropertyBorderBottomColor: case CSSPropertyBorderLeftColor: case CSSPropertyBorderRightColor: case CSSPropertyBorderTopColor: { bool allowQuirkyColors = inQuirksMode() && (currentShorthand == CSSPropertyInvalid || currentShorthand == CSSPropertyBorderColor); return consumeColor(m_range, m_context.mode, allowQuirkyColors); } case CSSPropertyBorderBottomWidth: case CSSPropertyBorderLeftWidth: case CSSPropertyBorderRightWidth: case CSSPropertyBorderTopWidth: { bool allowQuirkyLengths = inQuirksMode() && (currentShorthand == CSSPropertyInvalid || currentShorthand == CSSPropertyBorderWidth); UnitlessQuirk unitless = allowQuirkyLengths ? UnitlessQuirk::Allow : UnitlessQuirk::Forbid; return consumeBorderWidth(m_range, m_context.mode, unitless); } case CSSPropertyZIndex: return consumeZIndex(m_range); case CSSPropertyTextShadow: // CSS2 property, dropped in CSS2.1, back in CSS3, so treat as CSS3 case CSSPropertyBoxShadow: case CSSPropertyWebkitBoxShadow: case CSSPropertyWebkitSvgShadow: return consumeShadow(m_range, m_context.mode, property == CSSPropertyBoxShadow || property == CSSPropertyWebkitBoxShadow); case CSSPropertyFilter: #if ENABLE(FILTERS_LEVEL_2) case CSSPropertyWebkitBackdropFilter: #endif return consumeFilter(m_range, m_context); case CSSPropertyTextDecoration: case CSSPropertyWebkitTextDecorationsInEffect: case CSSPropertyWebkitTextDecorationLine: return consumeTextDecorationLine(m_range); case CSSPropertyWebkitTextEmphasisStyle: return consumeTextEmphasisStyle(m_range); case CSSPropertyOutlineColor: return consumeOutlineColor(m_range, m_context.mode); case CSSPropertyOutlineOffset: return consumeLength(m_range, m_context.mode, ValueRangeAll); case CSSPropertyOutlineWidth: return consumeLineWidth(m_range, m_context.mode, UnitlessQuirk::Forbid); case CSSPropertyTransform: return consumeTransform(m_range, m_context.mode); case CSSPropertyTransformOriginX: case CSSPropertyPerspectiveOriginX: return consumePositionX(m_range, m_context.mode); case CSSPropertyTransformOriginY: case CSSPropertyPerspectiveOriginY: return consumePositionY(m_range, m_context.mode); case CSSPropertyTransformOriginZ: return consumeLength(m_range, m_context.mode, ValueRangeAll); case CSSPropertyFill: case CSSPropertyStroke: return consumePaintStroke(m_range, m_context.mode); case CSSPropertyGlyphOrientationVertical: case CSSPropertyGlyphOrientationHorizontal: return consumeGlyphOrientation(m_range, m_context.mode, property); case CSSPropertyPaintOrder: return consumePaintOrder(m_range); case CSSPropertyMarkerStart: case CSSPropertyMarkerMid: case CSSPropertyMarkerEnd: case CSSPropertyClipPath: case CSSPropertyMask: return consumeNoneOrURI(m_range); case CSSPropertyFlexBasis: return consumeFlexBasis(m_range, m_context.mode); case CSSPropertyFlexGrow: case CSSPropertyFlexShrink: return consumeNumber(m_range, ValueRangeNonNegative); case CSSPropertyStrokeDasharray: return consumeStrokeDasharray(m_range); case CSSPropertyColumnRuleWidth: return consumeColumnRuleWidth(m_range, m_context.mode); case CSSPropertyStrokeOpacity: case CSSPropertyFillOpacity: case CSSPropertyStopOpacity: case CSSPropertyFloodOpacity: case CSSPropertyOpacity: case CSSPropertyWebkitBoxFlex: return consumeNumber(m_range, ValueRangeAll); case CSSPropertyBaselineShift: return consumeBaselineShift(m_range); case CSSPropertyKerning: return consumeKerning(m_range, m_context.mode); case CSSPropertyStrokeMiterlimit: return consumeNumber(m_range, ValueRangeNonNegative); case CSSPropertyStrokeWidth: case CSSPropertyStrokeDashoffset: case CSSPropertyCx: case CSSPropertyCy: case CSSPropertyX: case CSSPropertyY: case CSSPropertyR: return consumeLengthOrPercent(m_range, SVGAttributeMode, ValueRangeAll, UnitlessQuirk::Forbid); case CSSPropertyRx: case CSSPropertyRy: return consumeRxOrRy(m_range); case CSSPropertyCursor: return consumeCursor(m_range, m_context, inQuirksMode()); case CSSPropertyContent: return consumeContent(m_range, m_context); case CSSPropertyListStyleImage: case CSSPropertyBorderImageSource: case CSSPropertyWebkitMaskBoxImageSource: return consumeImageOrNone(m_range, m_context); case CSSPropertyPerspective: return consumePerspective(m_range, m_context.mode); case CSSPropertyBorderTopRightRadius: case CSSPropertyBorderTopLeftRadius: case CSSPropertyBorderBottomLeftRadius: case CSSPropertyBorderBottomRightRadius: return consumeBorderRadiusCorner(m_range, m_context.mode); case CSSPropertyWebkitBoxFlexGroup: return consumeInteger(m_range, 0); case CSSPropertyOrder: return consumeInteger(m_range); case CSSPropertyWebkitTextUnderlinePosition: // auto | alphabetic | [ under || [ left | right ] ], but we only support auto | alphabetic | under for now return consumeIdent(m_range); case CSSPropertyVerticalAlign: return consumeVerticalAlign(m_range, m_context.mode); case CSSPropertyShapeOutside: return consumeShapeOutside(m_range, m_context); case CSSPropertyWebkitClipPath: return consumeWebkitClipPath(m_range, m_context); case CSSPropertyJustifyContent: case CSSPropertyAlignContent: return consumeContentDistributionOverflowPosition(m_range); case CSSPropertyBorderImageRepeat: case CSSPropertyWebkitMaskBoxImageRepeat: return consumeBorderImageRepeat(m_range); case CSSPropertyBorderImageSlice: case CSSPropertyWebkitMaskBoxImageSlice: return consumeBorderImageSlice(property, m_range); case CSSPropertyBorderImageOutset: case CSSPropertyWebkitMaskBoxImageOutset: return consumeBorderImageOutset(m_range); case CSSPropertyBorderImageWidth: case CSSPropertyWebkitMaskBoxImageWidth: return consumeBorderImageWidth(m_range); case CSSPropertyWebkitBorderImage: case CSSPropertyWebkitMaskBoxImage: return consumeWebkitBorderImage(property, m_range, m_context); case CSSPropertyWebkitBoxReflect: return consumeReflect(m_range, m_context); case CSSPropertyWebkitLineBoxContain: return consumeLineBoxContain(m_range); #if ENABLE(CSS_IMAGE_ORIENTATION) case CSSPropertyImageOrientation: return consumeImageOrientation(m_range, m_context.mode); #endif case CSSPropertyBackgroundAttachment: case CSSPropertyBackgroundBlendMode: case CSSPropertyBackgroundClip: case CSSPropertyBackgroundImage: case CSSPropertyBackgroundOrigin: case CSSPropertyBackgroundPositionX: case CSSPropertyBackgroundPositionY: case CSSPropertyBackgroundSize: case CSSPropertyWebkitBackgroundClip: case CSSPropertyWebkitBackgroundOrigin: case CSSPropertyWebkitBackgroundComposite: case CSSPropertyWebkitBackgroundSize: case CSSPropertyWebkitMaskClip: case CSSPropertyWebkitMaskComposite: case CSSPropertyWebkitMaskImage: case CSSPropertyWebkitMaskOrigin: case CSSPropertyWebkitMaskPositionX: case CSSPropertyWebkitMaskPositionY: case CSSPropertyWebkitMaskSize: case CSSPropertyWebkitMaskSourceType: return consumeCommaSeparatedBackgroundComponent(property, m_range, m_context); case CSSPropertyWebkitMaskRepeatX: case CSSPropertyWebkitMaskRepeatY: return nullptr; case CSSPropertyAlignItems: if (!m_context.cssGridLayoutEnabled) return nullptr; return consumeAlignItems(m_range); case CSSPropertyJustifySelf: case CSSPropertyAlignSelf: if (!m_context.cssGridLayoutEnabled) return nullptr; return consumeSelfPositionOverflowPosition(m_range); case CSSPropertyJustifyItems: if (!m_context.cssGridLayoutEnabled) return nullptr; return consumeJustifyItems(m_range); case CSSPropertyGridColumnEnd: case CSSPropertyGridColumnStart: case CSSPropertyGridRowEnd: case CSSPropertyGridRowStart: if (!m_context.cssGridLayoutEnabled) return nullptr; return consumeGridLine(m_range); case CSSPropertyGridAutoColumns: case CSSPropertyGridAutoRows: if (!m_context.cssGridLayoutEnabled) return nullptr; return consumeGridTrackList(m_range, m_context.mode, GridAuto); case CSSPropertyGridTemplateColumns: case CSSPropertyGridTemplateRows: if (!m_context.cssGridLayoutEnabled) return nullptr; return consumeGridTemplatesRowsOrColumns(m_range, m_context.mode); case CSSPropertyGridTemplateAreas: if (!m_context.cssGridLayoutEnabled) return nullptr; return consumeGridTemplateAreas(m_range); case CSSPropertyGridAutoFlow: if (!m_context.cssGridLayoutEnabled) return nullptr; return consumeGridAutoFlow(m_range); #if ENABLE(CSS_REGIONS) case CSSPropertyWebkitFlowInto: case CSSPropertyWebkitFlowFrom: return consumeFlowProperty(m_range); #endif case CSSPropertyWebkitLineGrid: return consumeLineGrid(m_range); case CSSPropertyWebkitInitialLetter: return consumeInitialLetter(m_range); case CSSPropertyHangingPunctuation: return consumeHangingPunctuation(m_range); case CSSPropertyWebkitMarqueeIncrement: return consumeWebkitMarqueeIncrement(m_range, m_context.mode); case CSSPropertyWebkitMarqueeRepetition: return consumeWebkitMarqueeRepetition(m_range); case CSSPropertyWebkitMarqueeSpeed: return consumeWebkitMarqueeSpeed(m_range, m_context.mode); case CSSPropertyAlt: return consumeAlt(m_range, m_context); case CSSPropertyWebkitAspectRatio: return consumeWebkitAspectRatio(m_range); case CSSPropertyWebkitTextEmphasisPosition: return consumeTextEmphasisPosition(m_range); #if ENABLE(DASHBOARD_SUPPORT) case CSSPropertyWebkitDashboardRegion: return consumeWebkitDashboardRegion(m_range, m_context.mode); #endif default: return nullptr; } } static RefPtr consumeFontFaceUnicodeRange(CSSParserTokenRange& range) { RefPtr values = CSSValueList::createCommaSeparated(); do { const CSSParserToken& token = range.consumeIncludingWhitespace(); if (token.type() != UnicodeRangeToken) return nullptr; UChar32 start = token.unicodeRangeStart(); UChar32 end = token.unicodeRangeEnd(); if (start > end) return nullptr; values->append(CSSUnicodeRangeValue::create(start, end)); } while (consumeCommaIncludingWhitespace(range)); return values; } static RefPtr consumeFontFaceSrcURI(CSSParserTokenRange& range, const CSSParserContext& context) { String url = consumeUrlAsStringView(range).toString(); if (url.isNull()) return nullptr; RefPtr uriValue = CSSFontFaceSrcValue::create(context.completeURL(url)); if (range.peek().functionId() != CSSValueFormat) return uriValue; // FIXME: https://drafts.csswg.org/css-fonts says that format() contains a comma-separated list of strings, // but CSSFontFaceSrcValue stores only one format. Allowing one format for now. // FIXME: We're allowing the format to be an identifier as well as a string, because the old // parser did. It's not clear if we need to continue to support this behavior, but we have lots of // layout tests that rely on it. CSSParserTokenRange args = consumeFunction(range); const CSSParserToken& arg = args.consumeIncludingWhitespace(); if ((arg.type() != StringToken && arg.type() != IdentToken) || !args.atEnd()) return nullptr; uriValue->setFormat(arg.value().toString()); return uriValue; } static RefPtr consumeFontFaceSrcLocal(CSSParserTokenRange& range) { CSSParserTokenRange args = consumeFunction(range); if (args.peek().type() == StringToken) { const CSSParserToken& arg = args.consumeIncludingWhitespace(); if (!args.atEnd()) return nullptr; return CSSFontFaceSrcValue::createLocal(arg.value().toString()); } if (args.peek().type() == IdentToken) { String familyName = concatenateFamilyName(args); if (!args.atEnd()) return nullptr; return CSSFontFaceSrcValue::createLocal(familyName); } return nullptr; } static RefPtr consumeFontFaceSrc(CSSParserTokenRange& range, const CSSParserContext& context) { RefPtr values = CSSValueList::createCommaSeparated(); do { const CSSParserToken& token = range.peek(); RefPtr parsedValue; if (token.functionId() == CSSValueLocal) parsedValue = consumeFontFaceSrcLocal(range); else parsedValue = consumeFontFaceSrcURI(range, context); if (!parsedValue) return nullptr; values->append(parsedValue.releaseNonNull()); } while (consumeCommaIncludingWhitespace(range)); return values; } bool CSSPropertyParser::parseFontFaceDescriptor(CSSPropertyID propId) { RefPtr parsedValue; switch (propId) { case CSSPropertyFontFamily: parsedValue = consumeFontFamilyDescriptor(m_range); break; case CSSPropertySrc: // This is a list of urls or local references. parsedValue = consumeFontFaceSrc(m_range, m_context); break; case CSSPropertyUnicodeRange: parsedValue = consumeFontFaceUnicodeRange(m_range); break; case CSSPropertyFontStretch: case CSSPropertyFontStyle: { CSSValueID id = m_range.consumeIncludingWhitespace().id(); if (!CSSParserFastPaths::isValidKeywordPropertyAndValue(propId, id, m_context.mode)) return false; parsedValue = CSSValuePool::singleton().createIdentifierValue(id); break; } case CSSPropertyFontVariantCaps: parsedValue = consumeFontVariantCaps(m_range); break; case CSSPropertyFontVariantLigatures: parsedValue = consumeFontVariantLigatures(m_range); break; case CSSPropertyFontVariantNumeric: parsedValue = consumeFontVariantNumeric(m_range); break; case CSSPropertyFontVariantEastAsian: parsedValue = consumeFontVariantEastAsian(m_range); break; case CSSPropertyFontVariantAlternates: parsedValue = consumeFontVariantAlternates(m_range); break; case CSSPropertyFontVariantPosition: parsedValue = consumeFontVariantPosition(m_range); break; case CSSPropertyFontVariant: return consumeFontVariantShorthand(false); case CSSPropertyFontWeight: parsedValue = consumeFontWeight(m_range); break; case CSSPropertyFontFeatureSettings: parsedValue = consumeFontFeatureSettings(m_range); break; default: break; } if (!parsedValue || !m_range.atEnd()) return false; addProperty(propId, CSSPropertyInvalid, *parsedValue, false); return true; } bool CSSPropertyParser::consumeSystemFont(bool important) { CSSValueID systemFontID = m_range.consumeIncludingWhitespace().id(); ASSERT(systemFontID >= CSSValueCaption && systemFontID <= CSSValueStatusBar); if (!m_range.atEnd()) return false; FontCascadeDescription fontDescription; RenderTheme::defaultTheme()->systemFont(systemFontID, fontDescription); if (!fontDescription.isAbsoluteSize()) return false; addProperty(CSSPropertyFontStyle, CSSPropertyFont, CSSValuePool::singleton().createIdentifierValue(fontDescription.italic() == FontItalicOn ? CSSValueItalic : CSSValueNormal), important); addProperty(CSSPropertyFontWeight, CSSPropertyFont, CSSValuePool::singleton().createValue(fontDescription.weight()), important); addProperty(CSSPropertyFontSize, CSSPropertyFont, CSSValuePool::singleton().createValue(fontDescription.specifiedSize(), CSSPrimitiveValue::CSS_PX), important); Ref fontFamilyList = CSSValueList::createCommaSeparated(); fontFamilyList->append(CSSValuePool::singleton().createFontFamilyValue(fontDescription.familyAt(0), FromSystemFontID::Yes)); addProperty(CSSPropertyFontFamily, CSSPropertyFont, WTFMove(fontFamilyList), important); addProperty(CSSPropertyFontVariantCaps, CSSPropertyFont, CSSValuePool::singleton().createIdentifierValue(CSSValueNormal), important); addProperty(CSSPropertyLineHeight, CSSPropertyFont, CSSValuePool::singleton().createIdentifierValue(CSSValueNormal), important); // FIXME_NEWPARSER: What about FontVariantNumeric and FontVariantLigatures? return true; } bool CSSPropertyParser::consumeFont(bool important) { // Let's check if there is an inherit or initial somewhere in the shorthand. CSSParserTokenRange range = m_range; while (!range.atEnd()) { CSSValueID id = range.consumeIncludingWhitespace().id(); if (id == CSSValueInherit || id == CSSValueInitial) return false; } // Optional font-style, font-variant, font-stretch and font-weight. RefPtr fontStyle; RefPtr fontVariantCaps; RefPtr fontWeight; // FIXME-NEWPARSER: Implement. RefPtr fontStretch; while (!m_range.atEnd()) { CSSValueID id = m_range.peek().id(); if (!fontStyle && CSSParserFastPaths::isValidKeywordPropertyAndValue(CSSPropertyFontStyle, id, m_context.mode)) { fontStyle = consumeIdent(m_range); continue; } if (!fontVariantCaps && (id == CSSValueNormal || id == CSSValueSmallCaps)) { // Font variant in the shorthand is particular, it only accepts normal or small-caps. // See https://drafts.csswg.org/css-fonts/#propdef-font fontVariantCaps = consumeFontVariantCSS21(m_range); if (fontVariantCaps) continue; } if (!fontWeight) { fontWeight = consumeFontWeight(m_range); if (fontWeight) continue; } /* FIXME-NEWPARSER: Implement if (!fontStretch && CSSParserFastPaths::isValidKeywordPropertyAndValue(CSSPropertyFontStretch, id, m_context.mode, m_styleSheetContents)) fontStretch = consumeIdent(m_range); else*/ break; } if (m_range.atEnd()) return false; bool hasStyle = fontStyle; bool hasVariant = fontVariantCaps; bool hasWeight = fontWeight; addProperty(CSSPropertyFontStyle, CSSPropertyFont, fontStyle ? fontStyle.releaseNonNull() : CSSValuePool::singleton().createIdentifierValue(CSSValueNormal), important, !hasStyle); addProperty(CSSPropertyFontVariantCaps, CSSPropertyFont, fontVariantCaps ? fontVariantCaps.releaseNonNull() : CSSValuePool::singleton().createIdentifierValue(CSSValueNormal), important, !hasVariant); /* // FIXME-NEWPARSER: What do we do with these? They aren't part of our fontShorthand(). addProperty(CSSPropertyFontVariantLigatures, CSSPropertyFont, CSSValuePool::singleton().createIdentifierValue(CSSValueNormal), important, true); addProperty(CSSPropertyFontVariantNumeric, CSSPropertyFont, CSSValuePool::singleton().createIdentifierValue(CSSValueNormal), important, true); */ addProperty(CSSPropertyFontWeight, CSSPropertyFont, fontWeight ? fontWeight.releaseNonNull() : CSSValuePool::singleton().createIdentifierValue(CSSValueNormal), important, !hasWeight); // Now a font size _must_ come. RefPtr fontSize = consumeFontSize(m_range, m_context.mode); if (!fontSize || m_range.atEnd()) return false; addProperty(CSSPropertyFontSize, CSSPropertyFont, *fontSize, important); if (consumeSlashIncludingWhitespace(m_range)) { RefPtr lineHeight = consumeLineHeight(m_range, m_context.mode); if (!lineHeight) return false; addProperty(CSSPropertyLineHeight, CSSPropertyFont, lineHeight.releaseNonNull(), important); } else { addProperty(CSSPropertyLineHeight, CSSPropertyFont, CSSValuePool::singleton().createIdentifierValue(CSSValueNormal), important, true); } // Font family must come now. RefPtr parsedFamilyValue = consumeFontFamily(m_range); if (!parsedFamilyValue) return false; addProperty(CSSPropertyFontFamily, CSSPropertyFont, parsedFamilyValue.releaseNonNull(), important); return m_range.atEnd(); } bool CSSPropertyParser::consumeFontVariantShorthand(bool important) { if (identMatches(m_range.peek().id())) { addProperty(CSSPropertyFontVariantLigatures, CSSPropertyFontVariant, consumeIdent(m_range).releaseNonNull(), important); addProperty(CSSPropertyFontVariantCaps, CSSPropertyFontVariant, CSSValuePool::singleton().createIdentifierValue(CSSValueNormal), important); addProperty(CSSPropertyFontVariantEastAsian, CSSPropertyFontVariant, CSSValuePool::singleton().createIdentifierValue(CSSValueNormal), important); addProperty(CSSPropertyFontVariantPosition, CSSPropertyFontVariant, CSSValuePool::singleton().createIdentifierValue(CSSValueNormal), important); return m_range.atEnd(); } RefPtr capsValue; RefPtr alternatesValue; RefPtr positionValue; RefPtr eastAsianValue; FontVariantLigaturesParser ligaturesParser; FontVariantNumericParser numericParser; do { if (!capsValue) { capsValue = consumeFontVariantCaps(m_range); if (capsValue) continue; } if (!positionValue) { positionValue = consumeFontVariantPosition(m_range); if (positionValue) continue; } if (!alternatesValue) { alternatesValue = consumeFontVariantAlternates(m_range); if (alternatesValue) continue; } FontVariantLigaturesParser::ParseResult ligaturesParseResult = ligaturesParser.consumeLigature(m_range); FontVariantNumericParser::ParseResult numericParseResult = numericParser.consumeNumeric(m_range); if (ligaturesParseResult == FontVariantLigaturesParser::ParseResult::ConsumedValue || numericParseResult == FontVariantNumericParser::ParseResult::ConsumedValue) continue; if (ligaturesParseResult == FontVariantLigaturesParser::ParseResult::DisallowedValue || numericParseResult == FontVariantNumericParser::ParseResult::DisallowedValue) return false; if (!eastAsianValue) { eastAsianValue = consumeFontVariantEastAsian(m_range); if (eastAsianValue) continue; } // Saw some value that didn't match anything else. return false; } while (!m_range.atEnd()); addProperty(CSSPropertyFontVariantLigatures, CSSPropertyFontVariant, ligaturesParser.finalizeValue().releaseNonNull(), important); addProperty(CSSPropertyFontVariantNumeric, CSSPropertyFontVariant, numericParser.finalizeValue().releaseNonNull(), important); addProperty(CSSPropertyFontVariantCaps, CSSPropertyFontVariant, capsValue ? capsValue.releaseNonNull() : CSSValuePool::singleton().createIdentifierValue(CSSValueNormal), important); addProperty(CSSPropertyFontVariantAlternates, CSSPropertyFontVariant, alternatesValue ? alternatesValue.releaseNonNull() : CSSValuePool::singleton().createIdentifierValue(CSSValueNormal), important); addProperty(CSSPropertyFontVariantPosition, CSSPropertyFontVariant, positionValue ? positionValue.releaseNonNull() : CSSValuePool::singleton().createIdentifierValue(CSSValueNormal), important); if (!eastAsianValue) eastAsianValue = CSSValuePool::singleton().createIdentifierValue(CSSValueNormal); addProperty(CSSPropertyFontVariantEastAsian, CSSPropertyFontVariant, eastAsianValue.releaseNonNull(), important); return true; } bool CSSPropertyParser::consumeBorderSpacing(bool important) { RefPtr horizontalSpacing = consumeLength(m_range, m_context.mode, ValueRangeNonNegative, UnitlessQuirk::Allow); if (!horizontalSpacing) return false; RefPtr verticalSpacing = horizontalSpacing; if (!m_range.atEnd()) verticalSpacing = consumeLength(m_range, m_context.mode, ValueRangeNonNegative, UnitlessQuirk::Allow); if (!verticalSpacing || !m_range.atEnd()) return false; addProperty(CSSPropertyWebkitBorderHorizontalSpacing, CSSPropertyBorderSpacing, horizontalSpacing.releaseNonNull(), important); addProperty(CSSPropertyWebkitBorderVerticalSpacing, CSSPropertyBorderSpacing, verticalSpacing.releaseNonNull(), important); return true; } #if ENABLE(CSS_DEVICE_ADAPTATION) static RefPtr consumeSingleViewportDescriptor(CSSParserTokenRange& range, CSSPropertyID propId, CSSParserMode cssParserMode) { CSSValueID id = range.peek().id(); switch (propId) { case CSSPropertyMinWidth: case CSSPropertyMaxWidth: case CSSPropertyMinHeight: case CSSPropertyMaxHeight: if (id == CSSValueAuto) return consumeIdent(range); return consumeLengthOrPercent(range, cssParserMode, ValueRangeNonNegative); case CSSPropertyMinZoom: case CSSPropertyMaxZoom: case CSSPropertyZoom: { if (id == CSSValueAuto) return consumeIdent(range); RefPtr parsedValue = consumeNumber(range, ValueRangeNonNegative); if (parsedValue) return parsedValue; return consumePercent(range, ValueRangeNonNegative); } case CSSPropertyUserZoom: return consumeIdent(range); case CSSPropertyOrientation: return consumeIdent(range); default: ASSERT_NOT_REACHED(); break; } ASSERT_NOT_REACHED(); return nullptr; } bool CSSPropertyParser::parseViewportDescriptor(CSSPropertyID propId, bool important) { switch (propId) { case CSSPropertyWidth: { RefPtr minWidth = consumeSingleViewportDescriptor(m_range, CSSPropertyMinWidth, m_context.mode); if (!minWidth) return false; RefPtr maxWidth = minWidth; if (!m_range.atEnd()) maxWidth = consumeSingleViewportDescriptor(m_range, CSSPropertyMaxWidth, m_context.mode); if (!maxWidth || !m_range.atEnd()) return false; addProperty(CSSPropertyMinWidth, CSSPropertyInvalid, *minWidth, important); addProperty(CSSPropertyMaxWidth, CSSPropertyInvalid, *maxWidth, important); return true; } case CSSPropertyHeight: { RefPtr minHeight = consumeSingleViewportDescriptor(m_range, CSSPropertyMinHeight, m_context.mode); if (!minHeight) return false; RefPtr maxHeight = minHeight; if (!m_range.atEnd()) maxHeight = consumeSingleViewportDescriptor(m_range, CSSPropertyMaxHeight, m_context.mode); if (!maxHeight || !m_range.atEnd()) return false; addProperty(CSSPropertyMinHeight, CSSPropertyInvalid, *minHeight, important); addProperty(CSSPropertyMaxHeight, CSSPropertyInvalid, *maxHeight, important); return true; } case CSSPropertyMinWidth: case CSSPropertyMaxWidth: case CSSPropertyMinHeight: case CSSPropertyMaxHeight: case CSSPropertyMinZoom: case CSSPropertyMaxZoom: case CSSPropertyZoom: case CSSPropertyUserZoom: case CSSPropertyOrientation: { RefPtr parsedValue = consumeSingleViewportDescriptor(m_range, propId, m_context.mode); if (!parsedValue || !m_range.atEnd()) return false; addProperty(propId, CSSPropertyInvalid, parsedValue.releaseNonNull(), important); return true; } default: return false; } } #endif bool CSSPropertyParser::consumeColumns(bool important) { RefPtr columnWidth; RefPtr columnCount; bool hasPendingExplicitAuto = false; for (unsigned propertiesParsed = 0; propertiesParsed < 2 && !m_range.atEnd(); ++propertiesParsed) { if (!propertiesParsed && m_range.peek().id() == CSSValueAuto) { // 'auto' is a valid value for any of the two longhands, and at this point // we don't know which one(s) it is meant for. We need to see if there are other values first. consumeIdent(m_range); hasPendingExplicitAuto = true; } else { if (!columnWidth) { if ((columnWidth = consumeColumnWidth(m_range))) continue; } if (!columnCount) { if ((columnCount = consumeColumnCount(m_range))) continue; } // If we didn't find at least one match, this is an invalid shorthand and we have to ignore it. return false; } } if (!m_range.atEnd()) return false; // Any unassigned property at this point will become implicit 'auto'. if (columnWidth) addProperty(CSSPropertyColumnWidth, CSSPropertyInvalid, columnWidth.releaseNonNull(), important); else { addProperty(CSSPropertyColumnWidth, CSSPropertyInvalid, CSSValuePool::singleton().createIdentifierValue(CSSValueAuto), important, !hasPendingExplicitAuto /* implicit */); hasPendingExplicitAuto = false; } if (columnCount) addProperty(CSSPropertyColumnCount, CSSPropertyInvalid, columnCount.releaseNonNull(), important); else addProperty(CSSPropertyColumnCount, CSSPropertyInvalid, CSSValuePool::singleton().createIdentifierValue(CSSValueAuto), important, !hasPendingExplicitAuto /* implicit */); return true; } bool CSSPropertyParser::consumeShorthandGreedily(const StylePropertyShorthand& shorthand, bool important) { ASSERT(shorthand.length() <= 6); // Existing shorthands have at most 6 longhands. RefPtr longhands[6]; const CSSPropertyID* shorthandProperties = shorthand.properties(); do { bool foundLonghand = false; for (size_t i = 0; !foundLonghand && i < shorthand.length(); ++i) { if (longhands[i]) continue; longhands[i] = parseSingleValue(shorthandProperties[i], shorthand.id()); if (longhands[i]) foundLonghand = true; } if (!foundLonghand) return false; } while (!m_range.atEnd()); for (size_t i = 0; i < shorthand.length(); ++i) { if (longhands[i]) addProperty(shorthandProperties[i], shorthand.id(), longhands[i].releaseNonNull(), important); else addProperty(shorthandProperties[i], shorthand.id(), CSSValuePool::singleton().createImplicitInitialValue(), important); } return true; } bool CSSPropertyParser::consumeFlex(bool important) { static const double unsetValue = -1; double flexGrow = unsetValue; double flexShrink = unsetValue; RefPtr flexBasis; if (m_range.peek().id() == CSSValueNone) { flexGrow = 0; flexShrink = 0; flexBasis = CSSValuePool::singleton().createIdentifierValue(CSSValueAuto); m_range.consumeIncludingWhitespace(); } else { unsigned index = 0; while (!m_range.atEnd() && index++ < 3) { double num; if (consumeNumberRaw(m_range, num)) { if (num < 0) return false; if (flexGrow == unsetValue) flexGrow = num; else if (flexShrink == unsetValue) flexShrink = num; else if (!num) // flex only allows a basis of 0 (sans units) if flex-grow and flex-shrink values have already been set. flexBasis = CSSPrimitiveValue::create(0, CSSPrimitiveValue::UnitType::CSS_PX); else return false; } else if (!flexBasis) { if (m_range.peek().id() == CSSValueAuto) flexBasis = consumeIdent(m_range); if (!flexBasis) flexBasis = consumeLengthOrPercent(m_range, m_context.mode, ValueRangeNonNegative); if (index == 2 && !m_range.atEnd()) return false; } } if (index == 0) return false; if (flexGrow == unsetValue) flexGrow = 1; if (flexShrink == unsetValue) flexShrink = 1; if (!flexBasis) flexBasis = CSSPrimitiveValue::create(0, CSSPrimitiveValue::UnitType::CSS_PX); } if (!m_range.atEnd()) return false; addProperty(CSSPropertyFlexGrow, CSSPropertyFlex, CSSPrimitiveValue::create(clampTo(flexGrow), CSSPrimitiveValue::UnitType::CSS_NUMBER), important); addProperty(CSSPropertyFlexShrink, CSSPropertyFlex, CSSPrimitiveValue::create(clampTo(flexShrink), CSSPrimitiveValue::UnitType::CSS_NUMBER), important); addProperty(CSSPropertyFlexBasis, CSSPropertyFlex, flexBasis.releaseNonNull(), important); return true; } bool CSSPropertyParser::consumeBorder(bool important) { RefPtr width; RefPtr style; RefPtr color; while (!width || !style || !color) { if (!width) { width = consumeLineWidth(m_range, m_context.mode, UnitlessQuirk::Forbid); if (width) continue; } if (!style) { style = parseSingleValue(CSSPropertyBorderLeftStyle, CSSPropertyBorder); if (style) continue; } if (!color) { color = consumeColor(m_range, m_context.mode); if (color) continue; } break; } if (!width && !style && !color) return false; if (!width) width = CSSValuePool::singleton().createImplicitInitialValue(); if (!style) style = CSSValuePool::singleton().createImplicitInitialValue(); if (!color) color = CSSValuePool::singleton().createImplicitInitialValue(); addExpandedPropertyForValue(CSSPropertyBorderWidth, width.releaseNonNull(), important); addExpandedPropertyForValue(CSSPropertyBorderStyle, style.releaseNonNull(), important); addExpandedPropertyForValue(CSSPropertyBorderColor, color.releaseNonNull(), important); addExpandedPropertyForValue(CSSPropertyBorderImage, CSSValuePool::singleton().createImplicitInitialValue(), important); return m_range.atEnd(); } bool CSSPropertyParser::consume4Values(const StylePropertyShorthand& shorthand, bool important) { ASSERT(shorthand.length() == 4); const CSSPropertyID* longhands = shorthand.properties(); RefPtr top = parseSingleValue(longhands[0], shorthand.id()); if (!top) return false; RefPtr right = parseSingleValue(longhands[1], shorthand.id()); RefPtr bottom; RefPtr left; if (right) { bottom = parseSingleValue(longhands[2], shorthand.id()); if (bottom) left = parseSingleValue(longhands[3], shorthand.id()); } bool rightImplicit = !right; bool bottomImplicit = !bottom; bool leftImplicit = !left; if (!right) right = top; if (!bottom) bottom = top; if (!left) left = right; addProperty(longhands[0], shorthand.id(), top.releaseNonNull(), important); addProperty(longhands[1], shorthand.id(), right.releaseNonNull(), important, rightImplicit); addProperty(longhands[2], shorthand.id(), bottom.releaseNonNull(), important, bottomImplicit); addProperty(longhands[3], shorthand.id(), left.releaseNonNull(), important, leftImplicit); return m_range.atEnd(); } bool CSSPropertyParser::consumeBorderImage(CSSPropertyID property, bool important) { RefPtr source; RefPtr slice; RefPtr width; RefPtr outset; RefPtr repeat; if (consumeBorderImageComponents(property, m_range, m_context, source, slice, width, outset, repeat)) { if (!source) source = CSSValuePool::singleton().createImplicitInitialValue(); if (!slice) slice = CSSValuePool::singleton().createImplicitInitialValue(); if (!width) width = CSSValuePool::singleton().createImplicitInitialValue(); if (!outset) outset = CSSValuePool::singleton().createImplicitInitialValue(); if (!repeat) repeat = CSSValuePool::singleton().createImplicitInitialValue(); switch (property) { case CSSPropertyWebkitMaskBoxImage: addProperty(CSSPropertyWebkitMaskBoxImageSource, CSSPropertyWebkitMaskBoxImage, source.releaseNonNull(), important); addProperty(CSSPropertyWebkitMaskBoxImageSlice, CSSPropertyWebkitMaskBoxImage, slice.releaseNonNull(), important); addProperty(CSSPropertyWebkitMaskBoxImageWidth, CSSPropertyWebkitMaskBoxImage, width.releaseNonNull(), important); addProperty(CSSPropertyWebkitMaskBoxImageOutset, CSSPropertyWebkitMaskBoxImage, outset.releaseNonNull(), important); addProperty(CSSPropertyWebkitMaskBoxImageRepeat, CSSPropertyWebkitMaskBoxImage, repeat.releaseNonNull(), important); return true; case CSSPropertyBorderImage: addProperty(CSSPropertyBorderImageSource, CSSPropertyBorderImage, source.releaseNonNull(), important); addProperty(CSSPropertyBorderImageSlice, CSSPropertyBorderImage, slice.releaseNonNull(), important); addProperty(CSSPropertyBorderImageWidth, CSSPropertyBorderImage, width.releaseNonNull() , important); addProperty(CSSPropertyBorderImageOutset, CSSPropertyBorderImage, outset.releaseNonNull(), important); addProperty(CSSPropertyBorderImageRepeat, CSSPropertyBorderImage, repeat.releaseNonNull(), important); return true; default: ASSERT_NOT_REACHED(); return false; } } return false; } static inline CSSValueID mapFromPageBreakBetween(CSSValueID value) { if (value == CSSValueAlways) return CSSValuePage; if (value == CSSValueAuto || value == CSSValueLeft || value == CSSValueRight) return value; if (value == CSSValueAvoid) return CSSValueAvoidPage; return CSSValueInvalid; } static inline CSSValueID mapFromColumnBreakBetween(CSSValueID value) { if (value == CSSValueAlways) return CSSValueColumn; if (value == CSSValueAuto) return value; if (value == CSSValueAvoid) return CSSValueAvoidColumn; return CSSValueInvalid; } #if ENABLE(CSS_REGIONS) static inline CSSValueID mapFromRegionBreakBetween(CSSValueID value) { if (value == CSSValueAlways) return CSSValueRegion; if (value == CSSValueAuto) return value; if (value == CSSValueAvoid) return CSSValueAvoidRegion; return CSSValueInvalid; } #endif static inline CSSValueID mapFromColumnRegionOrPageBreakInside(CSSValueID value) { if (value == CSSValueAuto || value == CSSValueAvoid) return value; return CSSValueInvalid; } static inline CSSPropertyID mapFromLegacyBreakProperty(CSSPropertyID property) { if (property == CSSPropertyPageBreakAfter || property == CSSPropertyWebkitColumnBreakAfter) return CSSPropertyBreakAfter; if (property == CSSPropertyPageBreakBefore || property == CSSPropertyWebkitColumnBreakBefore) return CSSPropertyBreakBefore; #if ENABLE(CSS_REGIONS) if (property == CSSPropertyWebkitRegionBreakAfter) return CSSPropertyBreakAfter; if (property == CSSPropertyWebkitRegionBreakBefore) return CSSPropertyBreakBefore; ASSERT(property == CSSPropertyPageBreakInside || property == CSSPropertyWebkitColumnBreakInside || property == CSSPropertyWebkitRegionBreakInside); #else ASSERT(property == CSSPropertyPageBreakInside || property == CSSPropertyWebkitColumnBreakInside); #endif return CSSPropertyBreakInside; } bool CSSPropertyParser::consumeLegacyBreakProperty(CSSPropertyID property, bool important) { // The fragmentation spec says that page-break-(after|before|inside) are to be treated as // shorthands for their break-(after|before|inside) counterparts. We'll do the same for the // non-standard properties -webkit-column-break-(after|before|inside). RefPtr keyword = consumeIdent(m_range); if (!keyword) return false; if (!m_range.atEnd()) return false; CSSValueID value = keyword->valueID(); switch (property) { case CSSPropertyPageBreakAfter: case CSSPropertyPageBreakBefore: value = mapFromPageBreakBetween(value); break; case CSSPropertyWebkitColumnBreakAfter: case CSSPropertyWebkitColumnBreakBefore: value = mapFromColumnBreakBetween(value); break; #if ENABLE(CSS_REGIONS) case CSSPropertyWebkitRegionBreakAfter: case CSSPropertyWebkitRegionBreakBefore: value = mapFromRegionBreakBetween(value); break; case CSSPropertyWebkitRegionBreakInside: #endif case CSSPropertyPageBreakInside: case CSSPropertyWebkitColumnBreakInside: value = mapFromColumnRegionOrPageBreakInside(value); break; default: ASSERT_NOT_REACHED(); } if (value == CSSValueInvalid) return false; CSSPropertyID genericBreakProperty = mapFromLegacyBreakProperty(property); addProperty(genericBreakProperty, property, CSSValuePool::singleton().createIdentifierValue(value), important); return true; } static bool consumeBackgroundPosition(CSSParserTokenRange& range, const CSSParserContext& context, UnitlessQuirk unitless, RefPtr& resultX, RefPtr& resultY) { do { RefPtr positionX; RefPtr positionY; if (!consumePosition(range, context.mode, unitless, positionX, positionY)) return false; addBackgroundValue(resultX, positionX.releaseNonNull()); addBackgroundValue(resultY, positionY.releaseNonNull()); } while (consumeCommaIncludingWhitespace(range)); return true; } static bool consumeRepeatStyleComponent(CSSParserTokenRange& range, RefPtr& value1, RefPtr& value2, bool& implicit) { if (consumeIdent(range)) { value1 = CSSValuePool::singleton().createIdentifierValue(CSSValueRepeat); value2 = CSSValuePool::singleton().createIdentifierValue(CSSValueNoRepeat); implicit = true; return true; } if (consumeIdent(range)) { value1 = CSSValuePool::singleton().createIdentifierValue(CSSValueNoRepeat); value2 = CSSValuePool::singleton().createIdentifierValue(CSSValueRepeat); implicit = true; return true; } value1 = consumeIdent(range); if (!value1) return false; value2 = consumeIdent(range); if (!value2) { value2 = value1; implicit = true; } return true; } static bool consumeRepeatStyle(CSSParserTokenRange& range, RefPtr& resultX, RefPtr& resultY, bool& implicit) { do { RefPtr repeatX; RefPtr repeatY; if (!consumeRepeatStyleComponent(range, repeatX, repeatY, implicit)) return false; addBackgroundValue(resultX, repeatX.releaseNonNull()); addBackgroundValue(resultY, repeatY.releaseNonNull()); } while (consumeCommaIncludingWhitespace(range)); return true; } // Note: consumeBackgroundShorthand assumes y properties (for example background-position-y) follow // the x properties in the shorthand array. bool CSSPropertyParser::consumeBackgroundShorthand(const StylePropertyShorthand& shorthand, bool important) { const unsigned longhandCount = shorthand.length(); RefPtr longhands[10]; ASSERT(longhandCount <= 10); bool implicit = false; do { bool parsedLonghand[10] = { false }; RefPtr originValue; do { bool foundProperty = false; for (size_t i = 0; i < longhandCount; ++i) { if (parsedLonghand[i]) continue; RefPtr value; RefPtr valueY; CSSPropertyID property = shorthand.properties()[i]; if (property == CSSPropertyBackgroundRepeatX || property == CSSPropertyWebkitMaskRepeatX) { RefPtr primitiveValue; RefPtr primitiveValueY; consumeRepeatStyleComponent(m_range, primitiveValue, primitiveValueY, implicit); value = primitiveValue; valueY = primitiveValueY; } else if (property == CSSPropertyBackgroundPositionX || property == CSSPropertyWebkitMaskPositionX) { CSSParserTokenRange rangeCopy = m_range; RefPtr primitiveValue; RefPtr primitiveValueY; if (!consumePosition(rangeCopy, m_context.mode, UnitlessQuirk::Forbid, primitiveValue, primitiveValueY)) continue; value = primitiveValue; valueY = primitiveValueY; m_range = rangeCopy; } else if (property == CSSPropertyBackgroundSize || property == CSSPropertyWebkitMaskSize) { if (!consumeSlashIncludingWhitespace(m_range)) continue; value = consumeBackgroundSize(property, m_range, m_context.mode); if (!value || !parsedLonghand[i - 1]) // Position must have been parsed in the current layer. return false; } else if (property == CSSPropertyBackgroundPositionY || property == CSSPropertyBackgroundRepeatY || property == CSSPropertyWebkitMaskPositionY || property == CSSPropertyWebkitMaskRepeatY) { continue; } else { value = consumeBackgroundComponent(property, m_range, m_context); } if (value) { if (property == CSSPropertyBackgroundOrigin || property == CSSPropertyWebkitMaskOrigin) originValue = value; parsedLonghand[i] = true; foundProperty = true; addBackgroundValue(longhands[i], value.releaseNonNull()); if (valueY) { parsedLonghand[i + 1] = true; addBackgroundValue(longhands[i + 1], valueY.releaseNonNull()); } } } if (!foundProperty) return false; } while (!m_range.atEnd() && m_range.peek().type() != CommaToken); // FIXME: This will make invalid longhands, see crbug.com/386459 for (size_t i = 0; i < longhandCount; ++i) { CSSPropertyID property = shorthand.properties()[i]; if (property == CSSPropertyBackgroundColor && !m_range.atEnd()) { if (parsedLonghand[i]) return false; // Colors are only allowed in the last layer. continue; } if ((property == CSSPropertyBackgroundClip || property == CSSPropertyWebkitMaskClip) && !parsedLonghand[i] && originValue) { addBackgroundValue(longhands[i], originValue.releaseNonNull()); continue; } if (!parsedLonghand[i]) addBackgroundValue(longhands[i], CSSValuePool::singleton().createImplicitInitialValue()); } } while (consumeCommaIncludingWhitespace(m_range)); if (!m_range.atEnd()) return false; for (size_t i = 0; i < longhandCount; ++i) { CSSPropertyID property = shorthand.properties()[i]; if (property == CSSPropertyBackgroundSize && longhands[i] && m_context.useLegacyBackgroundSizeShorthandBehavior) continue; addProperty(property, shorthand.id(), *longhands[i], important, implicit); } return true; } // FIXME-NEWPARSER: Hack to work around the fact that we aren't using CSSCustomIdentValue // for stuff yet. This can be replaced by CSSValue::isCustomIdentValue() once we switch // to using CSSCustomIdentValue everywhere. static bool isCustomIdentValue(const CSSValue& value) { return is(value) && downcast(value).isString(); } bool CSSPropertyParser::consumeGridItemPositionShorthand(CSSPropertyID shorthandId, bool important) { const StylePropertyShorthand& shorthand = shorthandForProperty(shorthandId); ASSERT(shorthand.length() == 2); RefPtr startValue = consumeGridLine(m_range); if (!startValue) return false; RefPtr endValue; if (consumeSlashIncludingWhitespace(m_range)) { endValue = consumeGridLine(m_range); if (!endValue) return false; } else { endValue = isCustomIdentValue(*startValue) ? startValue : CSSValuePool::singleton().createIdentifierValue(CSSValueAuto); } if (!m_range.atEnd()) return false; addProperty(shorthand.properties()[0], shorthandId, startValue.releaseNonNull(), important); addProperty(shorthand.properties()[1], shorthandId, endValue.releaseNonNull(), important); return true; } bool CSSPropertyParser::consumeGridAreaShorthand(bool important) { RefPtr rowStartValue = consumeGridLine(m_range); if (!rowStartValue) return false; RefPtr columnStartValue; RefPtr rowEndValue; RefPtr columnEndValue; if (consumeSlashIncludingWhitespace(m_range)) { columnStartValue = consumeGridLine(m_range); if (!columnStartValue) return false; if (consumeSlashIncludingWhitespace(m_range)) { rowEndValue = consumeGridLine(m_range); if (!rowEndValue) return false; if (consumeSlashIncludingWhitespace(m_range)) { columnEndValue = consumeGridLine(m_range); if (!columnEndValue) return false; } } } if (!m_range.atEnd()) return false; if (!columnStartValue) columnStartValue = isCustomIdentValue(*rowStartValue) ? rowStartValue : CSSValuePool::singleton().createIdentifierValue(CSSValueAuto); if (!rowEndValue) rowEndValue = isCustomIdentValue(*rowStartValue) ? rowStartValue : CSSValuePool::singleton().createIdentifierValue(CSSValueAuto); if (!columnEndValue) columnEndValue = isCustomIdentValue(*columnStartValue) ? columnStartValue : CSSValuePool::singleton().createIdentifierValue(CSSValueAuto); addProperty(CSSPropertyGridRowStart, CSSPropertyGridArea, rowStartValue.releaseNonNull(), important); addProperty(CSSPropertyGridColumnStart, CSSPropertyGridArea, columnStartValue.releaseNonNull(), important); addProperty(CSSPropertyGridRowEnd, CSSPropertyGridArea, rowEndValue.releaseNonNull(), important); addProperty(CSSPropertyGridColumnEnd, CSSPropertyGridArea, columnEndValue.releaseNonNull(), important); return true; } bool CSSPropertyParser::consumeGridTemplateRowsAndAreasAndColumns(CSSPropertyID shorthandId, bool important) { NamedGridAreaMap gridAreaMap; size_t rowCount = 0; size_t columnCount = 0; RefPtr templateRows = CSSValueList::createSpaceSeparated(); // Persists between loop iterations so we can use the same value for // consecutive values RefPtr lineNames; do { // Handle leading *. bool hasPreviousLineNames = lineNames; lineNames = consumeGridLineNames(m_range, lineNames.get()); if (lineNames && !hasPreviousLineNames) templateRows->append(*lineNames); // Handle a template-area's row. if (m_range.peek().type() != StringToken || !parseGridTemplateAreasRow(m_range.consumeIncludingWhitespace().value().toString(), gridAreaMap, rowCount, columnCount)) return false; ++rowCount; // Handle template-rows's track-size. RefPtr value = consumeGridTrackSize(m_range, m_context.mode); if (!value) value = CSSValuePool::singleton().createIdentifierValue(CSSValueAuto); templateRows->append(*value); // This will handle the trailing/leading * in the grammar. lineNames = consumeGridLineNames(m_range); if (lineNames) templateRows->append(lineNames.releaseNonNull()); } while (!m_range.atEnd() && !(m_range.peek().type() == DelimiterToken && m_range.peek().delimiter() == '/')); RefPtr columnsValue; if (!m_range.atEnd()) { if (!consumeSlashIncludingWhitespace(m_range)) return false; columnsValue = consumeGridTrackList(m_range, m_context.mode, GridTemplateNoRepeat); if (!columnsValue || !m_range.atEnd()) return false; } else { columnsValue = CSSValuePool::singleton().createIdentifierValue(CSSValueNone); } addProperty(CSSPropertyGridTemplateRows, shorthandId, templateRows.releaseNonNull(), important); addProperty(CSSPropertyGridTemplateColumns, shorthandId, columnsValue.releaseNonNull(), important); addProperty(CSSPropertyGridTemplateAreas, shorthandId, CSSGridTemplateAreasValue::create(gridAreaMap, rowCount, columnCount), important); return true; } bool CSSPropertyParser::consumeGridTemplateShorthand(CSSPropertyID shorthandId, bool important) { CSSParserTokenRange rangeCopy = m_range; RefPtr rowsValue = consumeIdent(m_range); // 1- 'none' case. if (rowsValue && m_range.atEnd()) { addProperty(CSSPropertyGridTemplateRows, shorthandId, CSSValuePool::singleton().createIdentifierValue(CSSValueNone), important); addProperty(CSSPropertyGridTemplateColumns, shorthandId, CSSValuePool::singleton().createIdentifierValue(CSSValueNone), important); addProperty(CSSPropertyGridTemplateAreas, shorthandId, CSSValuePool::singleton().createIdentifierValue(CSSValueNone), important); return true; } // 2- / if (!rowsValue) rowsValue = consumeGridTrackList(m_range, m_context.mode, GridTemplate); if (rowsValue) { if (!consumeSlashIncludingWhitespace(m_range)) return false; RefPtr columnsValue = consumeGridTemplatesRowsOrColumns(m_range, m_context.mode); if (!columnsValue || !m_range.atEnd()) return false; addProperty(CSSPropertyGridTemplateRows, shorthandId, rowsValue.releaseNonNull(), important); addProperty(CSSPropertyGridTemplateColumns, shorthandId, columnsValue.releaseNonNull(), important); addProperty(CSSPropertyGridTemplateAreas, shorthandId, CSSValuePool::singleton().createIdentifierValue(CSSValueNone), important); return true; } // 3- [ ? ? ? ]+ [ / ]? m_range = rangeCopy; return consumeGridTemplateRowsAndAreasAndColumns(shorthandId, important); } static RefPtr consumeImplicitGridAutoFlow(CSSParserTokenRange& range, Ref&& flowDirection) { // [ auto-flow && dense? ] if (range.atEnd()) return nullptr; auto list = CSSValueList::createSpaceSeparated(); list->append(WTFMove(flowDirection)); if (range.peek().id() == CSSValueAutoFlow) { range.consumeIncludingWhitespace(); RefPtr denseIdent = consumeIdent(range); if (denseIdent) list->append(denseIdent.releaseNonNull()); } else { // Dense case if (range.peek().id() != CSSValueDense) return nullptr; range.consumeIncludingWhitespace(); if (range.atEnd() || range.peek().id() != CSSValueAutoFlow) return nullptr; range.consumeIncludingWhitespace(); list->append(CSSValuePool::singleton().createIdentifierValue(CSSValueDense)); } return WTFMove(list); } bool CSSPropertyParser::consumeGridShorthand(bool important) { ASSERT(shorthandForProperty(CSSPropertyGrid).length() == 8); CSSParserTokenRange rangeCopy = m_range; // 1- if (consumeGridTemplateShorthand(CSSPropertyGrid, important)) { // It can only be specified the explicit or the implicit grid properties in a single grid declaration. // The sub-properties not specified are set to their initial value, as normal for shorthands. addProperty(CSSPropertyGridAutoFlow, CSSPropertyGrid, CSSValuePool::singleton().createImplicitInitialValue(), important); addProperty(CSSPropertyGridAutoColumns, CSSPropertyGrid, CSSValuePool::singleton().createImplicitInitialValue(), important); addProperty(CSSPropertyGridAutoRows, CSSPropertyGrid, CSSValuePool::singleton().createImplicitInitialValue(), important); addProperty(CSSPropertyGridColumnGap, CSSPropertyGrid, CSSValuePool::singleton().createImplicitInitialValue(), important); addProperty(CSSPropertyGridRowGap, CSSPropertyGrid, CSSValuePool::singleton().createImplicitInitialValue(), important); return true; } m_range = rangeCopy; RefPtr autoColumnsValue; RefPtr autoRowsValue; RefPtr templateRows; RefPtr templateColumns; RefPtr gridAutoFlow; if (m_range.peek().id() == CSSValueAutoFlow || m_range.peek().id() == CSSValueDense) { // 2- [ auto-flow && dense? ] ? / gridAutoFlow = consumeImplicitGridAutoFlow(m_range, CSSValuePool::singleton().createIdentifierValue(CSSValueRow)); if (!gridAutoFlow || m_range.atEnd()) return false; if (consumeSlashIncludingWhitespace(m_range)) autoRowsValue = CSSValuePool::singleton().createImplicitInitialValue(); else { autoRowsValue = consumeGridTrackList(m_range, m_context.mode, GridAuto); if (!autoRowsValue) return false; if (!consumeSlashIncludingWhitespace(m_range)) return false; } if (m_range.atEnd()) return false; templateColumns = consumeGridTemplatesRowsOrColumns(m_range, m_context.mode); if (!templateColumns) return false; templateRows = CSSValuePool::singleton().createImplicitInitialValue(); autoColumnsValue = CSSValuePool::singleton().createImplicitInitialValue(); } else { // 3- / [ auto-flow && dense? ] ? templateRows = consumeGridTemplatesRowsOrColumns(m_range, m_context.mode); if (!templateRows) return false; if (!consumeSlashIncludingWhitespace(m_range) || m_range.atEnd()) return false; gridAutoFlow = consumeImplicitGridAutoFlow(m_range, CSSValuePool::singleton().createIdentifierValue(CSSValueColumn)); if (!gridAutoFlow) return false; if (m_range.atEnd()) autoColumnsValue = CSSValuePool::singleton().createImplicitInitialValue(); else { autoColumnsValue = consumeGridTrackList(m_range, m_context.mode, GridAuto); if (!autoColumnsValue) return false; } templateColumns = CSSValuePool::singleton().createImplicitInitialValue(); autoRowsValue = CSSValuePool::singleton().createImplicitInitialValue(); } if (!m_range.atEnd()) return false; // It can only be specified the explicit or the implicit grid properties in a single grid declaration. // The sub-properties not specified are set to their initial value, as normal for shorthands. addProperty(CSSPropertyGridTemplateColumns, CSSPropertyGrid, templateColumns.releaseNonNull(), important); addProperty(CSSPropertyGridTemplateRows, CSSPropertyGrid, templateRows.releaseNonNull(), important); addProperty(CSSPropertyGridTemplateAreas, CSSPropertyGrid, CSSValuePool::singleton().createImplicitInitialValue(), important); addProperty(CSSPropertyGridAutoFlow, CSSPropertyGrid, gridAutoFlow.releaseNonNull(), important); addProperty(CSSPropertyGridAutoColumns, CSSPropertyGrid, autoColumnsValue.releaseNonNull(), important); addProperty(CSSPropertyGridAutoRows, CSSPropertyGrid, autoRowsValue.releaseNonNull(), important); addProperty(CSSPropertyGridColumnGap, CSSPropertyGrid, CSSValuePool::singleton().createImplicitInitialValue(), important); addProperty(CSSPropertyGridRowGap, CSSPropertyGrid, CSSValuePool::singleton().createImplicitInitialValue(), important); return true; } bool CSSPropertyParser::parseShorthand(CSSPropertyID property, bool important) { switch (property) { case CSSPropertyWebkitMarginCollapse: { CSSValueID id = m_range.consumeIncludingWhitespace().id(); if (!CSSParserFastPaths::isValidKeywordPropertyAndValue(CSSPropertyWebkitMarginBeforeCollapse, id, m_context.mode)) return false; addProperty(CSSPropertyWebkitMarginBeforeCollapse, CSSPropertyWebkitMarginCollapse, CSSValuePool::singleton().createIdentifierValue(id), important); if (m_range.atEnd()) { addProperty(CSSPropertyWebkitMarginAfterCollapse, CSSPropertyWebkitMarginCollapse, CSSValuePool::singleton().createIdentifierValue(id), important); return true; } id = m_range.consumeIncludingWhitespace().id(); if (!CSSParserFastPaths::isValidKeywordPropertyAndValue(CSSPropertyWebkitMarginAfterCollapse, id, m_context.mode)) return false; addProperty(CSSPropertyWebkitMarginAfterCollapse, CSSPropertyWebkitMarginCollapse, CSSValuePool::singleton().createIdentifierValue(id), important); return true; } case CSSPropertyOverflow: { CSSValueID id = m_range.consumeIncludingWhitespace().id(); if (!CSSParserFastPaths::isValidKeywordPropertyAndValue(CSSPropertyOverflowY, id, m_context.mode)) return false; if (!m_range.atEnd()) return false; RefPtr overflowYValue = CSSValuePool::singleton().createIdentifierValue(id); RefPtr overflowXValue; // FIXME: -webkit-paged-x or -webkit-paged-y only apply to overflow-y. If this value has been // set using the shorthand, then for now overflow-x will default to auto, but once we implement // pagination controls, it should default to hidden. If the overflow-y value is anything but // paged-x or paged-y, then overflow-x and overflow-y should have the same value. if (id == CSSValueWebkitPagedX || id == CSSValueWebkitPagedY) overflowXValue = CSSValuePool::singleton().createIdentifierValue(CSSValueAuto); else overflowXValue = overflowYValue; addProperty(CSSPropertyOverflowX, CSSPropertyOverflow, *overflowXValue, important); addProperty(CSSPropertyOverflowY, CSSPropertyOverflow, *overflowYValue, important); return true; } case CSSPropertyFont: { const CSSParserToken& token = m_range.peek(); if (token.id() >= CSSValueCaption && token.id() <= CSSValueStatusBar) return consumeSystemFont(important); return consumeFont(important); } case CSSPropertyFontVariant: return consumeFontVariantShorthand(important); case CSSPropertyBorderSpacing: return consumeBorderSpacing(important); case CSSPropertyColumns: return consumeColumns(important); case CSSPropertyAnimation: return consumeAnimationShorthand(animationShorthandForParsing(), important); case CSSPropertyTransition: return consumeAnimationShorthand(transitionShorthandForParsing(), important); case CSSPropertyTextDecoration: case CSSPropertyWebkitTextDecoration: // FIXME-NEWPARSER: We need to unprefix -line/-style/-color ASAP and get rid // of -webkit-text-decoration completely. return consumeShorthandGreedily(webkitTextDecorationShorthand(), important); case CSSPropertyMargin: return consume4Values(marginShorthand(), important); case CSSPropertyPadding: return consume4Values(paddingShorthand(), important); #if ENABLE(CSS_SCROLL_SNAP) case CSSPropertyScrollSnapMargin: return consume4Values(scrollSnapMarginShorthand(), important); case CSSPropertyScrollPadding: return consume4Values(scrollPaddingShorthand(), important); #endif case CSSPropertyWebkitTextEmphasis: return consumeShorthandGreedily(webkitTextEmphasisShorthand(), important); case CSSPropertyOutline: return consumeShorthandGreedily(outlineShorthand(), important); case CSSPropertyWebkitBorderStart: return consumeShorthandGreedily(webkitBorderStartShorthand(), important); case CSSPropertyWebkitBorderEnd: return consumeShorthandGreedily(webkitBorderEndShorthand(), important); case CSSPropertyWebkitBorderBefore: return consumeShorthandGreedily(webkitBorderBeforeShorthand(), important); case CSSPropertyWebkitBorderAfter: return consumeShorthandGreedily(webkitBorderAfterShorthand(), important); case CSSPropertyWebkitTextStroke: return consumeShorthandGreedily(webkitTextStrokeShorthand(), important); case CSSPropertyMarker: { RefPtr marker = parseSingleValue(CSSPropertyMarkerStart); if (!marker || !m_range.atEnd()) return false; auto markerRef = marker.releaseNonNull(); addProperty(CSSPropertyMarkerStart, CSSPropertyMarker, markerRef.copyRef(), important); addProperty(CSSPropertyMarkerMid, CSSPropertyMarker, markerRef.copyRef(), important); addProperty(CSSPropertyMarkerEnd, CSSPropertyMarker, markerRef.copyRef(), important); return true; } case CSSPropertyFlex: return consumeFlex(important); case CSSPropertyFlexFlow: return consumeShorthandGreedily(flexFlowShorthand(), important); case CSSPropertyColumnRule: return consumeShorthandGreedily(columnRuleShorthand(), important); case CSSPropertyListStyle: return consumeShorthandGreedily(listStyleShorthand(), important); case CSSPropertyBorderRadius: case CSSPropertyWebkitBorderRadius: { RefPtr horizontalRadii[4]; RefPtr verticalRadii[4]; if (!consumeRadii(horizontalRadii, verticalRadii, m_range, m_context.mode, property == CSSPropertyWebkitBorderRadius)) return false; addProperty(CSSPropertyBorderTopLeftRadius, CSSPropertyBorderRadius, createPrimitiveValuePair(horizontalRadii[0].releaseNonNull(), verticalRadii[0].releaseNonNull(), Pair::IdenticalValueEncoding::Coalesce), important); addProperty(CSSPropertyBorderTopRightRadius, CSSPropertyBorderRadius, createPrimitiveValuePair(horizontalRadii[1].releaseNonNull(), verticalRadii[1].releaseNonNull(), Pair::IdenticalValueEncoding::Coalesce), important); addProperty(CSSPropertyBorderBottomRightRadius, CSSPropertyBorderRadius, createPrimitiveValuePair(horizontalRadii[2].releaseNonNull(), verticalRadii[2].releaseNonNull(), Pair::IdenticalValueEncoding::Coalesce), important); addProperty(CSSPropertyBorderBottomLeftRadius, CSSPropertyBorderRadius, createPrimitiveValuePair(horizontalRadii[3].releaseNonNull(), verticalRadii[3].releaseNonNull(), Pair::IdenticalValueEncoding::Coalesce), important); return true; } case CSSPropertyBorderColor: return consume4Values(borderColorShorthand(), important); case CSSPropertyBorderStyle: return consume4Values(borderStyleShorthand(), important); case CSSPropertyBorderWidth: return consume4Values(borderWidthShorthand(), important); case CSSPropertyBorderTop: return consumeShorthandGreedily(borderTopShorthand(), important); case CSSPropertyBorderRight: return consumeShorthandGreedily(borderRightShorthand(), important); case CSSPropertyBorderBottom: return consumeShorthandGreedily(borderBottomShorthand(), important); case CSSPropertyBorderLeft: return consumeShorthandGreedily(borderLeftShorthand(), important); case CSSPropertyBorder: return consumeBorder(important); case CSSPropertyBorderImage: return consumeBorderImage(property, important); case CSSPropertyWebkitMaskPosition: case CSSPropertyBackgroundPosition: { RefPtr resultX; RefPtr resultY; if (!consumeBackgroundPosition(m_range, m_context, UnitlessQuirk::Allow, resultX, resultY) || !m_range.atEnd()) return false; addProperty(property == CSSPropertyBackgroundPosition ? CSSPropertyBackgroundPositionX : CSSPropertyWebkitMaskPositionX, property, resultX.releaseNonNull(), important); addProperty(property == CSSPropertyBackgroundPosition ? CSSPropertyBackgroundPositionY : CSSPropertyWebkitMaskPositionY, property, resultY.releaseNonNull(), important); return true; } case CSSPropertyBackgroundRepeat: case CSSPropertyWebkitMaskRepeat: { RefPtr resultX; RefPtr resultY; bool implicit = false; if (!consumeRepeatStyle(m_range, resultX, resultY, implicit) || !m_range.atEnd()) return false; addProperty(property == CSSPropertyBackgroundRepeat ? CSSPropertyBackgroundRepeatX : CSSPropertyWebkitMaskRepeatX, property, resultX.releaseNonNull(), important, implicit); addProperty(property == CSSPropertyBackgroundRepeat ? CSSPropertyBackgroundRepeatY : CSSPropertyWebkitMaskRepeatY, property, resultY.releaseNonNull(), important, implicit); return true; } case CSSPropertyBackground: return consumeBackgroundShorthand(backgroundShorthand(), important); case CSSPropertyWebkitMask: return consumeBackgroundShorthand(webkitMaskShorthand(), important); case CSSPropertyTransformOrigin: return consumeTransformOrigin(important); case CSSPropertyPerspectiveOrigin: return consumePerspectiveOrigin(important); case CSSPropertyGridGap: { RefPtr rowGap = consumeLength(m_range, m_context.mode, ValueRangeNonNegative); RefPtr columnGap = consumeLength(m_range, m_context.mode, ValueRangeNonNegative); if (!rowGap || !m_range.atEnd()) return false; if (!columnGap) columnGap = rowGap; addProperty(CSSPropertyGridRowGap, CSSPropertyGridGap, rowGap.releaseNonNull(), important); addProperty(CSSPropertyGridColumnGap, CSSPropertyGridGap, columnGap.releaseNonNull(), important); return true; } case CSSPropertyGridColumn: case CSSPropertyGridRow: return consumeGridItemPositionShorthand(property, important); case CSSPropertyGridArea: return consumeGridAreaShorthand(important); case CSSPropertyGridTemplate: return consumeGridTemplateShorthand(CSSPropertyGridTemplate, important); case CSSPropertyGrid: return consumeGridShorthand(important); case CSSPropertyWebkitMarquee: return consumeShorthandGreedily(webkitMarqueeShorthand(), important); default: return false; } } } // namespace WebCore