diff options
Diffstat (limited to 'Source/WebCore/css/CSSParser.cpp')
| -rw-r--r-- | Source/WebCore/css/CSSParser.cpp | 8162 |
1 files changed, 8162 insertions, 0 deletions
diff --git a/Source/WebCore/css/CSSParser.cpp b/Source/WebCore/css/CSSParser.cpp new file mode 100644 index 000000000..7940b2774 --- /dev/null +++ b/Source/WebCore/css/CSSParser.cpp @@ -0,0 +1,8162 @@ +/* + * Copyright (C) 2003 Lars Knoll (knoll@kde.org) + * Copyright (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com) + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved. + * Copyright (C) 2007 Nicholas Shanks <webkit@nickshanks.com> + * Copyright (C) 2008 Eric Seidel <eric@webkit.org> + * Copyright (C) 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include "CSSParser.h" + +#include "CSSAspectRatioValue.h" +#include "CSSBorderImageValue.h" +#include "CSSCanvasValue.h" +#include "CSSCharsetRule.h" +#include "CSSCrossfadeValue.h" +#include "CSSCursorImageValue.h" +#include "CSSFlexValue.h" +#include "CSSFontFaceRule.h" +#include "CSSFontFaceSrcValue.h" +#include "CSSFunctionValue.h" +#include "CSSGradientValue.h" +#include "CSSImageValue.h" +#include "CSSImportRule.h" +#include "CSSInheritedValue.h" +#include "CSSInitialValue.h" +#include "CSSLineBoxContainValue.h" +#include "CSSMediaRule.h" +#include "CSSMutableStyleDeclaration.h" +#include "CSSPageRule.h" +#include "CSSPrimitiveValue.h" +#include "CSSProperty.h" +#include "CSSPropertyNames.h" +#include "CSSPropertySourceData.h" +#include "CSSReflectValue.h" +#include "CSSRuleList.h" +#include "CSSSelector.h" +#include "CSSStyleRule.h" +#include "CSSStyleSheet.h" +#include "CSSTimingFunctionValue.h" +#include "CSSUnicodeRangeValue.h" +#include "CSSValueKeywords.h" +#include "CSSValueList.h" +#include "CSSValuePool.h" +#include "CSSWrapShapes.h" +#include "Counter.h" +#include "Document.h" +#include "FloatConversion.h" +#include "FontFamilyValue.h" +#include "FontFeatureValue.h" +#include "FontValue.h" +#include "HTMLParserIdioms.h" +#include "HashTools.h" +#include "MediaList.h" +#include "MediaQueryExp.h" +#include "Page.h" +#include "Pair.h" +#include "Rect.h" +#include "RenderTheme.h" +#include "ShadowValue.h" +#if ENABLE(CSS_FILTERS) +#include "WebKitCSSFilterValue.h" +#endif +#include "WebKitCSSKeyframeRule.h" +#include "WebKitCSSKeyframesRule.h" +#include "WebKitCSSRegionRule.h" +#include "WebKitCSSTransformValue.h" +#if ENABLE(CSS_SHADERS) +#include "WebKitCSSShaderValue.h" +#endif +#include <limits.h> +#include <wtf/HexNumber.h> +#include <wtf/dtoa.h> +#include <wtf/text/StringBuffer.h> +#include <wtf/text/StringBuilder.h> + +#if ENABLE(DASHBOARD_SUPPORT) +#include "DashboardRegion.h" +#endif + +#define YYDEBUG 0 + +#if YYDEBUG > 0 +extern int cssyydebug; +#endif + +extern int cssyyparse(void* parser); + +using namespace std; +using namespace WTF; + +namespace { + +enum PropertyType { + PropertyExplicit, + PropertyImplicit +}; + +class ImplicitScope { + WTF_MAKE_NONCOPYABLE(ImplicitScope); +public: + ImplicitScope(WebCore::CSSParser* parser, PropertyType propertyType) + : m_parser(parser) + { + m_parser->m_implicitShorthand = propertyType == PropertyImplicit; + } + + ~ImplicitScope() + { + m_parser->m_implicitShorthand = false; + } + +private: + WebCore::CSSParser* m_parser; +}; + +} // namespace + +namespace WebCore { + +static const unsigned INVALID_NUM_PARSED_PROPERTIES = UINT_MAX; +static const double MAX_SCALE = 1000000; + +static bool equal(const CSSParserString& a, const char* b) +{ + for (int i = 0; i < a.length; ++i) { + if (!b[i]) + return false; + if (a.characters[i] != b[i]) + return false; + } + return !b[a.length]; +} + +static bool equalIgnoringCase(const CSSParserString& a, const char* b) +{ + for (int i = 0; i < a.length; ++i) { + if (!b[i]) + return false; + ASSERT(!isASCIIUpper(b[i])); + if (toASCIILower(a.characters[i]) != b[i]) + return false; + } + return !b[a.length]; +} + +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; +} + +inline void CSSParser::ensureCSSValuePool() +{ + if (!m_cssValuePool) + m_cssValuePool = CSSValuePool::create(); +} + +// FIXME: Can m_parsedProperties just be a Vector? + +CSSParser::CSSParser(bool strictParsing) + : m_strict(strictParsing) + , m_important(false) + , m_id(0) + , m_styleSheet(0) + , m_parsedProperties(static_cast<CSSProperty**>(fastMalloc(32 * sizeof(CSSProperty*)))) + , m_numParsedProperties(0) + , m_maxParsedProperties(32) + , m_numParsedPropertiesBeforeMarginBox(INVALID_NUM_PARSED_PROPERTIES) + , m_inParseShorthand(0) + , m_currentShorthand(0) + , m_implicitShorthand(false) + , m_hasFontFaceOnlyValues(false) + , m_hadSyntacticallyValidCSSRule(false) + , m_defaultNamespace(starAtom) + , m_inStyleRuleOrDeclaration(false) + , m_selectorListRange(0, 0) + , m_ruleBodyRange(0, 0) + , m_propertyRange(UINT_MAX, UINT_MAX) + , m_ruleRangeMap(0) + , m_currentRuleData(0) + , yy_start(1) + , m_lineNumber(0) + , m_lastSelectorLineNumber(0) + , m_allowImportRules(true) + , m_allowNamespaceDeclarations(true) +{ +#if YYDEBUG > 0 + cssyydebug = 1; +#endif + CSSPropertySourceData::init(); +} + +CSSParser::~CSSParser() +{ + clearProperties(); + fastFree(m_parsedProperties); + + fastDeleteAllValues(m_floatingSelectors); + deleteAllValues(m_floatingSelectorVectors); + deleteAllValues(m_floatingValueLists); + deleteAllValues(m_floatingFunctions); +} + +void CSSParserString::lower() +{ + // FIXME: If we need Unicode lowercasing here, then we probably want the real kind + // that can potentially change the length of the string rather than the character + // by character kind. If we don't need Unicode lowercasing, it would be good to + // simplify this function. + + if (charactersAreAllASCII(characters, length)) { + // Fast case for all-ASCII. + for (int i = 0; i < length; i++) + characters[i] = toASCIILower(characters[i]); + } else { + for (int i = 0; i < length; i++) + characters[i] = Unicode::toLower(characters[i]); + } +} + +void CSSParser::setupParser(const char* prefix, const String& string, const char* suffix) +{ + int length = string.length() + strlen(prefix) + strlen(suffix) + 2; + + m_data = adoptArrayPtr(new UChar[length]); + for (unsigned i = 0; i < strlen(prefix); i++) + m_data[i] = prefix[i]; + + memcpy(m_data.get() + strlen(prefix), string.characters(), string.length() * sizeof(UChar)); + + unsigned start = strlen(prefix) + string.length(); + unsigned end = start + strlen(suffix); + for (unsigned i = start; i < end; i++) + m_data[i] = suffix[i - start]; + + m_data[length - 1] = 0; + m_data[length - 2] = 0; + + yy_hold_char = 0; + yyleng = 0; + yytext = m_data.get(); + yy_c_buf_p = yytext; + yy_hold_char = *yy_c_buf_p; + resetRuleBodyMarks(); +} + +void CSSParser::parseSheet(CSSStyleSheet* sheet, const String& string, int startLineNumber, StyleRuleRangeMap* ruleRangeMap) +{ + setStyleSheet(sheet); + m_defaultNamespace = starAtom; // Reset the default namespace. + m_ruleRangeMap = ruleRangeMap; + if (ruleRangeMap) { + m_currentRuleData = CSSRuleSourceData::create(); + m_currentRuleData->styleSourceData = CSSStyleSourceData::create(); + } + + m_lineNumber = startLineNumber; + setupParser("", string, ""); + cssyyparse(this); + m_ruleRangeMap = 0; + m_currentRuleData = 0; + m_rule = 0; +} + +PassRefPtr<CSSRule> CSSParser::parseRule(CSSStyleSheet* sheet, const String& string) +{ + setStyleSheet(sheet); + m_allowNamespaceDeclarations = false; + setupParser("@-webkit-rule{", string, "} "); + cssyyparse(this); + return m_rule.release(); +} + +PassRefPtr<WebKitCSSKeyframeRule> CSSParser::parseKeyframeRule(CSSStyleSheet *sheet, const String &string) +{ + setStyleSheet(sheet); + setupParser("@-webkit-keyframe-rule{ ", string, "} "); + cssyyparse(this); + return m_keyframe.release(); +} + +static inline bool isColorPropertyID(int propertyId) +{ + switch (propertyId) { + case CSSPropertyColor: + case CSSPropertyBackgroundColor: + case CSSPropertyBorderBottomColor: + case CSSPropertyBorderLeftColor: + case CSSPropertyBorderRightColor: + case CSSPropertyBorderTopColor: + case CSSPropertyOutlineColor: + case CSSPropertyTextLineThroughColor: + case CSSPropertyTextOverlineColor: + case CSSPropertyTextUnderlineColor: + case CSSPropertyWebkitBorderAfterColor: + case CSSPropertyWebkitBorderBeforeColor: + case CSSPropertyWebkitBorderEndColor: + case CSSPropertyWebkitBorderStartColor: + case CSSPropertyWebkitColumnRuleColor: + case CSSPropertyWebkitTextEmphasisColor: + case CSSPropertyWebkitTextFillColor: + case CSSPropertyWebkitTextStrokeColor: + return true; + default: + return false; + } +} + +static bool parseColorValue(CSSMutableStyleDeclaration* declaration, int propertyId, const String& string, bool important, bool strict, CSSStyleSheet* contextStyleSheet = 0) +{ + if (!string.length()) + return false; + if (!isColorPropertyID(propertyId)) + return false; + CSSParserString cssString; + cssString.characters = const_cast<UChar*>(string.characters()); + cssString.length = string.length(); + int valueID = cssValueKeywordID(cssString); + bool validPrimitive = false; + if (valueID == CSSValueWebkitText) + validPrimitive = true; + else if (valueID == CSSValueCurrentcolor) + validPrimitive = true; + else if ((valueID >= CSSValueAqua && valueID <= CSSValueWindowtext) || valueID == CSSValueMenu + || (valueID >= CSSValueWebkitFocusRingColor && valueID < CSSValueWebkitText && !strict)) { + validPrimitive = true; + } + + CSSStyleSheet* styleSheet = contextStyleSheet ? contextStyleSheet : declaration->parentStyleSheet(); + if (!styleSheet) + return false; + Document* document = styleSheet->findDocument(); + if (!document) + return false; + if (validPrimitive) { + CSSProperty property(propertyId, document->cssValuePool()->createIdentifierValue(valueID), important); + declaration->addParsedProperty(property); + return true; + } + RGBA32 color; + if (!CSSParser::fastParseColor(color, string, strict && string[0] != '#')) + return false; + CSSProperty property(propertyId, document->cssValuePool()->createColorValue(color), important); + declaration->addParsedProperty(property); + return true; +} + +static inline bool isSimpleLengthPropertyID(int propertyId, bool& acceptsNegativeNumbers) +{ + switch (propertyId) { + case CSSPropertyFontSize: + case CSSPropertyHeight: + case CSSPropertyWidth: + case CSSPropertyMinHeight: + case CSSPropertyMinWidth: + case CSSPropertyPaddingBottom: + case CSSPropertyPaddingLeft: + case CSSPropertyPaddingRight: + case CSSPropertyPaddingTop: + case CSSPropertyWebkitLogicalWidth: + case CSSPropertyWebkitLogicalHeight: + case CSSPropertyWebkitMinLogicalWidth: + case CSSPropertyWebkitMinLogicalHeight: + case CSSPropertyWebkitPaddingAfter: + case CSSPropertyWebkitPaddingBefore: + case CSSPropertyWebkitPaddingEnd: + case CSSPropertyWebkitPaddingStart: + case CSSPropertyWebkitWrapMargin: + case CSSPropertyWebkitWrapPadding: + acceptsNegativeNumbers = false; + return true; + case CSSPropertyBottom: + case CSSPropertyLeft: + case CSSPropertyMarginBottom: + case CSSPropertyMarginLeft: + case CSSPropertyMarginRight: + case CSSPropertyMarginTop: + case CSSPropertyRight: + case CSSPropertyTextIndent: + case CSSPropertyTop: + case CSSPropertyWebkitMarginAfter: + case CSSPropertyWebkitMarginBefore: + case CSSPropertyWebkitMarginEnd: + case CSSPropertyWebkitMarginStart: + acceptsNegativeNumbers = true; + return true; + default: + return false; + } +} + +static bool parseSimpleLengthValue(CSSMutableStyleDeclaration* declaration, int propertyId, const String& string, bool important, bool strict, CSSStyleSheet* contextStyleSheet = 0) +{ + const UChar* characters = string.characters(); + unsigned length = string.length(); + if (!characters || !length) + return false; + bool acceptsNegativeNumbers; + if (!isSimpleLengthPropertyID(propertyId, acceptsNegativeNumbers)) + return false; + + CSSPrimitiveValue::UnitTypes unit = CSSPrimitiveValue::CSS_NUMBER; + if (length > 2 && (characters[length - 2] | 0x20) == 'p' && (characters[length - 1] | 0x20) == 'x') { + length -= 2; + unit = CSSPrimitiveValue::CSS_PX; + } else if (length > 1 && characters[length - 1] == '%') { + length -= 1; + unit = CSSPrimitiveValue::CSS_PERCENTAGE; + } + + // We rely on charactersToDouble for validation as well. The function + // will set "ok" to "false" if the entire passed-in character range does + // not represent a double. + bool ok; + double number = charactersToDouble(characters, length, &ok); + if (!ok) + return false; + if (unit == CSSPrimitiveValue::CSS_NUMBER) { + if (number && strict) + return false; + unit = CSSPrimitiveValue::CSS_PX; + } + if (number < 0 && !acceptsNegativeNumbers) + return false; + + CSSStyleSheet* styleSheet = contextStyleSheet ? contextStyleSheet : declaration->parentStyleSheet(); + if (!styleSheet) + return false; + Document* document = styleSheet->findDocument(); + if (!document) + return false; + CSSProperty property(propertyId, document->cssValuePool()->createValue(number, unit), important); + declaration->addParsedProperty(property); + return true; +} + +bool CSSParser::parseMappedAttributeValue(CSSMappedAttributeDeclaration* declaration, StyledElement* element, int propertyId, const String& value) +{ + ASSERT(declaration); + ASSERT(element); + ASSERT(element->document()); + CSSStyleSheet* elementSheet = element->document()->elementSheet(); + if (parseSimpleLengthValue(declaration, propertyId, value, false, false, elementSheet)) + return true; + if (parseColorValue(declaration, propertyId, value, false, false, elementSheet)) + return true; + CSSParser parser(false); + return parser.parseValue(declaration, propertyId, value, false, elementSheet); +} + +bool CSSParser::parseValue(CSSMutableStyleDeclaration* declaration, int propertyId, const String& string, bool important, bool strict) +{ + if (parseSimpleLengthValue(declaration, propertyId, string, important, strict)) + return true; + if (parseColorValue(declaration, propertyId, string, important, strict)) + return true; + CSSParser parser(strict); + return parser.parseValue(declaration, propertyId, string, important); +} + +bool CSSParser::parseValue(CSSMutableStyleDeclaration* declaration, int propertyId, const String& string, bool important, CSSStyleSheet* contextStyleSheet) +{ + if (contextStyleSheet) + setStyleSheet(contextStyleSheet); + else + setStyleSheet(declaration->parentStyleSheet()); + + setupParser("@-webkit-value{", string, "} "); + + m_id = propertyId; + m_important = important; + + cssyyparse(this); + + m_rule = 0; + + bool ok = false; + if (m_hasFontFaceOnlyValues) + deleteFontFaceOnlyValues(); + if (m_numParsedProperties) { + ok = true; + declaration->addParsedProperties(m_parsedProperties, m_numParsedProperties); + clearProperties(); + } + + return ok; +} + +// The color will only be changed when string contains a valid CSS color, so callers +// can set it to a default color and ignore the boolean result. +bool CSSParser::parseColor(RGBA32& color, const String& string, bool strict) +{ + // First try creating a color specified by name, rgba(), rgb() or "#" syntax. + if (fastParseColor(color, string, strict)) + return true; + + CSSParser parser(true); + + // In case the fast-path parser didn't understand the color, try the full parser. + if (!parser.parseColor(string)) + return false; + + CSSValue* value = parser.m_parsedProperties[0]->value(); + if (!value->isPrimitiveValue()) + return false; + + CSSPrimitiveValue* primitiveValue = static_cast<CSSPrimitiveValue*>(value); + if (primitiveValue->primitiveType() != CSSPrimitiveValue::CSS_RGBCOLOR) + return false; + + color = primitiveValue->getRGBA32Value(); + return true; +} + +bool CSSParser::parseColor(const String& string) +{ + // This function may be called without a stylesheet set on the parser, so we need to + // make sure that we have a CSSValuePool or we'll crash below cssyyparse(). + ensureCSSValuePool(); + + setupParser("@-webkit-decls{color:", string, "} "); + cssyyparse(this); + m_rule = 0; + + return (m_numParsedProperties && m_parsedProperties[0]->m_id == CSSPropertyColor); +} + +bool CSSParser::parseSystemColor(RGBA32& color, const String& string, Document* document) +{ + if (!document || !document->page()) + return false; + + CSSParserString cssColor; + cssColor.characters = const_cast<UChar*>(string.characters()); + cssColor.length = string.length(); + int id = cssValueKeywordID(cssColor); + if (id <= 0) + return false; + + color = document->page()->theme()->systemColor(id).rgb(); + return true; +} + +void CSSParser::parseSelector(const String& string, Document* doc, CSSSelectorList& selectorList) +{ + RefPtr<CSSStyleSheet> dummyStyleSheet = CSSStyleSheet::create(doc); + + setStyleSheet(dummyStyleSheet.get()); + m_selectorListForParseSelector = &selectorList; + + setupParser("@-webkit-selector{", string, "}"); + + cssyyparse(this); + + m_selectorListForParseSelector = 0; + + // The style sheet will be deleted right away, so it won't outlive the document. + ASSERT(dummyStyleSheet->hasOneRef()); +} + +bool CSSParser::parseDeclaration(CSSMutableStyleDeclaration* declaration, const String& string, RefPtr<CSSStyleSourceData>* styleSourceData, CSSStyleSheet* contextStyleSheet) +{ + // Length of the "@-webkit-decls{" prefix. + static const unsigned prefixLength = 15; + + if (contextStyleSheet) + setStyleSheet(contextStyleSheet); + else + setStyleSheet(declaration->parentStyleSheet()); + if (styleSourceData) { + m_currentRuleData = CSSRuleSourceData::create(); + m_currentRuleData->styleSourceData = CSSStyleSourceData::create(); + m_inStyleRuleOrDeclaration = true; + } + + setupParser("@-webkit-decls{", string, "} "); + cssyyparse(this); + m_rule = 0; + + bool ok = false; + if (m_hasFontFaceOnlyValues) + deleteFontFaceOnlyValues(); + if (m_numParsedProperties) { + ok = true; + declaration->addParsedProperties(m_parsedProperties, m_numParsedProperties); + clearProperties(); + } + + if (m_currentRuleData) { + m_currentRuleData->styleSourceData->styleBodyRange.start = 0; + m_currentRuleData->styleSourceData->styleBodyRange.end = string.length(); + for (Vector<CSSPropertySourceData>::iterator it = m_currentRuleData->styleSourceData->propertyData.begin(), endIt = m_currentRuleData->styleSourceData->propertyData.end(); it != endIt; ++it) { + (*it).range.start -= prefixLength; + (*it).range.end -= prefixLength; + } + } + + if (styleSourceData) { + *styleSourceData = m_currentRuleData->styleSourceData.release(); + m_currentRuleData = 0; + m_inStyleRuleOrDeclaration = false; + } + return ok; +} + +bool CSSParser::parseMediaQuery(MediaList* queries, const String& string) +{ + if (string.isEmpty()) + return true; + + ASSERT(!m_mediaQuery); + + // can't use { because tokenizer state switches from mediaquery to initial state when it sees { token. + // instead insert one " " (which is WHITESPACE in CSSGrammar.y) + setupParser("@-webkit-mediaquery ", string, "} "); + cssyyparse(this); + + bool ok = false; + if (m_mediaQuery) { + ok = true; + queries->appendMediaQuery(m_mediaQuery.release()); + } + + return ok; +} + +void CSSParser::addProperty(int propId, PassRefPtr<CSSValue> value, bool important, bool implicit) +{ + OwnPtr<CSSProperty> prop(adoptPtr(new CSSProperty(propId, value, important, m_currentShorthand, m_implicitShorthand || implicit))); + if (m_numParsedProperties >= m_maxParsedProperties) { + if (m_numParsedProperties > (UINT_MAX / sizeof(CSSProperty*)) - 32) + CRASH(); // Avoid inconsistencies with rollbackLastProperties. + m_maxParsedProperties += 32; + m_parsedProperties = static_cast<CSSProperty**>(fastRealloc(m_parsedProperties, + m_maxParsedProperties * sizeof(CSSProperty*))); + } + m_parsedProperties[m_numParsedProperties++] = prop.leakPtr(); +} + +void CSSParser::rollbackLastProperties(int num) +{ + ASSERT(num >= 0); + ASSERT(m_numParsedProperties >= static_cast<unsigned>(num)); + + for (int i = 0; i < num; ++i) + delete m_parsedProperties[--m_numParsedProperties]; +} + +void CSSParser::clearProperties() +{ + for (unsigned i = 0; i < m_numParsedProperties; i++) + delete m_parsedProperties[i]; + m_numParsedProperties = 0; + m_numParsedPropertiesBeforeMarginBox = INVALID_NUM_PARSED_PROPERTIES; + m_hasFontFaceOnlyValues = false; +} + +void CSSParser::setStyleSheet(CSSStyleSheet* styleSheet) +{ + m_styleSheet = styleSheet; + Document* document = findDocument(); + m_cssValuePool = document ? document->cssValuePool() : CSSValuePool::create(); +} + +Document* CSSParser::findDocument() const +{ + if (!m_styleSheet) + return 0; + return m_styleSheet->findDocument(); +} + +bool CSSParser::validUnit(CSSParserValue* value, Units unitflags, bool strict) +{ + bool b = false; + switch (value->unit) { + case CSSPrimitiveValue::CSS_NUMBER: + b = (unitflags & FNumber); + if (!b && ((unitflags & (FLength | FAngle | FTime)) && (value->fValue == 0 || !strict))) { + value->unit = (unitflags & FLength) ? CSSPrimitiveValue::CSS_PX : + ((unitflags & FAngle) ? CSSPrimitiveValue::CSS_DEG : CSSPrimitiveValue::CSS_MS); + b = true; + } + if (!b && (unitflags & FInteger) && value->isInt) + b = true; + break; + case CSSPrimitiveValue::CSS_PERCENTAGE: + b = (unitflags & FPercent); + break; + case CSSParserValue::Q_EMS: + case CSSPrimitiveValue::CSS_EMS: + case CSSPrimitiveValue::CSS_REMS: + case CSSPrimitiveValue::CSS_EXS: + case CSSPrimitiveValue::CSS_PX: + case CSSPrimitiveValue::CSS_CM: + case CSSPrimitiveValue::CSS_MM: + case CSSPrimitiveValue::CSS_IN: + case CSSPrimitiveValue::CSS_PT: + case CSSPrimitiveValue::CSS_PC: + b = (unitflags & FLength); + break; + case CSSPrimitiveValue::CSS_MS: + case CSSPrimitiveValue::CSS_S: + b = (unitflags & FTime); + break; + case CSSPrimitiveValue::CSS_DEG: + case CSSPrimitiveValue::CSS_RAD: + case CSSPrimitiveValue::CSS_GRAD: + case CSSPrimitiveValue::CSS_TURN: + b = (unitflags & FAngle); + break; + case CSSPrimitiveValue::CSS_HZ: + case CSSPrimitiveValue::CSS_KHZ: + case CSSPrimitiveValue::CSS_DIMENSION: + default: + break; + } + if (b && unitflags & FNonNeg && value->fValue < 0) + b = false; + return b; +} + +inline PassRefPtr<CSSPrimitiveValue> CSSParser::createPrimitiveNumericValue(CSSParserValue* value) +{ + ASSERT((value->unit >= CSSPrimitiveValue::CSS_NUMBER && value->unit <= CSSPrimitiveValue::CSS_KHZ) + || (value->unit >= CSSPrimitiveValue::CSS_TURN && value->unit <= CSSPrimitiveValue::CSS_REMS)); + return cssValuePool()->createValue(value->fValue, static_cast<CSSPrimitiveValue::UnitTypes>(value->unit)); +} + +inline PassRefPtr<CSSPrimitiveValue> CSSParser::createPrimitiveStringValue(CSSParserValue* value) +{ + ASSERT(value->unit == CSSPrimitiveValue::CSS_STRING || value->unit == CSSPrimitiveValue::CSS_IDENT); + return cssValuePool()->createValue(value->string, CSSPrimitiveValue::CSS_STRING); +} + +static int unitFromString(CSSParserValue* value) +{ + if (value->unit != CSSPrimitiveValue::CSS_IDENT || value->id) + return 0; + + if (equal(value->string, "em")) + return CSSPrimitiveValue::CSS_EMS; + if (equal(value->string, "rem")) + return CSSPrimitiveValue::CSS_REMS; + if (equal(value->string, "ex")) + return CSSPrimitiveValue::CSS_EXS; + if (equal(value->string, "px")) + return CSSPrimitiveValue::CSS_PX; + if (equal(value->string, "cm")) + return CSSPrimitiveValue::CSS_CM; + if (equal(value->string, "mm")) + return CSSPrimitiveValue::CSS_MM; + if (equal(value->string, "in")) + return CSSPrimitiveValue::CSS_IN; + if (equal(value->string, "pt")) + return CSSPrimitiveValue::CSS_PT; + if (equal(value->string, "pc")) + return CSSPrimitiveValue::CSS_PC; + if (equal(value->string, "deg")) + return CSSPrimitiveValue::CSS_DEG; + if (equal(value->string, "rad")) + return CSSPrimitiveValue::CSS_RAD; + if (equal(value->string, "grad")) + return CSSPrimitiveValue::CSS_GRAD; + if (equal(value->string, "turn")) + return CSSPrimitiveValue::CSS_TURN; + if (equal(value->string, "ms")) + return CSSPrimitiveValue::CSS_MS; + if (equal(value->string, "s")) + return CSSPrimitiveValue::CSS_S; + if (equal(value->string, "Hz")) + return CSSPrimitiveValue::CSS_HZ; + if (equal(value->string, "kHz")) + return CSSPrimitiveValue::CSS_KHZ; + + return 0; +} + +void CSSParser::checkForOrphanedUnits() +{ + if (m_strict || inShorthand()) + return; + + // The purpose of this code is to implement the WinIE quirk that allows unit types to be separated from their numeric values + // by whitespace, so e.g., width: 20 px instead of width:20px. This is invalid CSS, so we don't do this in strict mode. + CSSParserValue* numericVal = 0; + unsigned size = m_valueList->size(); + for (unsigned i = 0; i < size; i++) { + CSSParserValue* value = m_valueList->valueAt(i); + + if (numericVal) { + // Change the unit type of the numeric val to match. + int unit = unitFromString(value); + if (unit) { + numericVal->unit = unit; + numericVal = 0; + + // Now delete the bogus unit value. + m_valueList->deleteValueAt(i); + i--; // We're safe even though |i| is unsigned, since we only hit this code if we had a previous numeric value (so |i| is always > 0 here). + size--; + continue; + } + } + + numericVal = (value->unit == CSSPrimitiveValue::CSS_NUMBER) ? value : 0; + } +} + +inline PassRefPtr<CSSPrimitiveValue> CSSParser::parseValidPrimitive(int id, CSSParserValue* value) +{ + if (id) + return cssValuePool()->createIdentifierValue(id); + if (value->unit == CSSPrimitiveValue::CSS_STRING) + return createPrimitiveStringValue(value); + if (value->unit >= CSSPrimitiveValue::CSS_NUMBER && value->unit <= CSSPrimitiveValue::CSS_KHZ) + return createPrimitiveNumericValue(value); + if (value->unit >= CSSPrimitiveValue::CSS_TURN && value->unit <= CSSPrimitiveValue::CSS_REMS) + return createPrimitiveNumericValue(value); + if (value->unit >= CSSParserValue::Q_EMS) + return CSSPrimitiveValue::createAllowingMarginQuirk(value->fValue, CSSPrimitiveValue::CSS_EMS); + return 0; +} + +bool CSSParser::parseValue(int propId, bool important) +{ + if (!m_valueList) + return false; + + CSSParserValue* value = m_valueList->current(); + + if (!value) + return false; + + int id = value->id; + + // In quirks mode, we will look for units that have been incorrectly separated from the number they belong to + // by a space. We go ahead and associate the unit with the number even though it is invalid CSS. + checkForOrphanedUnits(); + + int num = inShorthand() ? 1 : m_valueList->size(); + + if (id == CSSValueInherit) { + if (num != 1) + return false; + addProperty(propId, cssValuePool()->createInheritedValue(), important); + return true; + } + else if (id == CSSValueInitial) { + if (num != 1) + return false; + addProperty(propId, cssValuePool()->createExplicitInitialValue(), important); + return true; + } + + bool validPrimitive = false; + RefPtr<CSSValue> parsedValue; + + switch (static_cast<CSSPropertyID>(propId)) { + /* The comment to the left defines all valid value of this properties as defined + * in CSS 2, Appendix F. Property index + */ + + /* All the CSS properties are not supported by the renderer at the moment. + * Note that all the CSS2 Aural properties are only checked, if CSS_AURAL is defined + * (see parseAuralValues). As we don't support them at all this seems reasonable. + */ + + case CSSPropertySize: // <length>{1,2} | auto | [ <page-size> || [ portrait | landscape] ] + return parseSize(propId, important); + + case CSSPropertyQuotes: // [<string> <string>]+ | none | inherit + if (id) + validPrimitive = true; + else + return parseQuotes(propId, important); + break; + case CSSPropertyUnicodeBidi: // normal | embed | bidi-override | isolate | plaintext | inherit + if (id == CSSValueNormal + || id == CSSValueEmbed + || id == CSSValueBidiOverride + || id == CSSValueWebkitIsolate + || id == CSSValueWebkitPlaintext) + validPrimitive = true; + break; + + case CSSPropertyPosition: // static | relative | absolute | fixed | inherit + if (id == CSSValueStatic || + id == CSSValueRelative || + id == CSSValueAbsolute || + id == CSSValueFixed) + validPrimitive = true; + break; + + case CSSPropertyPageBreakAfter: // auto | always | avoid | left | right | inherit + case CSSPropertyPageBreakBefore: + case CSSPropertyWebkitColumnBreakAfter: + case CSSPropertyWebkitColumnBreakBefore: + case CSSPropertyWebkitRegionBreakAfter: + case CSSPropertyWebkitRegionBreakBefore: + if (id == CSSValueAuto || + id == CSSValueAlways || + id == CSSValueAvoid || + id == CSSValueLeft || + id == CSSValueRight) + validPrimitive = true; + break; + + case CSSPropertyPageBreakInside: // avoid | auto | inherit + case CSSPropertyWebkitColumnBreakInside: + case CSSPropertyWebkitRegionBreakInside: + if (id == CSSValueAuto || id == CSSValueAvoid) + validPrimitive = true; + break; + + case CSSPropertyEmptyCells: // show | hide | inherit + if (id == CSSValueShow || + id == CSSValueHide) + validPrimitive = true; + break; + + case CSSPropertyContent: // [ <string> | <uri> | <counter> | attr(X) | open-quote | + // close-quote | no-open-quote | no-close-quote ]+ | inherit + return parseContent(propId, important); + + case CSSPropertyWhiteSpace: // normal | pre | nowrap | inherit + if (id == CSSValueNormal || + id == CSSValuePre || + id == CSSValuePreWrap || + id == CSSValuePreLine || + id == CSSValueNowrap) + validPrimitive = true; + break; + + case CSSPropertyClip: // <shape> | auto | inherit + if (id == CSSValueAuto) + validPrimitive = true; + else if (value->unit == CSSParserValue::Function) + return parseShape(propId, important); + break; + + /* Start of supported CSS properties with validation. This is needed for parseShorthand to work + * correctly and allows optimization in WebCore::applyRule(..) + */ + case CSSPropertyCaptionSide: // top | bottom | left | right | inherit + if (id == CSSValueLeft || id == CSSValueRight || + id == CSSValueTop || id == CSSValueBottom) + validPrimitive = true; + break; + + case CSSPropertyBorderCollapse: // collapse | separate | inherit + if (id == CSSValueCollapse || id == CSSValueSeparate) + validPrimitive = true; + break; + + case CSSPropertyVisibility: // visible | hidden | collapse | inherit + if (id == CSSValueVisible || id == CSSValueHidden || id == CSSValueCollapse) + validPrimitive = true; + break; + + case CSSPropertyOverflow: { + ShorthandScope scope(this, propId); + if (num != 1 || !parseValue(CSSPropertyOverflowX, important)) + return false; + CSSValue* value = m_parsedProperties[m_numParsedProperties - 1]->value(); + addProperty(CSSPropertyOverflowY, value, important); + return true; + } + case CSSPropertyOverflowX: + case CSSPropertyOverflowY: // visible | hidden | scroll | auto | marquee | overlay | inherit + if (id == CSSValueVisible || id == CSSValueHidden || id == CSSValueScroll || id == CSSValueAuto || + id == CSSValueOverlay || id == CSSValueWebkitMarquee) + validPrimitive = true; + break; + + case CSSPropertyListStylePosition: // inside | outside | inherit + if (id == CSSValueInside || id == CSSValueOutside) + validPrimitive = true; + break; + + case CSSPropertyListStyleType: + // See section CSS_PROP_LIST_STYLE_TYPE of file CSSValueKeywords.in + // for the list of supported list-style-types. + if ((id >= CSSValueDisc && id <= CSSValueKatakanaIroha) || id == CSSValueNone) + validPrimitive = true; + break; + + case CSSPropertyDisplay: + // inline | block | list-item | run-in | inline-block | table | + // inline-table | table-row-group | table-header-group | table-footer-group | table-row | + // table-column-group | table-column | table-cell | table-caption | -webkit-box | -webkit-inline-box | none | inherit + // -webkit-flexbox | -webkit-inline-flexbox | -webkit-grid | -webkit-inline-grid + if ((id >= CSSValueInline && id <= CSSValueWebkitInlineFlexbox) || id == CSSValueNone) + validPrimitive = true; +#if ENABLE(CSS_GRID_LAYOUT) + if (id == CSSValueWebkitGrid || id == CSSValueWebkitInlineGrid) + validPrimitive = true; +#endif + break; + + case CSSPropertyDirection: // ltr | rtl | inherit + if (id == CSSValueLtr || id == CSSValueRtl) + validPrimitive = true; + break; + + case CSSPropertyTextTransform: // capitalize | uppercase | lowercase | none | inherit + if ((id >= CSSValueCapitalize && id <= CSSValueLowercase) || id == CSSValueNone) + validPrimitive = true; + break; + + case CSSPropertyFloat: // left | right | none | positioned | center (for buggy CSS, maps to none) + if (id == CSSValueLeft || id == CSSValueRight + || id == CSSValueNone || id == CSSValueCenter || id == CSSValueWebkitPositioned) + validPrimitive = true; + break; + + case CSSPropertyClear: // none | left | right | both | inherit + if (id == CSSValueNone || id == CSSValueLeft || + id == CSSValueRight|| id == CSSValueBoth) + validPrimitive = true; + break; + + case CSSPropertyTextAlign: + // left | right | center | justify | webkit_left | webkit_right | webkit_center | webkit_match_parent | + // start | end | <string> | inherit + if ((id >= CSSValueWebkitAuto && id <= CSSValueWebkitMatchParent) || id == CSSValueStart || id == CSSValueEnd + || value->unit == CSSPrimitiveValue::CSS_STRING) + validPrimitive = true; + break; + + case CSSPropertyOutlineStyle: // (<border-style> except hidden) | auto | inherit + if (id == CSSValueAuto || id == CSSValueNone || (id >= CSSValueInset && id <= CSSValueDouble)) + validPrimitive = true; + break; + + case CSSPropertyBorderTopStyle: //// <border-style> | inherit + case CSSPropertyBorderRightStyle: // Defined as: none | hidden | dotted | dashed | + case CSSPropertyBorderBottomStyle: // solid | double | groove | ridge | inset | outset + case CSSPropertyBorderLeftStyle: + case CSSPropertyWebkitBorderStartStyle: + case CSSPropertyWebkitBorderEndStyle: + case CSSPropertyWebkitBorderBeforeStyle: + case CSSPropertyWebkitBorderAfterStyle: + case CSSPropertyWebkitColumnRuleStyle: + if (id >= CSSValueNone && id <= CSSValueDouble) + validPrimitive = true; + break; + + case CSSPropertyFontWeight: // normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | inherit + return parseFontWeight(important); + + case CSSPropertyBorderSpacing: { + const int properties[2] = { CSSPropertyWebkitBorderHorizontalSpacing, + CSSPropertyWebkitBorderVerticalSpacing }; + if (num == 1) { + ShorthandScope scope(this, CSSPropertyBorderSpacing); + if (!parseValue(properties[0], important)) + return false; + CSSValue* value = m_parsedProperties[m_numParsedProperties-1]->value(); + addProperty(properties[1], value, important); + return true; + } + else if (num == 2) { + ShorthandScope scope(this, CSSPropertyBorderSpacing); + if (!parseValue(properties[0], important) || !parseValue(properties[1], important)) + return false; + return true; + } + return false; + } + case CSSPropertyWebkitBorderHorizontalSpacing: + case CSSPropertyWebkitBorderVerticalSpacing: + validPrimitive = validUnit(value, FLength | FNonNeg, m_strict); + break; + case CSSPropertyOutlineColor: // <color> | invert | inherit + // Outline color has "invert" as additional keyword. + // Also, we want to allow the special focus color even in strict parsing mode. + if (id == CSSValueInvert || id == CSSValueWebkitFocusRingColor) { + validPrimitive = true; + break; + } + /* nobreak */ + case CSSPropertyBackgroundColor: // <color> | inherit + case CSSPropertyBorderTopColor: // <color> | inherit + case CSSPropertyBorderRightColor: + case CSSPropertyBorderBottomColor: + case CSSPropertyBorderLeftColor: + case CSSPropertyWebkitBorderStartColor: + case CSSPropertyWebkitBorderEndColor: + case CSSPropertyWebkitBorderBeforeColor: + case CSSPropertyWebkitBorderAfterColor: + case CSSPropertyColor: // <color> | inherit + case CSSPropertyTextLineThroughColor: // CSS3 text decoration colors + case CSSPropertyTextUnderlineColor: + case CSSPropertyTextOverlineColor: + case CSSPropertyWebkitColumnRuleColor: + case CSSPropertyWebkitTextEmphasisColor: + case CSSPropertyWebkitTextFillColor: + case CSSPropertyWebkitTextStrokeColor: + if (id == CSSValueWebkitText) + validPrimitive = true; // Always allow this, even when strict parsing is on, + // since we use this in our UA sheets. + else if (id == CSSValueCurrentcolor) + validPrimitive = true; + else if ((id >= CSSValueAqua && id <= CSSValueWindowtext) || id == CSSValueMenu || + (id >= CSSValueWebkitFocusRingColor && id < CSSValueWebkitText && !m_strict)) { + validPrimitive = true; + } else { + parsedValue = parseColor(); + if (parsedValue) + m_valueList->next(); + } + break; + + case CSSPropertyCursor: { + // [<uri>,]* [ auto | crosshair | default | pointer | progress | move | e-resize | ne-resize | + // nw-resize | n-resize | se-resize | sw-resize | s-resize | w-resize | ew-resize | + // ns-resize | nesw-resize | nwse-resize | col-resize | row-resize | text | wait | help | + // vertical-text | cell | context-menu | alias | copy | no-drop | not-allowed | -webkit-zoom-in + // -webkit-zoom-out | all-scroll | -webkit-grab | -webkit-grabbing ] ] | inherit + RefPtr<CSSValueList> list; + while (value && value->unit == CSSPrimitiveValue::CSS_URI) { + if (!list) + list = CSSValueList::createCommaSeparated(); + String uri = value->string; + Vector<int> coords; + value = m_valueList->next(); + while (value && value->unit == CSSPrimitiveValue::CSS_NUMBER) { + coords.append(int(value->fValue)); + value = m_valueList->next(); + } + IntPoint hotSpot(-1, -1); + int nrcoords = coords.size(); + if (nrcoords > 0 && nrcoords != 2) + return false; + if (nrcoords == 2) + hotSpot = IntPoint(coords[0], coords[1]); + + if (!uri.isNull() && m_styleSheet) { + // FIXME: The completeURL call should be done when using the CSSCursorImageValue, + // not when creating it. + list->append(CSSCursorImageValue::create(m_styleSheet->completeURL(uri), hotSpot)); + } + + if ((m_strict && !value) || (value && !(value->unit == CSSParserValue::Operator && value->iValue == ','))) + return false; + value = m_valueList->next(); // comma + } + if (list) { + if (!value) { // no value after url list (MSIE 5 compatibility) + if (list->length() != 1) + return false; + } else if (!m_strict && value->id == CSSValueHand) // MSIE 5 compatibility :/ + list->append(cssValuePool()->createIdentifierValue(CSSValuePointer)); + else if (value && ((value->id >= CSSValueAuto && value->id <= CSSValueWebkitGrabbing) || value->id == CSSValueCopy || value->id == CSSValueNone)) + list->append(cssValuePool()->createIdentifierValue(value->id)); + m_valueList->next(); + parsedValue = list.release(); + break; + } + id = value->id; + if (!m_strict && value->id == CSSValueHand) { // MSIE 5 compatibility :/ + id = CSSValuePointer; + validPrimitive = true; + } else if ((value->id >= CSSValueAuto && value->id <= CSSValueWebkitGrabbing) || value->id == CSSValueCopy || value->id == CSSValueNone) + validPrimitive = true; + break; + } + + case CSSPropertyBackgroundAttachment: + case CSSPropertyBackgroundClip: + case CSSPropertyWebkitBackgroundClip: + case CSSPropertyWebkitBackgroundComposite: + case CSSPropertyBackgroundImage: + case CSSPropertyBackgroundOrigin: + case CSSPropertyWebkitBackgroundOrigin: + case CSSPropertyBackgroundPosition: + case CSSPropertyBackgroundPositionX: + case CSSPropertyBackgroundPositionY: + case CSSPropertyBackgroundSize: + case CSSPropertyWebkitBackgroundSize: + case CSSPropertyBackgroundRepeat: + case CSSPropertyBackgroundRepeatX: + case CSSPropertyBackgroundRepeatY: + case CSSPropertyWebkitMaskAttachment: + case CSSPropertyWebkitMaskClip: + case CSSPropertyWebkitMaskComposite: + case CSSPropertyWebkitMaskImage: + case CSSPropertyWebkitMaskOrigin: + case CSSPropertyWebkitMaskPosition: + case CSSPropertyWebkitMaskPositionX: + case CSSPropertyWebkitMaskPositionY: + case CSSPropertyWebkitMaskSize: + case CSSPropertyWebkitMaskRepeat: + case CSSPropertyWebkitMaskRepeatX: + case CSSPropertyWebkitMaskRepeatY: { + RefPtr<CSSValue> val1; + RefPtr<CSSValue> val2; + int propId1, propId2; + bool result = false; + if (parseFillProperty(propId, propId1, propId2, val1, val2)) { + OwnPtr<ShorthandScope> shorthandScope; + if (propId == CSSPropertyBackgroundPosition || + propId == CSSPropertyBackgroundRepeat || + propId == CSSPropertyWebkitMaskPosition || + propId == CSSPropertyWebkitMaskRepeat) { + shorthandScope = adoptPtr(new ShorthandScope(this, propId)); + } + addProperty(propId1, val1.release(), important); + if (val2) + addProperty(propId2, val2.release(), important); + result = true; + } + m_implicitShorthand = false; + return result; + } + case CSSPropertyListStyleImage: // <uri> | none | inherit + case CSSPropertyBorderImageSource: + case CSSPropertyWebkitMaskBoxImageSource: + if (id == CSSValueNone) { + parsedValue = CSSImageValue::create(); + m_valueList->next(); + } else if (value->unit == CSSPrimitiveValue::CSS_URI) { + if (m_styleSheet) { + // FIXME: The completeURL call should be done when using the CSSImageValue, + // not when creating it. + parsedValue = CSSImageValue::create(m_styleSheet->completeURL(value->string)); + m_valueList->next(); + } + } else if (isGeneratedImageValue(value)) { + if (parseGeneratedImage(m_valueList.get(), parsedValue)) + m_valueList->next(); + else + return false; + } + break; + + case CSSPropertyWebkitTextStrokeWidth: + case CSSPropertyOutlineWidth: // <border-width> | inherit + case CSSPropertyBorderTopWidth: //// <border-width> | inherit + case CSSPropertyBorderRightWidth: // Which is defined as + case CSSPropertyBorderBottomWidth: // thin | medium | thick | <length> + case CSSPropertyBorderLeftWidth: + case CSSPropertyWebkitBorderStartWidth: + case CSSPropertyWebkitBorderEndWidth: + case CSSPropertyWebkitBorderBeforeWidth: + case CSSPropertyWebkitBorderAfterWidth: + case CSSPropertyWebkitColumnRuleWidth: + if (id == CSSValueThin || id == CSSValueMedium || id == CSSValueThick) + validPrimitive = true; + else + validPrimitive = validUnit(value, FLength | FNonNeg, m_strict); + break; + + case CSSPropertyLetterSpacing: // normal | <length> | inherit + case CSSPropertyWordSpacing: // normal | <length> | inherit + if (id == CSSValueNormal) + validPrimitive = true; + else + validPrimitive = validUnit(value, FLength, m_strict); + break; + + case CSSPropertyWordBreak: // normal | break-all | break-word (this is a custom extension) + if (id == CSSValueNormal || id == CSSValueBreakAll || id == CSSValueBreakWord) + validPrimitive = true; + break; + + case CSSPropertyWordWrap: // normal | break-word + if (id == CSSValueNormal || id == CSSValueBreakWord) + validPrimitive = true; + break; + case CSSPropertySpeak: // none | normal | spell-out | digits | literal-punctuation | no-punctuation | inherit + if (id == CSSValueNone || id == CSSValueNormal || id == CSSValueSpellOut || id == CSSValueDigits + || id == CSSValueLiteralPunctuation || id == CSSValueNoPunctuation) + validPrimitive = true; + break; + + case CSSPropertyTextIndent: // <length> | <percentage> | inherit + validPrimitive = (!id && validUnit(value, FLength | FPercent, m_strict)); + break; + + case CSSPropertyPaddingTop: //// <padding-width> | inherit + case CSSPropertyPaddingRight: // Which is defined as + case CSSPropertyPaddingBottom: // <length> | <percentage> + case CSSPropertyPaddingLeft: //// + case CSSPropertyWebkitPaddingStart: + case CSSPropertyWebkitPaddingEnd: + case CSSPropertyWebkitPaddingBefore: + case CSSPropertyWebkitPaddingAfter: + validPrimitive = (!id && validUnit(value, FLength | FPercent | FNonNeg, m_strict)); + break; + + case CSSPropertyMaxHeight: // <length> | <percentage> | none | inherit + case CSSPropertyMaxWidth: // <length> | <percentage> | none | inherit + case CSSPropertyWebkitMaxLogicalWidth: + case CSSPropertyWebkitMaxLogicalHeight: + if (id == CSSValueNone || id == CSSValueIntrinsic || id == CSSValueMinIntrinsic) { + validPrimitive = true; + break; + } + /* nobreak */ + case CSSPropertyMinHeight: // <length> | <percentage> | inherit + case CSSPropertyMinWidth: // <length> | <percentage> | inherit + case CSSPropertyWebkitMinLogicalWidth: + case CSSPropertyWebkitMinLogicalHeight: + if (id == CSSValueIntrinsic || id == CSSValueMinIntrinsic) + validPrimitive = true; + else + validPrimitive = (!id && validUnit(value, FLength | FPercent | FNonNeg, m_strict)); + break; + + case CSSPropertyFontSize: + // <absolute-size> | <relative-size> | <length> | <percentage> | inherit + if (id >= CSSValueXxSmall && id <= CSSValueLarger) + validPrimitive = true; + else + validPrimitive = (validUnit(value, FLength | FPercent | FNonNeg, m_strict)); + break; + + case CSSPropertyFontStyle: // normal | italic | oblique | inherit + return parseFontStyle(important); + + case CSSPropertyFontVariant: // normal | small-caps | inherit + return parseFontVariant(important); + + case CSSPropertyVerticalAlign: + // baseline | sub | super | top | text-top | middle | bottom | text-bottom | + // <percentage> | <length> | inherit + + if (id >= CSSValueBaseline && id <= CSSValueWebkitBaselineMiddle) + validPrimitive = true; + else + validPrimitive = (!id && validUnit(value, FLength | FPercent, m_strict)); + break; + + case CSSPropertyHeight: // <length> | <percentage> | auto | inherit + case CSSPropertyWidth: // <length> | <percentage> | auto | inherit + case CSSPropertyWebkitLogicalWidth: + case CSSPropertyWebkitLogicalHeight: + if (id == CSSValueAuto || id == CSSValueIntrinsic || id == CSSValueMinIntrinsic) + validPrimitive = true; + else if (!id && validUnit(value, FLength | FPercent | FNonNeg, m_strict)) + // ### handle multilength case where we allow relative units + validPrimitive = true; + else if (value->unit == CSSParserValue::Function) + return parseFlex(propId, important); + break; + + case CSSPropertyBottom: // <length> | <percentage> | auto | inherit + case CSSPropertyLeft: // <length> | <percentage> | auto | inherit + case CSSPropertyRight: // <length> | <percentage> | auto | inherit + case CSSPropertyTop: // <length> | <percentage> | auto | inherit + case CSSPropertyMarginTop: //// <margin-width> | inherit + case CSSPropertyMarginRight: // Which is defined as + case CSSPropertyMarginBottom: // <length> | <percentage> | auto | inherit + case CSSPropertyMarginLeft: //// + case CSSPropertyWebkitMarginStart: + case CSSPropertyWebkitMarginEnd: + case CSSPropertyWebkitMarginBefore: + case CSSPropertyWebkitMarginAfter: + if (id == CSSValueAuto) + validPrimitive = true; + else + validPrimitive = (!id && validUnit(value, FLength | FPercent, m_strict)); + break; + + case CSSPropertyZIndex: // auto | <integer> | inherit + if (id == CSSValueAuto) { + validPrimitive = true; + break; + } + /* nobreak */ + case CSSPropertyOrphans: // <integer> | inherit + case CSSPropertyWidows: // <integer> | inherit + // ### not supported later on + validPrimitive = (!id && validUnit(value, FInteger, false)); + break; + + case CSSPropertyLineHeight: // normal | <number> | <length> | <percentage> | inherit + if (id == CSSValueNormal) + validPrimitive = true; + else + validPrimitive = (!id && validUnit(value, FNumber | FLength | FPercent | FNonNeg, m_strict)); + break; + case CSSPropertyCounterIncrement: // [ <identifier> <integer>? ]+ | none | inherit + if (id != CSSValueNone) + return parseCounter(propId, 1, important); + validPrimitive = true; + break; + case CSSPropertyCounterReset: // [ <identifier> <integer>? ]+ | none | inherit + if (id != CSSValueNone) + return parseCounter(propId, 0, important); + validPrimitive = true; + break; + case CSSPropertyFontFamily: + // [[ <family-name> | <generic-family> ],]* [<family-name> | <generic-family>] | inherit + { + parsedValue = parseFontFamily(); + break; + } + + case CSSPropertyTextDecoration: + case CSSPropertyWebkitTextDecorationsInEffect: + // none | [ underline || overline || line-through || blink ] | inherit + if (id == CSSValueNone) { + validPrimitive = true; + } else { + RefPtr<CSSValueList> list = CSSValueList::createSpaceSeparated(); + bool isValid = true; + while (isValid && value) { + switch (value->id) { + case CSSValueBlink: + break; + case CSSValueUnderline: + case CSSValueOverline: + case CSSValueLineThrough: + list->append(cssValuePool()->createIdentifierValue(value->id)); + break; + default: + isValid = false; + } + value = m_valueList->next(); + } + if (list->length() && isValid) { + parsedValue = list.release(); + m_valueList->next(); + } + } + break; + + case CSSPropertyZoom: // normal | reset | document | <number> | <percentage> | inherit + if (id == CSSValueNormal || id == CSSValueReset || id == CSSValueDocument) + validPrimitive = true; + else + validPrimitive = (!id && validUnit(value, FNumber | FPercent | FNonNeg, true)); + break; + + case CSSPropertyTableLayout: // auto | fixed | inherit + if (id == CSSValueAuto || id == CSSValueFixed) + validPrimitive = true; + break; + + case CSSPropertySrc: // Only used within @font-face, so cannot use inherit | initial or be !important. This is a list of urls or local references. + return parseFontFaceSrc(); + + case CSSPropertyUnicodeRange: + return parseFontFaceUnicodeRange(); + + /* CSS3 properties */ + case CSSPropertyWebkitAppearance: + if ((id >= CSSValueCheckbox && id <= CSSValueTextarea) || id == CSSValueNone) + validPrimitive = true; + break; + + case CSSPropertyBorderImage: + case CSSPropertyWebkitBorderImage: + case CSSPropertyWebkitMaskBoxImage: { + RefPtr<CSSValue> result; + if (parseBorderImage(propId, result)) { + addProperty(propId, result, important); + return true; + } + break; + } + case CSSPropertyBorderImageOutset: + case CSSPropertyWebkitMaskBoxImageOutset: { + RefPtr<CSSPrimitiveValue> result; + if (parseBorderImageOutset(result)) { + addProperty(propId, result, important); + return true; + } + break; + } + case CSSPropertyBorderImageRepeat: + case CSSPropertyWebkitMaskBoxImageRepeat: { + RefPtr<CSSValue> result; + if (parseBorderImageRepeat(result)) { + addProperty(propId, result, important); + return true; + } + break; + } + case CSSPropertyBorderImageSlice: + case CSSPropertyWebkitMaskBoxImageSlice: { + RefPtr<CSSBorderImageSliceValue> result; + if (parseBorderImageSlice(propId, result)) { + addProperty(propId, result, important); + return true; + } + break; + } + case CSSPropertyBorderImageWidth: + case CSSPropertyWebkitMaskBoxImageWidth: { + RefPtr<CSSPrimitiveValue> result; + if (parseBorderImageWidth(result)) { + addProperty(propId, result, important); + return true; + } + break; + } + case CSSPropertyBorderTopRightRadius: + case CSSPropertyBorderTopLeftRadius: + case CSSPropertyBorderBottomLeftRadius: + case CSSPropertyBorderBottomRightRadius: { + if (num != 1 && num != 2) + return false; + validPrimitive = validUnit(value, FLength | FPercent | FNonNeg, m_strict); + if (!validPrimitive) + return false; + RefPtr<CSSPrimitiveValue> parsedValue1 = createPrimitiveNumericValue(value); + RefPtr<CSSPrimitiveValue> parsedValue2; + if (num == 2) { + value = m_valueList->next(); + validPrimitive = validUnit(value, FLength | FPercent | FNonNeg, m_strict); + if (!validPrimitive) + return false; + parsedValue2 = createPrimitiveNumericValue(value); + } else + parsedValue2 = parsedValue1; + + RefPtr<Pair> pair = Pair::create(parsedValue1.release(), parsedValue2.release()); + RefPtr<CSSPrimitiveValue> val = cssValuePool()->createValue(pair.release()); + addProperty(propId, val.release(), important); + return true; + } + case CSSPropertyWebkitAspectRatio: + return parseAspectRatio(important); + case CSSPropertyBorderRadius: + case CSSPropertyWebkitBorderRadius: + return parseBorderRadius(propId, important); + case CSSPropertyOutlineOffset: + validPrimitive = validUnit(value, FLength | FPercent, m_strict); + break; + case CSSPropertyTextShadow: // CSS2 property, dropped in CSS2.1, back in CSS3, so treat as CSS3 + case CSSPropertyBoxShadow: + case CSSPropertyWebkitBoxShadow: + if (id == CSSValueNone) + validPrimitive = true; + else { + RefPtr<CSSValueList> shadowValueList = parseShadow(m_valueList.get(), propId); + if (shadowValueList) { + addProperty(propId, shadowValueList.release(), important); + m_valueList->next(); + return true; + } + return false; + } + break; + case CSSPropertyWebkitBoxReflect: + if (id == CSSValueNone) + validPrimitive = true; + else + return parseReflect(propId, important); + break; + case CSSPropertyOpacity: + validPrimitive = validUnit(value, FNumber, m_strict); + break; + case CSSPropertyWebkitBoxAlign: + if (id == CSSValueStretch || id == CSSValueStart || id == CSSValueEnd || + id == CSSValueCenter || id == CSSValueBaseline) + validPrimitive = true; + break; + case CSSPropertyWebkitBoxDirection: + if (id == CSSValueNormal || id == CSSValueReverse) + validPrimitive = true; + break; + case CSSPropertyWebkitBoxLines: + if (id == CSSValueSingle || id == CSSValueMultiple) + validPrimitive = true; + break; + case CSSPropertyWebkitBoxOrient: + if (id == CSSValueHorizontal || id == CSSValueVertical || + id == CSSValueInlineAxis || id == CSSValueBlockAxis) + validPrimitive = true; + break; + case CSSPropertyWebkitBoxPack: + if (id == CSSValueStart || id == CSSValueEnd || + id == CSSValueCenter || id == CSSValueJustify) + validPrimitive = true; + break; + case CSSPropertyWebkitBoxFlex: + validPrimitive = validUnit(value, FNumber, m_strict); + break; + case CSSPropertyWebkitBoxFlexGroup: + case CSSPropertyWebkitBoxOrdinalGroup: + validPrimitive = validUnit(value, FInteger | FNonNeg, true); + break; + case CSSPropertyBoxSizing: + validPrimitive = id == CSSValueBorderBox || id == CSSValueContentBox; + break; + case CSSPropertyWebkitColorCorrection: + validPrimitive = id == CSSValueSrgb || id == CSSValueDefault; + break; +#if ENABLE(CSS_FILTERS) + case CSSPropertyWebkitFilter: + if (id == CSSValueNone) + validPrimitive = true; + else { + RefPtr<CSSValue> val = parseFilter(); + if (val) { + addProperty(propId, val, important); + return true; + } + return false; + } + break; +#endif + case CSSPropertyWebkitFlexOrder: + if (validUnit(value, FInteger, true)) { + // We restrict the smallest value to int min + 2 because we use int min and int min + 1 as special values in a hash set. + parsedValue = cssValuePool()->createValue(max(static_cast<double>(std::numeric_limits<int>::min() + 2), value->fValue), + static_cast<CSSPrimitiveValue::UnitTypes>(value->unit)); + m_valueList->next(); + } + break; + case CSSPropertyWebkitFlexPack: + validPrimitive = id == CSSValueStart || id == CSSValueEnd || id == CSSValueCenter || id == CSSValueJustify; + break; + case CSSPropertyWebkitFlexAlign: + validPrimitive = id == CSSValueStart || id == CSSValueEnd || id == CSSValueCenter || id == CSSValueBaseline || id == CSSValueStretch; + break; + case CSSPropertyWebkitFlexDirection: + validPrimitive = id == CSSValueRow || id == CSSValueRowReverse || id == CSSValueColumn || id == CSSValueColumnReverse; + break; + case CSSPropertyWebkitFlexWrap: + validPrimitive = id == CSSValueNowrap || id == CSSValueWrap || id == CSSValueWrapReverse; + break; + case CSSPropertyWebkitMarquee: { + const int properties[5] = { CSSPropertyWebkitMarqueeDirection, CSSPropertyWebkitMarqueeIncrement, + CSSPropertyWebkitMarqueeRepetition, + CSSPropertyWebkitMarqueeStyle, CSSPropertyWebkitMarqueeSpeed }; + return parseShorthand(propId, properties, 5, important); + } + case CSSPropertyWebkitMarqueeDirection: + if (id == CSSValueForwards || id == CSSValueBackwards || id == CSSValueAhead || + id == CSSValueReverse || id == CSSValueLeft || id == CSSValueRight || id == CSSValueDown || + id == CSSValueUp || id == CSSValueAuto) + validPrimitive = true; + break; + case CSSPropertyWebkitMarqueeIncrement: + if (id == CSSValueSmall || id == CSSValueLarge || id == CSSValueMedium) + validPrimitive = true; + else + validPrimitive = validUnit(value, FLength | FPercent, m_strict); + break; + case CSSPropertyWebkitMarqueeStyle: + if (id == CSSValueNone || id == CSSValueSlide || id == CSSValueScroll || id == CSSValueAlternate) + validPrimitive = true; + break; + case CSSPropertyWebkitMarqueeRepetition: + if (id == CSSValueInfinite) + validPrimitive = true; + else + validPrimitive = validUnit(value, FInteger | FNonNeg, m_strict); + break; + case CSSPropertyWebkitMarqueeSpeed: + if (id == CSSValueNormal || id == CSSValueSlow || id == CSSValueFast) + validPrimitive = true; + else + validPrimitive = validUnit(value, FTime | FInteger | FNonNeg, m_strict); + break; + case CSSPropertyWebkitFlowInto: + return parseFlowThread(propId, important); + case CSSPropertyWebkitFlowFrom: + return parseRegionThread(propId, important); + case CSSPropertyWebkitRegionOverflow: + if (id == CSSValueAuto || id == CSSValueBreak) + validPrimitive = true; + break; + + case CSSPropertyWebkitUserDrag: // auto | none | element + if (id == CSSValueAuto || id == CSSValueNone || id == CSSValueElement) + validPrimitive = true; + break; + case CSSPropertyWebkitUserModify: // read-only | read-write + if (id == CSSValueReadOnly || id == CSSValueReadWrite || id == CSSValueReadWritePlaintextOnly) + validPrimitive = true; + break; + case CSSPropertyWebkitUserSelect: // auto | none | text + if (id == CSSValueAuto || id == CSSValueNone || id == CSSValueText) + validPrimitive = true; + break; + case CSSPropertyTextOverflow: // clip | ellipsis + if (id == CSSValueClip || id == CSSValueEllipsis) + validPrimitive = true; + break; + case CSSPropertyWebkitTransform: + if (id == CSSValueNone) + validPrimitive = true; + else { + PassRefPtr<CSSValue> val = parseTransform(); + if (val) { + addProperty(propId, val, important); + return true; + } + return false; + } + break; + case CSSPropertyWebkitTransformOrigin: + case CSSPropertyWebkitTransformOriginX: + case CSSPropertyWebkitTransformOriginY: + case CSSPropertyWebkitTransformOriginZ: { + RefPtr<CSSValue> val1; + RefPtr<CSSValue> val2; + RefPtr<CSSValue> val3; + int propId1, propId2, propId3; + if (parseTransformOrigin(propId, propId1, propId2, propId3, val1, val2, val3)) { + addProperty(propId1, val1.release(), important); + if (val2) + addProperty(propId2, val2.release(), important); + if (val3) + addProperty(propId3, val3.release(), important); + return true; + } + return false; + } + case CSSPropertyWebkitTransformStyle: + if (value->id == CSSValueFlat || value->id == CSSValuePreserve3d) + validPrimitive = true; + break; + case CSSPropertyWebkitBackfaceVisibility: + if (value->id == CSSValueVisible || value->id == CSSValueHidden) + validPrimitive = true; + break; + case CSSPropertyWebkitPrintColorAdjust: + if (value->id == CSSValueExact || value->id == CSSValueEconomy) + validPrimitive = true; + break; + case CSSPropertyWebkitPerspective: + if (id == CSSValueNone) + validPrimitive = true; + else { + // Accepting valueless numbers is a quirk of the -webkit prefixed version of the property. + if (validUnit(value, FNumber | FLength | FNonNeg, m_strict)) { + RefPtr<CSSValue> val = createPrimitiveNumericValue(value); + if (val) { + addProperty(propId, val.release(), important); + return true; + } + return false; + } + } + break; + case CSSPropertyWebkitPerspectiveOrigin: + case CSSPropertyWebkitPerspectiveOriginX: + case CSSPropertyWebkitPerspectiveOriginY: { + RefPtr<CSSValue> val1; + RefPtr<CSSValue> val2; + int propId1, propId2; + if (parsePerspectiveOrigin(propId, propId1, propId2, val1, val2)) { + addProperty(propId1, val1.release(), important); + if (val2) + addProperty(propId2, val2.release(), important); + return true; + } + return false; + } + case CSSPropertyWebkitAnimationDelay: + case CSSPropertyWebkitAnimationDirection: + case CSSPropertyWebkitAnimationDuration: + case CSSPropertyWebkitAnimationFillMode: + case CSSPropertyWebkitAnimationName: + case CSSPropertyWebkitAnimationPlayState: + case CSSPropertyWebkitAnimationIterationCount: + case CSSPropertyWebkitAnimationTimingFunction: + case CSSPropertyWebkitTransitionDelay: + case CSSPropertyWebkitTransitionDuration: + case CSSPropertyWebkitTransitionTimingFunction: + case CSSPropertyWebkitTransitionProperty: { + RefPtr<CSSValue> val; + if (parseAnimationProperty(propId, val)) { + addProperty(propId, val.release(), important); + return true; + } + return false; + } +#if ENABLE(CSS_GRID_LAYOUT) + case CSSPropertyWebkitGridColumns: + case CSSPropertyWebkitGridRows: + return parseGridTrackList(propId, important); +#endif + case CSSPropertyWebkitMarginCollapse: { + const int properties[2] = { CSSPropertyWebkitMarginBeforeCollapse, + CSSPropertyWebkitMarginAfterCollapse }; + if (num == 1) { + ShorthandScope scope(this, CSSPropertyWebkitMarginCollapse); + if (!parseValue(properties[0], important)) + return false; + CSSValue* value = m_parsedProperties[m_numParsedProperties-1]->value(); + addProperty(properties[1], value, important); + return true; + } + else if (num == 2) { + ShorthandScope scope(this, CSSPropertyWebkitMarginCollapse); + if (!parseValue(properties[0], important) || !parseValue(properties[1], important)) + return false; + return true; + } + return false; + } + case CSSPropertyWebkitMarginBeforeCollapse: + case CSSPropertyWebkitMarginAfterCollapse: + case CSSPropertyWebkitMarginTopCollapse: + case CSSPropertyWebkitMarginBottomCollapse: + if (id == CSSValueCollapse || id == CSSValueSeparate || id == CSSValueDiscard) + validPrimitive = true; + break; + case CSSPropertyTextLineThroughMode: + case CSSPropertyTextOverlineMode: + case CSSPropertyTextUnderlineMode: + if (id == CSSValueContinuous || id == CSSValueSkipWhiteSpace) + validPrimitive = true; + break; + case CSSPropertyTextLineThroughStyle: + case CSSPropertyTextOverlineStyle: + case CSSPropertyTextUnderlineStyle: + if (id == CSSValueNone || id == CSSValueSolid || id == CSSValueDouble || + id == CSSValueDashed || id == CSSValueDotDash || id == CSSValueDotDotDash || + id == CSSValueWave) + validPrimitive = true; + break; + case CSSPropertyTextRendering: // auto | optimizeSpeed | optimizeLegibility | geometricPrecision + if (id == CSSValueAuto || id == CSSValueOptimizespeed || id == CSSValueOptimizelegibility + || id == CSSValueGeometricprecision) + validPrimitive = true; + break; + case CSSPropertyTextLineThroughWidth: + case CSSPropertyTextOverlineWidth: + case CSSPropertyTextUnderlineWidth: + if (id == CSSValueAuto || id == CSSValueNormal || id == CSSValueThin || + id == CSSValueMedium || id == CSSValueThick) + validPrimitive = true; + else + validPrimitive = !id && validUnit(value, FNumber | FLength | FPercent, m_strict); + break; + case CSSPropertyResize: // none | both | horizontal | vertical | auto + if (id == CSSValueNone || id == CSSValueBoth || id == CSSValueHorizontal || id == CSSValueVertical || id == CSSValueAuto) + validPrimitive = true; + break; + case CSSPropertyWebkitColumnCount: + if (id == CSSValueAuto) + validPrimitive = true; + else + validPrimitive = !id && validUnit(value, FInteger | FNonNeg, false); + break; + case CSSPropertyWebkitColumnGap: // normal | <length> + if (id == CSSValueNormal) + validPrimitive = true; + else + validPrimitive = validUnit(value, FLength | FNonNeg, m_strict); + break; + case CSSPropertyWebkitColumnAxis: + if (id == CSSValueHorizontal || id == CSSValueVertical || id == CSSValueAuto) + validPrimitive = true; + break; + case CSSPropertyWebkitColumnSpan: // all | 1 + if (id == CSSValueAll) + validPrimitive = true; + else + validPrimitive = validUnit(value, FNumber | FNonNeg, m_strict) && value->fValue == 1; + break; + case CSSPropertyWebkitColumnWidth: // auto | <length> + if (id == CSSValueAuto) + validPrimitive = true; + else // Always parse this property in strict mode, since it would be ambiguous otherwise when used in the 'columns' shorthand property. + validPrimitive = validUnit(value, FLength, true); + break; + case CSSPropertyPointerEvents: + // none | visiblePainted | visibleFill | visibleStroke | visible | + // painted | fill | stroke | auto | all | inherit + if (id == CSSValueVisible || id == CSSValueNone || id == CSSValueAll || id == CSSValueAuto || + (id >= CSSValueVisiblepainted && id <= CSSValueStroke)) + validPrimitive = true; + break; + case CSSPropertyImageRendering: // auto | optimizeContrast + if (id == CSSValueAuto || id == CSSValueWebkitOptimizeContrast) + validPrimitive = true; + break; + // End of CSS3 properties + + // Apple specific properties. These will never be standardized and are purely to + // support custom WebKit-based Apple applications. + case CSSPropertyWebkitLineClamp: + // When specifying number of lines, don't allow 0 as a valid value + // When specifying either type of unit, require non-negative integers + validPrimitive = (!id && (value->unit == CSSPrimitiveValue::CSS_PERCENTAGE || value->fValue) && validUnit(value, FInteger | FPercent | FNonNeg, false)); + break; + case CSSPropertyWebkitTextSizeAdjust: + if (id == CSSValueAuto || id == CSSValueNone) + validPrimitive = true; + break; + case CSSPropertyWebkitRtlOrdering: + if (id == CSSValueLogical || id == CSSValueVisual) + validPrimitive = true; + break; + + case CSSPropertyWebkitFontSizeDelta: // <length> + validPrimitive = validUnit(value, FLength, m_strict); + break; + + case CSSPropertyWebkitNbspMode: // normal | space + if (id == CSSValueNormal || id == CSSValueSpace) + validPrimitive = true; + break; + + case CSSPropertyWebkitLineBreak: // normal | after-white-space + if (id == CSSValueNormal || id == CSSValueAfterWhiteSpace) + validPrimitive = true; + break; + + case CSSPropertyWebkitMatchNearestMailBlockquoteColor: // normal | match + if (id == CSSValueNormal || id == CSSValueMatch) + validPrimitive = true; + break; + + case CSSPropertyWebkitHighlight: + if (id == CSSValueNone || value->unit == CSSPrimitiveValue::CSS_STRING) + validPrimitive = true; + break; + + case CSSPropertyWebkitHyphens: + if (id == CSSValueNone || id == CSSValueManual || id == CSSValueAuto) + validPrimitive = true; + break; + + case CSSPropertyWebkitHyphenateCharacter: + if (id == CSSValueAuto || value->unit == CSSPrimitiveValue::CSS_STRING) + validPrimitive = true; + break; + + case CSSPropertyWebkitHyphenateLimitBefore: + case CSSPropertyWebkitHyphenateLimitAfter: + if (id == CSSValueAuto || validUnit(value, FInteger | FNonNeg, true)) + validPrimitive = true; + break; + + case CSSPropertyWebkitHyphenateLimitLines: + if (id == CSSValueNoLimit || validUnit(value, FInteger | FNonNeg, true)) + validPrimitive = true; + break; + + case CSSPropertyWebkitLineGrid: + if (id == CSSValueNone) + validPrimitive = true; + else if (value->unit == CSSPrimitiveValue::CSS_IDENT) { + String lineGridValue = String(value->string); + if (!lineGridValue.isEmpty()) { + addProperty(propId, cssValuePool()->createValue(lineGridValue, CSSPrimitiveValue::CSS_STRING), important); + return true; + } + } + break; + case CSSPropertyWebkitLineGridSnap: + if (id == CSSValueNone || id == CSSValueBaseline || id == CSSValueBounds) + validPrimitive = true; + break; + case CSSPropertyWebkitLocale: + if (id == CSSValueAuto || value->unit == CSSPrimitiveValue::CSS_STRING) + validPrimitive = true; + break; + + case CSSPropertyWebkitBorderFit: + if (id == CSSValueBorder || id == CSSValueLines) + validPrimitive = true; + break; + + case CSSPropertyWebkitTextSecurity: + // disc | circle | square | none | inherit + if (id == CSSValueDisc || id == CSSValueCircle || id == CSSValueSquare|| id == CSSValueNone) + validPrimitive = true; + break; + + case CSSPropertyWebkitFontSmoothing: + if (id == CSSValueAuto || id == CSSValueNone + || id == CSSValueAntialiased || id == CSSValueSubpixelAntialiased) + validPrimitive = true; + break; + +#if ENABLE(DASHBOARD_SUPPORT) + case CSSPropertyWebkitDashboardRegion: // <dashboard-region> | <dashboard-region> + if (value->unit == CSSParserValue::Function || id == CSSValueNone) + return parseDashboardRegions(propId, important); + break; +#endif + // End Apple-specific properties + +#if ENABLE(TOUCH_EVENTS) + case CSSPropertyWebkitTapHighlightColor: + if ((id >= CSSValueAqua && id <= CSSValueWindowtext) || id == CSSValueMenu + || (id >= CSSValueWebkitFocusRingColor && id < CSSValueWebkitText && !m_strict)) { + validPrimitive = true; + } else { + parsedValue = parseColor(); + if (parsedValue) + m_valueList->next(); + } + break; +#endif + /* shorthand properties */ + case CSSPropertyBackground: { + // Position must come before color in this array because a plain old "0" is a legal color + // in quirks mode but it's usually the X coordinate of a position. + // FIXME: Add CSSPropertyBackgroundSize to the shorthand. + const int properties[] = { CSSPropertyBackgroundImage, CSSPropertyBackgroundRepeat, + CSSPropertyBackgroundAttachment, CSSPropertyBackgroundPosition, CSSPropertyBackgroundOrigin, + CSSPropertyBackgroundClip, CSSPropertyBackgroundColor }; + return parseFillShorthand(propId, properties, 7, important); + } + case CSSPropertyWebkitMask: { + const int properties[] = { CSSPropertyWebkitMaskImage, CSSPropertyWebkitMaskRepeat, + CSSPropertyWebkitMaskAttachment, CSSPropertyWebkitMaskPosition, + CSSPropertyWebkitMaskOrigin, CSSPropertyWebkitMaskClip }; + return parseFillShorthand(propId, properties, 6, important); + } + case CSSPropertyBorder: + // [ 'border-width' || 'border-style' || <color> ] | inherit + { + const int properties[3] = { CSSPropertyBorderWidth, CSSPropertyBorderStyle, + CSSPropertyBorderColor }; + if (parseShorthand(propId, properties, 3, important)) { + // The CSS3 Borders and Backgrounds specification says that border also resets border-image. It's as + // though a value of none was specified for the image. + addProperty(CSSPropertyBorderImage, cssValuePool()->createImplicitInitialValue(), important); + return true; + } + return false; + } + case CSSPropertyBorderTop: + // [ 'border-top-width' || 'border-style' || <color> ] | inherit + { + const int properties[3] = { CSSPropertyBorderTopWidth, CSSPropertyBorderTopStyle, + CSSPropertyBorderTopColor}; + return parseShorthand(propId, properties, 3, important); + } + case CSSPropertyBorderRight: + // [ 'border-right-width' || 'border-style' || <color> ] | inherit + { + const int properties[3] = { CSSPropertyBorderRightWidth, CSSPropertyBorderRightStyle, + CSSPropertyBorderRightColor }; + return parseShorthand(propId, properties, 3, important); + } + case CSSPropertyBorderBottom: + // [ 'border-bottom-width' || 'border-style' || <color> ] | inherit + { + const int properties[3] = { CSSPropertyBorderBottomWidth, CSSPropertyBorderBottomStyle, + CSSPropertyBorderBottomColor }; + return parseShorthand(propId, properties, 3, important); + } + case CSSPropertyBorderLeft: + // [ 'border-left-width' || 'border-style' || <color> ] | inherit + { + const int properties[3] = { CSSPropertyBorderLeftWidth, CSSPropertyBorderLeftStyle, + CSSPropertyBorderLeftColor }; + return parseShorthand(propId, properties, 3, important); + } + case CSSPropertyWebkitBorderStart: + { + const int properties[3] = { CSSPropertyWebkitBorderStartWidth, CSSPropertyWebkitBorderStartStyle, + CSSPropertyWebkitBorderStartColor }; + return parseShorthand(propId, properties, 3, important); + } + case CSSPropertyWebkitBorderEnd: + { + const int properties[3] = { CSSPropertyWebkitBorderEndWidth, CSSPropertyWebkitBorderEndStyle, + CSSPropertyWebkitBorderEndColor }; + return parseShorthand(propId, properties, 3, important); + } + case CSSPropertyWebkitBorderBefore: + { + const int properties[3] = { CSSPropertyWebkitBorderBeforeWidth, CSSPropertyWebkitBorderBeforeStyle, + CSSPropertyWebkitBorderBeforeColor }; + return parseShorthand(propId, properties, 3, important); + } + case CSSPropertyWebkitBorderAfter: + { + const int properties[3] = { CSSPropertyWebkitBorderAfterWidth, CSSPropertyWebkitBorderAfterStyle, + CSSPropertyWebkitBorderAfterColor }; + return parseShorthand(propId, properties, 3, important); + } + case CSSPropertyOutline: + // [ 'outline-color' || 'outline-style' || 'outline-width' ] | inherit + { + const int properties[3] = { CSSPropertyOutlineWidth, CSSPropertyOutlineStyle, + CSSPropertyOutlineColor }; + return parseShorthand(propId, properties, 3, important); + } + case CSSPropertyBorderColor: + // <color>{1,4} | inherit + { + const int properties[4] = { CSSPropertyBorderTopColor, CSSPropertyBorderRightColor, + CSSPropertyBorderBottomColor, CSSPropertyBorderLeftColor }; + return parse4Values(propId, properties, important); + } + case CSSPropertyBorderWidth: + // <border-width>{1,4} | inherit + { + const int properties[4] = { CSSPropertyBorderTopWidth, CSSPropertyBorderRightWidth, + CSSPropertyBorderBottomWidth, CSSPropertyBorderLeftWidth }; + return parse4Values(propId, properties, important); + } + case CSSPropertyBorderStyle: + // <border-style>{1,4} | inherit + { + const int properties[4] = { CSSPropertyBorderTopStyle, CSSPropertyBorderRightStyle, + CSSPropertyBorderBottomStyle, CSSPropertyBorderLeftStyle }; + return parse4Values(propId, properties, important); + } + case CSSPropertyMargin: + // <margin-width>{1,4} | inherit + { + const int properties[4] = { CSSPropertyMarginTop, CSSPropertyMarginRight, + CSSPropertyMarginBottom, CSSPropertyMarginLeft }; + return parse4Values(propId, properties, important); + } + case CSSPropertyPadding: + // <padding-width>{1,4} | inherit + { + const int properties[4] = { CSSPropertyPaddingTop, CSSPropertyPaddingRight, + CSSPropertyPaddingBottom, CSSPropertyPaddingLeft }; + return parse4Values(propId, properties, important); + } + case CSSPropertyWebkitFlexFlow: + { + const int properties[] = { CSSPropertyWebkitFlexDirection, CSSPropertyWebkitFlexWrap }; + return parseShorthand(propId, properties, 2, important); + } + case CSSPropertyFont: + // [ [ 'font-style' || 'font-variant' || 'font-weight' ]? 'font-size' [ / 'line-height' ]? + // 'font-family' ] | caption | icon | menu | message-box | small-caption | status-bar | inherit + if (id >= CSSValueCaption && id <= CSSValueStatusBar) + validPrimitive = true; + else + return parseFont(important); + break; + case CSSPropertyListStyle: + { + const int properties[3] = { CSSPropertyListStyleType, CSSPropertyListStylePosition, + CSSPropertyListStyleImage }; + return parseShorthand(propId, properties, 3, important); + } + case CSSPropertyWebkitColumns: { + const int properties[2] = { CSSPropertyWebkitColumnWidth, CSSPropertyWebkitColumnCount }; + return parseShorthand(propId, properties, 2, important); + } + case CSSPropertyWebkitColumnRule: { + const int properties[3] = { CSSPropertyWebkitColumnRuleWidth, CSSPropertyWebkitColumnRuleStyle, + CSSPropertyWebkitColumnRuleColor }; + return parseShorthand(propId, properties, 3, important); + } + case CSSPropertyWebkitTextStroke: { + const int properties[2] = { CSSPropertyWebkitTextStrokeWidth, CSSPropertyWebkitTextStrokeColor }; + return parseShorthand(propId, properties, 2, important); + } + case CSSPropertyWebkitAnimation: + return parseAnimationShorthand(important); + case CSSPropertyWebkitTransition: + return parseTransitionShorthand(important); + case CSSPropertyInvalid: + return false; + case CSSPropertyPage: + return parsePage(propId, important); + case CSSPropertyFontStretch: + case CSSPropertyTextLineThrough: + case CSSPropertyTextOverline: + case CSSPropertyTextUnderline: + return false; + // CSS Text Layout Module Level 3: Vertical writing support + case CSSPropertyWebkitWritingMode: + if (id >= CSSValueHorizontalTb && id <= CSSValueHorizontalBt) + validPrimitive = true; + break; + + case CSSPropertyWebkitTextCombine: + if (id == CSSValueNone || id == CSSValueHorizontal) + validPrimitive = true; + break; + + case CSSPropertyWebkitTextEmphasis: { + const int properties[] = { CSSPropertyWebkitTextEmphasisStyle, CSSPropertyWebkitTextEmphasisColor }; + return parseShorthand(propId, properties, WTF_ARRAY_LENGTH(properties), important); + } + + case CSSPropertyWebkitTextEmphasisPosition: + if (id == CSSValueOver || id == CSSValueUnder) + validPrimitive = true; + break; + + case CSSPropertyWebkitTextEmphasisStyle: + return parseTextEmphasisStyle(important); + + case CSSPropertyWebkitTextOrientation: + // FIXME: For now just support upright and vertical-right. + if (id == CSSValueVerticalRight || id == CSSValueUpright) + validPrimitive = true; + break; + + case CSSPropertyWebkitLineBoxContain: + if (id == CSSValueNone) + validPrimitive = true; + else + return parseLineBoxContain(important); + break; + case CSSPropertyWebkitFontFeatureSettings: + if (id == CSSValueNormal) + validPrimitive = true; + else + return parseFontFeatureSettings(important); + break; + + case CSSPropertyWebkitWrapShapeInside: + case CSSPropertyWebkitWrapShapeOutside: + if (id == CSSValueAuto) + validPrimitive = true; + else if (value->unit == CSSParserValue::Function) + return parseWrapShape((propId == CSSPropertyWebkitWrapShapeInside), important); + break; + case CSSPropertyWebkitWrapFlow: + if (id == CSSValueAuto || id == CSSValueBoth || id == CSSValueLeft || id == CSSValueRight || id == CSSValueMaximum || id == CSSValueClear) + validPrimitive = true; + break; + + case CSSPropertyWebkitWrapThrough: + if (id == CSSValueWrap || id == CSSValueNone) + validPrimitive = true; + break; + case CSSPropertyWebkitWrapMargin: + case CSSPropertyWebkitWrapPadding: + validPrimitive = (!id && validUnit(value, FLength | FNonNeg, m_strict)); + break; + case CSSPropertyWebkitWrap: { + const int properties[] = { CSSPropertyWebkitWrapFlow, CSSPropertyWebkitWrapMargin, CSSPropertyWebkitWrapPadding }; + return parseShorthand(propId, properties, WTF_ARRAY_LENGTH(properties), important); + } +#if ENABLE(SVG) + default: + return parseSVGValue(propId, important); +#endif + } + + if (validPrimitive) { + parsedValue = parseValidPrimitive(id, value); + m_valueList->next(); + } + if (parsedValue) { + if (!m_valueList->current() || inShorthand()) { + addProperty(propId, parsedValue.release(), important); + return true; + } + } + return false; +} + +void CSSParser::addFillValue(RefPtr<CSSValue>& lval, PassRefPtr<CSSValue> rval) +{ + if (lval) { + if (lval->isValueList()) + static_cast<CSSValueList*>(lval.get())->append(rval); + else { + PassRefPtr<CSSValue> oldlVal(lval.release()); + PassRefPtr<CSSValueList> list = CSSValueList::createCommaSeparated(); + list->append(oldlVal); + list->append(rval); + lval = list; + } + } + else + lval = rval; +} + +static bool parseBackgroundClip(CSSParserValue* parserValue, RefPtr<CSSValue>& cssValue, CSSValuePool* cssValuePool) +{ + if (parserValue->id == CSSValueBorderBox || parserValue->id == CSSValuePaddingBox + || parserValue->id == CSSValueContentBox || parserValue->id == CSSValueWebkitText) { + cssValue = cssValuePool->createIdentifierValue(parserValue->id); + return true; + } + return false; +} + +const int cMaxFillProperties = 9; + +bool CSSParser::parseFillShorthand(int propId, const int* properties, int numProperties, bool important) +{ + ASSERT(numProperties <= cMaxFillProperties); + if (numProperties > cMaxFillProperties) + return false; + + ShorthandScope scope(this, propId); + + bool parsedProperty[cMaxFillProperties] = { false }; + RefPtr<CSSValue> values[cMaxFillProperties]; + RefPtr<CSSValue> clipValue; + RefPtr<CSSValue> positionYValue; + RefPtr<CSSValue> repeatYValue; + bool foundClip = false; + int i; + + while (m_valueList->current()) { + CSSParserValue* val = m_valueList->current(); + if (val->unit == CSSParserValue::Operator && val->iValue == ',') { + // We hit the end. Fill in all remaining values with the initial value. + m_valueList->next(); + for (i = 0; i < numProperties; ++i) { + if (properties[i] == CSSPropertyBackgroundColor && parsedProperty[i]) + // Color is not allowed except as the last item in a list for backgrounds. + // Reject the entire property. + return false; + + if (!parsedProperty[i] && properties[i] != CSSPropertyBackgroundColor) { + addFillValue(values[i], cssValuePool()->createImplicitInitialValue()); + if (properties[i] == CSSPropertyBackgroundPosition || properties[i] == CSSPropertyWebkitMaskPosition) + addFillValue(positionYValue, cssValuePool()->createImplicitInitialValue()); + if (properties[i] == CSSPropertyBackgroundRepeat || properties[i] == CSSPropertyWebkitMaskRepeat) + addFillValue(repeatYValue, cssValuePool()->createImplicitInitialValue()); + if ((properties[i] == CSSPropertyBackgroundOrigin || properties[i] == CSSPropertyWebkitMaskOrigin) && !parsedProperty[i]) { + // If background-origin wasn't present, then reset background-clip also. + addFillValue(clipValue, cssValuePool()->createImplicitInitialValue()); + } + } + parsedProperty[i] = false; + } + if (!m_valueList->current()) + break; + } + + bool found = false; + for (i = 0; !found && i < numProperties; ++i) { + if (!parsedProperty[i]) { + RefPtr<CSSValue> val1; + RefPtr<CSSValue> val2; + int propId1, propId2; + CSSParserValue* parserValue = m_valueList->current(); + if (parseFillProperty(properties[i], propId1, propId2, val1, val2)) { + parsedProperty[i] = found = true; + addFillValue(values[i], val1.release()); + if (properties[i] == CSSPropertyBackgroundPosition || properties[i] == CSSPropertyWebkitMaskPosition) + addFillValue(positionYValue, val2.release()); + if (properties[i] == CSSPropertyBackgroundRepeat || properties[i] == CSSPropertyWebkitMaskRepeat) + addFillValue(repeatYValue, val2.release()); + if (properties[i] == CSSPropertyBackgroundOrigin || properties[i] == CSSPropertyWebkitMaskOrigin) { + // Reparse the value as a clip, and see if we succeed. + if (parseBackgroundClip(parserValue, val1, cssValuePool())) + addFillValue(clipValue, val1.release()); // The property parsed successfully. + else + addFillValue(clipValue, cssValuePool()->createImplicitInitialValue()); // Some value was used for origin that is not supported by clip. Just reset clip instead. + } + if (properties[i] == CSSPropertyBackgroundClip || properties[i] == CSSPropertyWebkitMaskClip) { + // Update clipValue + addFillValue(clipValue, val1.release()); + foundClip = true; + } + } + } + } + + // if we didn't find at least one match, this is an + // invalid shorthand and we have to ignore it + if (!found) + return false; + } + + // Fill in any remaining properties with the initial value. + for (i = 0; i < numProperties; ++i) { + if (!parsedProperty[i]) { + addFillValue(values[i], cssValuePool()->createImplicitInitialValue()); + if (properties[i] == CSSPropertyBackgroundPosition || properties[i] == CSSPropertyWebkitMaskPosition) + addFillValue(positionYValue, cssValuePool()->createImplicitInitialValue()); + if (properties[i] == CSSPropertyBackgroundRepeat || properties[i] == CSSPropertyWebkitMaskRepeat) + addFillValue(repeatYValue, cssValuePool()->createImplicitInitialValue()); + if ((properties[i] == CSSPropertyBackgroundOrigin || properties[i] == CSSPropertyWebkitMaskOrigin) && !parsedProperty[i]) { + // If background-origin wasn't present, then reset background-clip also. + addFillValue(clipValue, cssValuePool()->createImplicitInitialValue()); + } + } + } + + // Now add all of the properties we found. + for (i = 0; i < numProperties; i++) { + if (properties[i] == CSSPropertyBackgroundPosition) { + addProperty(CSSPropertyBackgroundPositionX, values[i].release(), important); + // it's OK to call positionYValue.release() since we only see CSSPropertyBackgroundPosition once + addProperty(CSSPropertyBackgroundPositionY, positionYValue.release(), important); + } else if (properties[i] == CSSPropertyWebkitMaskPosition) { + addProperty(CSSPropertyWebkitMaskPositionX, values[i].release(), important); + // it's OK to call positionYValue.release() since we only see CSSPropertyWebkitMaskPosition once + addProperty(CSSPropertyWebkitMaskPositionY, positionYValue.release(), important); + } else if (properties[i] == CSSPropertyBackgroundRepeat) { + addProperty(CSSPropertyBackgroundRepeatX, values[i].release(), important); + // it's OK to call repeatYValue.release() since we only see CSSPropertyBackgroundPosition once + addProperty(CSSPropertyBackgroundRepeatY, repeatYValue.release(), important); + } else if (properties[i] == CSSPropertyWebkitMaskRepeat) { + addProperty(CSSPropertyWebkitMaskRepeatX, values[i].release(), important); + // it's OK to call repeatYValue.release() since we only see CSSPropertyBackgroundPosition once + addProperty(CSSPropertyWebkitMaskRepeatY, repeatYValue.release(), important); + } else if ((properties[i] == CSSPropertyBackgroundClip || properties[i] == CSSPropertyWebkitMaskClip) && !foundClip) + // Value is already set while updating origin + continue; + else + addProperty(properties[i], values[i].release(), important); + + // Add in clip values when we hit the corresponding origin property. + if (properties[i] == CSSPropertyBackgroundOrigin && !foundClip) + addProperty(CSSPropertyBackgroundClip, clipValue.release(), important); + else if (properties[i] == CSSPropertyWebkitMaskOrigin && !foundClip) + addProperty(CSSPropertyWebkitMaskClip, clipValue.release(), important); + } + + return true; +} + +void CSSParser::addAnimationValue(RefPtr<CSSValue>& lval, PassRefPtr<CSSValue> rval) +{ + if (lval) { + if (lval->isValueList()) + static_cast<CSSValueList*>(lval.get())->append(rval); + else { + PassRefPtr<CSSValue> oldVal(lval.release()); + PassRefPtr<CSSValueList> list = CSSValueList::createCommaSeparated(); + list->append(oldVal); + list->append(rval); + lval = list; + } + } + else + lval = rval; +} + +bool CSSParser::parseAnimationShorthand(bool important) +{ + const int properties[] = { CSSPropertyWebkitAnimationName, + CSSPropertyWebkitAnimationDuration, + CSSPropertyWebkitAnimationTimingFunction, + CSSPropertyWebkitAnimationDelay, + CSSPropertyWebkitAnimationIterationCount, + CSSPropertyWebkitAnimationDirection, + CSSPropertyWebkitAnimationFillMode }; + const int numProperties = WTF_ARRAY_LENGTH(properties); + + ShorthandScope scope(this, CSSPropertyWebkitAnimation); + + bool parsedProperty[numProperties] = { false }; // compiler will repeat false as necessary + RefPtr<CSSValue> values[numProperties]; + + int i; + while (m_valueList->current()) { + CSSParserValue* val = m_valueList->current(); + if (val->unit == CSSParserValue::Operator && val->iValue == ',') { + // We hit the end. Fill in all remaining values with the initial value. + m_valueList->next(); + for (i = 0; i < numProperties; ++i) { + if (!parsedProperty[i]) + addAnimationValue(values[i], cssValuePool()->createImplicitInitialValue()); + parsedProperty[i] = false; + } + if (!m_valueList->current()) + break; + } + + bool found = false; + for (i = 0; !found && i < numProperties; ++i) { + if (!parsedProperty[i]) { + RefPtr<CSSValue> val; + if (parseAnimationProperty(properties[i], val)) { + parsedProperty[i] = found = true; + addAnimationValue(values[i], val.release()); + } + } + } + + // if we didn't find at least one match, this is an + // invalid shorthand and we have to ignore it + if (!found) + return false; + } + + // Fill in any remaining properties with the initial value. + for (i = 0; i < numProperties; ++i) { + if (!parsedProperty[i]) + addAnimationValue(values[i], cssValuePool()->createImplicitInitialValue()); + } + + // Now add all of the properties we found. + for (i = 0; i < numProperties; i++) + addProperty(properties[i], values[i].release(), important); + + return true; +} + +bool CSSParser::parseTransitionShorthand(bool important) +{ + const int properties[] = { CSSPropertyWebkitTransitionProperty, + CSSPropertyWebkitTransitionDuration, + CSSPropertyWebkitTransitionTimingFunction, + CSSPropertyWebkitTransitionDelay }; + const int numProperties = WTF_ARRAY_LENGTH(properties); + + ShorthandScope scope(this, CSSPropertyWebkitTransition); + + bool parsedProperty[numProperties] = { false }; // compiler will repeat false as necessary + RefPtr<CSSValue> values[numProperties]; + + int i; + while (m_valueList->current()) { + CSSParserValue* val = m_valueList->current(); + if (val->unit == CSSParserValue::Operator && val->iValue == ',') { + // We hit the end. Fill in all remaining values with the initial value. + m_valueList->next(); + for (i = 0; i < numProperties; ++i) { + if (!parsedProperty[i]) + addAnimationValue(values[i], cssValuePool()->createImplicitInitialValue()); + parsedProperty[i] = false; + } + if (!m_valueList->current()) + break; + } + + bool found = false; + for (i = 0; !found && i < numProperties; ++i) { + if (!parsedProperty[i]) { + RefPtr<CSSValue> val; + if (parseAnimationProperty(properties[i], val)) { + parsedProperty[i] = found = true; + addAnimationValue(values[i], val.release()); + } + } + } + + // if we didn't find at least one match, this is an + // invalid shorthand and we have to ignore it + if (!found) + return false; + } + + // Fill in any remaining properties with the initial value. + for (i = 0; i < numProperties; ++i) { + if (!parsedProperty[i]) + addAnimationValue(values[i], cssValuePool()->createImplicitInitialValue()); + } + + // Now add all of the properties we found. + for (i = 0; i < numProperties; i++) + addProperty(properties[i], values[i].release(), important); + + return true; +} + +bool CSSParser::parseShorthand(int propId, const int *properties, int numProperties, bool important) +{ + // We try to match as many properties as possible + // We set up an array of booleans to mark which property has been found, + // and we try to search for properties until it makes no longer any sense. + ShorthandScope scope(this, propId); + + bool found = false; + bool fnd[6]; // Trust me ;) + for (int i = 0; i < numProperties; i++) + fnd[i] = false; + + while (m_valueList->current()) { + found = false; + for (int propIndex = 0; !found && propIndex < numProperties; ++propIndex) { + if (!fnd[propIndex]) { + if (parseValue(properties[propIndex], important)) + fnd[propIndex] = found = true; + } + } + + // if we didn't find at least one match, this is an + // invalid shorthand and we have to ignore it + if (!found) + return false; + } + + // Fill in any remaining properties with the initial value. + ImplicitScope implicitScope(this, PropertyImplicit); + for (int i = 0; i < numProperties; ++i) { + if (!fnd[i]) + addProperty(properties[i], cssValuePool()->createImplicitInitialValue(), important); + } + + return true; +} + +bool CSSParser::parse4Values(int propId, const int *properties, bool important) +{ + /* From the CSS 2 specs, 8.3 + * If there is only one value, it applies to all sides. If there are two values, the top and + * bottom margins are set to the first value and the right and left margins are set to the second. + * If there are three values, the top is set to the first value, the left and right are set to the + * second, and the bottom is set to the third. If there are four values, they apply to the top, + * right, bottom, and left, respectively. + */ + + int num = inShorthand() ? 1 : m_valueList->size(); + + ShorthandScope scope(this, propId); + + // the order is top, right, bottom, left + switch (num) { + case 1: { + if (!parseValue(properties[0], important)) + return false; + CSSValue *value = m_parsedProperties[m_numParsedProperties-1]->value(); + ImplicitScope implicitScope(this, PropertyImplicit); + addProperty(properties[1], value, important); + addProperty(properties[2], value, important); + addProperty(properties[3], value, important); + break; + } + case 2: { + if (!parseValue(properties[0], important) || !parseValue(properties[1], important)) + return false; + CSSValue *value = m_parsedProperties[m_numParsedProperties-2]->value(); + ImplicitScope implicitScope(this, PropertyImplicit); + addProperty(properties[2], value, important); + value = m_parsedProperties[m_numParsedProperties-2]->value(); + addProperty(properties[3], value, important); + break; + } + case 3: { + if (!parseValue(properties[0], important) || !parseValue(properties[1], important) || !parseValue(properties[2], important)) + return false; + CSSValue *value = m_parsedProperties[m_numParsedProperties-2]->value(); + ImplicitScope implicitScope(this, PropertyImplicit); + addProperty(properties[3], value, important); + break; + } + case 4: { + if (!parseValue(properties[0], important) || !parseValue(properties[1], important) || + !parseValue(properties[2], important) || !parseValue(properties[3], important)) + return false; + break; + } + default: { + return false; + } + } + + return true; +} + +// auto | <identifier> +bool CSSParser::parsePage(int propId, bool important) +{ + ASSERT(propId == CSSPropertyPage); + + if (m_valueList->size() != 1) + return false; + + CSSParserValue* value = m_valueList->current(); + if (!value) + return false; + + if (value->id == CSSValueAuto) { + addProperty(propId, cssValuePool()->createIdentifierValue(value->id), important); + return true; + } else if (value->id == 0 && value->unit == CSSPrimitiveValue::CSS_IDENT) { + addProperty(propId, createPrimitiveStringValue(value), important); + return true; + } + return false; +} + +// <length>{1,2} | auto | [ <page-size> || [ portrait | landscape] ] +bool CSSParser::parseSize(int propId, bool important) +{ + ASSERT(propId == CSSPropertySize); + + if (m_valueList->size() > 2) + return false; + + CSSParserValue* value = m_valueList->current(); + if (!value) + return false; + + RefPtr<CSSValueList> parsedValues = CSSValueList::createSpaceSeparated(); + + // First parameter. + SizeParameterType paramType = parseSizeParameter(parsedValues.get(), value, None); + if (paramType == None) + return false; + + // Second parameter, if any. + value = m_valueList->next(); + if (value) { + paramType = parseSizeParameter(parsedValues.get(), value, paramType); + if (paramType == None) + return false; + } + + addProperty(propId, parsedValues.release(), important); + return true; +} + +CSSParser::SizeParameterType CSSParser::parseSizeParameter(CSSValueList* parsedValues, CSSParserValue* value, SizeParameterType prevParamType) +{ + switch (value->id) { + case CSSValueAuto: + if (prevParamType == None) { + parsedValues->append(cssValuePool()->createIdentifierValue(value->id)); + return Auto; + } + return None; + case CSSValueLandscape: + case CSSValuePortrait: + if (prevParamType == None || prevParamType == PageSize) { + parsedValues->append(cssValuePool()->createIdentifierValue(value->id)); + return Orientation; + } + return None; + case CSSValueA3: + case CSSValueA4: + case CSSValueA5: + case CSSValueB4: + case CSSValueB5: + case CSSValueLedger: + case CSSValueLegal: + case CSSValueLetter: + if (prevParamType == None || prevParamType == Orientation) { + // Normalize to Page Size then Orientation order by prepending. + // This is not specified by the CSS3 Paged Media specification, but for simpler processing later (CSSStyleSelector::applyPageSizeProperty). + parsedValues->prepend(cssValuePool()->createIdentifierValue(value->id)); + return PageSize; + } + return None; + case 0: + if (validUnit(value, FLength | FNonNeg, m_strict) && (prevParamType == None || prevParamType == Length)) { + parsedValues->append(createPrimitiveNumericValue(value)); + return Length; + } + return None; + default: + return None; + } +} + +// [ <string> <string> ]+ | inherit | none +// inherit and none are handled in parseValue. +bool CSSParser::parseQuotes(int propId, bool important) +{ + RefPtr<CSSValueList> values = CSSValueList::createCommaSeparated(); + while (CSSParserValue* val = m_valueList->current()) { + RefPtr<CSSValue> parsedValue; + if (val->unit == CSSPrimitiveValue::CSS_STRING) + parsedValue = CSSPrimitiveValue::create(val->string, CSSPrimitiveValue::CSS_STRING); + else + break; + values->append(parsedValue.release()); + m_valueList->next(); + } + if (values->length()) { + addProperty(propId, values.release(), important); + m_valueList->next(); + return true; + } + return false; +} + +// [ <string> | <uri> | <counter> | attr(X) | open-quote | close-quote | no-open-quote | no-close-quote ]+ | inherit +// in CSS 2.1 this got somewhat reduced: +// [ <string> | attr(X) | open-quote | close-quote | no-open-quote | no-close-quote ]+ | inherit +bool CSSParser::parseContent(int propId, bool important) +{ + RefPtr<CSSValueList> values = CSSValueList::createCommaSeparated(); + + while (CSSParserValue* val = m_valueList->current()) { + RefPtr<CSSValue> parsedValue; + if (val->unit == CSSPrimitiveValue::CSS_URI && m_styleSheet) { + // url + // FIXME: The completeURL call should be done when using the CSSImageValue, + // not when creating it. + parsedValue = CSSImageValue::create(m_styleSheet->completeURL(val->string)); + } else if (val->unit == CSSParserValue::Function) { + // attr(X) | counter(X [,Y]) | counters(X, Y, [,Z]) | -webkit-gradient(...) + CSSParserValueList* args = val->function->args.get(); + if (!args) + return false; + if (equalIgnoringCase(val->function->name, "attr(")) { + parsedValue = parseAttr(args); + if (!parsedValue) + return false; + } else if (equalIgnoringCase(val->function->name, "counter(")) { + parsedValue = parseCounterContent(args, false); + if (!parsedValue) + return false; + } else if (equalIgnoringCase(val->function->name, "counters(")) { + parsedValue = parseCounterContent(args, true); + if (!parsedValue) + return false; + } else if (isGeneratedImageValue(val)) { + if (!parseGeneratedImage(m_valueList.get(), parsedValue)) + return false; + } else + return false; + } else if (val->unit == CSSPrimitiveValue::CSS_IDENT) { + // open-quote + // close-quote + // no-open-quote + // no-close-quote + // inherit + // FIXME: These are not yet implemented (http://bugs.webkit.org/show_bug.cgi?id=6503). + // none + // normal + switch (val->id) { + case CSSValueOpenQuote: + case CSSValueCloseQuote: + case CSSValueNoOpenQuote: + case CSSValueNoCloseQuote: + case CSSValueNone: + case CSSValueNormal: + parsedValue = cssValuePool()->createIdentifierValue(val->id); + } + } else if (val->unit == CSSPrimitiveValue::CSS_STRING) { + parsedValue = createPrimitiveStringValue(val); + } + if (!parsedValue) + break; + values->append(parsedValue.release()); + m_valueList->next(); + } + + if (values->length()) { + addProperty(propId, values.release(), important); + m_valueList->next(); + return true; + } + + return false; +} + +PassRefPtr<CSSValue> CSSParser::parseAttr(CSSParserValueList* args) +{ + if (args->size() != 1) + return 0; + + CSSParserValue* a = args->current(); + + if (a->unit != CSSPrimitiveValue::CSS_IDENT) + return 0; + + String attrName = a->string; + // CSS allows identifiers with "-" at the start, like "-webkit-mask-image". + // But HTML attribute names can't have those characters, and we should not + // even parse them inside attr(). + if (attrName[0] == '-') + return 0; + + Document* document = findDocument(); + if (document && document->isHTMLDocument()) + attrName = attrName.lower(); + + return cssValuePool()->createValue(attrName, CSSPrimitiveValue::CSS_ATTR); +} + +PassRefPtr<CSSValue> CSSParser::parseBackgroundColor() +{ + int id = m_valueList->current()->id; + if (id == CSSValueWebkitText || (id >= CSSValueAqua && id <= CSSValueWindowtext) || id == CSSValueMenu || id == CSSValueCurrentcolor || + (id >= CSSValueGrey && id < CSSValueWebkitText && !m_strict)) + return cssValuePool()->createIdentifierValue(id); + return parseColor(); +} + +bool CSSParser::parseFillImage(CSSParserValueList* valueList, RefPtr<CSSValue>& value) +{ + if (valueList->current()->id == CSSValueNone) { + value = CSSImageValue::create(); + return true; + } + if (valueList->current()->unit == CSSPrimitiveValue::CSS_URI) { + // FIXME: The completeURL call should be done when using the CSSImageValue, + // not when creating it. + if (m_styleSheet) + value = CSSImageValue::create(m_styleSheet->completeURL(valueList->current()->string)); + return true; + } + + if (isGeneratedImageValue(valueList->current())) + return parseGeneratedImage(valueList, value); + + return false; +} + +PassRefPtr<CSSValue> CSSParser::parseFillPositionX(CSSParserValueList* valueList) +{ + int id = valueList->current()->id; + if (id == CSSValueLeft || id == CSSValueRight || id == CSSValueCenter) { + int percent = 0; + if (id == CSSValueRight) + percent = 100; + else if (id == CSSValueCenter) + percent = 50; + return cssValuePool()->createValue(percent, CSSPrimitiveValue::CSS_PERCENTAGE); + } + if (validUnit(valueList->current(), FPercent | FLength, m_strict)) + return createPrimitiveNumericValue(valueList->current()); + return 0; +} + +PassRefPtr<CSSValue> CSSParser::parseFillPositionY(CSSParserValueList* valueList) +{ + int id = valueList->current()->id; + if (id == CSSValueTop || id == CSSValueBottom || id == CSSValueCenter) { + int percent = 0; + if (id == CSSValueBottom) + percent = 100; + else if (id == CSSValueCenter) + percent = 50; + return cssValuePool()->createValue(percent, CSSPrimitiveValue::CSS_PERCENTAGE); + } + if (validUnit(valueList->current(), FPercent | FLength, m_strict)) + return createPrimitiveNumericValue(valueList->current()); + return 0; +} + +PassRefPtr<CSSValue> CSSParser::parseFillPositionComponent(CSSParserValueList* valueList, unsigned& cumulativeFlags, FillPositionFlag& individualFlag) +{ + int id = valueList->current()->id; + if (id == CSSValueLeft || id == CSSValueTop || id == CSSValueRight || id == CSSValueBottom || id == CSSValueCenter) { + int percent = 0; + if (id == CSSValueLeft || id == CSSValueRight) { + if (cumulativeFlags & XFillPosition) + return 0; + cumulativeFlags |= XFillPosition; + individualFlag = XFillPosition; + if (id == CSSValueRight) + percent = 100; + } + else if (id == CSSValueTop || id == CSSValueBottom) { + if (cumulativeFlags & YFillPosition) + return 0; + cumulativeFlags |= YFillPosition; + individualFlag = YFillPosition; + if (id == CSSValueBottom) + percent = 100; + } else if (id == CSSValueCenter) { + // Center is ambiguous, so we're not sure which position we've found yet, an x or a y. + percent = 50; + cumulativeFlags |= AmbiguousFillPosition; + individualFlag = AmbiguousFillPosition; + } + return cssValuePool()->createValue(percent, CSSPrimitiveValue::CSS_PERCENTAGE); + } + if (validUnit(valueList->current(), FPercent | FLength, m_strict)) { + if (!cumulativeFlags) { + cumulativeFlags |= XFillPosition; + individualFlag = XFillPosition; + } else if (cumulativeFlags & (XFillPosition | AmbiguousFillPosition)) { + cumulativeFlags |= YFillPosition; + individualFlag = YFillPosition; + } else + return 0; + return createPrimitiveNumericValue(valueList->current()); + } + return 0; +} + +void CSSParser::parseFillPosition(CSSParserValueList* valueList, RefPtr<CSSValue>& value1, RefPtr<CSSValue>& value2) +{ + CSSParserValue* value = valueList->current(); + + // Parse the first value. We're just making sure that it is one of the valid keywords or a percentage/length. + unsigned cumulativeFlags = 0; + FillPositionFlag value1Flag = InvalidFillPosition; + FillPositionFlag value2Flag = InvalidFillPosition; + value1 = parseFillPositionComponent(valueList, cumulativeFlags, value1Flag); + if (!value1) + return; + + // It only takes one value for background-position to be correctly parsed if it was specified in a shorthand (since we + // can assume that any other values belong to the rest of the shorthand). If we're not parsing a shorthand, though, the + // value was explicitly specified for our property. + value = valueList->next(); + + // First check for the comma. If so, we are finished parsing this value or value pair. + if (value && value->unit == CSSParserValue::Operator && value->iValue == ',') + value = 0; + + if (value) { + value2 = parseFillPositionComponent(valueList, cumulativeFlags, value2Flag); + if (value2) + valueList->next(); + else { + if (!inShorthand()) { + value1.clear(); + return; + } + } + } + + if (!value2) + // Only one value was specified. If that value was not a keyword, then it sets the x position, and the y position + // is simply 50%. This is our default. + // For keywords, the keyword was either an x-keyword (left/right), a y-keyword (top/bottom), or an ambiguous keyword (center). + // For left/right/center, the default of 50% in the y is still correct. + value2 = cssValuePool()->createValue(50, CSSPrimitiveValue::CSS_PERCENTAGE); + + if (value1Flag == YFillPosition || value2Flag == XFillPosition) + value1.swap(value2); +} + +void CSSParser::parseFillRepeat(RefPtr<CSSValue>& value1, RefPtr<CSSValue>& value2) +{ + CSSParserValue* value = m_valueList->current(); + + int id = m_valueList->current()->id; + if (id == CSSValueRepeatX) { + m_implicitShorthand = true; + value1 = cssValuePool()->createIdentifierValue(CSSValueRepeat); + value2 = cssValuePool()->createIdentifierValue(CSSValueNoRepeat); + m_valueList->next(); + return; + } + if (id == CSSValueRepeatY) { + m_implicitShorthand = true; + value1 = cssValuePool()->createIdentifierValue(CSSValueNoRepeat); + value2 = cssValuePool()->createIdentifierValue(CSSValueRepeat); + m_valueList->next(); + return; + } + if (id == CSSValueRepeat || id == CSSValueNoRepeat || id == CSSValueRound || id == CSSValueSpace) + value1 = cssValuePool()->createIdentifierValue(id); + else { + value1 = 0; + return; + } + + value = m_valueList->next(); + + // First check for the comma. If so, we are finished parsing this value or value pair. + if (value && value->unit == CSSParserValue::Operator && value->iValue == ',') + value = 0; + + if (value) + id = m_valueList->current()->id; + + if (value && (id == CSSValueRepeat || id == CSSValueNoRepeat || id == CSSValueRound || id == CSSValueSpace)) { + value2 = cssValuePool()->createIdentifierValue(id); + m_valueList->next(); + } else { + // If only one value was specified, value2 is the same as value1. + m_implicitShorthand = true; + value2 = cssValuePool()->createIdentifierValue(static_cast<CSSPrimitiveValue*>(value1.get())->getIdent()); + } +} + +PassRefPtr<CSSValue> CSSParser::parseFillSize(int propId, bool& allowComma) +{ + allowComma = true; + CSSParserValue* value = m_valueList->current(); + + if (value->id == CSSValueContain || value->id == CSSValueCover) + return cssValuePool()->createIdentifierValue(value->id); + + RefPtr<CSSPrimitiveValue> parsedValue1; + + if (value->id == CSSValueAuto) + parsedValue1 = cssValuePool()->createIdentifierValue(CSSValueAuto); + else { + if (!validUnit(value, FLength | FPercent, m_strict)) + return 0; + parsedValue1 = createPrimitiveNumericValue(value); + } + + CSSPropertyID property = static_cast<CSSPropertyID>(propId); + RefPtr<CSSPrimitiveValue> parsedValue2; + if ((value = m_valueList->next())) { + if (value->unit == CSSParserValue::Operator && value->iValue == ',') + allowComma = false; + else if (value->id != CSSValueAuto) { + if (!validUnit(value, FLength | FPercent, m_strict)) + return 0; + parsedValue2 = createPrimitiveNumericValue(value); + } + } else if (!parsedValue2 && property == CSSPropertyWebkitBackgroundSize) { + // For backwards compatibility we set the second value to the first if it is omitted. + // We only need to do this for -webkit-background-size. It should be safe to let masks match + // the real property. + parsedValue2 = parsedValue1; + } + + if (!parsedValue2) + return parsedValue1; + return cssValuePool()->createValue(Pair::create(parsedValue1.release(), parsedValue2.release())); +} + +bool CSSParser::parseFillProperty(int propId, int& propId1, int& propId2, + RefPtr<CSSValue>& retValue1, RefPtr<CSSValue>& retValue2) +{ + RefPtr<CSSValueList> values; + RefPtr<CSSValueList> values2; + CSSParserValue* val; + RefPtr<CSSValue> value; + RefPtr<CSSValue> value2; + + bool allowComma = false; + + retValue1 = retValue2 = 0; + propId1 = propId; + propId2 = propId; + if (propId == CSSPropertyBackgroundPosition) { + propId1 = CSSPropertyBackgroundPositionX; + propId2 = CSSPropertyBackgroundPositionY; + } else if (propId == CSSPropertyWebkitMaskPosition) { + propId1 = CSSPropertyWebkitMaskPositionX; + propId2 = CSSPropertyWebkitMaskPositionY; + } else if (propId == CSSPropertyBackgroundRepeat) { + propId1 = CSSPropertyBackgroundRepeatX; + propId2 = CSSPropertyBackgroundRepeatY; + } else if (propId == CSSPropertyWebkitMaskRepeat) { + propId1 = CSSPropertyWebkitMaskRepeatX; + propId2 = CSSPropertyWebkitMaskRepeatY; + } + + while ((val = m_valueList->current())) { + RefPtr<CSSValue> currValue; + RefPtr<CSSValue> currValue2; + + if (allowComma) { + if (val->unit != CSSParserValue::Operator || val->iValue != ',') + return false; + m_valueList->next(); + allowComma = false; + } else { + allowComma = true; + switch (propId) { + case CSSPropertyBackgroundColor: + currValue = parseBackgroundColor(); + if (currValue) + m_valueList->next(); + break; + case CSSPropertyBackgroundAttachment: + case CSSPropertyWebkitMaskAttachment: + if (val->id == CSSValueScroll || val->id == CSSValueFixed || val->id == CSSValueLocal) { + currValue = cssValuePool()->createIdentifierValue(val->id); + m_valueList->next(); + } + break; + case CSSPropertyBackgroundImage: + case CSSPropertyWebkitMaskImage: + if (parseFillImage(m_valueList.get(), currValue)) + m_valueList->next(); + break; + case CSSPropertyWebkitBackgroundClip: + case CSSPropertyWebkitBackgroundOrigin: + case CSSPropertyWebkitMaskClip: + case CSSPropertyWebkitMaskOrigin: + // The first three values here are deprecated and do not apply to the version of the property that has + // the -webkit- prefix removed. + if (val->id == CSSValueBorder || val->id == CSSValuePadding || val->id == CSSValueContent || + val->id == CSSValueBorderBox || val->id == CSSValuePaddingBox || val->id == CSSValueContentBox || + ((propId == CSSPropertyWebkitBackgroundClip || propId == CSSPropertyWebkitMaskClip) && + (val->id == CSSValueText || val->id == CSSValueWebkitText))) { + currValue = cssValuePool()->createIdentifierValue(val->id); + m_valueList->next(); + } + break; + case CSSPropertyBackgroundClip: + if (parseBackgroundClip(val, currValue, cssValuePool())) + m_valueList->next(); + break; + case CSSPropertyBackgroundOrigin: + if (val->id == CSSValueBorderBox || val->id == CSSValuePaddingBox || val->id == CSSValueContentBox) { + currValue = cssValuePool()->createIdentifierValue(val->id); + m_valueList->next(); + } + break; + case CSSPropertyBackgroundPosition: + case CSSPropertyWebkitMaskPosition: + parseFillPosition(m_valueList.get(), currValue, currValue2); + // parseFillPosition advances the m_valueList pointer + break; + case CSSPropertyBackgroundPositionX: + case CSSPropertyWebkitMaskPositionX: { + currValue = parseFillPositionX(m_valueList.get()); + if (currValue) + m_valueList->next(); + break; + } + case CSSPropertyBackgroundPositionY: + case CSSPropertyWebkitMaskPositionY: { + currValue = parseFillPositionY(m_valueList.get()); + if (currValue) + m_valueList->next(); + break; + } + case CSSPropertyWebkitBackgroundComposite: + case CSSPropertyWebkitMaskComposite: + if ((val->id >= CSSValueClear && val->id <= CSSValuePlusLighter) || val->id == CSSValueHighlight) { + currValue = cssValuePool()->createIdentifierValue(val->id); + m_valueList->next(); + } + break; + case CSSPropertyBackgroundRepeat: + case CSSPropertyWebkitMaskRepeat: + parseFillRepeat(currValue, currValue2); + // parseFillRepeat advances the m_valueList pointer + break; + case CSSPropertyBackgroundSize: + case CSSPropertyWebkitBackgroundSize: + case CSSPropertyWebkitMaskSize: { + currValue = parseFillSize(propId, allowComma); + if (currValue) + m_valueList->next(); + break; + } + } + if (!currValue) + return false; + + if (value && !values) { + values = CSSValueList::createCommaSeparated(); + values->append(value.release()); + } + + if (value2 && !values2) { + values2 = CSSValueList::createCommaSeparated(); + values2->append(value2.release()); + } + + if (values) + values->append(currValue.release()); + else + value = currValue.release(); + if (currValue2) { + if (values2) + values2->append(currValue2.release()); + else + value2 = currValue2.release(); + } + } + + // When parsing any fill shorthand property, we let it handle building up the lists for all + // properties. + if (inShorthand()) + break; + } + + if (values && values->length()) { + retValue1 = values.release(); + if (values2 && values2->length()) + retValue2 = values2.release(); + return true; + } + if (value) { + retValue1 = value.release(); + retValue2 = value2.release(); + return true; + } + return false; +} + +PassRefPtr<CSSValue> CSSParser::parseAnimationDelay() +{ + CSSParserValue* value = m_valueList->current(); + if (validUnit(value, FTime, m_strict)) + return createPrimitiveNumericValue(value); + return 0; +} + +PassRefPtr<CSSValue> CSSParser::parseAnimationDirection() +{ + CSSParserValue* value = m_valueList->current(); + if (value->id == CSSValueNormal || value->id == CSSValueAlternate) + return cssValuePool()->createIdentifierValue(value->id); + return 0; +} + +PassRefPtr<CSSValue> CSSParser::parseAnimationDuration() +{ + CSSParserValue* value = m_valueList->current(); + if (validUnit(value, FTime | FNonNeg, m_strict)) + return createPrimitiveNumericValue(value); + return 0; +} + +PassRefPtr<CSSValue> CSSParser::parseAnimationFillMode() +{ + CSSParserValue* value = m_valueList->current(); + if (value->id == CSSValueNone || value->id == CSSValueForwards || value->id == CSSValueBackwards || value->id == CSSValueBoth) + return cssValuePool()->createIdentifierValue(value->id); + return 0; +} + +PassRefPtr<CSSValue> CSSParser::parseAnimationIterationCount() +{ + CSSParserValue* value = m_valueList->current(); + if (value->id == CSSValueInfinite) + return cssValuePool()->createIdentifierValue(value->id); + if (validUnit(value, FInteger | FNonNeg, m_strict)) + return createPrimitiveNumericValue(value); + return 0; +} + +PassRefPtr<CSSValue> CSSParser::parseAnimationName() +{ + CSSParserValue* value = m_valueList->current(); + if (value->unit == CSSPrimitiveValue::CSS_STRING || value->unit == CSSPrimitiveValue::CSS_IDENT) { + if (value->id == CSSValueNone || (value->unit == CSSPrimitiveValue::CSS_STRING && equalIgnoringCase(value->string, "none"))) { + return cssValuePool()->createIdentifierValue(CSSValueNone); + } else { + return createPrimitiveStringValue(value); + } + } + return 0; +} + +PassRefPtr<CSSValue> CSSParser::parseAnimationPlayState() +{ + CSSParserValue* value = m_valueList->current(); + if (value->id == CSSValueRunning || value->id == CSSValuePaused) + return cssValuePool()->createIdentifierValue(value->id); + return 0; +} + +PassRefPtr<CSSValue> CSSParser::parseAnimationProperty() +{ + CSSParserValue* value = m_valueList->current(); + if (value->unit != CSSPrimitiveValue::CSS_IDENT) + return 0; + int result = cssPropertyID(value->string); + if (result) + return cssValuePool()->createIdentifierValue(result); + if (equalIgnoringCase(value->string, "all")) + return cssValuePool()->createIdentifierValue(CSSValueAll); + if (equalIgnoringCase(value->string, "none")) + return cssValuePool()->createIdentifierValue(CSSValueNone); + return 0; +} + +bool CSSParser::parseTransformOriginShorthand(RefPtr<CSSValue>& value1, RefPtr<CSSValue>& value2, RefPtr<CSSValue>& value3) +{ + parseFillPosition(m_valueList.get(), value1, value2); + + // now get z + if (m_valueList->current()) { + if (validUnit(m_valueList->current(), FLength, m_strict)) { + value3 = createPrimitiveNumericValue(m_valueList->current()); + m_valueList->next(); + return true; + } + return false; + } + return true; +} + +bool CSSParser::parseCubicBezierTimingFunctionValue(CSSParserValueList*& args, double& result) +{ + CSSParserValue* v = args->current(); + if (!validUnit(v, FNumber, m_strict)) + return false; + result = v->fValue; + v = args->next(); + if (!v) + // The last number in the function has no comma after it, so we're done. + return true; + if (v->unit != CSSParserValue::Operator && v->iValue != ',') + return false; + v = args->next(); + return true; +} + +PassRefPtr<CSSValue> CSSParser::parseAnimationTimingFunction() +{ + CSSParserValue* value = m_valueList->current(); + if (value->id == CSSValueEase || value->id == CSSValueLinear || value->id == CSSValueEaseIn || value->id == CSSValueEaseOut + || value->id == CSSValueEaseInOut || value->id == CSSValueStepStart || value->id == CSSValueStepEnd) + return cssValuePool()->createIdentifierValue(value->id); + + // We must be a function. + if (value->unit != CSSParserValue::Function) + return 0; + + CSSParserValueList* args = value->function->args.get(); + + if (equalIgnoringCase(value->function->name, "steps(")) { + // For steps, 1 or 2 params must be specified (comma-separated) + if (!args || (args->size() != 1 && args->size() != 3)) + return 0; + + // There are two values. + int numSteps; + bool stepAtStart = false; + + CSSParserValue* v = args->current(); + if (!validUnit(v, FInteger, m_strict)) + return 0; + numSteps = clampToInteger(v->fValue); + if (numSteps < 1) + return 0; + v = args->next(); + + if (v) { + // There is a comma so we need to parse the second value + if (v->unit != CSSParserValue::Operator && v->iValue != ',') + return 0; + v = args->next(); + if (v->id != CSSValueStart && v->id != CSSValueEnd) + return 0; + stepAtStart = v->id == CSSValueStart; + } + + return CSSStepsTimingFunctionValue::create(numSteps, stepAtStart); + } + + if (equalIgnoringCase(value->function->name, "cubic-bezier(")) { + // For cubic bezier, 4 values must be specified. + if (!args || args->size() != 7) + return 0; + + // There are two points specified. The values must be between 0 and 1. + double x1, y1, x2, y2; + + if (!parseCubicBezierTimingFunctionValue(args, x1)) + return 0; + if (!parseCubicBezierTimingFunctionValue(args, y1)) + return 0; + if (!parseCubicBezierTimingFunctionValue(args, x2)) + return 0; + if (!parseCubicBezierTimingFunctionValue(args, y2)) + return 0; + + return CSSCubicBezierTimingFunctionValue::create(x1, y1, x2, y2); + } + + return 0; +} + +bool CSSParser::parseAnimationProperty(int propId, RefPtr<CSSValue>& result) +{ + RefPtr<CSSValueList> values; + CSSParserValue* val; + RefPtr<CSSValue> value; + bool allowComma = false; + + result = 0; + + while ((val = m_valueList->current())) { + RefPtr<CSSValue> currValue; + if (allowComma) { + if (val->unit != CSSParserValue::Operator || val->iValue != ',') + return false; + m_valueList->next(); + allowComma = false; + } + else { + switch (propId) { + case CSSPropertyWebkitAnimationDelay: + case CSSPropertyWebkitTransitionDelay: + currValue = parseAnimationDelay(); + if (currValue) + m_valueList->next(); + break; + case CSSPropertyWebkitAnimationDirection: + currValue = parseAnimationDirection(); + if (currValue) + m_valueList->next(); + break; + case CSSPropertyWebkitAnimationDuration: + case CSSPropertyWebkitTransitionDuration: + currValue = parseAnimationDuration(); + if (currValue) + m_valueList->next(); + break; + case CSSPropertyWebkitAnimationFillMode: + currValue = parseAnimationFillMode(); + if (currValue) + m_valueList->next(); + break; + case CSSPropertyWebkitAnimationIterationCount: + currValue = parseAnimationIterationCount(); + if (currValue) + m_valueList->next(); + break; + case CSSPropertyWebkitAnimationName: + currValue = parseAnimationName(); + if (currValue) + m_valueList->next(); + break; + case CSSPropertyWebkitAnimationPlayState: + currValue = parseAnimationPlayState(); + if (currValue) + m_valueList->next(); + break; + case CSSPropertyWebkitTransitionProperty: + currValue = parseAnimationProperty(); + if (currValue) + m_valueList->next(); + break; + case CSSPropertyWebkitAnimationTimingFunction: + case CSSPropertyWebkitTransitionTimingFunction: + currValue = parseAnimationTimingFunction(); + if (currValue) + m_valueList->next(); + break; + } + + if (!currValue) + return false; + + if (value && !values) { + values = CSSValueList::createCommaSeparated(); + values->append(value.release()); + } + + if (values) + values->append(currValue.release()); + else + value = currValue.release(); + + allowComma = true; + } + + // When parsing the 'transition' shorthand property, we let it handle building up the lists for all + // properties. + if (inShorthand()) + break; + } + + if (values && values->length()) { + result = values.release(); + return true; + } + if (value) { + result = value.release(); + return true; + } + return false; +} + +#if ENABLE(CSS_GRID_LAYOUT) +bool CSSParser::parseGridTrackList(int propId, bool important) +{ + CSSParserValue* value = m_valueList->current(); + if (value->id == CSSValueNone) { + if (m_valueList->next()) + return false; + + addProperty(propId, cssValuePool()->createIdentifierValue(value->id), important); + return true; + } + + RefPtr<CSSValueList> values = CSSValueList::createSpaceSeparated(); + while (value) { + bool valid = validUnit(value, FLength | FPercent, m_strict) || value->id == CSSValueAuto; + if (!valid) + return false; + + RefPtr<CSSPrimitiveValue> primitiveValue = value->id == CSSValueAuto ? cssValuePool()->createIdentifierValue(CSSValueAuto) : createPrimitiveNumericValue(value); + values->append(primitiveValue.release()); + value = m_valueList->next(); + } + addProperty(propId, values.release(), important); + return true; +} +#endif + + + +#if ENABLE(DASHBOARD_SUPPORT) + +#define DASHBOARD_REGION_NUM_PARAMETERS 6 +#define DASHBOARD_REGION_SHORT_NUM_PARAMETERS 2 + +static CSSParserValue* skipCommaInDashboardRegion(CSSParserValueList *args) +{ + if (args->size() == (DASHBOARD_REGION_NUM_PARAMETERS*2-1) || + args->size() == (DASHBOARD_REGION_SHORT_NUM_PARAMETERS*2-1)) { + CSSParserValue* current = args->current(); + if (current->unit == CSSParserValue::Operator && current->iValue == ',') + return args->next(); + } + return args->current(); +} + +bool CSSParser::parseDashboardRegions(int propId, bool important) +{ + bool valid = true; + + CSSParserValue* value = m_valueList->current(); + + if (value->id == CSSValueNone) { + if (m_valueList->next()) + return false; + addProperty(propId, cssValuePool()->createIdentifierValue(value->id), important); + return valid; + } + + RefPtr<DashboardRegion> firstRegion = DashboardRegion::create(); + DashboardRegion* region = 0; + + while (value) { + if (region == 0) { + region = firstRegion.get(); + } else { + RefPtr<DashboardRegion> nextRegion = DashboardRegion::create(); + region->m_next = nextRegion; + region = nextRegion.get(); + } + + if (value->unit != CSSParserValue::Function) { + valid = false; + break; + } + + // Commas count as values, so allow: + // dashboard-region(label, type, t, r, b, l) or dashboard-region(label type t r b l) + // dashboard-region(label, type, t, r, b, l) or dashboard-region(label type t r b l) + // also allow + // dashboard-region(label, type) or dashboard-region(label type) + // dashboard-region(label, type) or dashboard-region(label type) + CSSParserValueList* args = value->function->args.get(); + if (!equalIgnoringCase(value->function->name, "dashboard-region(") || !args) { + valid = false; + break; + } + + int numArgs = args->size(); + if ((numArgs != DASHBOARD_REGION_NUM_PARAMETERS && numArgs != (DASHBOARD_REGION_NUM_PARAMETERS*2-1)) && + (numArgs != DASHBOARD_REGION_SHORT_NUM_PARAMETERS && numArgs != (DASHBOARD_REGION_SHORT_NUM_PARAMETERS*2-1))) { + valid = false; + break; + } + + // First arg is a label. + CSSParserValue* arg = args->current(); + if (arg->unit != CSSPrimitiveValue::CSS_IDENT) { + valid = false; + break; + } + + region->m_label = arg->string; + + // Second arg is a type. + arg = args->next(); + arg = skipCommaInDashboardRegion(args); + if (arg->unit != CSSPrimitiveValue::CSS_IDENT) { + valid = false; + break; + } + + if (equalIgnoringCase(arg->string, "circle")) + region->m_isCircle = true; + else if (equalIgnoringCase(arg->string, "rectangle")) + region->m_isRectangle = true; + else { + valid = false; + break; + } + + region->m_geometryType = arg->string; + + if (numArgs == DASHBOARD_REGION_SHORT_NUM_PARAMETERS || numArgs == (DASHBOARD_REGION_SHORT_NUM_PARAMETERS*2-1)) { + // This originally used CSSValueInvalid by accident. It might be more logical to use something else. + RefPtr<CSSPrimitiveValue> amount = cssValuePool()->createIdentifierValue(CSSValueInvalid); + + region->setTop(amount); + region->setRight(amount); + region->setBottom(amount); + region->setLeft(amount); + } else { + // Next four arguments must be offset numbers + int i; + for (i = 0; i < 4; i++) { + arg = args->next(); + arg = skipCommaInDashboardRegion(args); + + valid = arg->id == CSSValueAuto || validUnit(arg, FLength, m_strict); + if (!valid) + break; + + RefPtr<CSSPrimitiveValue> amount = arg->id == CSSValueAuto ? + cssValuePool()->createIdentifierValue(CSSValueAuto) : + createPrimitiveNumericValue(arg); + + if (i == 0) + region->setTop(amount); + else if (i == 1) + region->setRight(amount); + else if (i == 2) + region->setBottom(amount); + else + region->setLeft(amount); + } + } + + if (args->next()) + return false; + + value = m_valueList->next(); + } + + if (valid) + addProperty(propId, cssValuePool()->createValue(firstRegion.release()), important); + + return valid; +} + +#endif /* ENABLE(DASHBOARD_SUPPORT) */ + +PassRefPtr<CSSValue> CSSParser::parseCounterContent(CSSParserValueList* args, bool counters) +{ + unsigned numArgs = args->size(); + if (counters && numArgs != 3 && numArgs != 5) + return 0; + if (!counters && numArgs != 1 && numArgs != 3) + return 0; + + CSSParserValue* i = args->current(); + if (i->unit != CSSPrimitiveValue::CSS_IDENT) + return 0; + RefPtr<CSSPrimitiveValue> identifier = createPrimitiveStringValue(i); + + RefPtr<CSSPrimitiveValue> separator; + if (!counters) + separator = cssValuePool()->createValue(String(), CSSPrimitiveValue::CSS_STRING); + else { + i = args->next(); + if (i->unit != CSSParserValue::Operator || i->iValue != ',') + return 0; + + i = args->next(); + if (i->unit != CSSPrimitiveValue::CSS_STRING) + return 0; + + separator = createPrimitiveStringValue(i); + } + + RefPtr<CSSPrimitiveValue> listStyle; + i = args->next(); + if (!i) // Make the list style default decimal + listStyle = cssValuePool()->createIdentifierValue(CSSValueDecimal); + else { + if (i->unit != CSSParserValue::Operator || i->iValue != ',') + return 0; + + i = args->next(); + if (i->unit != CSSPrimitiveValue::CSS_IDENT) + return 0; + + int listStyleID = 0; + if (i->id == CSSValueNone || (i->id >= CSSValueDisc && i->id <= CSSValueKatakanaIroha)) + listStyleID = i->id; + else + return 0; + + listStyle = cssValuePool()->createIdentifierValue(listStyleID); + } + + return cssValuePool()->createValue(Counter::create(identifier.release(), listStyle.release(), separator.release())); +} + +bool CSSParser::parseShape(int propId, bool important) +{ + CSSParserValue* value = m_valueList->current(); + CSSParserValueList* args = value->function->args.get(); + + if (!equalIgnoringCase(value->function->name, "rect(") || !args) + return false; + + // rect(t, r, b, l) || rect(t r b l) + if (args->size() != 4 && args->size() != 7) + return false; + RefPtr<Rect> rect = Rect::create(); + bool valid = true; + int i = 0; + CSSParserValue* a = args->current(); + while (a) { + valid = a->id == CSSValueAuto || validUnit(a, FLength, m_strict); + if (!valid) + break; + RefPtr<CSSPrimitiveValue> length = a->id == CSSValueAuto ? + cssValuePool()->createIdentifierValue(CSSValueAuto) : + createPrimitiveNumericValue(a); + if (i == 0) + rect->setTop(length); + else if (i == 1) + rect->setRight(length); + else if (i == 2) + rect->setBottom(length); + else + rect->setLeft(length); + a = args->next(); + if (a && args->size() == 7) { + if (a->unit == CSSParserValue::Operator && a->iValue == ',') { + a = args->next(); + } else { + valid = false; + break; + } + } + i++; + } + if (valid) { + addProperty(propId, cssValuePool()->createValue(rect.release()), important); + m_valueList->next(); + return true; + } + return false; +} + +PassRefPtr<CSSWrapShape> CSSParser::parseWrapShapeRect(CSSParserValueList* args) +{ + ASSERT(args); + + // rect(x, y, width, height, [[rx], ry]) + if (args->size() != 7 && args->size() != 9 && args->size() != 11) + return 0; + + RefPtr<CSSWrapShapeRect> shape = CSSWrapShapeRect::create(); + + unsigned argumentNumber = 0; + CSSParserValue* argument = args->current(); + while (argument) { + if (!validUnit(argument, FLength, m_strict)) + return 0; + + RefPtr<CSSPrimitiveValue> length = createPrimitiveNumericValue(argument); + ASSERT(argumentNumber < 6); + switch (argumentNumber) { + case 0: + shape->setLeft(length); + break; + case 1: + shape->setTop(length); + break; + case 2: + shape->setWidth(length); + break; + case 3: + shape->setHeight(length); + break; + case 4: + shape->setRadiusX(length); + break; + case 5: + shape->setRadiusY(length); + break; + } + argument = args->next(); + if (argument) { + if (argument->unit != CSSParserValue::Operator || argument->iValue != ',') + return 0; + + argument = args->next(); + } + argumentNumber++; + } + + if (argumentNumber < 4) + return 0; + return shape; +} + +PassRefPtr<CSSWrapShape> CSSParser::parseWrapShapeCircle(CSSParserValueList* args) +{ + ASSERT(args); + + // circle(x, y, r) + if (args->size() != 5) + return 0; + + RefPtr<CSSWrapShapeCircle> shape = CSSWrapShapeCircle::create(); + + unsigned argumentNumber = 0; + CSSParserValue* argument = args->current(); + while (argument) { + if (!validUnit(argument, FLength, m_strict)) + return 0; + + RefPtr<CSSPrimitiveValue> length = createPrimitiveNumericValue(argument); + ASSERT(argumentNumber < 3); + switch (argumentNumber) { + case 0: + shape->setLeft(length); + break; + case 1: + shape->setTop(length); + break; + case 2: + shape->setRadius(length); + break; + } + + argument = args->next(); + if (argument) { + if (argument->unit != CSSParserValue::Operator || argument->iValue != ',') + return 0; + argument = args->next(); + } + argumentNumber++; + } + + if (argumentNumber < 3) + return 0; + return shape; +} + +PassRefPtr<CSSWrapShape> CSSParser::parseWrapShapeEllipse(CSSParserValueList* args) +{ + ASSERT(args); + + // ellipse(x, y, rx, ry) + if (args->size() != 7) + return 0; + + RefPtr<CSSWrapShapeEllipse> shape = CSSWrapShapeEllipse::create(); + unsigned argumentNumber = 0; + CSSParserValue* argument = args->current(); + while (argument) { + if (!validUnit(argument, FLength, m_strict)) + return 0; + + RefPtr<CSSPrimitiveValue> length = createPrimitiveNumericValue(argument); + ASSERT(argumentNumber < 4); + switch (argumentNumber) { + case 0: + shape->setLeft(length); + break; + case 1: + shape->setTop(length); + break; + case 2: + shape->setRadiusX(length); + break; + case 3: + shape->setRadiusY(length); + break; + } + + argument = args->next(); + if (argument) { + if (argument->unit != CSSParserValue::Operator || argument->iValue != ',') + return 0; + argument = args->next(); + } + argumentNumber++; + } + + if (argumentNumber < 4) + return 0; + return shape; +} + +PassRefPtr<CSSWrapShape> CSSParser::parseWrapShapePolygon(CSSParserValueList* args) +{ + ASSERT(args); + + unsigned size = args->size(); + if (!size) + return 0; + + RefPtr<CSSWrapShapePolygon> shape = CSSWrapShapePolygon::create(); + + CSSParserValue* argument = args->current(); + if (argument->id == CSSValueEvenodd || argument->id == CSSValueNonzero) { + shape->setWindRule(argument->id == CSSValueEvenodd ? RULE_EVENODD : RULE_NONZERO); + + CSSParserValue* comma = args->next(); + if (!comma || comma->unit != CSSParserValue::Operator || comma->iValue != ',') + return 0; + + argument = args->next(); + size -= 2; + } + + // <length>, <length> ... <length>, <length> -> each pair has 3 elements + if (!size || (size % 3)) + return 0; + + CSSParserValue* argumentX = argument; + while (argumentX) { + if (!validUnit(argumentX, FLength, m_strict)) + return 0; + + CSSParserValue* comma = args->next(); + if (!comma || comma->unit != CSSParserValue::Operator || comma->iValue != ',') + return 0; + + CSSParserValue* argumentY = args->next(); + if (!argumentY || !validUnit(argumentY, FLength, m_strict)) + return 0; + + RefPtr<CSSPrimitiveValue> xLength = createPrimitiveNumericValue(argumentX); + RefPtr<CSSPrimitiveValue> yLength = createPrimitiveNumericValue(argumentY); + + shape->appendPoint(xLength.release(), yLength.release()); + + argumentX = args->next(); + } + + return shape; +} + +bool CSSParser::parseWrapShape(bool shapeInside, bool important) +{ + CSSParserValue* value = m_valueList->current(); + CSSParserValueList* args = value->function->args.get(); + + if (!args) + return false; + + RefPtr<CSSWrapShape> shape; + + if (equalIgnoringCase(value->function->name, "rect(")) + shape = parseWrapShapeRect(args); + else if (equalIgnoringCase(value->function->name, "circle(")) + shape = parseWrapShapeCircle(args); + else if (equalIgnoringCase(value->function->name, "ellipse(")) + shape = parseWrapShapeEllipse(args); + else if (equalIgnoringCase(value->function->name, "polygon(")) + shape = parseWrapShapePolygon(args); + + if (shape) { + addProperty(shapeInside ? CSSPropertyWebkitWrapShapeInside : CSSPropertyWebkitWrapShapeOutside, cssValuePool()->createValue(shape.release()), important); + m_valueList->next(); + return true; + } + + return false; +} + +// [ 'font-style' || 'font-variant' || 'font-weight' ]? 'font-size' [ / 'line-height' ]? 'font-family' +bool CSSParser::parseFont(bool important) +{ + bool valid = true; + bool styleImplicit = true; + bool variantImplicit = true; + bool weightImplicit = true; + bool lineHeightImplicit = true; + int valueOrdinal = 0; + + CSSParserValue *value = m_valueList->current(); + RefPtr<FontValue> font = FontValue::create(); + // Optional font-style, font-variant and font-weight. + while (value) { + int id = value->id; + if (id) { + if (id == CSSValueNormal) { + // It's the initial value for all three, so mark the corresponding longhand as explicit. + switch (valueOrdinal) { + case 0: + styleImplicit = false; + break; + case 1: + variantImplicit = false; + break; + case 2: + weightImplicit = false; + break; + } + } else if (id == CSSValueItalic || id == CSSValueOblique) { + if (font->style) + return false; + font->style = cssValuePool()->createIdentifierValue(id); + styleImplicit = false; + } else if (id == CSSValueSmallCaps) { + if (font->variant) + return false; + font->variant = cssValuePool()->createIdentifierValue(id); + variantImplicit = false; + } else if (id >= CSSValueBold && id <= CSSValueLighter) { + if (font->weight) + return false; + font->weight = cssValuePool()->createIdentifierValue(id); + weightImplicit = false; + } else + valid = false; + } else if (!font->weight && validUnit(value, FInteger | FNonNeg, true)) { + int weight = static_cast<int>(value->fValue); + int val = 0; + if (weight == 100) + val = CSSValue100; + else if (weight == 200) + val = CSSValue200; + else if (weight == 300) + val = CSSValue300; + else if (weight == 400) + val = CSSValue400; + else if (weight == 500) + val = CSSValue500; + else if (weight == 600) + val = CSSValue600; + else if (weight == 700) + val = CSSValue700; + else if (weight == 800) + val = CSSValue800; + else if (weight == 900) + val = CSSValue900; + + if (val) { + font->weight = cssValuePool()->createIdentifierValue(val); + weightImplicit = false; + } else + valid = false; + } else { + valid = false; + } + if (!valid) + break; + value = m_valueList->next(); + ++valueOrdinal; + } + if (!value) + return false; + + // Set undefined values to default. + if (!font->style) + font->style = cssValuePool()->createIdentifierValue(CSSValueNormal); + if (!font->variant) + font->variant = cssValuePool()->createIdentifierValue(CSSValueNormal); + if (!font->weight) + font->weight = cssValuePool()->createIdentifierValue(CSSValueNormal); + + // Now a font size _must_ come. + // <absolute-size> | <relative-size> | <length> | <percentage> | inherit + if (value->id >= CSSValueXxSmall && value->id <= CSSValueLarger) + font->size = cssValuePool()->createIdentifierValue(value->id); + else if (validUnit(value, FLength | FPercent | FNonNeg, m_strict)) + font->size = createPrimitiveNumericValue(value); + value = m_valueList->next(); + if (!font->size || !value) + return false; + + if (value->unit == CSSParserValue::Operator && value->iValue == '/') { + // The line-height property. + value = m_valueList->next(); + if (!value) + return false; + if (value->id == CSSValueNormal) { + // Default value, just mark the property as explicit. + lineHeightImplicit = false; + } else if (validUnit(value, FNumber | FLength | FPercent | FNonNeg, m_strict)) { + font->lineHeight = createPrimitiveNumericValue(value); + lineHeightImplicit = false; + } else + return false; + value = m_valueList->next(); + if (!value) + return false; + } + + if (!font->lineHeight) + font->lineHeight = cssValuePool()->createIdentifierValue(CSSValueNormal); + + // Font family must come now. + font->family = parseFontFamily(); + + if (m_valueList->current() || !font->family) + return false; + + ShorthandScope scope(this, CSSPropertyFont); + addProperty(CSSPropertyFontFamily, font->family, important); + addProperty(CSSPropertyFontSize, font->size, important); + addProperty(CSSPropertyFontStyle, font->style, important, styleImplicit); + addProperty(CSSPropertyFontVariant, font->variant, important, variantImplicit); + addProperty(CSSPropertyFontWeight, font->weight, important, weightImplicit); + addProperty(CSSPropertyLineHeight, font->lineHeight, important, lineHeightImplicit); + + // FIXME: http://www.w3.org/TR/2011/WD-css3-fonts-20110324/#font-prop requires that + // "font-stretch", "font-size-adjust", and "font-kerning" be reset to their initial values + // but we don't seem to support them at the moment. They should also be added here once implemented. + + return true; +} + +PassRefPtr<CSSValueList> CSSParser::parseFontFamily() +{ + RefPtr<CSSValueList> list = CSSValueList::createCommaSeparated(); + CSSParserValue* value = m_valueList->current(); + + FontFamilyValue* currFamily = 0; + while (value) { + CSSParserValue* nextValue = m_valueList->next(); + bool nextValBreaksFont = !nextValue || + (nextValue->unit == CSSParserValue::Operator && nextValue->iValue == ','); + bool nextValIsFontName = nextValue && + ((nextValue->id >= CSSValueSerif && nextValue->id <= CSSValueWebkitBody) || + (nextValue->unit == CSSPrimitiveValue::CSS_STRING || nextValue->unit == CSSPrimitiveValue::CSS_IDENT)); + + if (value->id >= CSSValueSerif && value->id <= CSSValueWebkitBody) { + if (currFamily) + currFamily->appendSpaceSeparated(value->string.characters, value->string.length); + else if (nextValBreaksFont || !nextValIsFontName) + list->append(cssValuePool()->createIdentifierValue(value->id)); + else { + RefPtr<FontFamilyValue> newFamily = FontFamilyValue::create(value->string); + currFamily = newFamily.get(); + list->append(newFamily.release()); + } + } else if (value->unit == CSSPrimitiveValue::CSS_STRING) { + // Strings never share in a family name. + currFamily = 0; + list->append(FontFamilyValue::create(value->string)); + } else if (value->unit == CSSPrimitiveValue::CSS_IDENT) { + if (currFamily) + currFamily->appendSpaceSeparated(value->string.characters, value->string.length); + else if (nextValBreaksFont || !nextValIsFontName) + list->append(FontFamilyValue::create(value->string)); + else { + RefPtr<FontFamilyValue> newFamily = FontFamilyValue::create(value->string); + currFamily = newFamily.get(); + list->append(newFamily.release()); + } + } else { + break; + } + + if (!nextValue) + break; + + if (nextValBreaksFont) { + value = m_valueList->next(); + currFamily = 0; + } + else if (nextValIsFontName) + value = nextValue; + else + break; + } + if (!list->length()) + list = 0; + return list.release(); +} + +bool CSSParser::parseFontStyle(bool important) +{ + RefPtr<CSSValueList> values; + if (m_valueList->size() > 1) + values = CSSValueList::createCommaSeparated(); + CSSParserValue* val; + bool expectComma = false; + while ((val = m_valueList->current())) { + RefPtr<CSSPrimitiveValue> parsedValue; + if (!expectComma) { + expectComma = true; + if (val->id == CSSValueNormal || val->id == CSSValueItalic || val->id == CSSValueOblique) + parsedValue = cssValuePool()->createIdentifierValue(val->id); + else if (val->id == CSSValueAll && !values) { + // 'all' is only allowed in @font-face and with no other values. Make a value list to + // indicate that we are in the @font-face case. + values = CSSValueList::createCommaSeparated(); + parsedValue = cssValuePool()->createIdentifierValue(val->id); + } + } else if (val->unit == CSSParserValue::Operator && val->iValue == ',') { + expectComma = false; + m_valueList->next(); + continue; + } + + if (!parsedValue) + return false; + + m_valueList->next(); + + if (values) + values->append(parsedValue.release()); + else { + addProperty(CSSPropertyFontStyle, parsedValue.release(), important); + return true; + } + } + + if (values && values->length()) { + m_hasFontFaceOnlyValues = true; + addProperty(CSSPropertyFontStyle, values.release(), important); + return true; + } + + return false; +} + +bool CSSParser::parseFontVariant(bool important) +{ + RefPtr<CSSValueList> values; + if (m_valueList->size() > 1) + values = CSSValueList::createCommaSeparated(); + CSSParserValue* val; + bool expectComma = false; + while ((val = m_valueList->current())) { + RefPtr<CSSPrimitiveValue> parsedValue; + if (!expectComma) { + expectComma = true; + if (val->id == CSSValueNormal || val->id == CSSValueSmallCaps) + parsedValue = cssValuePool()->createIdentifierValue(val->id); + else if (val->id == CSSValueAll && !values) { + // 'all' is only allowed in @font-face and with no other values. Make a value list to + // indicate that we are in the @font-face case. + values = CSSValueList::createCommaSeparated(); + parsedValue = cssValuePool()->createIdentifierValue(val->id); + } + } else if (val->unit == CSSParserValue::Operator && val->iValue == ',') { + expectComma = false; + m_valueList->next(); + continue; + } + + if (!parsedValue) + return false; + + m_valueList->next(); + + if (values) + values->append(parsedValue.release()); + else { + addProperty(CSSPropertyFontVariant, parsedValue.release(), important); + return true; + } + } + + if (values && values->length()) { + m_hasFontFaceOnlyValues = true; + addProperty(CSSPropertyFontVariant, values.release(), important); + return true; + } + + return false; +} + +bool CSSParser::parseFontWeight(bool important) +{ + RefPtr<CSSValueList> values; + if (m_valueList->size() > 1) + values = CSSValueList::createCommaSeparated(); + CSSParserValue* val; + bool expectComma = false; + while ((val = m_valueList->current())) { + RefPtr<CSSPrimitiveValue> parsedValue; + if (!expectComma) { + expectComma = true; + if (val->unit == CSSPrimitiveValue::CSS_IDENT) { + if (val->id >= CSSValueNormal && val->id <= CSSValue900) + parsedValue = cssValuePool()->createIdentifierValue(val->id); + else if (val->id == CSSValueAll && !values) { + // 'all' is only allowed in @font-face and with no other values. Make a value list to + // indicate that we are in the @font-face case. + values = CSSValueList::createCommaSeparated(); + parsedValue = cssValuePool()->createIdentifierValue(val->id); + } + } else if (validUnit(val, FInteger | FNonNeg, false)) { + int weight = static_cast<int>(val->fValue); + if (!(weight % 100) && weight >= 100 && weight <= 900) + parsedValue = cssValuePool()->createIdentifierValue(CSSValue100 + weight / 100 - 1); + } + } else if (val->unit == CSSParserValue::Operator && val->iValue == ',') { + expectComma = false; + m_valueList->next(); + continue; + } + + if (!parsedValue) + return false; + + m_valueList->next(); + + if (values) + values->append(parsedValue.release()); + else { + addProperty(CSSPropertyFontWeight, parsedValue.release(), important); + return true; + } + } + + if (values && values->length()) { + m_hasFontFaceOnlyValues = true; + addProperty(CSSPropertyFontWeight, values.release(), important); + return true; + } + + return false; +} + +bool CSSParser::parseFontFaceSrcURI(CSSValueList* valueList) +{ + // FIXME: The completeURL call should be done when using the CSSFontFaceSrcValue, + // not when creating it. + RefPtr<CSSFontFaceSrcValue> uriValue(CSSFontFaceSrcValue::create(m_styleSheet->completeURL(m_valueList->current()->string))); + + CSSParserValue* value = m_valueList->next(); + if (!value) { + valueList->append(uriValue.release()); + return true; + } + if (value->unit == CSSParserValue::Operator && value->iValue == ',') { + m_valueList->next(); + valueList->append(uriValue.release()); + return true; + } + + if (value->unit != CSSParserValue::Function || !equalIgnoringCase(value->function->name, "format(")) + return false; + + // FIXME: http://www.w3.org/TR/2011/WD-css3-fonts-20111004/ says that format() contains a comma-separated list of strings, + // but CSSFontFaceSrcValue stores only one format. Allowing one format for now. + CSSParserValueList* args = value->function->args.get(); + if (!args || args->size() != 1 || (args->current()->unit != CSSPrimitiveValue::CSS_STRING && args->current()->unit != CSSPrimitiveValue::CSS_IDENT)) + return false; + uriValue->setFormat(args->current()->string); + valueList->append(uriValue.release()); + value = m_valueList->next(); + if (value && value->unit == CSSParserValue::Operator && value->iValue == ',') + m_valueList->next(); + return true; +} + +bool CSSParser::parseFontFaceSrcLocal(CSSValueList* valueList) +{ + CSSParserValueList* args = m_valueList->current()->function->args.get(); + if (!args || !args->size()) + return false; + + if (args->size() == 1 && args->current()->unit == CSSPrimitiveValue::CSS_STRING) + valueList->append(CSSFontFaceSrcValue::createLocal(args->current()->string)); + else if (args->current()->unit == CSSPrimitiveValue::CSS_IDENT) { + StringBuilder builder; + for (CSSParserValue* localValue = args->current(); localValue; localValue = args->next()) { + if (localValue->unit != CSSPrimitiveValue::CSS_IDENT) + return false; + if (!builder.isEmpty()) + builder.append(' '); + builder.append(localValue->string); + } + valueList->append(CSSFontFaceSrcValue::createLocal(builder.toString())); + } else + return false; + + if (CSSParserValue* value = m_valueList->next()) { + if (value->unit == CSSParserValue::Operator && value->iValue == ',') + m_valueList->next(); + } + return true; +} + +bool CSSParser::parseFontFaceSrc() +{ + RefPtr<CSSValueList> values(CSSValueList::createCommaSeparated()); + + while (CSSParserValue* value = m_valueList->current()) { + if (value->unit == CSSPrimitiveValue::CSS_URI && m_styleSheet) { + if (!parseFontFaceSrcURI(values.get())) + return false; + } else if (value->unit == CSSParserValue::Function && equalIgnoringCase(value->function->name, "local(")) { + if (!parseFontFaceSrcLocal(values.get())) + return false; + } else + return false; + } + if (!values->length()) + return false; + + addProperty(CSSPropertySrc, values.release(), m_important); + m_valueList->next(); + return true; +} + +bool CSSParser::parseFontFaceUnicodeRange() +{ + RefPtr<CSSValueList> values = CSSValueList::createCommaSeparated(); + bool failed = false; + bool operatorExpected = false; + for (; m_valueList->current(); m_valueList->next(), operatorExpected = !operatorExpected) { + if (operatorExpected) { + if (m_valueList->current()->unit == CSSParserValue::Operator && m_valueList->current()->iValue == ',') + continue; + failed = true; + break; + } + if (m_valueList->current()->unit != CSSPrimitiveValue::CSS_UNICODE_RANGE) { + failed = true; + break; + } + + String rangeString = m_valueList->current()->string; + UChar32 from = 0; + UChar32 to = 0; + unsigned length = rangeString.length(); + + if (length < 3) { + failed = true; + break; + } + + unsigned i = 2; + while (i < length) { + UChar c = rangeString[i]; + if (c == '-' || c == '?') + break; + from *= 16; + if (c >= '0' && c <= '9') + from += c - '0'; + else if (c >= 'A' && c <= 'F') + from += 10 + c - 'A'; + else if (c >= 'a' && c <= 'f') + from += 10 + c - 'a'; + else { + failed = true; + break; + } + i++; + } + if (failed) + break; + + if (i == length) + to = from; + else if (rangeString[i] == '?') { + unsigned span = 1; + while (i < length && rangeString[i] == '?') { + span *= 16; + from *= 16; + i++; + } + if (i < length) + failed = true; + to = from + span - 1; + } else { + if (length < i + 2) { + failed = true; + break; + } + i++; + while (i < length) { + UChar c = rangeString[i]; + to *= 16; + if (c >= '0' && c <= '9') + to += c - '0'; + else if (c >= 'A' && c <= 'F') + to += 10 + c - 'A'; + else if (c >= 'a' && c <= 'f') + to += 10 + c - 'a'; + else { + failed = true; + break; + } + i++; + } + if (failed) + break; + } + if (from <= to) + values->append(CSSUnicodeRangeValue::create(from, to)); + } + if (failed || !values->length()) + return false; + addProperty(CSSPropertyUnicodeRange, values.release(), m_important); + return true; +} + +// Returns the number of characters which form a valid double +// and are terminated by the given terminator character +static int checkForValidDouble(const UChar* string, const UChar* end, const char terminator) +{ + int length = end - string; + if (length < 1) + return 0; + + bool decimalMarkSeen = false; + int processedLength = 0; + + for (int i = 0; i < length; ++i) { + if (string[i] == terminator) { + processedLength = i; + break; + } + if (!isASCIIDigit(string[i])) { + if (!decimalMarkSeen && string[i] == '.') + decimalMarkSeen = true; + else + return 0; + } + } + + if (decimalMarkSeen && processedLength == 1) + return 0; + + return processedLength; +} + +// Returns the number of characters consumed for parsing a valid double +// terminated by the given terminator character +static int parseDouble(const UChar* string, const UChar* end, const char terminator, double& value) +{ + int length = checkForValidDouble(string, end, terminator); + if (!length) + return 0; + + int position = 0; + double localValue = 0; + + // The consumed characters here are guaranteed to be + // ASCII digits with or without a decimal mark + for (; position < length; ++position) { + if (string[position] == '.') + break; + localValue = localValue * 10 + string[position] - '0'; + } + + if (++position == length) { + value = localValue; + return length; + } + + double fraction = 0; + double scale = 1; + + while (position < length && scale < MAX_SCALE) { + fraction = fraction * 10 + string[position++] - '0'; + scale *= 10; + } + + value = localValue + fraction / scale; + return length; +} + +static bool parseColorIntOrPercentage(const UChar*& string, const UChar* end, const char terminator, CSSPrimitiveValue::UnitTypes& expect, int& value) +{ + const UChar* current = string; + double localValue = 0; + bool negative = false; + while (current != end && isHTMLSpace(*current)) + current++; + if (current != end && *current == '-') { + negative = true; + current++; + } + if (current == end || !isASCIIDigit(*current)) + return false; + while (current != end && isASCIIDigit(*current)) { + double newValue = localValue * 10 + *current++ - '0'; + if (newValue >= 255) { + // Clamp values at 255. + localValue = 255; + while (current != end && isASCIIDigit(*current)) + ++current; + break; + } + localValue = newValue; + } + + if (current == end) + return false; + + if (expect == CSSPrimitiveValue::CSS_NUMBER && (*current == '.' || *current == '%')) + return false; + + if (*current == '.') { + // We already parsed the integral part, try to parse + // the fraction part of the percentage value. + double percentage = 0; + int numCharactersParsed = parseDouble(current, end, '%', percentage); + if (!numCharactersParsed) + return false; + current += numCharactersParsed; + if (*current != '%') + return false; + localValue += percentage; + } + + if (expect == CSSPrimitiveValue::CSS_PERCENTAGE && *current != '%') + return false; + + if (*current == '%') { + expect = CSSPrimitiveValue::CSS_PERCENTAGE; + localValue = localValue / 100.0 * 256.0; + // Clamp values at 255 for percentages over 100% + if (localValue > 255) + localValue = 255; + current++; + } else + expect = CSSPrimitiveValue::CSS_NUMBER; + + while (current != end && isHTMLSpace(*current)) + current++; + if (current == end || *current++ != terminator) + return false; + // Clamp negative values at zero. + value = negative ? 0 : static_cast<int>(localValue); + string = current; + return true; +} + +static inline bool isTenthAlpha(const UChar* string, const int length) +{ + // "0.X" + if (length == 3 && string[0] == '0' && string[1] == '.' && isASCIIDigit(string[2])) + return true; + + // ".X" + if (length == 2 && string[0] == '.' && isASCIIDigit(string[1])) + return true; + + return false; +} + +static inline bool parseAlphaValue(const UChar*& string, const UChar* end, const char terminator, int& value) +{ + while (string != end && isHTMLSpace(*string)) + string++; + + bool negative = false; + + if (string != end && *string == '-') { + negative = true; + string++; + } + + value = 0; + + int length = end - string; + if (length < 2) + return false; + + if (string[length - 1] != terminator) + return false; + + if (string[0] != '0' && string[0] != '1' && string[0] != '.') { + if (checkForValidDouble(string, end, terminator)) { + value = negative ? 0 : 255; + string = end; + return true; + } + return false; + } + + if (length == 2 && string[0] != '.') { + value = !negative && string[0] == '1' ? 255 : 0; + string = end; + return true; + } + + if (isTenthAlpha(string, length - 1)) { + static const int tenthAlphaValues[] = { 0, 25, 51, 76, 102, 127, 153, 179, 204, 230 }; + value = negative ? 0 : tenthAlphaValues[string[length - 2] - '0']; + string = end; + return true; + } + + double alpha = 0; + if (!parseDouble(string, end, terminator, alpha)) + return false; + value = negative ? 0 : static_cast<int>(alpha * nextafter(256.0, 0.0)); + string = end; + return true; +} + +static inline bool mightBeRGBA(const UChar* characters, unsigned length) +{ + if (length < 5) + return false; + return characters[4] == '(' + && (characters[0] | 0x20) == 'r' + && (characters[1] | 0x20) == 'g' + && (characters[2] | 0x20) == 'b' + && (characters[3] | 0x20) == 'a'; +} + +static inline bool mightBeRGB(const UChar* characters, unsigned length) +{ + if (length < 4) + return false; + return characters[3] == '(' + && (characters[0] | 0x20) == 'r' + && (characters[1] | 0x20) == 'g' + && (characters[2] | 0x20) == 'b'; +} + +bool CSSParser::fastParseColor(RGBA32& rgb, const String& name, bool strict) +{ + const UChar* characters = name.characters(); + unsigned length = name.length(); + CSSPrimitiveValue::UnitTypes expect = CSSPrimitiveValue::CSS_UNKNOWN; + + if (!strict && length >= 3) { + if (name[0] == '#') { + if (Color::parseHexColor(characters + 1, length - 1, rgb)) + return true; + } else { + if (Color::parseHexColor(characters, length, rgb)) + return true; + } + } + + // Try rgba() syntax. + if (mightBeRGBA(characters, length)) { + const UChar* current = characters + 5; + const UChar* end = characters + length; + int red; + int green; + int blue; + int alpha; + + if (!parseColorIntOrPercentage(current, end, ',', expect, red)) + return false; + if (!parseColorIntOrPercentage(current, end, ',', expect, green)) + return false; + if (!parseColorIntOrPercentage(current, end, ',', expect, blue)) + return false; + if (!parseAlphaValue(current, end, ')', alpha)) + return false; + if (current != end) + return false; + rgb = makeRGBA(red, green, blue, alpha); + return true; + } + + // Try rgb() syntax. + if (mightBeRGB(characters, length)) { + const UChar* current = characters + 4; + const UChar* end = characters + length; + int red; + int green; + int blue; + if (!parseColorIntOrPercentage(current, end, ',', expect, red)) + return false; + if (!parseColorIntOrPercentage(current, end, ',', expect, green)) + return false; + if (!parseColorIntOrPercentage(current, end, ')', expect, blue)) + return false; + if (current != end) + return false; + rgb = makeRGB(red, green, blue); + return true; + } + + // Try named colors. + Color tc; + tc.setNamedColor(name); + if (tc.isValid()) { + rgb = tc.rgb(); + return true; + } + return false; +} + +static inline int colorIntFromValue(CSSParserValue* v) +{ + if (v->fValue <= 0.0) + return 0; + + if (v->unit == CSSPrimitiveValue::CSS_PERCENTAGE) { + if (v->fValue >= 100.0) + return 255; + return static_cast<int>(v->fValue * 256.0 / 100.0); + } + + if (v->fValue >= 255.0) + return 255; + + return static_cast<int>(v->fValue); +} + +bool CSSParser::parseColorParameters(CSSParserValue* value, int* colorArray, bool parseAlpha) +{ + CSSParserValueList* args = value->function->args.get(); + CSSParserValue* v = args->current(); + Units unitType = FUnknown; + // Get the first value and its type + if (validUnit(v, FInteger, true)) + unitType = FInteger; + else if (validUnit(v, FPercent, true)) + unitType = FPercent; + else + return false; + colorArray[0] = colorIntFromValue(v); + for (int i = 1; i < 3; i++) { + v = args->next(); + if (v->unit != CSSParserValue::Operator && v->iValue != ',') + return false; + v = args->next(); + if (!validUnit(v, unitType, true)) + return false; + colorArray[i] = colorIntFromValue(v); + } + if (parseAlpha) { + v = args->next(); + if (v->unit != CSSParserValue::Operator && v->iValue != ',') + return false; + v = args->next(); + if (!validUnit(v, FNumber, true)) + return false; + // Convert the floating pointer number of alpha to an integer in the range [0, 256), + // with an equal distribution across all 256 values. + colorArray[3] = static_cast<int>(max(0.0, min(1.0, v->fValue)) * nextafter(256.0, 0.0)); + } + return true; +} + +// The CSS3 specification defines the format of a HSL color as +// hsl(<number>, <percent>, <percent>) +// and with alpha, the format is +// hsla(<number>, <percent>, <percent>, <number>) +// The first value, HUE, is in an angle with a value between 0 and 360 +bool CSSParser::parseHSLParameters(CSSParserValue* value, double* colorArray, bool parseAlpha) +{ + CSSParserValueList* args = value->function->args.get(); + CSSParserValue* v = args->current(); + // Get the first value + if (!validUnit(v, FNumber, true)) + return false; + // normalize the Hue value and change it to be between 0 and 1.0 + colorArray[0] = (((static_cast<int>(v->fValue) % 360) + 360) % 360) / 360.0; + for (int i = 1; i < 3; i++) { + v = args->next(); + if (v->unit != CSSParserValue::Operator && v->iValue != ',') + return false; + v = args->next(); + if (!validUnit(v, FPercent, true)) + return false; + colorArray[i] = max(0.0, min(100.0, v->fValue)) / 100.0; // needs to be value between 0 and 1.0 + } + if (parseAlpha) { + v = args->next(); + if (v->unit != CSSParserValue::Operator && v->iValue != ',') + return false; + v = args->next(); + if (!validUnit(v, FNumber, true)) + return false; + colorArray[3] = max(0.0, min(1.0, v->fValue)); + } + return true; +} + +PassRefPtr<CSSPrimitiveValue> CSSParser::parseColor(CSSParserValue* value) +{ + RGBA32 c = Color::transparent; + if (!parseColorFromValue(value ? value : m_valueList->current(), c)) + return 0; + return cssValuePool()->createColorValue(c); +} + +bool CSSParser::parseColorFromValue(CSSParserValue* value, RGBA32& c) +{ + if (!m_strict && value->unit == CSSPrimitiveValue::CSS_NUMBER && + value->fValue >= 0. && value->fValue < 1000000.) { + String str = String::format("%06d", static_cast<int>((value->fValue+.5))); + if (!fastParseColor(c, str, m_strict)) + return false; + } else if (value->unit == CSSPrimitiveValue::CSS_PARSER_HEXCOLOR || + value->unit == CSSPrimitiveValue::CSS_IDENT || + (!m_strict && value->unit == CSSPrimitiveValue::CSS_DIMENSION)) { + if (!fastParseColor(c, value->string, m_strict && value->unit == CSSPrimitiveValue::CSS_IDENT)) + return false; + } else if (value->unit == CSSParserValue::Function && + value->function->args != 0 && + value->function->args->size() == 5 /* rgb + two commas */ && + equalIgnoringCase(value->function->name, "rgb(")) { + int colorValues[3]; + if (!parseColorParameters(value, colorValues, false)) + return false; + c = makeRGB(colorValues[0], colorValues[1], colorValues[2]); + } else { + if (value->unit == CSSParserValue::Function && + value->function->args != 0 && + value->function->args->size() == 7 /* rgba + three commas */ && + equalIgnoringCase(value->function->name, "rgba(")) { + int colorValues[4]; + if (!parseColorParameters(value, colorValues, true)) + return false; + c = makeRGBA(colorValues[0], colorValues[1], colorValues[2], colorValues[3]); + } else if (value->unit == CSSParserValue::Function && + value->function->args != 0 && + value->function->args->size() == 5 /* hsl + two commas */ && + equalIgnoringCase(value->function->name, "hsl(")) { + double colorValues[3]; + if (!parseHSLParameters(value, colorValues, false)) + return false; + c = makeRGBAFromHSLA(colorValues[0], colorValues[1], colorValues[2], 1.0); + } else if (value->unit == CSSParserValue::Function && + value->function->args != 0 && + value->function->args->size() == 7 /* hsla + three commas */ && + equalIgnoringCase(value->function->name, "hsla(")) { + double colorValues[4]; + if (!parseHSLParameters(value, colorValues, true)) + return false; + c = makeRGBAFromHSLA(colorValues[0], colorValues[1], colorValues[2], colorValues[3]); + } else + return false; + } + + return true; +} + +// This class tracks parsing state for shadow values. If it goes out of scope (e.g., due to an early return) +// without the allowBreak bit being set, then it will clean up all of the objects and destroy them. +struct ShadowParseContext { + ShadowParseContext(CSSPropertyID prop, CSSValuePool* cssValuePool) + : property(prop) + , m_cssValuePool(cssValuePool) + , allowX(true) + , allowY(false) + , allowBlur(false) + , allowSpread(false) + , allowColor(true) + , allowStyle(prop == CSSPropertyWebkitBoxShadow || prop == CSSPropertyBoxShadow) + , allowBreak(true) + { + } + + bool allowLength() { return allowX || allowY || allowBlur || allowSpread; } + + void commitValue() + { + // Handle the ,, case gracefully by doing nothing. + if (x || y || blur || spread || color || style) { + if (!values) + values = CSSValueList::createCommaSeparated(); + + // Construct the current shadow value and add it to the list. + values->append(ShadowValue::create(x.release(), y.release(), blur.release(), spread.release(), style.release(), color.release())); + } + + // Now reset for the next shadow value. + x = 0; + y = 0; + blur = 0; + spread = 0; + style = 0; + color = 0; + + allowX = true; + allowColor = true; + allowBreak = true; + allowY = false; + allowBlur = false; + allowSpread = false; + allowStyle = property == CSSPropertyWebkitBoxShadow || property == CSSPropertyBoxShadow; + } + + void commitLength(CSSParserValue* v) + { + RefPtr<CSSPrimitiveValue> val = m_cssValuePool->createValue(v->fValue, (CSSPrimitiveValue::UnitTypes)v->unit); + + if (allowX) { + x = val.release(); + allowX = false; + allowY = true; + allowColor = false; + allowStyle = false; + allowBreak = false; + } else if (allowY) { + y = val.release(); + allowY = false; + allowBlur = true; + allowColor = true; + allowStyle = property == CSSPropertyWebkitBoxShadow || property == CSSPropertyBoxShadow; + allowBreak = true; + } else if (allowBlur) { + blur = val.release(); + allowBlur = false; + allowSpread = property == CSSPropertyWebkitBoxShadow || property == CSSPropertyBoxShadow; + } else if (allowSpread) { + spread = val.release(); + allowSpread = false; + } + } + + void commitColor(PassRefPtr<CSSPrimitiveValue> val) + { + color = val; + allowColor = false; + if (allowX) { + allowStyle = false; + allowBreak = false; + } else { + allowBlur = false; + allowSpread = false; + allowStyle = property == CSSPropertyWebkitBoxShadow || property == CSSPropertyBoxShadow; + } + } + + void commitStyle(CSSParserValue* v) + { + style = m_cssValuePool->createIdentifierValue(v->id); + allowStyle = false; + if (allowX) + allowBreak = false; + else { + allowBlur = false; + allowSpread = false; + allowColor = false; + } + } + + CSSPropertyID property; + CSSValuePool* m_cssValuePool; + + RefPtr<CSSValueList> values; + RefPtr<CSSPrimitiveValue> x; + RefPtr<CSSPrimitiveValue> y; + RefPtr<CSSPrimitiveValue> blur; + RefPtr<CSSPrimitiveValue> spread; + RefPtr<CSSPrimitiveValue> style; + RefPtr<CSSPrimitiveValue> color; + + bool allowX; + bool allowY; + bool allowBlur; + bool allowSpread; + bool allowColor; + bool allowStyle; // inset or not. + bool allowBreak; +}; + +PassRefPtr<CSSValueList> CSSParser::parseShadow(CSSParserValueList* valueList, int propId) +{ + ShadowParseContext context(static_cast<CSSPropertyID>(propId), cssValuePool()); + CSSParserValue* val; + while ((val = valueList->current())) { + // Check for a comma break first. + if (val->unit == CSSParserValue::Operator) { + if (val->iValue != ',' || !context.allowBreak) + // Other operators aren't legal or we aren't done with the current shadow + // value. Treat as invalid. + return 0; +#if ENABLE(SVG) + // -webkit-svg-shadow does not support multiple values. + if (static_cast<CSSPropertyID>(propId) == CSSPropertyWebkitSvgShadow) + return 0; +#endif + // The value is good. Commit it. + context.commitValue(); + } else if (validUnit(val, FLength, true)) { + // We required a length and didn't get one. Invalid. + if (!context.allowLength()) + return 0; + + // A length is allowed here. Construct the value and add it. + context.commitLength(val); + } else if (val->id == CSSValueInset) { + if (!context.allowStyle) + return 0; + + context.commitStyle(val); + } else { + // The only other type of value that's ok is a color value. + RefPtr<CSSPrimitiveValue> parsedColor; + bool isColor = ((val->id >= CSSValueAqua && val->id <= CSSValueWindowtext) || val->id == CSSValueMenu + || (val->id >= CSSValueWebkitFocusRingColor && val->id <= CSSValueWebkitText && !m_strict) + || val->id == CSSValueCurrentcolor); + if (isColor) { + if (!context.allowColor) + return 0; + parsedColor = cssValuePool()->createIdentifierValue(val->id); + } + + if (!parsedColor) + // It's not built-in. Try to parse it as a color. + parsedColor = parseColor(val); + + if (!parsedColor || !context.allowColor) + return 0; // This value is not a color or length and is invalid or + // it is a color, but a color isn't allowed at this point. + + context.commitColor(parsedColor.release()); + } + + valueList->next(); + } + + if (context.allowBreak) { + context.commitValue(); + if (context.values && context.values->length()) + return context.values.release(); + } + + return 0; +} + +bool CSSParser::parseReflect(int propId, bool important) +{ + // box-reflect: <direction> <offset> <mask> + + // Direction comes first. + CSSParserValue* val = m_valueList->current(); + CSSReflectionDirection direction; + switch (val->id) { + case CSSValueAbove: + direction = ReflectionAbove; + break; + case CSSValueBelow: + direction = ReflectionBelow; + break; + case CSSValueLeft: + direction = ReflectionLeft; + break; + case CSSValueRight: + direction = ReflectionRight; + break; + default: + return false; + } + + // The offset comes next. + val = m_valueList->next(); + RefPtr<CSSPrimitiveValue> offset; + if (!val) + offset = cssValuePool()->createValue(0, CSSPrimitiveValue::CSS_PX); + else { + if (!validUnit(val, FLength | FPercent, m_strict)) + return false; + offset = createPrimitiveNumericValue(val); + } + + // Now for the mask. + RefPtr<CSSValue> mask; + val = m_valueList->next(); + if (val) { + if (!parseBorderImage(propId, mask)) + return false; + } + + RefPtr<CSSReflectValue> reflectValue = CSSReflectValue::create(direction, offset.release(), mask.release()); + addProperty(propId, reflectValue.release(), important); + m_valueList->next(); + return true; +} + +bool CSSParser::parseFlex(int propId, bool important) +{ + CSSParserValue* value = m_valueList->current(); + CSSParserValueList* args = value->function->args.get(); + if (!equalIgnoringCase(value->function->name, "-webkit-flex(") || !args || !args->size() || args->size() > 3 || m_valueList->next()) + return false; + + static const double unsetValue = -1; + double positiveFlex = unsetValue; + double negativeFlex = unsetValue; + RefPtr<CSSPrimitiveValue> preferredSize; + + while (CSSParserValue* arg = args->current()) { + if (validUnit(arg, FNumber | FNonNeg, m_strict)) { + if (positiveFlex == unsetValue) + positiveFlex = arg->fValue; + else if (negativeFlex == unsetValue) + negativeFlex = arg->fValue; + else if (!arg->fValue) { + // flex() only allows a preferred size of 0 (sans units) if the positive and negative flex values have already been set. + preferredSize = cssValuePool()->createValue(0, CSSPrimitiveValue::CSS_PX); + } else { + // We only allow 3 numbers without units if the last value is 0. E.g., flex(1 1 1) is invalid. + return false; + } + } else if (!preferredSize && (arg->id == CSSValueAuto || validUnit(arg, FLength | FPercent | FNonNeg, m_strict))) + preferredSize = parseValidPrimitive(arg->id, arg); + else { + // Not a valid arg for flex(). + return false; + } + args->next(); + } + + if (positiveFlex == unsetValue) + positiveFlex = 1; + if (negativeFlex == unsetValue) + negativeFlex = 0; + if (!preferredSize) + preferredSize = cssValuePool()->createValue(0, CSSPrimitiveValue::CSS_PX); + + RefPtr<CSSFlexValue> flex = CSSFlexValue::create(clampToFloat(positiveFlex), clampToFloat(negativeFlex), preferredSize); + addProperty(propId, flex.release(), important); + return true; +} + +struct BorderImageParseContext { + BorderImageParseContext(CSSValuePool* cssValuePool) + : m_cssValuePool(cssValuePool) + , m_canAdvance(false) + , m_allowCommit(true) + , m_allowImage(true) + , m_allowImageSlice(true) + , m_allowRepeat(true) + , m_allowSlash(false) + , m_requireWidth(false) + , m_requireOutset(false) + {} + + bool canAdvance() const { return m_canAdvance; } + void setCanAdvance(bool canAdvance) { m_canAdvance = canAdvance; } + + bool allowCommit() const { return m_allowCommit; } + bool allowImage() const { return m_allowImage; } + bool allowImageSlice() const { return m_allowImageSlice; } + bool allowRepeat() const { return m_allowRepeat; } + bool allowSlash() const { return m_allowSlash; } + + bool requireWidth() const { return m_requireWidth; } + bool requireOutset() const { return m_requireOutset; } + + void commitImage(PassRefPtr<CSSValue> image) + { + m_image = image; + m_canAdvance = true; + m_allowCommit = true; + m_allowImage = m_allowSlash = m_requireWidth = m_requireOutset = false; + m_allowImageSlice = !m_imageSlice; + m_allowRepeat = !m_repeat; + } + void commitImageSlice(PassRefPtr<CSSBorderImageSliceValue> slice) + { + m_imageSlice = slice; + m_canAdvance = true; + m_allowCommit = m_allowSlash = true; + m_allowImageSlice = m_requireWidth = m_requireOutset = false; + m_allowImage = !m_image; + m_allowRepeat = !m_repeat; + } + void commitSlash() + { + m_canAdvance = true; + m_allowCommit = m_allowImage = m_allowImageSlice = m_allowRepeat = m_allowSlash = false; + if (!m_borderSlice) { + m_requireWidth = true; + m_requireOutset = false; + } else { + m_requireOutset = true; + m_requireWidth = false; + } + } + void commitBorderWidth(PassRefPtr<CSSPrimitiveValue> slice) + { + m_borderSlice = slice; + m_canAdvance = true; + m_allowCommit = m_allowSlash = true; + m_allowImageSlice = m_requireWidth = m_requireOutset = false; + m_allowImage = !m_image; + m_allowRepeat = !m_repeat; + } + void commitBorderOutset(PassRefPtr<CSSPrimitiveValue> outset) + { + m_outset = outset; + m_canAdvance = true; + m_allowCommit = true; + m_allowImageSlice = m_allowSlash = m_requireWidth = m_requireOutset = false; + m_allowImage = !m_image; + m_allowRepeat = !m_repeat; + } + void commitRepeat(PassRefPtr<CSSValue> repeat) + { + m_repeat = repeat; + m_canAdvance = true; + m_allowCommit = true; + m_allowRepeat = m_allowSlash = m_requireWidth = m_requireOutset = false; + m_allowImageSlice = !m_imageSlice; + m_allowImage = !m_image; + } + + PassRefPtr<CSSValue> commitBorderImage() + { + // Make our new border image value now. + return CSSBorderImageValue::create(m_image, m_imageSlice, m_borderSlice, m_outset, m_repeat); + } + + CSSValuePool* m_cssValuePool; + + bool m_canAdvance; + + bool m_allowCommit; + bool m_allowImage; + bool m_allowImageSlice; + bool m_allowRepeat; + bool m_allowSlash; + + bool m_requireWidth; + bool m_requireOutset; + + RefPtr<CSSValue> m_image; + RefPtr<CSSBorderImageSliceValue> m_imageSlice; + RefPtr<CSSPrimitiveValue> m_borderSlice; + RefPtr<CSSPrimitiveValue> m_outset; + + RefPtr<CSSValue> m_repeat; +}; + +bool CSSParser::parseBorderImage(int propId, RefPtr<CSSValue>& result) +{ + ShorthandScope scope(this, propId); + BorderImageParseContext context(cssValuePool()); + while (CSSParserValue* val = m_valueList->current()) { + context.setCanAdvance(false); + + if (!context.canAdvance() && context.allowSlash() && val->unit == CSSParserValue::Operator && val->iValue == '/') + context.commitSlash(); + + if (!context.canAdvance() && context.allowImage()) { + if (val->unit == CSSPrimitiveValue::CSS_URI && m_styleSheet) { + // FIXME: The completeURL call should be done when using the CSSImageValue, + // not when creating it. + context.commitImage(CSSImageValue::create(m_styleSheet->completeURL(val->string))); + } else if (isGeneratedImageValue(val)) { + RefPtr<CSSValue> value; + if (parseGeneratedImage(m_valueList.get(), value)) + context.commitImage(value); + else + return false; + } else if (val->id == CSSValueNone) + context.commitImage(CSSImageValue::create()); + } + + if (!context.canAdvance() && context.allowImageSlice()) { + RefPtr<CSSBorderImageSliceValue> imageSlice; + if (parseBorderImageSlice(propId, imageSlice)) + context.commitImageSlice(imageSlice.release()); + } + + if (!context.canAdvance() && context.allowRepeat()) { + RefPtr<CSSValue> repeat; + if (parseBorderImageRepeat(repeat)) + context.commitRepeat(repeat); + } + + if (!context.canAdvance() && context.requireWidth()) { + RefPtr<CSSPrimitiveValue> borderSlice; + if (parseBorderImageWidth(borderSlice)) + context.commitBorderWidth(borderSlice.release()); + } + + if (!context.canAdvance() && context.requireOutset()) { + RefPtr<CSSPrimitiveValue> borderOutset; + if (parseBorderImageOutset(borderOutset)) + context.commitBorderOutset(borderOutset.release()); + } + + if (!context.canAdvance()) + return false; + + m_valueList->next(); + } + + if (context.allowCommit()) { + // Need to fully commit as a single value. + result = context.commitBorderImage(); + return true; + } + + return false; +} + +static bool isBorderImageRepeatKeyword(int id) +{ + return id == CSSValueStretch || id == CSSValueRepeat || id == CSSValueSpace || id == CSSValueRound; +} + +bool CSSParser::parseBorderImageRepeat(RefPtr<CSSValue>& result) +{ + RefPtr<CSSPrimitiveValue> firstValue; + RefPtr<CSSPrimitiveValue> secondValue; + CSSParserValue* val = m_valueList->current(); + if (isBorderImageRepeatKeyword(val->id)) + firstValue = cssValuePool()->createIdentifierValue(val->id); + else + return false; + + val = m_valueList->next(); + if (val) { + if (isBorderImageRepeatKeyword(val->id)) + secondValue = cssValuePool()->createIdentifierValue(val->id); + else if (!inShorthand()) { + // If we're not parsing a shorthand then we are invalid. + return false; + } else { + // We need to rewind the value list, so that when its advanced we'll + // end up back at this value. + m_valueList->previous(); + } + } else + secondValue = firstValue; + + result = cssValuePool()->createValue(Pair::create(firstValue, secondValue)); + return true; +} + +class BorderImageSliceParseContext { +public: + BorderImageSliceParseContext(CSSValuePool* cssValuePool) + : m_cssValuePool(cssValuePool) + , m_allowNumber(true) + , m_allowFill(false) + , m_allowFinalCommit(false) + , m_fill(false) + { } + + bool allowNumber() const { return m_allowNumber; } + bool allowFill() const { return m_allowFill; } + bool allowFinalCommit() const { return m_allowFinalCommit; } + CSSPrimitiveValue* top() const { return m_top.get(); } + + void commitNumber(CSSParserValue* v) + { + RefPtr<CSSPrimitiveValue> val = m_cssValuePool->createValue(v->fValue, (CSSPrimitiveValue::UnitTypes)v->unit); + if (!m_top) + m_top = val; + else if (!m_right) + m_right = val; + else if (!m_bottom) + m_bottom = val; + else { + ASSERT(!m_left); + m_left = val; + } + + m_allowNumber = !m_left; + m_allowFill = true; + m_allowFinalCommit = true; + } + + void commitFill() { m_fill = true; m_allowFill = false; m_allowNumber = false; } + + void setAllowFinalCommit() { m_allowFinalCommit = true; } + void setTop(PassRefPtr<CSSPrimitiveValue> val) { m_top = val; } + + PassRefPtr<CSSBorderImageSliceValue> commitBorderImageSlice() + { + // We need to clone and repeat values for any omissions. + ASSERT(m_top); + if (!m_right) { + m_right = m_cssValuePool->createValue(m_top->getDoubleValue(), (CSSPrimitiveValue::UnitTypes)m_top->primitiveType()); + m_bottom = m_cssValuePool->createValue(m_top->getDoubleValue(), (CSSPrimitiveValue::UnitTypes)m_top->primitiveType()); + m_left = m_cssValuePool->createValue(m_top->getDoubleValue(), (CSSPrimitiveValue::UnitTypes)m_top->primitiveType()); + } + if (!m_bottom) { + m_bottom = m_cssValuePool->createValue(m_top->getDoubleValue(), (CSSPrimitiveValue::UnitTypes)m_top->primitiveType()); + m_left = m_cssValuePool->createValue(m_right->getDoubleValue(), (CSSPrimitiveValue::UnitTypes)m_right->primitiveType()); + } + if (!m_left) + m_left = m_cssValuePool->createValue(m_right->getDoubleValue(), (CSSPrimitiveValue::UnitTypes)m_right->primitiveType()); + + // Now build a rect value to hold all four of our primitive values. + RefPtr<Quad> quad = Quad::create(); + quad->setTop(m_top); + quad->setRight(m_right); + quad->setBottom(m_bottom); + quad->setLeft(m_left); + + // Make our new border image value now. + return CSSBorderImageSliceValue::create(m_cssValuePool->createValue(quad.release()), m_fill); + } + +private: + CSSValuePool* m_cssValuePool; + + bool m_allowNumber; + bool m_allowFill; + bool m_allowFinalCommit; + + RefPtr<CSSPrimitiveValue> m_top; + RefPtr<CSSPrimitiveValue> m_right; + RefPtr<CSSPrimitiveValue> m_bottom; + RefPtr<CSSPrimitiveValue> m_left; + + bool m_fill; +}; + +bool CSSParser::parseBorderImageSlice(int propId, RefPtr<CSSBorderImageSliceValue>& result) +{ + BorderImageSliceParseContext context(cssValuePool()); + CSSParserValue* val; + while ((val = m_valueList->current())) { + if (context.allowNumber() && validUnit(val, FInteger | FNonNeg | FPercent, true)) { + context.commitNumber(val); + } else if (context.allowFill() && val->id == CSSValueFill) + context.commitFill(); + else if (!inShorthand()) { + // If we're not parsing a shorthand then we are invalid. + return false; + } else { + if (context.allowFinalCommit()) { + // We're going to successfully parse, but we don't want to consume this token. + m_valueList->previous(); + } + break; + } + m_valueList->next(); + } + + if (context.allowFinalCommit()) { + // 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 (propId == CSSPropertyWebkitBorderImage || propId == CSSPropertyWebkitMaskBoxImage || propId == CSSPropertyWebkitBoxReflect) + context.commitFill(); + + // Need to fully commit as a single value. + result = context.commitBorderImageSlice(); + return true; + } + + return false; +} + +class BorderImageQuadParseContext { +public: + BorderImageQuadParseContext(CSSValuePool* cssValuePool) + : m_cssValuePool(cssValuePool) + , m_allowNumber(true) + , m_allowFinalCommit(false) + { } + + bool allowNumber() const { return m_allowNumber; } + bool allowFinalCommit() const { return m_allowFinalCommit; } + CSSPrimitiveValue* top() const { return m_top.get(); } + + void commitNumber(CSSParserValue* v) + { + RefPtr<CSSPrimitiveValue> val; + if (v->id == CSSValueAuto) + val = m_cssValuePool->createIdentifierValue(v->id); + else + val = m_cssValuePool->createValue(v->fValue, (CSSPrimitiveValue::UnitTypes)v->unit); + + if (!m_top) + m_top = val; + else if (!m_right) + m_right = val; + else if (!m_bottom) + m_bottom = val; + else { + ASSERT(!m_left); + m_left = val; + } + + m_allowNumber = !m_left; + m_allowFinalCommit = true; + } + + void setAllowFinalCommit() { m_allowFinalCommit = true; } + void setTop(PassRefPtr<CSSPrimitiveValue> val) { m_top = val; } + + PassRefPtr<CSSPrimitiveValue> commitBorderImageQuad() + { + // We need to clone and repeat values for any omissions. + ASSERT(m_top); + if (!m_right) { + m_right = m_cssValuePool->createValue(m_top->getDoubleValue(), (CSSPrimitiveValue::UnitTypes)m_top->primitiveType()); + m_bottom = m_cssValuePool->createValue(m_top->getDoubleValue(), (CSSPrimitiveValue::UnitTypes)m_top->primitiveType()); + m_left = m_cssValuePool->createValue(m_top->getDoubleValue(), (CSSPrimitiveValue::UnitTypes)m_top->primitiveType()); + } + if (!m_bottom) { + m_bottom = m_cssValuePool->createValue(m_top->getDoubleValue(), (CSSPrimitiveValue::UnitTypes)m_top->primitiveType()); + m_left = m_cssValuePool->createValue(m_right->getDoubleValue(), (CSSPrimitiveValue::UnitTypes)m_right->primitiveType()); + } + if (!m_left) + m_left = m_cssValuePool->createValue(m_right->getDoubleValue(), (CSSPrimitiveValue::UnitTypes)m_right->primitiveType()); + + // Now build a quad value to hold all four of our primitive values. + RefPtr<Quad> quad = Quad::create(); + quad->setTop(m_top); + quad->setRight(m_right); + quad->setBottom(m_bottom); + quad->setLeft(m_left); + + // Make our new value now. + return m_cssValuePool->createValue(quad.release()); + } + +private: + CSSValuePool* m_cssValuePool; + + bool m_allowNumber; + bool m_allowFinalCommit; + + RefPtr<CSSPrimitiveValue> m_top; + RefPtr<CSSPrimitiveValue> m_right; + RefPtr<CSSPrimitiveValue> m_bottom; + RefPtr<CSSPrimitiveValue> m_left; +}; + +bool CSSParser::parseBorderImageQuad(Units validUnits, RefPtr<CSSPrimitiveValue>& result) +{ + BorderImageQuadParseContext context(cssValuePool()); + CSSParserValue* val; + while ((val = m_valueList->current())) { + if (context.allowNumber() && (validUnit(val, validUnits, true) || val->id == CSSValueAuto)) { + context.commitNumber(val); + } else if (!inShorthand()) { + // If we're not parsing a shorthand then we are invalid. + return false; + } else { + if (context.allowFinalCommit()) + m_valueList->previous(); // The shorthand loop will advance back to this point. + break; + } + m_valueList->next(); + } + + if (context.allowFinalCommit()) { + // Need to fully commit as a single value. + result = context.commitBorderImageQuad(); + return true; + } + return false; +} + +bool CSSParser::parseBorderImageWidth(RefPtr<CSSPrimitiveValue>& result) +{ + return parseBorderImageQuad(FLength | FInteger | FNonNeg | FPercent, result); +} + +bool CSSParser::parseBorderImageOutset(RefPtr<CSSPrimitiveValue>& result) +{ + return parseBorderImageQuad(FLength | FInteger | FNonNeg, result); +} + +static void completeBorderRadii(RefPtr<CSSPrimitiveValue> radii[4]) +{ + if (radii[3]) + return; + if (!radii[2]) { + if (!radii[1]) + radii[1] = radii[0]; + radii[2] = radii[0]; + } + radii[3] = radii[1]; +} + +bool CSSParser::parseBorderRadius(int propId, bool important) +{ + unsigned num = m_valueList->size(); + if (num > 9) + return false; + + ShorthandScope scope(this, propId); + RefPtr<CSSPrimitiveValue> radii[2][4]; + + unsigned indexAfterSlash = 0; + for (unsigned i = 0; i < num; ++i) { + CSSParserValue* value = m_valueList->valueAt(i); + if (value->unit == CSSParserValue::Operator) { + if (value->iValue != '/') + return false; + + if (!i || indexAfterSlash || i + 1 == num || num > i + 5) + return false; + + indexAfterSlash = i + 1; + completeBorderRadii(radii[0]); + continue; + } + + if (i - indexAfterSlash >= 4) + return false; + + if (!validUnit(value, FLength | FPercent | FNonNeg, m_strict)) + return false; + + RefPtr<CSSPrimitiveValue> radius = createPrimitiveNumericValue(value); + + if (!indexAfterSlash) { + radii[0][i] = radius; + + // Legacy syntax: -webkit-border-radius: l1 l2; is equivalent to border-radius: l1 / l2; + if (num == 2 && propId == CSSPropertyWebkitBorderRadius) { + indexAfterSlash = 1; + completeBorderRadii(radii[0]); + } + } else + radii[1][i - indexAfterSlash] = radius.release(); + } + + if (!indexAfterSlash) { + completeBorderRadii(radii[0]); + for (unsigned i = 0; i < 4; ++i) + radii[1][i] = radii[0][i]; + } else + completeBorderRadii(radii[1]); + + ImplicitScope implicitScope(this, PropertyImplicit); + addProperty(CSSPropertyBorderTopLeftRadius, cssValuePool()->createValue(Pair::create(radii[0][0].release(), radii[1][0].release())), important); + addProperty(CSSPropertyBorderTopRightRadius, cssValuePool()->createValue(Pair::create(radii[0][1].release(), radii[1][1].release())), important); + addProperty(CSSPropertyBorderBottomRightRadius, cssValuePool()->createValue(Pair::create(radii[0][2].release(), radii[1][2].release())), important); + addProperty(CSSPropertyBorderBottomLeftRadius, cssValuePool()->createValue(Pair::create(radii[0][3].release(), radii[1][3].release())), important); + return true; +} + +bool CSSParser::parseAspectRatio(bool important) +{ + unsigned num = m_valueList->size(); + if (num == 1 && m_valueList->valueAt(0)->id == CSSValueNone) { + addProperty(CSSPropertyWebkitAspectRatio, cssValuePool()->createIdentifierValue(CSSValueNone), important); + return true; + } + + if (num != 3) + return false; + + CSSParserValue* lvalue = m_valueList->valueAt(0); + CSSParserValue* op = m_valueList->valueAt(1); + CSSParserValue* rvalue = m_valueList->valueAt(2); + + if (op->unit != CSSParserValue::Operator || op->iValue != '/') + return false; + + if (!validUnit(lvalue, FNumber | FNonNeg, m_strict) || !validUnit(rvalue, FNumber | FNonNeg, m_strict)) + return false; + + if (!lvalue->fValue || !rvalue->fValue) + return false; + + addProperty(CSSPropertyWebkitAspectRatio, CSSAspectRatioValue::create(narrowPrecisionToFloat(lvalue->fValue), narrowPrecisionToFloat(rvalue->fValue)), important); + + return true; +} + +bool CSSParser::parseCounter(int propId, int defaultValue, bool important) +{ + enum { ID, VAL } state = ID; + + RefPtr<CSSValueList> list = CSSValueList::createCommaSeparated(); + RefPtr<CSSPrimitiveValue> counterName; + + while (true) { + CSSParserValue* val = m_valueList->current(); + switch (state) { + case ID: + if (val && val->unit == CSSPrimitiveValue::CSS_IDENT) { + counterName = createPrimitiveStringValue(val); + state = VAL; + m_valueList->next(); + continue; + } + break; + case VAL: { + int i = defaultValue; + if (val && val->unit == CSSPrimitiveValue::CSS_NUMBER) { + i = clampToInteger(val->fValue); + m_valueList->next(); + } + + list->append(cssValuePool()->createValue(Pair::create(counterName.release(), + cssValuePool()->createValue(i, CSSPrimitiveValue::CSS_NUMBER)))); + state = ID; + continue; + } + } + break; + } + + if (list->length() > 0) { + addProperty(propId, list.release(), important); + return true; + } + + return false; +} + +// This should go away once we drop support for -webkit-gradient +static PassRefPtr<CSSPrimitiveValue> parseDeprecatedGradientPoint(CSSParserValue* a, bool horizontal, CSSValuePool* cssValuePool) +{ + RefPtr<CSSPrimitiveValue> result; + if (a->unit == CSSPrimitiveValue::CSS_IDENT) { + if ((equalIgnoringCase(a->string, "left") && horizontal) + || (equalIgnoringCase(a->string, "top") && !horizontal)) + result = cssValuePool->createValue(0., CSSPrimitiveValue::CSS_PERCENTAGE); + else if ((equalIgnoringCase(a->string, "right") && horizontal) + || (equalIgnoringCase(a->string, "bottom") && !horizontal)) + result = cssValuePool->createValue(100., CSSPrimitiveValue::CSS_PERCENTAGE); + else if (equalIgnoringCase(a->string, "center")) + result = cssValuePool->createValue(50., CSSPrimitiveValue::CSS_PERCENTAGE); + } else if (a->unit == CSSPrimitiveValue::CSS_NUMBER || a->unit == CSSPrimitiveValue::CSS_PERCENTAGE) + result = cssValuePool->createValue(a->fValue, (CSSPrimitiveValue::UnitTypes)a->unit); + return result; +} + +static bool parseDeprecatedGradientColorStop(CSSParser* p, CSSParserValue* a, CSSGradientColorStop& stop) +{ + if (a->unit != CSSParserValue::Function) + return false; + + if (!equalIgnoringCase(a->function->name, "from(") && + !equalIgnoringCase(a->function->name, "to(") && + !equalIgnoringCase(a->function->name, "color-stop(")) + return false; + + CSSParserValueList* args = a->function->args.get(); + if (!args) + return false; + + if (equalIgnoringCase(a->function->name, "from(") + || equalIgnoringCase(a->function->name, "to(")) { + // The "from" and "to" stops expect 1 argument. + if (args->size() != 1) + return false; + + if (equalIgnoringCase(a->function->name, "from(")) + stop.m_position = p->cssValuePool()->createValue(0, CSSPrimitiveValue::CSS_NUMBER); + else + stop.m_position = p->cssValuePool()->createValue(1, CSSPrimitiveValue::CSS_NUMBER); + + int id = args->current()->id; + if (id == CSSValueWebkitText || (id >= CSSValueAqua && id <= CSSValueWindowtext) || id == CSSValueMenu) + stop.m_color = p->cssValuePool()->createIdentifierValue(id); + else + stop.m_color = p->parseColor(args->current()); + if (!stop.m_color) + return false; + } + + // The "color-stop" function expects 3 arguments. + if (equalIgnoringCase(a->function->name, "color-stop(")) { + if (args->size() != 3) + return false; + + CSSParserValue* stopArg = args->current(); + if (stopArg->unit == CSSPrimitiveValue::CSS_PERCENTAGE) + stop.m_position = p->cssValuePool()->createValue(stopArg->fValue / 100, CSSPrimitiveValue::CSS_NUMBER); + else if (stopArg->unit == CSSPrimitiveValue::CSS_NUMBER) + stop.m_position = p->cssValuePool()->createValue(stopArg->fValue, CSSPrimitiveValue::CSS_NUMBER); + else + return false; + + stopArg = args->next(); + if (stopArg->unit != CSSParserValue::Operator || stopArg->iValue != ',') + return false; + + stopArg = args->next(); + int id = stopArg->id; + if (id == CSSValueWebkitText || (id >= CSSValueAqua && id <= CSSValueWindowtext) || id == CSSValueMenu) + stop.m_color = p->cssValuePool()->createIdentifierValue(id); + else + stop.m_color = p->parseColor(stopArg); + if (!stop.m_color) + return false; + } + + return true; +} + +bool CSSParser::parseDeprecatedGradient(CSSParserValueList* valueList, RefPtr<CSSValue>& gradient) +{ + // Walk the arguments. + CSSParserValueList* args = valueList->current()->function->args.get(); + if (!args || args->size() == 0) + return false; + + // The first argument is the gradient type. It is an identifier. + CSSGradientType gradientType; + CSSParserValue* a = args->current(); + if (!a || a->unit != CSSPrimitiveValue::CSS_IDENT) + return false; + if (equalIgnoringCase(a->string, "linear")) + gradientType = CSSLinearGradient; + else if (equalIgnoringCase(a->string, "radial")) + gradientType = CSSRadialGradient; + else + return false; + + RefPtr<CSSGradientValue> result; + switch (gradientType) { + case CSSLinearGradient: + result = CSSLinearGradientValue::create(NonRepeating, true); + break; + case CSSRadialGradient: + result = CSSRadialGradientValue::create(NonRepeating, true); + break; + } + + // Comma. + a = args->next(); + if (!a || a->unit != CSSParserValue::Operator || a->iValue != ',') + return false; + + // Next comes the starting point for the gradient as an x y pair. There is no + // comma between the x and the y values. + // First X. It can be left, right, number or percent. + a = args->next(); + if (!a) + return false; + RefPtr<CSSPrimitiveValue> point = parseDeprecatedGradientPoint(a, true, cssValuePool()); + if (!point) + return false; + result->setFirstX(point.release()); + + // First Y. It can be top, bottom, number or percent. + a = args->next(); + if (!a) + return false; + point = parseDeprecatedGradientPoint(a, false, cssValuePool()); + if (!point) + return false; + result->setFirstY(point.release()); + + // Comma after the first point. + a = args->next(); + if (!a || a->unit != CSSParserValue::Operator || a->iValue != ',') + return false; + + // For radial gradients only, we now expect a numeric radius. + if (gradientType == CSSRadialGradient) { + a = args->next(); + if (!a || a->unit != CSSPrimitiveValue::CSS_NUMBER) + return false; + static_cast<CSSRadialGradientValue*>(result.get())->setFirstRadius(createPrimitiveNumericValue(a)); + + // Comma after the first radius. + a = args->next(); + if (!a || a->unit != CSSParserValue::Operator || a->iValue != ',') + return false; + } + + // Next is the ending point for the gradient as an x, y pair. + // Second X. It can be left, right, number or percent. + a = args->next(); + if (!a) + return false; + point = parseDeprecatedGradientPoint(a, true, cssValuePool()); + if (!point) + return false; + result->setSecondX(point.release()); + + // Second Y. It can be top, bottom, number or percent. + a = args->next(); + if (!a) + return false; + point = parseDeprecatedGradientPoint(a, false, cssValuePool()); + if (!point) + return false; + result->setSecondY(point.release()); + + // For radial gradients only, we now expect the second radius. + if (gradientType == CSSRadialGradient) { + // Comma after the second point. + a = args->next(); + if (!a || a->unit != CSSParserValue::Operator || a->iValue != ',') + return false; + + a = args->next(); + if (!a || a->unit != CSSPrimitiveValue::CSS_NUMBER) + return false; + static_cast<CSSRadialGradientValue*>(result.get())->setSecondRadius(createPrimitiveNumericValue(a)); + } + + // We now will accept any number of stops (0 or more). + a = args->next(); + while (a) { + // Look for the comma before the next stop. + if (a->unit != CSSParserValue::Operator || a->iValue != ',') + return false; + + // Now examine the stop itself. + a = args->next(); + if (!a) + return false; + + // The function name needs to be one of "from", "to", or "color-stop." + CSSGradientColorStop stop; + if (!parseDeprecatedGradientColorStop(this, a, stop)) + return false; + result->addStop(stop); + + // Advance + a = args->next(); + } + + gradient = result.release(); + return true; +} + +static PassRefPtr<CSSPrimitiveValue> valueFromSideKeyword(CSSParserValue* a, bool& isHorizontal, CSSValuePool* cssValuePool) +{ + if (a->unit != CSSPrimitiveValue::CSS_IDENT) + return 0; + + switch (a->id) { + case CSSValueLeft: + case CSSValueRight: + isHorizontal = true; + break; + case CSSValueTop: + case CSSValueBottom: + isHorizontal = false; + break; + default: + return 0; + } + return cssValuePool->createIdentifierValue(a->id); +} + +static PassRefPtr<CSSPrimitiveValue> parseGradientColorOrKeyword(CSSParser* p, CSSParserValue* value) +{ + int id = value->id; + if (id == CSSValueWebkitText || (id >= CSSValueAqua && id <= CSSValueWindowtext) || id == CSSValueMenu || id == CSSValueCurrentcolor) + return p->cssValuePool()->createIdentifierValue(id); + + return p->parseColor(value); +} + +bool CSSParser::parseLinearGradient(CSSParserValueList* valueList, RefPtr<CSSValue>& gradient, CSSGradientRepeat repeating) +{ + RefPtr<CSSLinearGradientValue> result = CSSLinearGradientValue::create(repeating); + + // Walk the arguments. + CSSParserValueList* args = valueList->current()->function->args.get(); + if (!args || !args->size()) + return false; + + CSSParserValue* a = args->current(); + if (!a) + return false; + + bool expectComma = false; + // Look for angle. + if (validUnit(a, FAngle, true)) { + result->setAngle(createPrimitiveNumericValue(a)); + + a = args->next(); + expectComma = true; + } else { + // Look one or two optional keywords that indicate a side or corner. + RefPtr<CSSPrimitiveValue> startX, startY; + + RefPtr<CSSPrimitiveValue> location; + bool isHorizontal = false; + if ((location = valueFromSideKeyword(a, isHorizontal, cssValuePool()))) { + if (isHorizontal) + startX = location; + else + startY = location; + + a = args->next(); + if (a) { + if ((location = valueFromSideKeyword(a, isHorizontal, cssValuePool()))) { + if (isHorizontal) { + if (startX) + return false; + startX = location; + } else { + if (startY) + return false; + startY = location; + } + + a = args->next(); + } + } + + expectComma = true; + } + + if (!startX && !startY) + startY = cssValuePool()->createIdentifierValue(CSSValueTop); + + result->setFirstX(startX.release()); + result->setFirstY(startY.release()); + } + + if (!parseGradientColorStops(args, result.get(), expectComma)) + return false; + + Vector<CSSGradientColorStop>& stops = result->stops(); + if (stops.isEmpty()) + return false; + + gradient = result.release(); + return true; +} + +bool CSSParser::parseRadialGradient(CSSParserValueList* valueList, RefPtr<CSSValue>& gradient, CSSGradientRepeat repeating) +{ + RefPtr<CSSRadialGradientValue> result = CSSRadialGradientValue::create(repeating); + + // Walk the arguments. + CSSParserValueList* args = valueList->current()->function->args.get(); + if (!args || !args->size()) + return false; + + CSSParserValue* a = args->current(); + if (!a) + return false; + + bool expectComma = false; + + // Optional background-position + RefPtr<CSSValue> centerX; + RefPtr<CSSValue> centerY; + // parseFillPosition advances the args next pointer. + parseFillPosition(args, centerX, centerY); + a = args->current(); + if (!a) + return false; + + if (centerX || centerY) { + // Comma + if (a->unit != CSSParserValue::Operator || a->iValue != ',') + return false; + + a = args->next(); + if (!a) + return false; + } + + ASSERT(!centerX || centerX->isPrimitiveValue()); + ASSERT(!centerY || centerY->isPrimitiveValue()); + + result->setFirstX(static_cast<CSSPrimitiveValue*>(centerX.get())); + result->setSecondX(static_cast<CSSPrimitiveValue*>(centerX.get())); + // CSS3 radial gradients always share the same start and end point. + result->setFirstY(static_cast<CSSPrimitiveValue*>(centerY.get())); + result->setSecondY(static_cast<CSSPrimitiveValue*>(centerY.get())); + + RefPtr<CSSPrimitiveValue> shapeValue; + RefPtr<CSSPrimitiveValue> sizeValue; + + // Optional shape and/or size in any order. + for (int i = 0; i < 2; ++i) { + if (a->unit != CSSPrimitiveValue::CSS_IDENT) + break; + + bool foundValue = false; + switch (a->id) { + case CSSValueCircle: + case CSSValueEllipse: + shapeValue = cssValuePool()->createIdentifierValue(a->id); + foundValue = true; + break; + case CSSValueClosestSide: + case CSSValueClosestCorner: + case CSSValueFarthestSide: + case CSSValueFarthestCorner: + case CSSValueContain: + case CSSValueCover: + sizeValue = cssValuePool()->createIdentifierValue(a->id); + foundValue = true; + break; + } + + if (foundValue) { + a = args->next(); + if (!a) + return false; + + expectComma = true; + } + } + + result->setShape(shapeValue); + result->setSizingBehavior(sizeValue); + + // Or, two lengths or percentages + RefPtr<CSSPrimitiveValue> horizontalSize; + RefPtr<CSSPrimitiveValue> verticalSize; + + if (!shapeValue && !sizeValue) { + if (validUnit(a, FLength | FPercent, m_strict)) { + horizontalSize = createPrimitiveNumericValue(a); + a = args->next(); + if (!a) + return false; + + expectComma = true; + } + + if (validUnit(a, FLength | FPercent, m_strict)) { + verticalSize = createPrimitiveNumericValue(a); + + a = args->next(); + if (!a) + return false; + expectComma = true; + } + } + + // Must have neither or both. + if (!horizontalSize != !verticalSize) + return false; + + result->setEndHorizontalSize(horizontalSize); + result->setEndVerticalSize(verticalSize); + + if (!parseGradientColorStops(args, result.get(), expectComma)) + return false; + + gradient = result.release(); + return true; +} + +bool CSSParser::parseGradientColorStops(CSSParserValueList* valueList, CSSGradientValue* gradient, bool expectComma) +{ + CSSParserValue* a = valueList->current(); + + // Now look for color stops. + while (a) { + // Look for the comma before the next stop. + if (expectComma) { + if (a->unit != CSSParserValue::Operator || a->iValue != ',') + return false; + + a = valueList->next(); + if (!a) + return false; + } + + // <color-stop> = <color> [ <percentage> | <length> ]? + CSSGradientColorStop stop; + stop.m_color = parseGradientColorOrKeyword(this, a); + if (!stop.m_color) + return false; + + a = valueList->next(); + if (a) { + if (validUnit(a, FLength | FPercent, m_strict)) { + stop.m_position = createPrimitiveNumericValue(a); + a = valueList->next(); + } + } + + gradient->addStop(stop); + expectComma = true; + } + + // Must have 2 or more stops to be valid. + return gradient->stops().size() > 1; +} + +bool CSSParser::isGeneratedImageValue(CSSParserValue* val) const +{ + if (val->unit != CSSParserValue::Function) + return false; + + return equalIgnoringCase(val->function->name, "-webkit-gradient(") + || equalIgnoringCase(val->function->name, "-webkit-linear-gradient(") + || equalIgnoringCase(val->function->name, "-webkit-repeating-linear-gradient(") + || equalIgnoringCase(val->function->name, "-webkit-radial-gradient(") + || equalIgnoringCase(val->function->name, "-webkit-repeating-radial-gradient(") + || equalIgnoringCase(val->function->name, "-webkit-canvas(") + || equalIgnoringCase(val->function->name, "-webkit-cross-fade("); +} + +bool CSSParser::parseGeneratedImage(CSSParserValueList* valueList, RefPtr<CSSValue>& value) +{ + CSSParserValue* val = valueList->current(); + + if (val->unit != CSSParserValue::Function) + return false; + + if (equalIgnoringCase(val->function->name, "-webkit-gradient(")) + return parseDeprecatedGradient(valueList, value); + + if (equalIgnoringCase(val->function->name, "-webkit-linear-gradient(")) + return parseLinearGradient(valueList, value, NonRepeating); + + if (equalIgnoringCase(val->function->name, "-webkit-repeating-linear-gradient(")) + return parseLinearGradient(valueList, value, Repeating); + + if (equalIgnoringCase(val->function->name, "-webkit-radial-gradient(")) + return parseRadialGradient(valueList, value, NonRepeating); + + if (equalIgnoringCase(val->function->name, "-webkit-repeating-radial-gradient(")) + return parseRadialGradient(valueList, value, Repeating); + + if (equalIgnoringCase(val->function->name, "-webkit-canvas(")) + return parseCanvas(valueList, value); + + if (equalIgnoringCase(val->function->name, "-webkit-cross-fade(")) + return parseCrossfade(valueList, value); + + return false; +} + +bool CSSParser::parseCrossfade(CSSParserValueList* valueList, RefPtr<CSSValue>& crossfade) +{ + RefPtr<CSSCrossfadeValue> result; + + // Walk the arguments. + CSSParserValueList* args = valueList->current()->function->args.get(); + if (!args || args->size() != 5) + return false; + CSSParserValue* a = args->current(); + RefPtr<CSSValue> fromImageValue; + RefPtr<CSSValue> toImageValue; + + // The first argument is the "from" image. It is a fill image. + if (!a || !parseFillImage(args, fromImageValue)) + return false; + a = args->next(); + + // Skip a comma + if (a->unit != CSSParserValue::Operator || a->iValue != ',') + return false; + a = args->next(); + + // The second argument is the "to" image. It is a fill image. + if (!a || !parseFillImage(args, toImageValue)) + return false; + a = args->next(); + + // Skip a comma + if (a->unit != CSSParserValue::Operator || a->iValue != ',') + return false; + a = args->next(); + + // The third argument is the crossfade value. It is a percentage or a fractional number. + RefPtr<CSSPrimitiveValue> percentage; + if (!a) + return false; + + if (a->unit == CSSPrimitiveValue::CSS_PERCENTAGE) + percentage = cssValuePool()->createValue(clampTo<double>(a->fValue / 100, 0, 1), CSSPrimitiveValue::CSS_NUMBER); + else if (a->unit == CSSPrimitiveValue::CSS_NUMBER) + percentage = cssValuePool()->createValue(clampTo<double>(a->fValue, 0, 1), CSSPrimitiveValue::CSS_NUMBER); + else + return false; + + result = CSSCrossfadeValue::create(fromImageValue, toImageValue); + result->setPercentage(percentage); + + crossfade = result; + + return true; +} + +bool CSSParser::parseCanvas(CSSParserValueList* valueList, RefPtr<CSSValue>& canvas) +{ + RefPtr<CSSCanvasValue> result = CSSCanvasValue::create(); + + // Walk the arguments. + CSSParserValueList* args = valueList->current()->function->args.get(); + if (!args || args->size() != 1) + return false; + + // The first argument is the canvas name. It is an identifier. + CSSParserValue* a = args->current(); + if (!a || a->unit != CSSPrimitiveValue::CSS_IDENT) + return false; + result->setName(a->string); + canvas = result; + return true; +} + +class TransformOperationInfo { +public: + TransformOperationInfo(const CSSParserString& name) + : m_type(WebKitCSSTransformValue::UnknownTransformOperation) + , m_argCount(1) + , m_allowSingleArgument(false) + , m_unit(CSSParser::FUnknown) + { + if (equalIgnoringCase(name, "scale(") || equalIgnoringCase(name, "scalex(") || equalIgnoringCase(name, "scaley(") || equalIgnoringCase(name, "scalez(")) { + m_unit = CSSParser::FNumber; + if (equalIgnoringCase(name, "scale(")) + m_type = WebKitCSSTransformValue::ScaleTransformOperation; + else if (equalIgnoringCase(name, "scalex(")) + m_type = WebKitCSSTransformValue::ScaleXTransformOperation; + else if (equalIgnoringCase(name, "scaley(")) + m_type = WebKitCSSTransformValue::ScaleYTransformOperation; + else + m_type = WebKitCSSTransformValue::ScaleZTransformOperation; + } else if (equalIgnoringCase(name, "scale3d(")) { + m_type = WebKitCSSTransformValue::Scale3DTransformOperation; + m_argCount = 5; + m_unit = CSSParser::FNumber; + } else if (equalIgnoringCase(name, "rotate(")) { + m_type = WebKitCSSTransformValue::RotateTransformOperation; + m_unit = CSSParser::FAngle; + } else if (equalIgnoringCase(name, "rotatex(") || + equalIgnoringCase(name, "rotatey(") || + equalIgnoringCase(name, "rotatez(")) { + m_unit = CSSParser::FAngle; + if (equalIgnoringCase(name, "rotatex(")) + m_type = WebKitCSSTransformValue::RotateXTransformOperation; + else if (equalIgnoringCase(name, "rotatey(")) + m_type = WebKitCSSTransformValue::RotateYTransformOperation; + else + m_type = WebKitCSSTransformValue::RotateZTransformOperation; + } else if (equalIgnoringCase(name, "rotate3d(")) { + m_type = WebKitCSSTransformValue::Rotate3DTransformOperation; + m_argCount = 7; + m_unit = CSSParser::FNumber; + } else if (equalIgnoringCase(name, "skew(") || equalIgnoringCase(name, "skewx(") || equalIgnoringCase(name, "skewy(")) { + m_unit = CSSParser::FAngle; + if (equalIgnoringCase(name, "skew(")) + m_type = WebKitCSSTransformValue::SkewTransformOperation; + else if (equalIgnoringCase(name, "skewx(")) + m_type = WebKitCSSTransformValue::SkewXTransformOperation; + else + m_type = WebKitCSSTransformValue::SkewYTransformOperation; + } else if (equalIgnoringCase(name, "translate(") || equalIgnoringCase(name, "translatex(") || equalIgnoringCase(name, "translatey(") || equalIgnoringCase(name, "translatez(")) { + m_unit = CSSParser::FLength | CSSParser::FPercent; + if (equalIgnoringCase(name, "translate(")) + m_type = WebKitCSSTransformValue::TranslateTransformOperation; + else if (equalIgnoringCase(name, "translatex(")) + m_type = WebKitCSSTransformValue::TranslateXTransformOperation; + else if (equalIgnoringCase(name, "translatey(")) + m_type = WebKitCSSTransformValue::TranslateYTransformOperation; + else + m_type = WebKitCSSTransformValue::TranslateZTransformOperation; + } else if (equalIgnoringCase(name, "translate3d(")) { + m_type = WebKitCSSTransformValue::Translate3DTransformOperation; + m_argCount = 5; + m_unit = CSSParser::FLength | CSSParser::FPercent; + } else if (equalIgnoringCase(name, "matrix(")) { + m_type = WebKitCSSTransformValue::MatrixTransformOperation; + m_argCount = 11; + m_unit = CSSParser::FNumber; + } else if (equalIgnoringCase(name, "matrix3d(")) { + m_type = WebKitCSSTransformValue::Matrix3DTransformOperation; + m_argCount = 31; + m_unit = CSSParser::FNumber; + } else if (equalIgnoringCase(name, "perspective(")) { + m_type = WebKitCSSTransformValue::PerspectiveTransformOperation; + m_unit = CSSParser::FNumber; + } + + if (equalIgnoringCase(name, "scale(") || equalIgnoringCase(name, "skew(") || equalIgnoringCase(name, "translate(")) { + m_allowSingleArgument = true; + m_argCount = 3; + } + } + + WebKitCSSTransformValue::TransformOperationType type() const { return m_type; } + unsigned argCount() const { return m_argCount; } + CSSParser::Units unit() const { return m_unit; } + + bool unknown() const { return m_type == WebKitCSSTransformValue::UnknownTransformOperation; } + bool hasCorrectArgCount(unsigned argCount) { return m_argCount == argCount || (m_allowSingleArgument && argCount == 1); } + +private: + WebKitCSSTransformValue::TransformOperationType m_type; + unsigned m_argCount; + bool m_allowSingleArgument; + CSSParser::Units m_unit; +}; + +PassRefPtr<CSSValueList> CSSParser::parseTransform() +{ + if (!m_valueList) + return 0; + + // The transform is a list of functional primitives that specify transform operations. + // We collect a list of WebKitCSSTransformValues, where each value specifies a single operation. + RefPtr<CSSValueList> list = CSSValueList::createSpaceSeparated(); + for (CSSParserValue* value = m_valueList->current(); value; value = m_valueList->next()) { + if (value->unit != CSSParserValue::Function || !value->function) + return 0; + + // Every primitive requires at least one argument. + CSSParserValueList* args = value->function->args.get(); + if (!args) + return 0; + + // See if the specified primitive is one we understand. + TransformOperationInfo info(value->function->name); + if (info.unknown()) + return 0; + + if (!info.hasCorrectArgCount(args->size())) + return 0; + + // Create the new WebKitCSSTransformValue for this operation and add it to our list. + RefPtr<WebKitCSSTransformValue> transformValue = WebKitCSSTransformValue::create(info.type()); + list->append(transformValue); + + // Snag our values. + CSSParserValue* a = args->current(); + unsigned argNumber = 0; + while (a) { + CSSParser::Units unit = info.unit(); + + if (info.type() == WebKitCSSTransformValue::Rotate3DTransformOperation && argNumber == 3) { + // 4th param of rotate3d() is an angle rather than a bare number, validate it as such + if (!validUnit(a, FAngle, true)) + return 0; + } else if (info.type() == WebKitCSSTransformValue::Translate3DTransformOperation && argNumber == 2) { + // 3rd param of translate3d() cannot be a percentage + if (!validUnit(a, FLength, true)) + return 0; + } else if (info.type() == WebKitCSSTransformValue::TranslateZTransformOperation && argNumber == 0) { + // 1st param of translateZ() cannot be a percentage + if (!validUnit(a, FLength, true)) + return 0; + } else if (info.type() == WebKitCSSTransformValue::PerspectiveTransformOperation && argNumber == 0) { + // 1st param of perspective() must be a non-negative number (deprecated) or length. + if (!validUnit(a, FNumber | FLength | FNonNeg, true)) + return 0; + } else if (!validUnit(a, unit, true)) + return 0; + + // Add the value to the current transform operation. + transformValue->append(createPrimitiveNumericValue(a)); + + a = args->next(); + if (!a) + break; + if (a->unit != CSSParserValue::Operator || a->iValue != ',') + return 0; + a = args->next(); + + argNumber++; + } + } + + return list.release(); +} + +#if ENABLE(CSS_FILTERS) + +static void filterInfoForName(const CSSParserString& name, WebKitCSSFilterValue::FilterOperationType& filterType, unsigned& maximumArgumentCount) +{ + if (equalIgnoringCase(name, "grayscale(")) + filterType = WebKitCSSFilterValue::GrayscaleFilterOperation; + else if (equalIgnoringCase(name, "sepia(")) + filterType = WebKitCSSFilterValue::SepiaFilterOperation; + else if (equalIgnoringCase(name, "saturate(")) + filterType = WebKitCSSFilterValue::SaturateFilterOperation; + else if (equalIgnoringCase(name, "hue-rotate(")) + filterType = WebKitCSSFilterValue::HueRotateFilterOperation; + else if (equalIgnoringCase(name, "invert(")) + filterType = WebKitCSSFilterValue::InvertFilterOperation; + else if (equalIgnoringCase(name, "opacity(")) + filterType = WebKitCSSFilterValue::OpacityFilterOperation; + else if (equalIgnoringCase(name, "brightness(")) + filterType = WebKitCSSFilterValue::BrightnessFilterOperation; + else if (equalIgnoringCase(name, "contrast(")) + filterType = WebKitCSSFilterValue::ContrastFilterOperation; + else if (equalIgnoringCase(name, "blur(")) + filterType = WebKitCSSFilterValue::BlurFilterOperation; + else if (equalIgnoringCase(name, "drop-shadow(")) { + filterType = WebKitCSSFilterValue::DropShadowFilterOperation; + maximumArgumentCount = 4; // x-offset, y-offset, blur-radius, color -- spread and inset style not allowed. + } +#if ENABLE(CSS_SHADERS) + else if (equalIgnoringCase(name, "custom(")) + filterType = WebKitCSSFilterValue::CustomFilterOperation; +#endif +} + +#if ENABLE(CSS_SHADERS) +static bool acceptCommaOperator(CSSParserValueList* argsList) +{ + if (CSSParserValue* arg = argsList->current()) { + if (arg->unit != CSSParserValue::Operator || arg->iValue != ',') + return false; + argsList->next(); + } + return true; +} + +PassRefPtr<WebKitCSSFilterValue> CSSParser::parseCustomFilter(CSSParserValue* value) +{ + CSSParserValueList* argsList = value->function->args.get(); + if (!argsList) + return 0; + + RefPtr<WebKitCSSFilterValue> filterValue = WebKitCSSFilterValue::create(WebKitCSSFilterValue::CustomFilterOperation); + + // Custom filter syntax: + // + // vertexShader: <uri> | none + // fragmentShader: <uri> | none + // + // box: filter-box | border-box | padding-box | content-box + // vertexMesh: +<integer>{1,2}[wsp<box>][wsp'detached'] + // + // param-value: true|false[wsp+true|false]{0-3} | + // <number>[wsp+<number>]{0-3} | + // <array> | + // <transform> | + // <texture(<uri>)> + // array: 'array('<number>[wsp<number>]*')' + // css-3d-transform: <transform-function>;[<transform-function>]* + // transform: <css-3d-transform> | <mat> + // mat: 'mat2('<number>(,<number>){3}')' | + // 'mat3('<number>(,<number>){8}')' | + // 'mat4('<number>(,<number>){15}')' ) + // param-def: <param-name>wsp<param-value> + // param-name: <ident> + // params: [<param-def>[,<param-def>*]] + // + // custom(<vertex-shader>[wsp<fragment-shader>][,<vertex-mesh>][,<params>]) + + // 1. Parse the shader URLs: <vertex-shader>[wsp<fragment-shader>] + RefPtr<CSSValueList> shadersList = CSSValueList::createSpaceSeparated(); + bool hadAtLeastOneCustomShader = false; + CSSParserValue* arg; + while ((arg = argsList->current())) { + RefPtr<CSSValue> value; + if (arg->id == CSSValueNone) + value = cssValuePool()->createIdentifierValue(CSSValueNone); + else if (arg->unit == CSSPrimitiveValue::CSS_URI) { + KURL shaderURL = m_styleSheet ? m_styleSheet->completeURL(arg->string) : KURL(); + value = WebKitCSSShaderValue::create(shaderURL.string()); + hadAtLeastOneCustomShader = true; + } + if (!value) + break; + shadersList->append(value.release()); + argsList->next(); + } + + if (!shadersList->length() || !hadAtLeastOneCustomShader || shadersList->length() > 2 || !acceptCommaOperator(argsList)) + return 0; + + filterValue->append(shadersList.release()); + + // 2. Parse the mesh size <vertex-mesh> + RefPtr<CSSValueList> meshSizeList = CSSValueList::createSpaceSeparated(); + + while ((arg = argsList->current())) { + if (!validUnit(arg, FInteger | FNonNeg, true)) + break; + int integerValue = clampToInteger(arg->fValue); + // According to the specification we can only accept positive non-zero values. + if (integerValue < 1) + return 0; + meshSizeList->append(cssValuePool()->createValue(integerValue, CSSPrimitiveValue::CSS_NUMBER)); + argsList->next(); + } + + if (meshSizeList->length() > 2) + return 0; + + if ((arg = argsList->current()) && (arg->id == CSSValueBorderBox || arg->id == CSSValuePaddingBox + || arg->id == CSSValueContentBox || arg->id == CSSValueFilterBox)) { + meshSizeList->append(cssValuePool()->createIdentifierValue(arg->id)); + argsList->next(); + } + + if ((arg = argsList->current()) && arg->id == CSSValueDetached) { + meshSizeList->append(cssValuePool()->createIdentifierValue(arg->id)); + argsList->next(); + } + + if (meshSizeList->length()) { + if (!acceptCommaOperator(argsList)) + return 0; + filterValue->append(meshSizeList.release()); + } + + // 3. Parser the parameters. + RefPtr<CSSValueList> paramList = CSSValueList::createCommaSeparated(); + + while ((arg = argsList->current())) { + if (arg->id || arg->unit != CSSPrimitiveValue::CSS_IDENT) + return 0; + + RefPtr<CSSValueList> parameter = CSSValueList::createSpaceSeparated(); + parameter->append(createPrimitiveStringValue(arg)); + argsList->next(); + + if (!(arg = argsList->current())) + return 0; + + // TODO: Implement other parameters types parsing. + // textures: https://bugs.webkit.org/show_bug.cgi?id=71442 + // 3d-transforms: https://bugs.webkit.org/show_bug.cgi?id=71443 + // mat2, mat3, mat4: https://bugs.webkit.org/show_bug.cgi?id=71444 + RefPtr<CSSValueList> paramValueList = CSSValueList::createSpaceSeparated(); + while ((arg = argsList->current())) { + // If we hit a comma it means we finished this parameter's values. + if (arg->unit == CSSParserValue::Operator && arg->iValue == ',') + break; + if (!validUnit(arg, FNumber, true)) + return 0; + paramValueList->append(cssValuePool()->createValue(arg->fValue, CSSPrimitiveValue::CSS_NUMBER)); + argsList->next(); + } + if (!paramValueList->length() || paramValueList->length() > 4) + return 0; + parameter->append(paramValueList.release()); + paramList->append(parameter.release()); + if (!acceptCommaOperator(argsList)) + return 0; + } + + if (paramList->length()) + filterValue->append(paramList.release()); + + return filterValue; +} +#endif + +PassRefPtr<WebKitCSSFilterValue> CSSParser::parseBuiltinFilterArguments(CSSParserValueList* args, WebKitCSSFilterValue::FilterOperationType filterType) +{ + RefPtr<WebKitCSSFilterValue> filterValue = WebKitCSSFilterValue::create(filterType); + ASSERT(args); + + switch (filterType) { + case WebKitCSSFilterValue::GrayscaleFilterOperation: + case WebKitCSSFilterValue::SepiaFilterOperation: + case WebKitCSSFilterValue::SaturateFilterOperation: + case WebKitCSSFilterValue::InvertFilterOperation: + case WebKitCSSFilterValue::OpacityFilterOperation: + case WebKitCSSFilterValue::BrightnessFilterOperation: + case WebKitCSSFilterValue::ContrastFilterOperation: { + // One optional argument, 0-1 or 0%-100%, if missing use 100%. + if (args->size() > 1) + return 0; + + if (args->size()) { + CSSParserValue* value = args->current(); + if (!validUnit(value, FNumber | FPercent | FNonNeg, true)) + return 0; + + double amount = value->fValue; + + // Saturate, Brightness and Contrast allow values over 100%. + if (filterType != WebKitCSSFilterValue::SaturateFilterOperation + && filterType != WebKitCSSFilterValue::BrightnessFilterOperation + && filterType != WebKitCSSFilterValue::ContrastFilterOperation) { + double maxAllowed = value->unit == CSSPrimitiveValue::CSS_PERCENTAGE ? 100.0 : 1.0; + if (amount > maxAllowed) + return 0; + } + + filterValue->append(cssValuePool()->createValue(amount, static_cast<CSSPrimitiveValue::UnitTypes>(value->unit))); + } + break; + } + case WebKitCSSFilterValue::HueRotateFilterOperation: { + // hue-rotate() takes one optional angle. + if (args->size() > 1) + return 0; + + if (args->size()) { + CSSParserValue* argument = args->current(); + if (!validUnit(argument, FAngle, true)) + return 0; + + filterValue->append(createPrimitiveNumericValue(argument)); + } + break; + } + case WebKitCSSFilterValue::BlurFilterOperation: { + // Blur takes a single length. Zero parameters are allowed. + if (args->size() > 1) + return 0; + + if (args->size()) { + CSSParserValue* argument = args->current(); + if (!validUnit(argument, FLength | FNonNeg, true)) + return 0; + + filterValue->append(createPrimitiveNumericValue(argument)); + } + break; + } + case WebKitCSSFilterValue::DropShadowFilterOperation: { + // drop-shadow() takes a single shadow. + RefPtr<CSSValueList> shadowValueList = parseShadow(args, CSSPropertyWebkitFilter); + if (!shadowValueList || shadowValueList->length() != 1) + return 0; + + filterValue->append((shadowValueList.release())->itemWithoutBoundsCheck(0)); + break; + } + default: + ASSERT_NOT_REACHED(); + } + return filterValue.release(); +} + +PassRefPtr<CSSValueList> CSSParser::parseFilter() +{ + if (!m_valueList) + return 0; + + // The filter is a list of functional primitives that specify individual operations. + RefPtr<CSSValueList> list = CSSValueList::createSpaceSeparated(); + for (CSSParserValue* value = m_valueList->current(); value; value = m_valueList->next()) { + if (value->unit != CSSPrimitiveValue::CSS_URI && (value->unit != CSSParserValue::Function || !value->function)) + return 0; + + WebKitCSSFilterValue::FilterOperationType filterType = WebKitCSSFilterValue::UnknownFilterOperation; + + // See if the specified primitive is one we understand. + if (value->unit == CSSPrimitiveValue::CSS_URI) { + RefPtr<WebKitCSSFilterValue> referenceFilterValue = WebKitCSSFilterValue::create(WebKitCSSFilterValue::ReferenceFilterOperation); + list->append(referenceFilterValue); + referenceFilterValue->append(cssValuePool()->createValue(value->string, CSSPrimitiveValue::CSS_STRING)); + } else { + const CSSParserString name = value->function->name; + unsigned maximumArgumentCount = 1; + + filterInfoForName(name, filterType, maximumArgumentCount); + + if (filterType == WebKitCSSFilterValue::UnknownFilterOperation) + return 0; + +#if ENABLE(CSS_SHADERS) + if (filterType == WebKitCSSFilterValue::CustomFilterOperation) { + RefPtr<WebKitCSSFilterValue> filterValue = parseCustomFilter(value); + if (!filterValue) + return 0; + list->append(filterValue.release()); + continue; + } +#endif + CSSParserValueList* args = value->function->args.get(); + if (!args) + return 0; + + RefPtr<WebKitCSSFilterValue> filterValue = parseBuiltinFilterArguments(args, filterType); + if (!filterValue) + return 0; + + list->append(filterValue); + } + } + + return list.release(); +} +#endif + +static bool validFlowName(const String& flowName) +{ + if (equalIgnoringCase(flowName, "auto") + || equalIgnoringCase(flowName, "default") + || equalIgnoringCase(flowName, "inherit") + || equalIgnoringCase(flowName, "initial") + || equalIgnoringCase(flowName, "none")) + return false; + return true; +} + +// auto | <ident> +bool CSSParser::parseFlowThread(int propId, bool important) +{ + ASSERT(propId == CSSPropertyWebkitFlowInto); + + if (m_valueList->size() != 1) + return false; + + CSSParserValue* value = m_valueList->current(); + if (!value) + return false; + + if (value->unit != CSSPrimitiveValue::CSS_IDENT) + return false; + + if (value->id == CSSValueAuto) { + addProperty(propId, cssValuePool()->createIdentifierValue(value->id), important); + return true; + } + + String inputProperty = String(value->string); + if (!inputProperty.isEmpty()) { + if (!validFlowName(inputProperty)) + return false; + addProperty(propId, cssValuePool()->createValue(inputProperty, CSSPrimitiveValue::CSS_STRING), important); + } else + addProperty(propId, cssValuePool()->createIdentifierValue(CSSValueAuto), important); + + return true; +} + +// -webkit-flow-from: none | <ident> +bool CSSParser::parseRegionThread(int propId, bool important) +{ + ASSERT(propId == CSSPropertyWebkitFlowFrom); + + if (m_valueList->size() != 1) + return false; + + CSSParserValue* value = m_valueList->current(); + if (!value) + return false; + + if (value->unit != CSSPrimitiveValue::CSS_IDENT) + return false; + + if (value->id == CSSValueNone) + addProperty(propId, cssValuePool()->createIdentifierValue(value->id), important); + else { + String inputProperty = String(value->string); + if (!inputProperty.isEmpty()) { + if (!validFlowName(inputProperty)) + return false; + addProperty(propId, cssValuePool()->createValue(inputProperty, CSSPrimitiveValue::CSS_STRING), important); + } else + addProperty(propId, cssValuePool()->createIdentifierValue(CSSValueNone), important); + } + + return true; +} + +bool CSSParser::parseTransformOrigin(int propId, int& propId1, int& propId2, int& propId3, RefPtr<CSSValue>& value, RefPtr<CSSValue>& value2, RefPtr<CSSValue>& value3) +{ + propId1 = propId; + propId2 = propId; + propId3 = propId; + if (propId == CSSPropertyWebkitTransformOrigin) { + propId1 = CSSPropertyWebkitTransformOriginX; + propId2 = CSSPropertyWebkitTransformOriginY; + propId3 = CSSPropertyWebkitTransformOriginZ; + } + + switch (propId) { + case CSSPropertyWebkitTransformOrigin: + if (!parseTransformOriginShorthand(value, value2, value3)) + return false; + // parseTransformOriginShorthand advances the m_valueList pointer + break; + case CSSPropertyWebkitTransformOriginX: { + value = parseFillPositionX(m_valueList.get()); + if (value) + m_valueList->next(); + break; + } + case CSSPropertyWebkitTransformOriginY: { + value = parseFillPositionY(m_valueList.get()); + if (value) + m_valueList->next(); + break; + } + case CSSPropertyWebkitTransformOriginZ: { + if (validUnit(m_valueList->current(), FLength, m_strict)) + value = createPrimitiveNumericValue(m_valueList->current()); + if (value) + m_valueList->next(); + break; + } + } + + return value; +} + +bool CSSParser::parsePerspectiveOrigin(int propId, int& propId1, int& propId2, RefPtr<CSSValue>& value, RefPtr<CSSValue>& value2) +{ + propId1 = propId; + propId2 = propId; + if (propId == CSSPropertyWebkitPerspectiveOrigin) { + propId1 = CSSPropertyWebkitPerspectiveOriginX; + propId2 = CSSPropertyWebkitPerspectiveOriginY; + } + + switch (propId) { + case CSSPropertyWebkitPerspectiveOrigin: + parseFillPosition(m_valueList.get(), value, value2); + break; + case CSSPropertyWebkitPerspectiveOriginX: { + value = parseFillPositionX(m_valueList.get()); + if (value) + m_valueList->next(); + break; + } + case CSSPropertyWebkitPerspectiveOriginY: { + value = parseFillPositionY(m_valueList.get()); + if (value) + m_valueList->next(); + break; + } + } + + return value; +} + +bool CSSParser::parseTextEmphasisStyle(bool important) +{ + unsigned valueListSize = m_valueList->size(); + + RefPtr<CSSPrimitiveValue> fill; + RefPtr<CSSPrimitiveValue> shape; + + for (CSSParserValue* value = m_valueList->current(); value; value = m_valueList->next()) { + if (value->unit == CSSPrimitiveValue::CSS_STRING) { + if (fill || shape || (valueListSize != 1 && !inShorthand())) + return false; + addProperty(CSSPropertyWebkitTextEmphasisStyle, createPrimitiveStringValue(value), important); + m_valueList->next(); + return true; + } + + if (value->id == CSSValueNone) { + if (fill || shape || (valueListSize != 1 && !inShorthand())) + return false; + addProperty(CSSPropertyWebkitTextEmphasisStyle, cssValuePool()->createIdentifierValue(CSSValueNone), important); + m_valueList->next(); + return true; + } + + if (value->id == CSSValueOpen || value->id == CSSValueFilled) { + if (fill) + return false; + fill = cssValuePool()->createIdentifierValue(value->id); + } else if (value->id == CSSValueDot || value->id == CSSValueCircle || value->id == CSSValueDoubleCircle || value->id == CSSValueTriangle || value->id == CSSValueSesame) { + if (shape) + return false; + shape = cssValuePool()->createIdentifierValue(value->id); + } else if (!inShorthand()) + return false; + else + break; + } + + if (fill && shape) { + RefPtr<CSSValueList> parsedValues = CSSValueList::createSpaceSeparated(); + parsedValues->append(fill.release()); + parsedValues->append(shape.release()); + addProperty(CSSPropertyWebkitTextEmphasisStyle, parsedValues.release(), important); + return true; + } + if (fill) { + addProperty(CSSPropertyWebkitTextEmphasisStyle, fill.release(), important); + return true; + } + if (shape) { + addProperty(CSSPropertyWebkitTextEmphasisStyle, shape.release(), important); + return true; + } + + return false; +} + +bool CSSParser::parseLineBoxContain(bool important) +{ + LineBoxContain lineBoxContain = LineBoxContainNone; + + for (CSSParserValue* value = m_valueList->current(); value; value = m_valueList->next()) { + if (value->id == CSSValueBlock) { + if (lineBoxContain & LineBoxContainBlock) + return false; + lineBoxContain |= LineBoxContainBlock; + } else if (value->id == CSSValueInline) { + if (lineBoxContain & LineBoxContainInline) + return false; + lineBoxContain |= LineBoxContainInline; + } else if (value->id == CSSValueFont) { + if (lineBoxContain & LineBoxContainFont) + return false; + lineBoxContain |= LineBoxContainFont; + } else if (value->id == CSSValueGlyphs) { + if (lineBoxContain & LineBoxContainGlyphs) + return false; + lineBoxContain |= LineBoxContainGlyphs; + } else if (value->id == CSSValueReplaced) { + if (lineBoxContain & LineBoxContainReplaced) + return false; + lineBoxContain |= LineBoxContainReplaced; + } else if (value->id == CSSValueInlineBox) { + if (lineBoxContain & LineBoxContainInlineBox) + return false; + lineBoxContain |= LineBoxContainInlineBox; + } else + return false; + } + + if (!lineBoxContain) + return false; + + addProperty(CSSPropertyWebkitLineBoxContain, CSSLineBoxContainValue::create(lineBoxContain), important); + return true; +} + +bool CSSParser::parseFontFeatureTag(CSSValueList* settings) +{ + // Feature tag name consists of 4-letter characters. + static const int tagNameLength = 4; + + CSSParserValue* value = m_valueList->current(); + // Feature tag name comes first + if (value->unit != CSSPrimitiveValue::CSS_STRING && value->unit != CSSPrimitiveValue::CSS_IDENT) + return false; + if (value->string.length != tagNameLength) + return false; + for (int i = 0; i < tagNameLength; ++i) { + // Limits the range of characters to 0x20-0x7E, following the tag name rules defiend in the OpenType specification. + UChar character = value->string.characters[i]; + if (character < 0x20 || character > 0x7E) + return false; + } + + String tag = value->string; + int tagValue = 1; + // Feature tag values could follow: <integer> | on | off + value = m_valueList->next(); + if (value) { + if (value->unit == CSSPrimitiveValue::CSS_NUMBER && value->isInt && value->fValue >= 0) { + tagValue = clampToInteger(value->fValue); + if (tagValue < 0) + return false; + m_valueList->next(); + } else if (value->id == CSSValueOn || value->id == CSSValueOff) { + tagValue = value->id == CSSValueOn; + m_valueList->next(); + } + } + settings->append(FontFeatureValue::create(tag, tagValue)); + return true; +} + +bool CSSParser::parseFontFeatureSettings(bool important) +{ + if (m_valueList->size() == 1 && m_valueList->current()->id == CSSValueNormal) { + RefPtr<CSSPrimitiveValue> normalValue = cssValuePool()->createIdentifierValue(CSSValueNormal); + m_valueList->next(); + addProperty(CSSPropertyWebkitFontFeatureSettings, normalValue.release(), important); + return true; + } + + RefPtr<CSSValueList> settings = CSSValueList::createCommaSeparated(); + for (CSSParserValue* value = m_valueList->current(); value; value = m_valueList->next()) { + if (!parseFontFeatureTag(settings.get())) + return false; + + // If the list isn't parsed fully, the current value should be comma. + value = m_valueList->current(); + if (value && !(value->unit == CSSParserValue::Operator && value->iValue == ',')) + return false; + } + if (settings->length()) { + addProperty(CSSPropertyWebkitFontFeatureSettings, settings.release(), important); + return true; + } + return false; +} + +static inline int yyerror(const char*) { return 1; } + +#define END_TOKEN 0 + +#include "CSSGrammar.h" + +int CSSParser::lex(void* yylvalWithoutType) +{ + YYSTYPE* yylval = static_cast<YYSTYPE*>(yylvalWithoutType); + int length; + + lex(); + + UChar* t = text(&length); + + switch (token()) { + case WHITESPACE: + case SGML_CD: + case INCLUDES: + case DASHMATCH: + break; + + case URI: + case STRING: + case IDENT: + case NTH: + case HEX: + case IDSEL: + case DIMEN: + case UNICODERANGE: + case FUNCTION: + case ANYFUNCTION: + case NOTFUNCTION: + case CALCFUNCTION: + case MINFUNCTION: + case MAXFUNCTION: + yylval->string.characters = t; + yylval->string.length = length; + break; + + case IMPORT_SYM: + case PAGE_SYM: + case MEDIA_SYM: + case FONT_FACE_SYM: + case CHARSET_SYM: + case NAMESPACE_SYM: + case WEBKIT_KEYFRAMES_SYM: + + case IMPORTANT_SYM: + break; + + case QEMS: + length--; + case GRADS: + case TURNS: + length--; + case DEGS: + case RADS: + case KHERTZ: + case REMS: + length--; + case MSECS: + case HERTZ: + case EMS: + case EXS: + case PXS: + case CMS: + case MMS: + case INS: + case PTS: + case PCS: + length--; + case SECS: + case PERCENTAGE: + length--; + case FLOATTOKEN: + case INTEGER: + yylval->number = charactersToDouble(t, length); + break; + + default: + break; + } + + return token(); +} + +void CSSParser::recheckAtKeyword(const UChar* str, int len) +{ + String ruleName(str, len); + if (equalIgnoringCase(ruleName, "@import")) + yyTok = IMPORT_SYM; + else if (equalIgnoringCase(ruleName, "@page")) + yyTok = PAGE_SYM; + else if (equalIgnoringCase(ruleName, "@media")) + yyTok = MEDIA_SYM; + else if (equalIgnoringCase(ruleName, "@font-face")) + yyTok = FONT_FACE_SYM; + else if (equalIgnoringCase(ruleName, "@charset")) + yyTok = CHARSET_SYM; + else if (equalIgnoringCase(ruleName, "@namespace")) + yyTok = NAMESPACE_SYM; + else if (equalIgnoringCase(ruleName, "@-webkit-keyframes")) + yyTok = WEBKIT_KEYFRAMES_SYM; + else if (equalIgnoringCase(ruleName, "@-webkit-mediaquery")) + yyTok = WEBKIT_MEDIAQUERY_SYM; +} + +UChar* CSSParser::text(int *length) +{ + UChar* start = yytext; + int l = yyleng; + switch (yyTok) { + case STRING: + l--; + /* nobreak */ + case HEX: + case IDSEL: + start++; + l--; + break; + case URI: + // "url("{w}{string}{w}")" + // "url("{w}{url}{w}")" + // strip "url(" and ")" + start += 4; + l -= 5; + // strip {w} + while (l && isHTMLSpace(*start)) { + ++start; + --l; + } + while (l && isHTMLSpace(start[l - 1])) + --l; + if (l && (*start == '"' || *start == '\'')) { + ASSERT(l >= 2 && start[l - 1] == *start); + ++start; + l -= 2; + } + break; + default: + break; + } + + // process escapes + UChar* out = start; + UChar* escape = 0; + + bool sawEscape = false; + + for (int i = 0; i < l; i++) { + UChar* current = start + i; + if (escape == current - 1) { + if (isASCIIHexDigit(*current)) + continue; + if (yyTok == STRING && + (*current == '\n' || *current == '\r' || *current == '\f')) { + // ### handle \r\n case + if (*current != '\r') + escape = 0; + continue; + } + // in all other cases copy the char to output + // ### + *out++ = *current; + escape = 0; + continue; + } + if (escape == current - 2 && yyTok == STRING && + *(current-1) == '\r' && *current == '\n') { + escape = 0; + continue; + } + if (escape > current - 7 && isASCIIHexDigit(*current)) + continue; + if (escape) { + // add escaped char + unsigned uc = 0; + escape++; + while (escape < current) { + uc *= 16; + uc += toASCIIHexValue(*escape); + escape++; + } + // can't handle chars outside ucs2 + if (uc > 0xffff) + uc = 0xfffd; + *out++ = uc; + escape = 0; + if (isHTMLSpace(*current)) + continue; + } + if (!escape && *current == '\\') { + escape = current; + sawEscape = true; + continue; + } + *out++ = *current; + } + if (escape) { + // add escaped char + unsigned uc = 0; + escape++; + while (escape < start+l) { + uc *= 16; + uc += toASCIIHexValue(*escape); + escape++; + } + // can't handle chars outside ucs2 + if (uc > 0xffff) + uc = 0xfffd; + *out++ = uc; + } + + *length = out - start; + + // If we have an unrecognized @-keyword, and if we handled any escapes at all, then + // we should attempt to adjust yyTok to the correct type. + if (yyTok == ATKEYWORD && sawEscape) + recheckAtKeyword(start, *length); + + return start; +} + +void CSSParser::countLines() +{ + for (UChar* current = yytext; current < yytext + yyleng; ++current) { + if (*current == '\n') + ++m_lineNumber; + } +} + +CSSParserSelector* CSSParser::createFloatingSelector() +{ + CSSParserSelector* selector = new CSSParserSelector; + m_floatingSelectors.add(selector); + return selector; +} + +PassOwnPtr<CSSParserSelector> CSSParser::sinkFloatingSelector(CSSParserSelector* selector) +{ + if (selector) { + ASSERT(m_floatingSelectors.contains(selector)); + m_floatingSelectors.remove(selector); + } + return adoptPtr(selector); +} + +Vector<OwnPtr<CSSParserSelector> >* CSSParser::createFloatingSelectorVector() +{ + Vector<OwnPtr<CSSParserSelector> >* selectorVector = new Vector<OwnPtr<CSSParserSelector> >; + m_floatingSelectorVectors.add(selectorVector); + return selectorVector; +} + +PassOwnPtr<Vector<OwnPtr<CSSParserSelector> > > CSSParser::sinkFloatingSelectorVector(Vector<OwnPtr<CSSParserSelector> >* selectorVector) +{ + if (selectorVector) { + ASSERT(m_floatingSelectorVectors.contains(selectorVector)); + m_floatingSelectorVectors.remove(selectorVector); + } + return adoptPtr(selectorVector); +} + +CSSParserValueList* CSSParser::createFloatingValueList() +{ + CSSParserValueList* list = new CSSParserValueList; + m_floatingValueLists.add(list); + return list; +} + +PassOwnPtr<CSSParserValueList> CSSParser::sinkFloatingValueList(CSSParserValueList* list) +{ + if (list) { + ASSERT(m_floatingValueLists.contains(list)); + m_floatingValueLists.remove(list); + } + return adoptPtr(list); +} + +CSSParserFunction* CSSParser::createFloatingFunction() +{ + CSSParserFunction* function = new CSSParserFunction; + m_floatingFunctions.add(function); + return function; +} + +PassOwnPtr<CSSParserFunction> CSSParser::sinkFloatingFunction(CSSParserFunction* function) +{ + if (function) { + ASSERT(m_floatingFunctions.contains(function)); + m_floatingFunctions.remove(function); + } + return adoptPtr(function); +} + +CSSParserValue& CSSParser::sinkFloatingValue(CSSParserValue& value) +{ + if (value.unit == CSSParserValue::Function) { + ASSERT(m_floatingFunctions.contains(value.function)); + m_floatingFunctions.remove(value.function); + } + return value; +} + +MediaQueryExp* CSSParser::createFloatingMediaQueryExp(const AtomicString& mediaFeature, CSSParserValueList* values) +{ + m_floatingMediaQueryExp = MediaQueryExp::create(mediaFeature, values); + return m_floatingMediaQueryExp.get(); +} + +PassOwnPtr<MediaQueryExp> CSSParser::sinkFloatingMediaQueryExp(MediaQueryExp* expression) +{ + ASSERT_UNUSED(expression, expression == m_floatingMediaQueryExp); + return m_floatingMediaQueryExp.release(); +} + +Vector<OwnPtr<MediaQueryExp> >* CSSParser::createFloatingMediaQueryExpList() +{ + m_floatingMediaQueryExpList = adoptPtr(new Vector<OwnPtr<MediaQueryExp> >); + return m_floatingMediaQueryExpList.get(); +} + +PassOwnPtr<Vector<OwnPtr<MediaQueryExp> > > CSSParser::sinkFloatingMediaQueryExpList(Vector<OwnPtr<MediaQueryExp> >* list) +{ + ASSERT_UNUSED(list, list == m_floatingMediaQueryExpList); + return m_floatingMediaQueryExpList.release(); +} + +MediaQuery* CSSParser::createFloatingMediaQuery(MediaQuery::Restrictor restrictor, const String& mediaType, PassOwnPtr<Vector<OwnPtr<MediaQueryExp> > > expressions) +{ + m_floatingMediaQuery = adoptPtr(new MediaQuery(restrictor, mediaType, expressions)); + return m_floatingMediaQuery.get(); +} + +MediaQuery* CSSParser::createFloatingMediaQuery(PassOwnPtr<Vector<OwnPtr<MediaQueryExp> > > expressions) +{ + return createFloatingMediaQuery(MediaQuery::None, "all", expressions); +} + +PassOwnPtr<MediaQuery> CSSParser::sinkFloatingMediaQuery(MediaQuery* query) +{ + ASSERT_UNUSED(query, query == m_floatingMediaQuery); + return m_floatingMediaQuery.release(); +} + +MediaList* CSSParser::createMediaList() +{ + RefPtr<MediaList> list = MediaList::create(); + MediaList* result = list.get(); + m_parsedMediaLists.append(list.release()); + return result; +} + +CSSRule* CSSParser::createCharsetRule(const CSSParserString& charset) +{ + if (!m_styleSheet) + return 0; + RefPtr<CSSCharsetRule> rule = CSSCharsetRule::create(m_styleSheet, charset); + CSSCharsetRule* result = rule.get(); + m_parsedRules.append(rule.release()); + return result; +} + +CSSRule* CSSParser::createImportRule(const CSSParserString& url, MediaList* media) +{ + if (!media || !m_styleSheet || !m_allowImportRules) + return 0; + RefPtr<CSSImportRule> rule = CSSImportRule::create(m_styleSheet, url, media); + CSSImportRule* result = rule.get(); + m_parsedRules.append(rule.release()); + return result; +} + +CSSRule* CSSParser::createMediaRule(MediaList* media, CSSRuleList* rules) +{ + if (!media || !rules || !m_styleSheet) + return 0; + m_allowImportRules = m_allowNamespaceDeclarations = false; + RefPtr<CSSMediaRule> rule = CSSMediaRule::create(m_styleSheet, media, rules); + CSSMediaRule* result = rule.get(); + m_parsedRules.append(rule.release()); + return result; +} + +CSSRuleList* CSSParser::createRuleList() +{ + RefPtr<CSSRuleList> list = CSSRuleList::create(); + CSSRuleList* listPtr = list.get(); + + m_parsedRuleLists.append(list.release()); + return listPtr; +} + +WebKitCSSKeyframesRule* CSSParser::createKeyframesRule() +{ + m_allowImportRules = m_allowNamespaceDeclarations = false; + RefPtr<WebKitCSSKeyframesRule> rule = WebKitCSSKeyframesRule::create(m_styleSheet); + WebKitCSSKeyframesRule* rulePtr = rule.get(); + m_parsedRules.append(rule.release()); + return rulePtr; +} + +CSSRule* CSSParser::createStyleRule(Vector<OwnPtr<CSSParserSelector> >* selectors) +{ + CSSStyleRule* result = 0; + markRuleBodyEnd(); + if (selectors) { + m_allowImportRules = m_allowNamespaceDeclarations = false; + RefPtr<CSSStyleRule> rule = CSSStyleRule::create(m_styleSheet, m_lastSelectorLineNumber); + rule->adoptSelectorVector(*selectors); + if (m_hasFontFaceOnlyValues) + deleteFontFaceOnlyValues(); + rule->setDeclaration(CSSMutableStyleDeclaration::create(rule.get(), m_parsedProperties, m_numParsedProperties)); + result = rule.get(); + m_parsedRules.append(rule.release()); + if (m_ruleRangeMap) { + ASSERT(m_currentRuleData); + m_currentRuleData->styleSourceData->styleBodyRange = m_ruleBodyRange; + m_currentRuleData->selectorListRange = m_selectorListRange; + m_ruleRangeMap->set(result, m_currentRuleData.release()); + m_currentRuleData = CSSRuleSourceData::create(); + m_currentRuleData->styleSourceData = CSSStyleSourceData::create(); + m_inStyleRuleOrDeclaration = false; + } + } + resetSelectorListMarks(); + resetRuleBodyMarks(); + clearProperties(); + return result; +} + +CSSRule* CSSParser::createFontFaceRule() +{ + m_allowImportRules = m_allowNamespaceDeclarations = false; + for (unsigned i = 0; i < m_numParsedProperties; ++i) { + CSSProperty* property = m_parsedProperties[i]; + int id = property->id(); + if ((id == CSSPropertyFontWeight || id == CSSPropertyFontStyle || id == CSSPropertyFontVariant) && property->value()->isPrimitiveValue()) { + RefPtr<CSSValue> value = property->m_value.release(); + property->m_value = CSSValueList::createCommaSeparated(); + static_cast<CSSValueList*>(property->value())->append(value.release()); + } else if (id == CSSPropertyFontFamily && (!property->value()->isValueList() || static_cast<CSSValueList*>(property->value())->length() != 1)) { + // Unlike font-family property, font-family descriptor in @font-face rule + // has to be a value list with exactly one family name. It cannot have a + // have 'initial' value and cannot 'inherit' from parent. + // See http://dev.w3.org/csswg/css3-fonts/#font-family-desc + clearProperties(); + return 0; + } + } + RefPtr<CSSFontFaceRule> rule = CSSFontFaceRule::create(m_styleSheet); + rule->setDeclaration(CSSMutableStyleDeclaration::create(rule.get(), m_parsedProperties, m_numParsedProperties)); + clearProperties(); + CSSFontFaceRule* result = rule.get(); + m_parsedRules.append(rule.release()); + return result; +} + +void CSSParser::addNamespace(const AtomicString& prefix, const AtomicString& uri) +{ + if (!m_styleSheet || !m_allowNamespaceDeclarations) + return; + m_allowImportRules = false; + m_styleSheet->addNamespace(this, prefix, uri); +} + +void CSSParser::updateSpecifiersWithElementName(const AtomicString& namespacePrefix, const AtomicString& elementName, CSSParserSelector* specifiers) +{ + AtomicString determinedNamespace = namespacePrefix != nullAtom && m_styleSheet ? m_styleSheet->determineNamespace(namespacePrefix) : m_defaultNamespace; + QualifiedName tag = QualifiedName(namespacePrefix, elementName, determinedNamespace); + if (!specifiers->isUnknownPseudoElement()) { + specifiers->setTag(tag); + return; + } + + CSSParserSelector* lastShadowDescendant = specifiers; + CSSParserSelector* history = specifiers; + while (history->tagHistory()) { + history = history->tagHistory(); + if (history->hasShadowDescendant()) + lastShadowDescendant = history; + } + + if (lastShadowDescendant->tagHistory()) { + lastShadowDescendant->tagHistory()->setTag(tag); + return; + } + + // For shadow-ID pseudo-elements to be correctly matched, the ShadowDescendant combinator has to be used. + // We therefore create a new Selector with that combinator here in any case, even if matching any (host) element in any namespace (i.e. '*'). + OwnPtr<CSSParserSelector> elementNameSelector = adoptPtr(new CSSParserSelector); + elementNameSelector->setTag(tag); + lastShadowDescendant->setTagHistory(elementNameSelector.release()); + lastShadowDescendant->setRelation(CSSSelector::ShadowDescendant); +} + +CSSParserSelector* CSSParser::updateSpecifiers(CSSParserSelector* specifiers, CSSParserSelector* newSpecifier) +{ + if (newSpecifier->isUnknownPseudoElement()) { + // Unknown pseudo element always goes at the top of selector chain. + newSpecifier->appendTagHistory(CSSSelector::ShadowDescendant, sinkFloatingSelector(specifiers)); + return newSpecifier; + } + if (specifiers->isUnknownPseudoElement()) { + // Specifiers for unknown pseudo element go right behind it in the chain. + specifiers->insertTagHistory(CSSSelector::SubSelector, sinkFloatingSelector(newSpecifier), CSSSelector::ShadowDescendant); + return specifiers; + } + specifiers->appendTagHistory(CSSSelector::SubSelector, sinkFloatingSelector(newSpecifier)); + return specifiers; +} + +CSSRule* CSSParser::createPageRule(PassOwnPtr<CSSParserSelector> pageSelector) +{ + // FIXME: Margin at-rules are ignored. + m_allowImportRules = m_allowNamespaceDeclarations = false; + CSSPageRule* pageRule = 0; + if (pageSelector) { + RefPtr<CSSPageRule> rule = CSSPageRule::create(m_styleSheet, m_lastSelectorLineNumber); + Vector<OwnPtr<CSSParserSelector> > selectorVector; + selectorVector.append(pageSelector); + rule->adoptSelectorVector(selectorVector); + rule->setDeclaration(CSSMutableStyleDeclaration::create(rule.get(), m_parsedProperties, m_numParsedProperties)); + pageRule = rule.get(); + m_parsedRules.append(rule.release()); + } + clearProperties(); + return pageRule; +} + +void CSSParser::setReusableRegionSelectorVector(Vector<OwnPtr<CSSParserSelector> >* selectors) +{ + if (selectors) + m_reusableRegionSelectorVector.swap(*selectors); +} + +CSSRule* CSSParser::createRegionRule(Vector<OwnPtr<CSSParserSelector> >* regionSelector, CSSRuleList* rules) +{ + if (!regionSelector || !rules) + return 0; + + m_allowImportRules = m_allowNamespaceDeclarations = false; + + RefPtr<WebKitCSSRegionRule> regionRule = WebKitCSSRegionRule::create(m_styleSheet, regionSelector, rules); + + WebKitCSSRegionRule* result = regionRule.get(); + m_parsedRules.append(regionRule.release()); + + return result; +} + +CSSRule* CSSParser::createMarginAtRule(CSSSelector::MarginBoxType /* marginBox */) +{ + // FIXME: Implement margin at-rule here, using: + // - marginBox: margin box + // - m_parsedProperties: properties at [m_numParsedPropertiesBeforeMarginBox, m_numParsedProperties) are for this at-rule. + // Don't forget to also update the action for page symbol in CSSGrammar.y such that margin at-rule data is cleared if page_selector is invalid. + + endDeclarationsForMarginBox(); + return 0; // until this method is implemented. +} + +void CSSParser::startDeclarationsForMarginBox() +{ + m_numParsedPropertiesBeforeMarginBox = m_numParsedProperties; +} + +void CSSParser::endDeclarationsForMarginBox() +{ + ASSERT(m_numParsedPropertiesBeforeMarginBox != INVALID_NUM_PARSED_PROPERTIES); + rollbackLastProperties(m_numParsedProperties - m_numParsedPropertiesBeforeMarginBox); + m_numParsedPropertiesBeforeMarginBox = INVALID_NUM_PARSED_PROPERTIES; +} + +void CSSParser::deleteFontFaceOnlyValues() +{ + ASSERT(m_hasFontFaceOnlyValues); + int deletedProperties = 0; + + for (unsigned i = 0; i < m_numParsedProperties; ++i) { + CSSProperty* property = m_parsedProperties[i]; + int id = property->id(); + if ((id == CSSPropertyFontWeight || id == CSSPropertyFontStyle || id == CSSPropertyFontVariant) && property->value()->isValueList()) { + delete property; + deletedProperties++; + } else if (deletedProperties) + m_parsedProperties[i - deletedProperties] = m_parsedProperties[i]; + } + + m_numParsedProperties -= deletedProperties; +} + +WebKitCSSKeyframeRule* CSSParser::createKeyframeRule(CSSParserValueList* keys) +{ + // Create a key string from the passed keys + String keyString; + for (unsigned i = 0; i < keys->size(); ++i) { + float key = static_cast<float>(keys->valueAt(i)->fValue); + if (i != 0) + keyString += ","; + keyString += String::number(key); + keyString += "%"; + } + + RefPtr<WebKitCSSKeyframeRule> keyframe = WebKitCSSKeyframeRule::create(m_styleSheet); + keyframe->setKeyText(keyString); + keyframe->setDeclaration(CSSMutableStyleDeclaration::create(keyframe.get(), m_parsedProperties, m_numParsedProperties)); + + clearProperties(); + + WebKitCSSKeyframeRule* keyframePtr = keyframe.get(); + m_parsedRules.append(keyframe.release()); + return keyframePtr; +} + +void CSSParser::invalidBlockHit() +{ + if (m_styleSheet && !m_hadSyntacticallyValidCSSRule) + m_styleSheet->setHasSyntacticallyValidCSSHeader(false); +} + +void CSSParser::updateLastSelectorLineAndPosition() +{ + m_lastSelectorLineNumber = m_lineNumber; + markRuleBodyStart(); +} + +void CSSParser::updateLastMediaLine(MediaList* media) +{ + media->setLastLine(m_lineNumber); +} + +void CSSParser::markSelectorListStart() +{ + m_selectorListRange.start = yytext - m_data.get(); +} + +void CSSParser::markSelectorListEnd() +{ + if (!m_currentRuleData) + return; + UChar* listEnd = yytext; + while (listEnd > m_data.get() + 1) { + if (isHTMLSpace(*(listEnd - 1))) + --listEnd; + else + break; + } + m_selectorListRange.end = listEnd - m_data.get(); +} + +void CSSParser::markRuleBodyStart() +{ + unsigned offset = yytext - m_data.get(); + if (*yytext == '{') + ++offset; // Skip the rule body opening brace. + if (offset > m_ruleBodyRange.start) + m_ruleBodyRange.start = offset; + m_inStyleRuleOrDeclaration = true; +} + +void CSSParser::markRuleBodyEnd() +{ + unsigned offset = yytext - m_data.get(); + if (offset > m_ruleBodyRange.end) + m_ruleBodyRange.end = offset; +} + +void CSSParser::markPropertyStart() +{ + if (!m_inStyleRuleOrDeclaration) + return; + m_propertyRange.start = yytext - m_data.get(); +} + +void CSSParser::markPropertyEnd(bool isImportantFound, bool isPropertyParsed) +{ + if (!m_inStyleRuleOrDeclaration) + return; + unsigned offset = yytext - m_data.get(); + if (*yytext == ';') // Include semicolon into the property text. + ++offset; + m_propertyRange.end = offset; + if (m_propertyRange.start != UINT_MAX && m_currentRuleData) { + // This stuff is only executed when the style data retrieval is requested by client. + const unsigned start = m_propertyRange.start; + const unsigned end = m_propertyRange.end; + ASSERT(start < end); + String propertyString = String(m_data.get() + start, end - start).stripWhiteSpace(); + if (propertyString.endsWith(";", true)) + propertyString = propertyString.left(propertyString.length() - 1); + size_t colonIndex = propertyString.find(":"); + ASSERT(colonIndex != notFound); + + String name = propertyString.left(colonIndex).stripWhiteSpace(); + String value = propertyString.substring(colonIndex + 1, propertyString.length()).stripWhiteSpace(); + // The property range is relative to the declaration start offset. + m_currentRuleData->styleSourceData->propertyData.append( + CSSPropertySourceData(name, value, isImportantFound, isPropertyParsed, SourceRange(start - m_ruleBodyRange.start, end - m_ruleBodyRange.start))); + } + resetPropertyMarks(); +} + +static int cssPropertyID(const UChar* propertyName, unsigned length) +{ + if (!length) + return 0; + if (length > maxCSSPropertyNameLength) + return 0; + + char buffer[maxCSSPropertyNameLength + 1 + 1]; // 1 to turn "apple"/"khtml" into "webkit", 1 for null character + + for (unsigned i = 0; i != length; ++i) { + UChar c = propertyName[i]; + if (c == 0 || c >= 0x7F) + return 0; // illegal character + buffer[i] = toASCIILower(c); + } + buffer[length] = '\0'; + + const char* name = buffer; + if (buffer[0] == '-') { + // If the prefix is -apple- or -khtml-, change it to -webkit-. + // This makes the string one character longer. + if (hasPrefix(buffer, length, "-apple-") || hasPrefix(buffer, length, "-khtml-")) { + memmove(buffer + 7, buffer + 6, length + 1 - 6); + memcpy(buffer, "-webkit", 7); + ++length; + } + +#if PLATFORM(IOS) + if (!strcmp(buffer, "-webkit-hyphenate-locale")) { + // Worked in iOS 4.2. + const char* const webkitLocale = "-webkit-locale"; + name = webkitLocale; + length = strlen(webkitLocale); + } +#endif + } + + const Property* hashTableEntry = findProperty(name, length); + return hashTableEntry ? hashTableEntry->id : 0; +} + +int cssPropertyID(const String& string) +{ + return cssPropertyID(string.characters(), string.length()); +} + +int cssPropertyID(const CSSParserString& string) +{ + return cssPropertyID(string.characters, string.length); +} + +int cssValueKeywordID(const CSSParserString& string) +{ + unsigned length = string.length; + if (!length) + return 0; + if (length > maxCSSValueKeywordLength) + return 0; + + char buffer[maxCSSValueKeywordLength + 1 + 1]; // 1 to turn "apple"/"khtml" into "webkit", 1 for null character + + for (unsigned i = 0; i != length; ++i) { + UChar c = string.characters[i]; + if (c == 0 || c >= 0x7F) + return 0; // illegal character + 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. + if (hasPrefix(buffer, length, "-apple-") || 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 ? hashTableEntry->id : 0; +} + +// "ident" from the CSS tokenizer, minus backslash-escape sequences +static bool isCSSTokenizerIdentifier(const String& string) +{ + const UChar* p = string.characters(); + const UChar* end = p + string.length(); + + // -? + if (p != end && p[0] == '-') + ++p; + + // {nmstart} + if (p == end || !(p[0] == '_' || p[0] >= 128 || isASCIIAlpha(p[0]))) + return false; + ++p; + + // {nmchar}* + for (; p != end; ++p) { + if (!(p[0] == '_' || p[0] == '-' || p[0] >= 128 || isASCIIAlphanumeric(p[0]))) + return false; + } + + return true; +} + +// "url" from the CSS tokenizer, minus backslash-escape sequences +static bool isCSSTokenizerURL(const String& string) +{ + const UChar* p = string.characters(); + const UChar* end = p + string.length(); + + for (; p != end; ++p) { + UChar c = p[0]; + switch (c) { + case '!': + case '#': + case '$': + case '%': + case '&': + break; + default: + if (c < '*') + return false; + if (c <= '~') + break; + if (c < 128) + return false; + } + } + + return true; +} + +// We use single quotes for now because markup.cpp uses double quotes. +String quoteCSSString(const String& string) +{ + // For efficiency, we first pre-calculate the length of the quoted string, then we build the actual one. + // Please see below for the actual logic. + unsigned quotedStringSize = 2; // Two quotes surrounding the entire string. + bool afterEscape = false; + for (unsigned i = 0; i < string.length(); ++i) { + UChar ch = string[i]; + if (ch == '\\' || ch == '\'') { + quotedStringSize += 2; + afterEscape = false; + } else if (ch < 0x20 || ch == 0x7F) { + quotedStringSize += 2 + (ch >= 0x10); + afterEscape = true; + } else { + quotedStringSize += 1 + (afterEscape && (isASCIIHexDigit(ch) || ch == ' ')); + afterEscape = false; + } + } + + StringBuffer<UChar> buffer(quotedStringSize); + unsigned index = 0; + buffer[index++] = '\''; + afterEscape = false; + for (unsigned i = 0; i < string.length(); ++i) { + UChar ch = string[i]; + if (ch == '\\' || ch == '\'') { + buffer[index++] = '\\'; + buffer[index++] = ch; + afterEscape = false; + } else if (ch < 0x20 || ch == 0x7F) { // Control characters. + buffer[index++] = '\\'; + placeByteAsHexCompressIfPossible(ch, buffer, index, Lowercase); + afterEscape = true; + } else { + // Space character may be required to separate backslash-escape sequence and normal characters. + if (afterEscape && (isASCIIHexDigit(ch) || ch == ' ')) + buffer[index++] = ' '; + buffer[index++] = ch; + afterEscape = false; + } + } + buffer[index++] = '\''; + + ASSERT(quotedStringSize == index); + return String::adopt(buffer); +} + +String quoteCSSStringIfNeeded(const String& string) +{ + return isCSSTokenizerIdentifier(string) ? string : quoteCSSString(string); +} + +String quoteCSSURLIfNeeded(const String& string) +{ + return isCSSTokenizerURL(string) ? string : quoteCSSString(string); +} + +bool isValidNthToken(const CSSParserString& token) +{ + // The tokenizer checks for the construct of an+b. + // However, since the {ident} rule precedes the {nth} rule, some of those + // tokens are identified as string literal. Furthermore we need to accept + // "odd" and "even" which does not match to an+b. + return equalIgnoringCase(token, "odd") || equalIgnoringCase(token, "even") + || equalIgnoringCase(token, "n") || equalIgnoringCase(token, "-n"); +} + +#define YY_DECL int CSSParser::lex() +#define yyconst const +typedef int yy_state_type; +typedef unsigned YY_CHAR; +// The following line makes sure we treat non-Latin-1 Unicode characters correctly. +#define YY_SC_TO_UI(c) (c > 0xff ? 0xff : c) +#define YY_DO_BEFORE_ACTION \ + yytext = yy_bp; \ + yyleng = (int) (yy_cp - yy_bp); \ + yy_hold_char = *yy_cp; \ + *yy_cp = 0; \ + yy_c_buf_p = yy_cp; +#define YY_BREAK break; +#define ECHO +#define YY_RULE_SETUP +#define INITIAL 0 +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) +#define yyterminate() yyTok = END_TOKEN; return yyTok +#define YY_FATAL_ERROR(a) +// The following line is needed to build the tokenizer with a condition stack. +// The macro is used in the tokenizer grammar with lines containing +// BEGIN(mediaqueries) and BEGIN(initial). yy_start acts as index to +// tokenizer transition table, and 'mediaqueries' and 'initial' are +// offset multipliers that specify which transitions are active +// in the tokenizer during in each condition (tokenizer state). +#define BEGIN yy_start = 1 + 2 * + +#include "tokenizer.cpp" + +} |
