/* * 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 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 "CSSBorderImageValue.h" #include "CSSCanvasValue.h" #include "CSSCharsetRule.h" #include "CSSCursorImageValue.h" #include "CSSFontFaceRule.h" #include "CSSFontFaceSrcValue.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 "CSSPrimitiveValueCache.h" #include "CSSProperty.h" #include "CSSPropertyNames.h" #include "CSSPropertySourceData.h" #include "CSSQuirkPrimitiveValue.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 "Counter.h" #include "Document.h" #include "FloatConversion.h" #include "FontFamilyValue.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" #include "WebKitCSSKeyframeRule.h" #include "WebKitCSSKeyframesRule.h" #include "WebKitCSSTransformValue.h" #include <limits.h> #include <wtf/HexNumber.h> #include <wtf/dtoa.h> #include <wtf/text/StringBuffer.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; #ifdef ANDROID_INSTRUMENT #include "TimeCounter.h" #endif 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; } CSSParser::CSSParser(bool strictParsing) : m_strict(strictParsing) , m_important(false) , m_id(0) , m_styleSheet(0) , m_valueList(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) , m_data(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); delete m_valueList; fastFree(m_data); 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; fastFree(m_data); m_data = static_cast<UChar*>(fastMalloc(length * sizeof(UChar))); for (unsigned i = 0; i < strlen(prefix); i++) m_data[i] = prefix[i]; memcpy(m_data + 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 = yy_c_buf_p = m_data; yy_hold_char = *yy_c_buf_p; resetRuleBodyMarks(); } void CSSParser::parseSheet(CSSStyleSheet* sheet, const String& string, int startLineNumber, StyleRuleRangeMap* ruleRangeMap) { #ifdef ANDROID_INSTRUMENT android::TimeCounter::start(android::TimeCounter::CSSParseTimeCounter); #endif 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; #ifdef ANDROID_INSTRUMENT android::TimeCounter::record(android::TimeCounter::CSSParseTimeCounter, __FUNCTION__); #endif } PassRefPtr<CSSRule> CSSParser::parseRule(CSSStyleSheet* sheet, const String& string) { #ifdef ANDROID_INSTRUMENT android::TimeCounter::start(android::TimeCounter::CSSParseTimeCounter); #endif setStyleSheet(sheet); m_allowNamespaceDeclarations = false; setupParser("@-webkit-rule{", string, "} "); cssyyparse(this); #ifdef ANDROID_INSTRUMENT android::TimeCounter::record(android::TimeCounter::CSSParseTimeCounter, __FUNCTION__); #endif return m_rule.release(); } PassRefPtr<CSSRule> CSSParser::parseKeyframeRule(CSSStyleSheet *sheet, const String &string) { #ifdef ANDROID_INSTRUMENT android::TimeCounter::start(android::TimeCounter::CSSParseTimeCounter); #endif setStyleSheet(sheet); setupParser("@-webkit-keyframe-rule{ ", string, "} "); cssyyparse(this); #ifdef ANDROID_INSTRUMENT android::TimeCounter::record(android::TimeCounter::CSSParseTimeCounter, __FUNCTION__); #endif 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) { 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 = static_cast<CSSStyleSheet*>(declaration->stylesheet()); if (!stylesheet || !stylesheet->document()) return false; if (validPrimitive) { CSSProperty property(propertyId, stylesheet->document()->cssPrimitiveValueCache()->createIdentifierValue(valueID), important); declaration->addParsedProperty(property); return true; } RGBA32 color; if (!CSSParser::parseColor(string, color, strict && string[0] != '#')) return false; CSSProperty property(propertyId, stylesheet->document()->cssPrimitiveValueCache()->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: 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) { 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] == 'p' && characters[length - 1] == '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 = static_cast<CSSStyleSheet*>(declaration->stylesheet()); if (!stylesheet || !stylesheet->document()) return false; CSSProperty property(propertyId, stylesheet->document()->cssPrimitiveValueCache()->createValue(number, unit), important); declaration->addParsedProperty(property); return true; } 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) { #ifdef ANDROID_INSTRUMENT android::TimeCounter::start(android::TimeCounter::CSSParseTimeCounter); #endif ASSERT(!declaration->stylesheet() || declaration->stylesheet()->isCSSStyleSheet()); setStyleSheet(static_cast<CSSStyleSheet*>(declaration->stylesheet())); 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(); } #ifdef ANDROID_INSTRUMENT android::TimeCounter::record(android::TimeCounter::CSSParseTimeCounter, __FUNCTION__); #endif return ok; } // color will only be changed when string contains a valid css color, making it // possible to set up a default color. bool CSSParser::parseColor(RGBA32& color, const String& string, bool strict) { // First try creating a color specified by name, rgba(), rgb() or "#" syntax. if (parseColor(string, color, strict)) return true; CSSParser parser(true); RefPtr<CSSMutableStyleDeclaration> dummyStyleDeclaration = CSSMutableStyleDeclaration::create(); // Now try to create a color from rgba() syntax. if (!parser.parseColor(dummyStyleDeclaration.get(), string)) return false; CSSValue* value = parser.m_parsedProperties[0]->value(); if (value->cssValueType() != CSSValue::CSS_PRIMITIVE_VALUE) return false; CSSPrimitiveValue* primitiveValue = static_cast<CSSPrimitiveValue*>(value); if (primitiveValue->primitiveType() != CSSPrimitiveValue::CSS_RGBCOLOR) return false; color = primitiveValue->getRGBA32Value(); return true; } bool CSSParser::parseColor(CSSMutableStyleDeclaration* declaration, const String& string) { #ifdef ANDROID_INSTRUMENT android::TimeCounter::start(android::TimeCounter::CSSParseTimeCounter); #endif ASSERT(!declaration->stylesheet() || declaration->stylesheet()->isCSSStyleSheet()); setStyleSheet(static_cast<CSSStyleSheet*>(declaration->stylesheet())); setupParser("@-webkit-decls{color:", string, "} "); cssyyparse(this); m_rule = 0; #ifdef ANDROID_INSTRUMENT android::TimeCounter::record(android::TimeCounter::CSSParseTimeCounter, __FUNCTION__); #endif 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) { #ifdef ANDROID_INSTRUMENT android::TimeCounter::start(android::TimeCounter::CSSParseTimeCounter); #endif 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()); #ifdef ANDROID_INSTRUMENT android::TimeCounter::record(android::TimeCounter::CSSParseTimeCounter, __FUNCTION__); #endif } bool CSSParser::parseDeclaration(CSSMutableStyleDeclaration* declaration, const String& string, RefPtr<CSSStyleSourceData>* styleSourceData) { #ifdef ANDROID_INSTRUMENT android::TimeCounter::start(android::TimeCounter::CSSParseTimeCounter); #endif // Length of the "@-webkit-decls{" prefix. static const unsigned prefixLength = 15; ASSERT(!declaration->stylesheet() || declaration->stylesheet()->isCSSStyleSheet()); setStyleSheet(static_cast<CSSStyleSheet*>(declaration->stylesheet())); 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; } #ifdef ANDROID_INSTRUMENT android::TimeCounter::record(android::TimeCounter::CSSParseTimeCounter, __FUNCTION__); #endif return ok; } bool CSSParser::parseMediaQuery(MediaList* queries, const String& string) { if (string.isEmpty()) return true; #ifdef ANDROID_INSTRUMENT android::TimeCounter::start(android::TimeCounter::CSSParseTimeCounter); #endif 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()); } #ifdef ANDROID_INSTRUMENT android::TimeCounter::record(android::TimeCounter::CSSParseTimeCounter, __FUNCTION__); #endif return ok; } void CSSParser::addProperty(int propId, PassRefPtr<CSSValue> value, bool important) { OwnPtr<CSSProperty> prop(new CSSProperty(propId, value, important, m_currentShorthand, m_implicitShorthand)); if (m_numParsedProperties >= m_maxParsedProperties) { m_maxParsedProperties += 32; if (m_maxParsedProperties > UINT_MAX / sizeof(CSSProperty*)) return; 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; m_primitiveValueCache = document() ? document()->cssPrimitiveValueCache() : CSSPrimitiveValueCache::create(); } Document* CSSParser::document() const { StyleBase* root = m_styleSheet; while (root && root->parent()) root = root->parent(); if (!root) return 0; if (!root->isCSSStyleSheet()) return 0; return static_cast<CSSStyleSheet*>(root)->document(); } 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; } 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; } } 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, CSSInheritedValue::create(), important); return true; } else if (id == CSSValueInitial) { if (num != 1) return false; addProperty(propId, CSSInitialValue::createExplicit(), 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 | inherit if (id == CSSValueNormal || id == CSSValueEmbed || id == CSSValueBidiOverride || id == CSSValueWebkitIsolate) 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: if (id == CSSValueAuto || id == CSSValueAlways || id == CSSValueAvoid || id == CSSValueLeft || id == CSSValueRight) validPrimitive = true; break; case CSSPropertyPageBreakInside: // avoid | auto | inherit case CSSPropertyWebkitColumnBreakInside: 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 | box | inline-box | none | inherit #if ENABLE(WCSS) if ((id >= CSSValueInline && id <= CSSValueWapMarquee) || id == CSSValueNone) #else if ((id >= CSSValueInline && id <= CSSValueWebkitInlineBox) || id == CSSValueNone) #endif validPrimitive = true; 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 | inherit + center for buggy CSS if (id == CSSValueLeft || id == CSSValueRight || id == CSSValueNone || id == CSSValueCenter) 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(primitiveValueCache()->createIdentifierValue(CSSValuePointer)); else if (value && ((value->id >= CSSValueAuto && value->id <= CSSValueWebkitGrabbing) || value->id == CSSValueCopy || value->id == CSSValueNone)) list->append(primitiveValueCache()->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.set(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 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(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 // ### handle multilength case where we allow relative units validPrimitive = (!id && validUnit(value, FLength | FPercent | FNonNeg, m_strict)); 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(primitiveValueCache()->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 CSSPropertyWebkitBorderImage: case CSSPropertyWebkitMaskBoxImage: if (id == CSSValueNone) validPrimitive = true; else { RefPtr<CSSValue> result; if (parseBorderImage(propId, important, 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, m_strict); if (!validPrimitive) return false; RefPtr<CSSPrimitiveValue> parsedValue1 = primitiveValueCache()->createValue(value->fValue, (CSSPrimitiveValue::UnitTypes)value->unit); RefPtr<CSSPrimitiveValue> parsedValue2; if (num == 2) { value = m_valueList->next(); validPrimitive = validUnit(value, FLength | FPercent, m_strict); if (!validPrimitive) return false; parsedValue2 = primitiveValueCache()->createValue(value->fValue, (CSSPrimitiveValue::UnitTypes)value->unit); } else parsedValue2 = parsedValue1; RefPtr<Pair> pair = Pair::create(parsedValue1.release(), parsedValue2.release()); RefPtr<CSSPrimitiveValue> val = primitiveValueCache()->createValue(pair.release()); addProperty(propId, val.release(), important); return true; } 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 return parseShadow(propId, important); 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; 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; #if ENABLE(WCSS) case CSSPropertyWapMarqueeDir: if (id == CSSValueLtr || id == CSSValueRtl) validPrimitive = true; break; case CSSPropertyWapMarqueeStyle: if (id == CSSValueNone || id == CSSValueSlide || id == CSSValueScroll || id == CSSValueAlternate) validPrimitive = true; break; case CSSPropertyWapMarqueeLoop: if (id == CSSValueInfinite) validPrimitive = true; else validPrimitive = validUnit(value, FInteger | FNonNeg, m_strict); break; case CSSPropertyWapMarqueeSpeed: if (id == CSSValueNormal || id == CSSValueSlow || id == CSSValueFast) validPrimitive = true; else validPrimitive = validUnit(value, FTime | FInteger | FNonNeg, m_strict); break; #endif 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 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 = primitiveValueCache()->createValue(value->fValue, (CSSPrimitiveValue::UnitTypes)value->unit); 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; } 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 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; // 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 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 /* 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 }; return parseShorthand(propId, properties, 3, important); } 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 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; #if ENABLE(WCSS) case CSSPropertyWapInputFormat: validPrimitive = true; break; case CSSPropertyWapInputRequired: parsedValue = parseWCSSInputProperty(); break; #endif // 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; #ifdef ANDROID_CSS_RING case CSSPropertyWebkitRing: { const int properties[9] = { CSSPropertyWebkitRingFillColor, CSSPropertyWebkitRingInnerWidth, CSSPropertyWebkitRingOuterWidth, CSSPropertyWebkitRingOutset, CSSPropertyWebkitRingPressedInnerColor, CSSPropertyWebkitRingPressedOuterColor, CSSPropertyWebkitRingRadius, CSSPropertyWebkitRingSelectedInnerColor, CSSPropertyWebkitRingSelectedOuterColor }; return parseShorthand(propId, properties, 9, important); } case CSSPropertyWebkitRingFillColor: case CSSPropertyWebkitRingPressedInnerColor: case CSSPropertyWebkitRingPressedOuterColor: case CSSPropertyWebkitRingSelectedInnerColor: case CSSPropertyWebkitRingSelectedOuterColor: parsedValue = parseColor(); if (parsedValue) m_valueList->next(); break; case CSSPropertyWebkitRingInnerWidth: case CSSPropertyWebkitRingOuterWidth: case CSSPropertyWebkitRingOutset: case CSSPropertyWebkitRingRadius: validPrimitive = validUnit(value, FLength | FNonNeg, m_strict); break; #endif #ifdef ANDROID_CSS_TAP_HIGHLIGHT_COLOR case CSSPropertyWebkitTapHighlightColor: parsedValue = parseColor(); if (parsedValue) m_valueList->next(); break; #endif #if ENABLE(SVG) default: return parseSVGValue(propId, important); #endif } if (validPrimitive) { if (id != 0) parsedValue = primitiveValueCache()->createIdentifierValue(id); else if (value->unit == CSSPrimitiveValue::CSS_STRING) parsedValue = primitiveValueCache()->createValue(value->string, (CSSPrimitiveValue::UnitTypes) value->unit); else if (value->unit >= CSSPrimitiveValue::CSS_NUMBER && value->unit <= CSSPrimitiveValue::CSS_KHZ) parsedValue = primitiveValueCache()->createValue(value->fValue, (CSSPrimitiveValue::UnitTypes) value->unit); else if (value->unit >= CSSPrimitiveValue::CSS_TURN && value->unit <= CSSPrimitiveValue::CSS_REMS) parsedValue = primitiveValueCache()->createValue(value->fValue, (CSSPrimitiveValue::UnitTypes) value->unit); else if (value->unit >= CSSParserValue::Q_EMS) parsedValue = CSSQuirkPrimitiveValue::create(value->fValue, CSSPrimitiveValue::CSS_EMS); m_valueList->next(); } if (parsedValue) { if (!m_valueList->current() || inShorthand()) { addProperty(propId, parsedValue.release(), important); return true; } } return false; } #if ENABLE(WCSS) PassRefPtr<CSSValue> CSSParser::parseWCSSInputProperty() { RefPtr<CSSValue> parsedValue = 0; CSSParserValue* value = m_valueList->current(); String inputProperty; if (value->unit == CSSPrimitiveValue::CSS_STRING || value->unit == CSSPrimitiveValue::CSS_IDENT) inputProperty = String(value->string); if (!inputProperty.isEmpty()) parsedValue = primitiveValueCache()->createValue(inputProperty, CSSPrimitiveValue::CSS_STRING); while (m_valueList->next()) { // pass all other values, if any. If we don't do this, // the parser will think that it's not done and won't process this property } return parsedValue; } #endif 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, CSSPrimitiveValueCache* primitiveValueCache) { if (parserValue->id == CSSValueBorderBox || parserValue->id == CSSValuePaddingBox || parserValue->id == CSSValueContentBox || parserValue->id == CSSValueWebkitText) { cssValue = primitiveValueCache->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], CSSInitialValue::createImplicit()); if (properties[i] == CSSPropertyBackgroundPosition || properties[i] == CSSPropertyWebkitMaskPosition) addFillValue(positionYValue, CSSInitialValue::createImplicit()); if (properties[i] == CSSPropertyBackgroundRepeat || properties[i] == CSSPropertyWebkitMaskRepeat) addFillValue(repeatYValue, CSSInitialValue::createImplicit()); if ((properties[i] == CSSPropertyBackgroundOrigin || properties[i] == CSSPropertyWebkitMaskOrigin) && !parsedProperty[i]) { // If background-origin wasn't present, then reset background-clip also. addFillValue(clipValue, CSSInitialValue::createImplicit()); } } 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, primitiveValueCache())) addFillValue(clipValue, val1.release()); // The property parsed successfully. else addFillValue(clipValue, CSSInitialValue::createImplicit()); // 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], CSSInitialValue::createImplicit()); if (properties[i] == CSSPropertyBackgroundPosition || properties[i] == CSSPropertyWebkitMaskPosition) addFillValue(positionYValue, CSSInitialValue::createImplicit()); if (properties[i] == CSSPropertyBackgroundRepeat || properties[i] == CSSPropertyWebkitMaskRepeat) addFillValue(repeatYValue, CSSInitialValue::createImplicit()); if ((properties[i] == CSSPropertyBackgroundOrigin || properties[i] == CSSPropertyWebkitMaskOrigin) && !parsedProperty[i]) { // If background-origin wasn't present, then reset background-clip also. addFillValue(clipValue, CSSInitialValue::createImplicit()); } } } // 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], CSSInitialValue::createImplicit()); 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], CSSInitialValue::createImplicit()); } // 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], CSSInitialValue::createImplicit()); 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], CSSInitialValue::createImplicit()); } // 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. m_implicitShorthand = true; for (int i = 0; i < numProperties; ++i) { if (!fnd[i]) addProperty(properties[i], CSSInitialValue::createImplicit(), important); } m_implicitShorthand = false; 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(); m_implicitShorthand = true; addProperty(properties[1], value, important); addProperty(properties[2], value, important); addProperty(properties[3], value, important); m_implicitShorthand = false; break; } case 2: { if (!parseValue(properties[0], important) || !parseValue(properties[1], important)) return false; CSSValue *value = m_parsedProperties[m_numParsedProperties-2]->value(); m_implicitShorthand = true; addProperty(properties[2], value, important); value = m_parsedProperties[m_numParsedProperties-2]->value(); addProperty(properties[3], value, important); m_implicitShorthand = false; 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(); m_implicitShorthand = true; addProperty(properties[3], value, important); m_implicitShorthand = false; 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, primitiveValueCache()->createIdentifierValue(value->id), important); return true; } else if (value->id == 0 && value->unit == CSSPrimitiveValue::CSS_IDENT) { addProperty(propId, primitiveValueCache()->createValue(value->string, CSSPrimitiveValue::CSS_STRING), 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(primitiveValueCache()->createIdentifierValue(value->id)); return Auto; } return None; case CSSValueLandscape: case CSSValuePortrait: if (prevParamType == None || prevParamType == PageSize) { parsedValues->append(primitiveValueCache()->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(primitiveValueCache()->createIdentifierValue(value->id)); return PageSize; } return None; case 0: if (validUnit(value, FLength | FNonNeg, m_strict) && (prevParamType == None || prevParamType == Length)) { parsedValues->append(primitiveValueCache()->createValue(value->fValue, static_cast<CSSPrimitiveValue::UnitTypes>(value->unit))); 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(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 = primitiveValueCache()->createIdentifierValue(val->id); } } else if (val->unit == CSSPrimitiveValue::CSS_STRING) { parsedValue = primitiveValueCache()->createValue(val->string, CSSPrimitiveValue::CSS_STRING); } 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; if (document() && document()->isHTMLDocument()) attrName = attrName.lower(); return primitiveValueCache()->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 primitiveValueCache()->createIdentifierValue(id); return parseColor(); } bool CSSParser::parseFillImage(RefPtr<CSSValue>& value) { if (m_valueList->current()->id == CSSValueNone) { value = CSSImageValue::create(); return true; } if (m_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(m_valueList->current()->string)); return true; } if (isGeneratedImageValue(m_valueList->current())) return parseGeneratedImage(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 primitiveValueCache()->createValue(percent, CSSPrimitiveValue::CSS_PERCENTAGE); } if (validUnit(valueList->current(), FPercent | FLength, m_strict)) return primitiveValueCache()->createValue(valueList->current()->fValue, (CSSPrimitiveValue::UnitTypes)valueList->current()->unit); 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 primitiveValueCache()->createValue(percent, CSSPrimitiveValue::CSS_PERCENTAGE); } if (validUnit(valueList->current(), FPercent | FLength, m_strict)) return primitiveValueCache()->createValue(valueList->current()->fValue, (CSSPrimitiveValue::UnitTypes)valueList->current()->unit); 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 primitiveValueCache()->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 primitiveValueCache()->createValue(valueList->current()->fValue, (CSSPrimitiveValue::UnitTypes)valueList->current()->unit); } 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 = primitiveValueCache()->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 = primitiveValueCache()->createIdentifierValue(CSSValueRepeat); value2 = primitiveValueCache()->createIdentifierValue(CSSValueNoRepeat); m_valueList->next(); return; } if (id == CSSValueRepeatY) { m_implicitShorthand = true; value1 = primitiveValueCache()->createIdentifierValue(CSSValueNoRepeat); value2 = primitiveValueCache()->createIdentifierValue(CSSValueRepeat); m_valueList->next(); return; } if (id == CSSValueRepeat || id == CSSValueNoRepeat || id == CSSValueRound || id == CSSValueSpace) value1 = primitiveValueCache()->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 = primitiveValueCache()->createIdentifierValue(id); m_valueList->next(); } else { // If only one value was specified, value2 is the same as value1. m_implicitShorthand = true; value2 = primitiveValueCache()->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 primitiveValueCache()->createIdentifierValue(value->id); RefPtr<CSSPrimitiveValue> parsedValue1; if (value->id == CSSValueAuto) parsedValue1 = primitiveValueCache()->createValue(0, CSSPrimitiveValue::CSS_UNKNOWN); else { if (!validUnit(value, FLength | FPercent, m_strict)) return 0; parsedValue1 = primitiveValueCache()->createValue(value->fValue, (CSSPrimitiveValue::UnitTypes)value->unit); } CSSPropertyID property = static_cast<CSSPropertyID>(propId); RefPtr<CSSPrimitiveValue> parsedValue2; if ((value = m_valueList->next())) { if (value->id == CSSValueAuto) parsedValue2 = primitiveValueCache()->createValue(0, CSSPrimitiveValue::CSS_UNKNOWN); else if (value->unit == CSSParserValue::Operator && value->iValue == ',') allowComma = false; else { if (!validUnit(value, FLength | FPercent, m_strict)) return 0; parsedValue2 = primitiveValueCache()->createValue(value->fValue, (CSSPrimitiveValue::UnitTypes)value->unit); } } if (!parsedValue2) { if (property == CSSPropertyWebkitBackgroundSize || property == CSSPropertyWebkitMaskSize) parsedValue2 = parsedValue1; else parsedValue2 = primitiveValueCache()->createValue(0, CSSPrimitiveValue::CSS_UNKNOWN); } return primitiveValueCache()->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 = primitiveValueCache()->createIdentifierValue(val->id); m_valueList->next(); } break; case CSSPropertyBackgroundImage: case CSSPropertyWebkitMaskImage: if (parseFillImage(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 = primitiveValueCache()->createIdentifierValue(val->id); m_valueList->next(); } break; case CSSPropertyBackgroundClip: if (parseBackgroundClip(val, currValue, primitiveValueCache())) m_valueList->next(); break; case CSSPropertyBackgroundOrigin: if (val->id == CSSValueBorderBox || val->id == CSSValuePaddingBox || val->id == CSSValueContentBox) { currValue = primitiveValueCache()->createIdentifierValue(val->id); m_valueList->next(); } break; case CSSPropertyBackgroundPosition: case CSSPropertyWebkitMaskPosition: parseFillPosition(m_valueList, currValue, currValue2); // parseFillPosition advances the m_valueList pointer break; case CSSPropertyBackgroundPositionX: case CSSPropertyWebkitMaskPositionX: { currValue = parseFillPositionX(m_valueList); if (currValue) m_valueList->next(); break; } case CSSPropertyBackgroundPositionY: case CSSPropertyWebkitMaskPositionY: { currValue = parseFillPositionY(m_valueList); if (currValue) m_valueList->next(); break; } case CSSPropertyWebkitBackgroundComposite: case CSSPropertyWebkitMaskComposite: if ((val->id >= CSSValueClear && val->id <= CSSValuePlusLighter) || val->id == CSSValueHighlight) { currValue = primitiveValueCache()->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 primitiveValueCache()->createValue(value->fValue, (CSSPrimitiveValue::UnitTypes)value->unit); return 0; } PassRefPtr<CSSValue> CSSParser::parseAnimationDirection() { CSSParserValue* value = m_valueList->current(); if (value->id == CSSValueNormal || value->id == CSSValueAlternate) return primitiveValueCache()->createIdentifierValue(value->id); return 0; } PassRefPtr<CSSValue> CSSParser::parseAnimationDuration() { CSSParserValue* value = m_valueList->current(); if (validUnit(value, FTime | FNonNeg, m_strict)) return primitiveValueCache()->createValue(value->fValue, (CSSPrimitiveValue::UnitTypes)value->unit); 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 primitiveValueCache()->createIdentifierValue(value->id); return 0; } PassRefPtr<CSSValue> CSSParser::parseAnimationIterationCount() { CSSParserValue* value = m_valueList->current(); if (value->id == CSSValueInfinite) return primitiveValueCache()->createIdentifierValue(value->id); if (validUnit(value, FInteger | FNonNeg, m_strict)) return primitiveValueCache()->createValue(value->fValue, (CSSPrimitiveValue::UnitTypes)value->unit); 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 primitiveValueCache()->createIdentifierValue(CSSValueNone); } else { return primitiveValueCache()->createValue(value->string, CSSPrimitiveValue::CSS_STRING); } } return 0; } PassRefPtr<CSSValue> CSSParser::parseAnimationPlayState() { CSSParserValue* value = m_valueList->current(); if (value->id == CSSValueRunning || value->id == CSSValuePaused) return primitiveValueCache()->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 primitiveValueCache()->createIdentifierValue(result); if (equalIgnoringCase(value->string, "all")) return primitiveValueCache()->createIdentifierValue(CSSValueAll); if (equalIgnoringCase(value->string, "none")) return primitiveValueCache()->createIdentifierValue(CSSValueNone); return 0; } bool CSSParser::parseTransformOriginShorthand(RefPtr<CSSValue>& value1, RefPtr<CSSValue>& value2, RefPtr<CSSValue>& value3) { parseFillPosition(m_valueList, value1, value2); // now get z if (m_valueList->current()) { if (validUnit(m_valueList->current(), FLength, m_strict)) { value3 = primitiveValueCache()->createValue(m_valueList->current()->fValue, (CSSPrimitiveValue::UnitTypes)m_valueList->current()->unit); 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; if (result < 0 || result > 1.0) return false; 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 primitiveValueCache()->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 = (int) min(v->fValue, (double)INT_MAX); 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(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, primitiveValueCache()->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 = primitiveValueCache()->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 ? primitiveValueCache()->createIdentifierValue(CSSValueAuto) : primitiveValueCache()->createValue(arg->fValue, (CSSPrimitiveValue::UnitTypes) arg->unit); 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, primitiveValueCache()->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 = primitiveValueCache()->createValue(i->string, CSSPrimitiveValue::CSS_STRING); RefPtr<CSSPrimitiveValue> separator; if (!counters) separator = primitiveValueCache()->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 = primitiveValueCache()->createValue(i->string, (CSSPrimitiveValue::UnitTypes) i->unit); } RefPtr<CSSPrimitiveValue> listStyle; i = args->next(); if (!i) // Make the list style default decimal listStyle = primitiveValueCache()->createValue(CSSValueDecimal - CSSValueDisc, CSSPrimitiveValue::CSS_NUMBER); else { if (i->unit != CSSParserValue::Operator || i->iValue != ',') return 0; i = args->next(); if (i->unit != CSSPrimitiveValue::CSS_IDENT) return 0; short ls = 0; if (i->id == CSSValueNone) ls = CSSValueKatakanaIroha - CSSValueDisc + 1; else if (i->id >= CSSValueDisc && i->id <= CSSValueKatakanaIroha) ls = i->id - CSSValueDisc; else return 0; listStyle = primitiveValueCache()->createValue(ls, (CSSPrimitiveValue::UnitTypes) i->unit); } return primitiveValueCache()->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 ? primitiveValueCache()->createIdentifierValue(CSSValueAuto) : primitiveValueCache()->createValue(a->fValue, (CSSPrimitiveValue::UnitTypes) a->unit); 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, primitiveValueCache()->createValue(rect.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; 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) { // do nothing, it's the inital value for all three } else if (id == CSSValueItalic || id == CSSValueOblique) { if (font->style) return false; font->style = primitiveValueCache()->createIdentifierValue(id); } else if (id == CSSValueSmallCaps) { if (font->variant) return false; font->variant = primitiveValueCache()->createIdentifierValue(id); } else if (id >= CSSValueBold && id <= CSSValueLighter) { if (font->weight) return false; font->weight = primitiveValueCache()->createIdentifierValue(id); } else { valid = false; } } else if (!font->weight && validUnit(value, FInteger | FNonNeg, true)) { int weight = (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 = primitiveValueCache()->createIdentifierValue(val); else valid = false; } else { valid = false; } if (!valid) break; value = m_valueList->next(); } if (!value) return false; // set undefined values to default if (!font->style) font->style = primitiveValueCache()->createIdentifierValue(CSSValueNormal); if (!font->variant) font->variant = primitiveValueCache()->createIdentifierValue(CSSValueNormal); if (!font->weight) font->weight = primitiveValueCache()->createIdentifierValue(CSSValueNormal); // now a font size _must_ come // <absolute-size> | <relative-size> | <length> | <percentage> | inherit if (value->id >= CSSValueXxSmall && value->id <= CSSValueLarger) font->size = primitiveValueCache()->createIdentifierValue(value->id); else if (validUnit(value, FLength | FPercent | FNonNeg, m_strict)) font->size = primitiveValueCache()->createValue(value->fValue, (CSSPrimitiveValue::UnitTypes) value->unit); value = m_valueList->next(); if (!font->size || !value) return false; if (value->unit == CSSParserValue::Operator && value->iValue == '/') { // line-height value = m_valueList->next(); if (!value) return false; if (value->id == CSSValueNormal) { // default value, nothing to do } else if (validUnit(value, FNumber | FLength | FPercent | FNonNeg, m_strict)) font->lineHeight = primitiveValueCache()->createValue(value->fValue, (CSSPrimitiveValue::UnitTypes) value->unit); else return false; value = m_valueList->next(); if (!value) return false; } if (!font->lineHeight) font->lineHeight = primitiveValueCache()->createIdentifierValue(CSSValueNormal); // font family must come now font->family = parseFontFamily(); if (m_valueList->current() || !font->family) return false; addProperty(CSSPropertyFont, font.release(), important); 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(primitiveValueCache()->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 = primitiveValueCache()->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 = primitiveValueCache()->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 = primitiveValueCache()->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 = primitiveValueCache()->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 = primitiveValueCache()->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 = primitiveValueCache()->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 = primitiveValueCache()->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; } static bool isValidFormatFunction(CSSParserValue* val) { CSSParserValueList* args = val->function->args.get(); return equalIgnoringCase(val->function->name, "format(") && (args->current()->unit == CSSPrimitiveValue::CSS_STRING || args->current()->unit == CSSPrimitiveValue::CSS_IDENT); } bool CSSParser::parseFontFaceSrc() { RefPtr<CSSValueList> values(CSSValueList::createCommaSeparated()); CSSParserValue* val; bool expectComma = false; bool allowFormat = false; bool failed = false; RefPtr<CSSFontFaceSrcValue> uriValue; while ((val = m_valueList->current())) { RefPtr<CSSFontFaceSrcValue> parsedValue; if (val->unit == CSSPrimitiveValue::CSS_URI && !expectComma && m_styleSheet) { // FIXME: The completeURL call should be done when using the CSSFontFaceSrcValue, // not when creating it. parsedValue = CSSFontFaceSrcValue::create(m_styleSheet->completeURL(val->string)); uriValue = parsedValue; allowFormat = true; expectComma = true; } else if (val->unit == CSSParserValue::Function) { // There are two allowed functions: local() and format(). CSSParserValueList* args = val->function->args.get(); if (args && args->size() == 1) { if (equalIgnoringCase(val->function->name, "local(") && !expectComma && (args->current()->unit == CSSPrimitiveValue::CSS_STRING || args->current()->unit == CSSPrimitiveValue::CSS_IDENT)) { expectComma = true; allowFormat = false; CSSParserValue* a = args->current(); uriValue.clear(); parsedValue = CSSFontFaceSrcValue::createLocal(a->string); } else if (allowFormat && uriValue && isValidFormatFunction(val)) { expectComma = true; allowFormat = false; uriValue->setFormat(args->current()->string); uriValue.clear(); m_valueList->next(); continue; } } } else if (val->unit == CSSParserValue::Operator && val->iValue == ',' && expectComma) { expectComma = false; allowFormat = false; uriValue.clear(); m_valueList->next(); continue; } if (parsedValue) values->append(parsedValue.release()); else { failed = true; break; } m_valueList->next(); } if (values->length() && !failed) { addProperty(CSSPropertySrc, values.release(), m_important); m_valueList->next(); return true; } return false; } 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::parseColor(const String &name, RGBA32& rgb, 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 primitiveValueCache()->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", (int)(value->fValue+.5)); if (!CSSParser::parseColor(str, c, 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 (!CSSParser::parseColor(value->string, c, 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, CSSPrimitiveValueCache* primitiveValueCache) : property(prop) , m_primitiveValueCache(primitiveValueCache) , 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_primitiveValueCache->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_primitiveValueCache->createIdentifierValue(v->id); allowStyle = false; if (allowX) allowBreak = false; else { allowBlur = false; allowSpread = false; allowColor = false; } } CSSPropertyID property; CSSPrimitiveValueCache* m_primitiveValueCache; 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; }; bool CSSParser::parseShadow(int propId, bool important) { ShadowParseContext context(static_cast<CSSPropertyID>(propId), primitiveValueCache()); CSSParserValue* val; while ((val = m_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 false; #if ENABLE(SVG) // -webkit-svg-shadow does not support multiple values. if (static_cast<CSSPropertyID>(propId) == CSSPropertyWebkitSvgShadow) return false; #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 false; // A length is allowed here. Construct the value and add it. context.commitLength(val); } else if (val->id == CSSValueInset) { if (!context.allowStyle) return false; 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)); if (isColor) { if (!context.allowColor) return false; parsedColor = primitiveValueCache()->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 false; // 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()); } m_valueList->next(); } if (context.allowBreak) { context.commitValue(); if (context.values->length()) { addProperty(propId, context.values.release(), important); m_valueList->next(); return true; } } return false; } 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 = primitiveValueCache()->createValue(0, CSSPrimitiveValue::CSS_PX); else { if (!validUnit(val, FLength | FPercent, m_strict)) return false; offset = primitiveValueCache()->createValue(val->fValue, static_cast<CSSPrimitiveValue::UnitTypes>(val->unit)); } // Now for the mask. RefPtr<CSSValue> mask; val = m_valueList->next(); if (val) { if (!parseBorderImage(propId, important, mask)) return false; } RefPtr<CSSReflectValue> reflectValue = CSSReflectValue::create(direction, offset.release(), mask.release()); addProperty(propId, reflectValue.release(), important); m_valueList->next(); return true; } struct BorderImageParseContext { BorderImageParseContext(CSSPrimitiveValueCache* primitiveValueCache) : m_primitiveValueCache(primitiveValueCache) , m_allowBreak(false) , m_allowNumber(false) , m_allowSlash(false) , m_allowWidth(false) , m_allowRule(false) , m_borderTop(0) , m_borderRight(0) , m_borderBottom(0) , m_borderLeft(0) , m_horizontalRule(0) , m_verticalRule(0) {} bool allowBreak() const { return m_allowBreak; } bool allowNumber() const { return m_allowNumber; } bool allowSlash() const { return m_allowSlash; } bool allowWidth() const { return m_allowWidth; } bool allowRule() const { return m_allowRule; } void commitImage(PassRefPtr<CSSValue> image) { m_image = image; m_allowNumber = true; } void commitNumber(CSSParserValue* v) { PassRefPtr<CSSPrimitiveValue> val = m_primitiveValueCache->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_allowBreak = m_allowSlash = m_allowRule = true; m_allowNumber = !m_left; } void commitSlash() { m_allowBreak = m_allowSlash = m_allowNumber = false; m_allowWidth = true; } void commitWidth(CSSParserValue* val) { if (!m_borderTop) m_borderTop = val; else if (!m_borderRight) m_borderRight = val; else if (!m_borderBottom) m_borderBottom = val; else { ASSERT(!m_borderLeft); m_borderLeft = val; } m_allowBreak = m_allowRule = true; m_allowWidth = !m_borderLeft; } void commitRule(int keyword) { if (!m_horizontalRule) m_horizontalRule = keyword; else if (!m_verticalRule) m_verticalRule = keyword; m_allowRule = !m_verticalRule; } PassRefPtr<CSSValue> commitBorderImage(CSSParser* p, bool important) { // We need to clone and repeat values for any omissions. if (!m_right) { m_right = m_primitiveValueCache->createValue(m_top->getDoubleValue(), (CSSPrimitiveValue::UnitTypes)m_top->primitiveType()); m_bottom = m_primitiveValueCache->createValue(m_top->getDoubleValue(), (CSSPrimitiveValue::UnitTypes)m_top->primitiveType()); m_left = m_primitiveValueCache->createValue(m_top->getDoubleValue(), (CSSPrimitiveValue::UnitTypes)m_top->primitiveType()); } if (!m_bottom) { m_bottom = m_primitiveValueCache->createValue(m_top->getDoubleValue(), (CSSPrimitiveValue::UnitTypes)m_top->primitiveType()); m_left = m_primitiveValueCache->createValue(m_right->getDoubleValue(), (CSSPrimitiveValue::UnitTypes)m_right->primitiveType()); } if (!m_left) m_left = m_primitiveValueCache->createValue(m_right->getDoubleValue(), (CSSPrimitiveValue::UnitTypes)m_right->primitiveType()); // Now build a rect value to hold all four of our primitive values. RefPtr<Rect> rect = Rect::create(); rect->setTop(m_top); rect->setRight(m_right); rect->setBottom(m_bottom); rect->setLeft(m_left); // Fill in STRETCH as the default if it wasn't specified. if (!m_horizontalRule) m_horizontalRule = CSSValueStretch; // The vertical rule should match the horizontal rule if unspecified. if (!m_verticalRule) m_verticalRule = m_horizontalRule; // Now we have to deal with the border widths. The best way to deal with these is to actually put these values into a value // list and then make our parsing machinery do the parsing. if (m_borderTop) { CSSParserValueList newList; newList.addValue(*m_borderTop); if (m_borderRight) newList.addValue(*m_borderRight); if (m_borderBottom) newList.addValue(*m_borderBottom); if (m_borderLeft) newList.addValue(*m_borderLeft); CSSParserValueList* oldList = p->m_valueList; p->m_valueList = &newList; p->parseValue(CSSPropertyBorderWidth, important); p->m_valueList = oldList; } // Make our new border image value now. return CSSBorderImageValue::create(m_image, rect.release(), m_horizontalRule, m_verticalRule); } CSSPrimitiveValueCache* m_primitiveValueCache; bool m_allowBreak; bool m_allowNumber; bool m_allowSlash; bool m_allowWidth; bool m_allowRule; RefPtr<CSSValue> m_image; RefPtr<CSSPrimitiveValue> m_top; RefPtr<CSSPrimitiveValue> m_right; RefPtr<CSSPrimitiveValue> m_bottom; RefPtr<CSSPrimitiveValue> m_left; CSSParserValue* m_borderTop; CSSParserValue* m_borderRight; CSSParserValue* m_borderBottom; CSSParserValue* m_borderLeft; int m_horizontalRule; int m_verticalRule; }; bool CSSParser::parseBorderImage(int propId, bool important, RefPtr<CSSValue>& result) { // Look for an image initially. If the first value is not a URI, then we're done. BorderImageParseContext context(primitiveValueCache()); CSSParserValue* val = m_valueList->current(); 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(value)) context.commitImage(value); else return false; } else return false; while ((val = m_valueList->next())) { if (context.allowNumber() && validUnit(val, FInteger | FNonNeg | FPercent, true)) { context.commitNumber(val); } else if (propId == CSSPropertyWebkitBorderImage && context.allowSlash() && val->unit == CSSParserValue::Operator && val->iValue == '/') { context.commitSlash(); } else if (context.allowWidth() && (val->id == CSSValueThin || val->id == CSSValueMedium || val->id == CSSValueThick || validUnit(val, FLength, m_strict))) { context.commitWidth(val); } else if (context.allowRule() && (val->id == CSSValueStretch || val->id == CSSValueRound || val->id == CSSValueRepeat)) { context.commitRule(val->id); } else { // Something invalid was encountered. return false; } } if (context.allowNumber() && propId != CSSPropertyWebkitBorderImage) { // Allow the slices to be omitted for images that don't fit to a border. We just set the slices to be 0. context.m_top = primitiveValueCache()->createValue(0, CSSPrimitiveValue::CSS_NUMBER); context.m_allowBreak = true; } if (context.allowBreak()) { // Need to fully commit as a single value. result = context.commitBorderImage(this, important); return true; } return false; } 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, m_strict)) return false; RefPtr<CSSPrimitiveValue> radius = primitiveValueCache()->createValue(value->fValue, static_cast<CSSPrimitiveValue::UnitTypes>(value->unit)); 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]); m_implicitShorthand = true; addProperty(CSSPropertyBorderTopLeftRadius, primitiveValueCache()->createValue(Pair::create(radii[0][0].release(), radii[1][0].release())), important); addProperty(CSSPropertyBorderTopRightRadius, primitiveValueCache()->createValue(Pair::create(radii[0][1].release(), radii[1][1].release())), important); addProperty(CSSPropertyBorderBottomRightRadius, primitiveValueCache()->createValue(Pair::create(radii[0][2].release(), radii[1][2].release())), important); addProperty(CSSPropertyBorderBottomLeftRadius, primitiveValueCache()->createValue(Pair::create(radii[0][3].release(), radii[1][3].release())), important); m_implicitShorthand = false; 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 = primitiveValueCache()->createValue(val->string, CSSPrimitiveValue::CSS_STRING); 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(primitiveValueCache()->createValue(Pair::create(counterName.release(), primitiveValueCache()->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, CSSPrimitiveValueCache* primitiveValueCache) { RefPtr<CSSPrimitiveValue> result; if (a->unit == CSSPrimitiveValue::CSS_IDENT) { if ((equalIgnoringCase(a->string, "left") && horizontal) || (equalIgnoringCase(a->string, "top") && !horizontal)) result = primitiveValueCache->createValue(0., CSSPrimitiveValue::CSS_PERCENTAGE); else if ((equalIgnoringCase(a->string, "right") && horizontal) || (equalIgnoringCase(a->string, "bottom") && !horizontal)) result = primitiveValueCache->createValue(100., CSSPrimitiveValue::CSS_PERCENTAGE); else if (equalIgnoringCase(a->string, "center")) result = primitiveValueCache->createValue(50., CSSPrimitiveValue::CSS_PERCENTAGE); } else if (a->unit == CSSPrimitiveValue::CSS_NUMBER || a->unit == CSSPrimitiveValue::CSS_PERCENTAGE) result = primitiveValueCache->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->primitiveValueCache()->createValue(0, CSSPrimitiveValue::CSS_NUMBER); else stop.m_position = p->primitiveValueCache()->createValue(1, CSSPrimitiveValue::CSS_NUMBER); int id = args->current()->id; if (id == CSSValueWebkitText || (id >= CSSValueAqua && id <= CSSValueWindowtext) || id == CSSValueMenu) stop.m_color = p->primitiveValueCache()->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->primitiveValueCache()->createValue(stopArg->fValue / 100, CSSPrimitiveValue::CSS_NUMBER); else if (stopArg->unit == CSSPrimitiveValue::CSS_NUMBER) stop.m_position = p->primitiveValueCache()->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->primitiveValueCache()->createIdentifierValue(id); else stop.m_color = p->parseColor(stopArg); if (!stop.m_color) return false; } return true; } bool CSSParser::parseDeprecatedGradient(RefPtr<CSSValue>& gradient) { // Walk the arguments. CSSParserValueList* args = m_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, primitiveValueCache()); 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, primitiveValueCache()); 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(primitiveValueCache()->createValue(a->fValue, CSSPrimitiveValue::CSS_NUMBER)); // 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, primitiveValueCache()); 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, primitiveValueCache()); 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(primitiveValueCache()->createValue(a->fValue, CSSPrimitiveValue::CSS_NUMBER)); } // 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, CSSPrimitiveValueCache* primitiveValueCache) { 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 primitiveValueCache->createIdentifierValue(a->id); } static PassRefPtr<CSSPrimitiveValue> parseGradientColorOrKeyword(CSSParser* p, CSSParserValue* value) { int id = value->id; if (id == CSSValueWebkitText || (id >= CSSValueAqua && id <= CSSValueWindowtext) || id == CSSValueMenu) return p->primitiveValueCache()->createIdentifierValue(id); return p->parseColor(value); } bool CSSParser::parseLinearGradient(RefPtr<CSSValue>& gradient, CSSGradientRepeat repeating) { RefPtr<CSSLinearGradientValue> result = CSSLinearGradientValue::create(repeating); // Walk the arguments. CSSParserValueList* args = m_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(primitiveValueCache()->createValue(a->fValue, (CSSPrimitiveValue::UnitTypes)a->unit)); 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, primitiveValueCache()))) { if (isHorizontal) startX = location; else startY = location; a = args->next(); if (a) { if ((location = valueFromSideKeyword(a, isHorizontal, primitiveValueCache()))) { if (isHorizontal) { if (startX) return false; startX = location; } else { if (startY) return false; startY = location; } a = args->next(); } } expectComma = true; } if (!startX && !startY) startY = primitiveValueCache()->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(RefPtr<CSSValue>& gradient, CSSGradientRepeat repeating) { RefPtr<CSSRadialGradientValue> result = CSSRadialGradientValue::create(repeating); // Walk the arguments. CSSParserValueList* args = m_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 (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 = primitiveValueCache()->createIdentifierValue(a->id); foundValue = true; break; case CSSValueClosestSide: case CSSValueClosestCorner: case CSSValueFarthestSide: case CSSValueFarthestCorner: case CSSValueContain: case CSSValueCover: sizeValue = primitiveValueCache()->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 = primitiveValueCache()->createValue(a->fValue, (CSSPrimitiveValue::UnitTypes) a->unit); a = args->next(); if (!a) return false; expectComma = true; } if (validUnit(a, FLength | FPercent, m_strict)) { verticalSize = primitiveValueCache()->createValue(a->fValue, (CSSPrimitiveValue::UnitTypes) a->unit); 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 = primitiveValueCache()->createValue(a->fValue, (CSSPrimitiveValue::UnitTypes)a->unit); 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("); } bool CSSParser::parseGeneratedImage(RefPtr<CSSValue>& value) { CSSParserValue* val = m_valueList->current(); if (val->unit != CSSParserValue::Function) return false; if (equalIgnoringCase(val->function->name, "-webkit-gradient(")) return parseDeprecatedGradient(value); if (equalIgnoringCase(val->function->name, "-webkit-linear-gradient(")) return parseLinearGradient(value, NonRepeating); if (equalIgnoringCase(val->function->name, "-webkit-repeating-linear-gradient(")) return parseLinearGradient(value, Repeating); if (equalIgnoringCase(val->function->name, "-webkit-radial-gradient(")) return parseRadialGradient(value, NonRepeating); if (equalIgnoringCase(val->function->name, "-webkit-repeating-radial-gradient(")) return parseRadialGradient(value, Repeating); if (equalIgnoringCase(val->function->name, "-webkit-canvas(")) return parseCanvas(value); return false; } bool CSSParser::parseCanvas(RefPtr<CSSValue>& canvas) { RefPtr<CSSCanvasValue> result = CSSCanvasValue::create(); // Walk the arguments. CSSParserValueList* args = m_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(primitiveValueCache()->createValue(a->fValue, (CSSPrimitiveValue::UnitTypes) a->unit)); a = args->next(); if (!a) break; if (a->unit != CSSParserValue::Operator || a->iValue != ',') return 0; a = args->next(); argNumber++; } } return list.release(); } 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); if (value) m_valueList->next(); break; } case CSSPropertyWebkitTransformOriginY: { value = parseFillPositionY(m_valueList); if (value) m_valueList->next(); break; } case CSSPropertyWebkitTransformOriginZ: { if (validUnit(m_valueList->current(), FLength, m_strict)) value = primitiveValueCache()->createValue(m_valueList->current()->fValue, (CSSPrimitiveValue::UnitTypes)m_valueList->current()->unit); 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, value, value2); break; case CSSPropertyWebkitPerspectiveOriginX: { value = parseFillPositionX(m_valueList); if (value) m_valueList->next(); break; } case CSSPropertyWebkitPerspectiveOriginY: { value = parseFillPositionY(m_valueList); 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, primitiveValueCache()->createValue(value->string, CSSPrimitiveValue::CSS_STRING), important); m_valueList->next(); return true; } if (value->id == CSSValueNone) { if (fill || shape || (valueListSize != 1 && !inShorthand())) return false; addProperty(CSSPropertyWebkitTextEmphasisStyle, primitiveValueCache()->createIdentifierValue(CSSValueNone), important); m_valueList->next(); return true; } if (value->id == CSSValueOpen || value->id == CSSValueFilled) { if (fill) return false; fill = primitiveValueCache()->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 = primitiveValueCache()->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; } 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; } CSSParserValueList* CSSParser::sinkFloatingValueList(CSSParserValueList* list) { if (list) { ASSERT(m_floatingValueLists.contains(list)); m_floatingValueLists.remove(list); } return list; } CSSParserFunction* CSSParser::createFloatingFunction() { CSSParserFunction* function = new CSSParserFunction; m_floatingFunctions.add(function); return function; } CSSParserFunction* CSSParser::sinkFloatingFunction(CSSParserFunction* function) { if (function) { ASSERT(m_floatingFunctions.contains(function)); m_floatingFunctions.remove(function); } return 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_parsedStyleObjects.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_parsedStyleObjects.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_parsedStyleObjects.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_parsedStyleObjects.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_parsedStyleObjects.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_parsedStyleObjects.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_parsedStyleObjects.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; } specifiers->setRelation(CSSSelector::ShadowDescendant); if (CSSParserSelector* history = specifiers->tagHistory()) { history->setTag(tag); return; } // No need to create an extra element name selector if we are matching any element // in any namespace. if (elementName == starAtom && m_defaultNamespace == starAtom) return; CSSParserSelector* elementNameSelector = new CSSParserSelector; elementNameSelector->setTag(tag); specifiers->setTagHistory(elementNameSelector); } 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_parsedStyleObjects.append(rule.release()); } clearProperties(); return pageRule; } 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 = (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(0, m_parsedProperties, m_numParsedProperties)); clearProperties(); WebKitCSSKeyframeRule* keyframePtr = keyframe.get(); m_parsedStyleObjects.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::markSelectorListStart() { m_selectorListRange.start = yytext - m_data; } void CSSParser::markSelectorListEnd() { if (!m_currentRuleData) return; UChar* listEnd = yytext; while (listEnd > m_data + 1) { if (isHTMLSpace(*(listEnd - 1))) --listEnd; else break; } m_selectorListRange.end = listEnd - m_data; } void CSSParser::markRuleBodyStart() { unsigned offset = yytext - m_data; 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; if (offset > m_ruleBodyRange.end) m_ruleBodyRange.end = offset; } void CSSParser::markPropertyStart() { if (!m_inStyleRuleOrDeclaration) return; m_propertyRange.start = yytext - m_data; } void CSSParser::markPropertyEnd(bool isImportantFound, bool isPropertyParsed) { if (!m_inStyleRuleOrDeclaration) return; unsigned offset = yytext - m_data; 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 + start, end - start).stripWhiteSpace(); if (propertyString.endsWith(";", true)) propertyString = propertyString.left(propertyString.length() - 1); Vector<String> propertyComponents; 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 (hasPrefix(buffer, length, "-webkit")) { if (!strcmp(buffer, "-webkit-box-sizing")) { // -webkit-box-sizing worked in Safari 4 and earlier. const char* const boxSizing = "box-sizing"; name = boxSizing; length = strlen(boxSizing); } else if (!strcmp(buffer, "-webkit-opacity")) { // Honor -webkit-opacity as a synonym for opacity. // This was the only syntax that worked in Safari 1.1, and may be in use on some websites and widgets. const char* const opacity = "opacity"; name = opacity; length = strlen(opacity); #if PLATFORM(IOS) } else if (!strcmp(buffer, "-webkit-hyphenate-locale")) { // Worked in iOS 4.2. const char* const webkitLocale = "-webkit-locale"; name = webkitLocale; length = strlen(webkitLocale); #endif } else if (hasPrefix(buffer + 7, length - 7, "-border-")) { // -webkit-border-*-*-radius worked in Safari 4 and earlier. -webkit-border-radius syntax // differs from border-radius, so it is remains as a distinct property. if (!strcmp(buffer + 15, "top-left-radius") || !strcmp(buffer + 15, "top-right-radius") || !strcmp(buffer + 15, "bottom-right-radius") || !strcmp(buffer + 15, "bottom-left-radius")) { name = buffer + 8; length -= 8; } } } } 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 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. // nth can also accept "odd" or "even" but should not accept any other token. return equalIgnoringCase(token, "odd") || equalIgnoringCase(token, "even"); } #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" }