Chromium Code Reviews| Index: Source/core/css/parser/CSSPropertyParser.cpp |
| diff --git a/Source/core/css/parser/CSSPropertyParser.cpp b/Source/core/css/parser/CSSPropertyParser.cpp |
| index 2e6c483da002877dc8980b5b0be85f790357171b..b03689723686ef719395a9b0e00aa86e68611ddf 100644 |
| --- a/Source/core/css/parser/CSSPropertyParser.cpp |
| +++ b/Source/core/css/parser/CSSPropertyParser.cpp |
| @@ -7,6 +7,9 @@ |
| #include "core/StylePropertyShorthand.h" |
| #include "core/css/CSSCalculationValue.h" |
| +#include "core/css/CSSFontFaceSrcValue.h" |
| +#include "core/css/CSSFontFeatureValue.h" |
| +#include "core/css/CSSUnicodeRangeValue.h" |
| #include "core/css/CSSValuePool.h" |
| #include "core/css/parser/CSSParserFastPaths.h" |
| #include "core/css/parser/CSSParserValues.h" |
| @@ -91,6 +94,41 @@ static PassRefPtrWillBeRawPtr<CSSPrimitiveValue> consumeString(CSSParserTokenRan |
| return cssValuePool().createValue(range.consumeIncludingWhitespace().value(), CSSPrimitiveValue::UnitType::String); |
| } |
| +static String consumeUrl(CSSParserTokenRange& range) |
| +{ |
| + const CSSParserToken& token = range.peek(); |
| + if (token.type() == UrlToken) { |
| + range.consumeIncludingWhitespace(); |
| + return token.value(); |
| + } |
| + if (token.functionId() == CSSValueUrl) { |
| + CSSParserTokenRange urlRange = range; |
| + CSSParserTokenRange urlArgs = urlRange.consumeBlock(); |
| + const CSSParserToken& next = urlArgs.consumeIncludingWhitespace(); |
| + if (next.type() == BadStringToken || !urlArgs.atEnd()) |
| + return String(); |
| + ASSERT(next.type() == StringToken); |
| + range = urlRange; |
| + range.consumeWhitespace(); |
| + return next.value(); |
| + } |
| + |
| + return String(); |
| +} |
| + |
| +static CSSParserTokenRange consumeFunction(CSSParserTokenRange& range) |
| +{ |
| + ASSERT(range.peek().type() == FunctionToken); |
| + CSSParserTokenRange contens = range.consumeBlock(); |
|
Timothy Loh
2015/09/16 03:22:28
contens -> contents? :-)
|
| + range.consumeWhitespace(); |
| + return contens; |
| +} |
| + |
| +static inline bool isComma(const CSSParserToken& value) |
| +{ |
| + return value.type() == CommaToken; |
| +} |
| + |
| // Methods for consuming non-shorthand properties starts here. |
| static PassRefPtrWillBeRawPtr<CSSValue> consumeWillChange(CSSParserTokenRange& range) |
| { |
| @@ -142,6 +180,105 @@ static PassRefPtrWillBeRawPtr<CSSValue> consumeWillChange(CSSParserTokenRange& r |
| return values.release(); |
| } |
| +static PassRefPtrWillBeRawPtr<CSSValue> consumeFontVariantLigatures(CSSParserTokenRange& range) |
|
Timothy Loh
2015/09/16 03:22:28
Can we move the properties which are not @font-fac
|
| +{ |
| + if (range.peek().id() == CSSValueNormal) |
| + return consumeIdent(range); |
| + RefPtrWillBeRawPtr<CSSValueList> ligatureValues = CSSValueList::createSpaceSeparated(); |
| + bool sawCommonLigaturesValue = false; |
| + bool sawDiscretionaryLigaturesValue = false; |
| + bool sawHistoricalLigaturesValue = false; |
| + bool sawContextualLigaturesValue = false; |
| + do { |
| + CSSValueID id = range.peek().id(); |
| + switch (id) { |
| + case CSSValueNoCommonLigatures: |
| + case CSSValueCommonLigatures: |
| + if (sawCommonLigaturesValue) |
| + return nullptr; |
| + sawCommonLigaturesValue = true; |
| + break; |
| + case CSSValueNoDiscretionaryLigatures: |
| + case CSSValueDiscretionaryLigatures: |
| + if (sawDiscretionaryLigaturesValue) |
| + return nullptr; |
| + sawDiscretionaryLigaturesValue = true; |
| + break; |
| + case CSSValueNoHistoricalLigatures: |
| + case CSSValueHistoricalLigatures: |
| + if (sawHistoricalLigaturesValue) |
| + return nullptr; |
| + sawHistoricalLigaturesValue = true; |
| + break; |
| + case CSSValueNoContextual: |
| + case CSSValueContextual: |
| + if (sawContextualLigaturesValue) |
| + return nullptr; |
| + sawContextualLigaturesValue = true; |
| + break; |
| + default: |
| + return nullptr; |
| + } |
| + ligatureValues->append(consumeIdent(range)); |
| + } while (!range.atEnd()); |
| + |
| + if (!ligatureValues->length()) |
|
Timothy Loh
2015/09/16 03:22:28
The list will never be empty here
|
| + return nullptr; |
| + |
| + return ligatureValues.release(); |
| +} |
| + |
| +static bool consumeFontFeatureTag(CSSParserTokenRange& range, CSSValueList* settings) |
|
Timothy Loh
2015/09/16 03:22:28
should probably just return a CSSFontFeatureValue
|
| +{ |
| + // Feature tag name consists of 4-letter characters. |
| + static const unsigned tagNameLength = 4; |
| + |
| + CSSParserToken token = range.peek(); |
|
Timothy Loh
2015/09/16 03:22:28
const CSSParserToken&. Also maybe easier if this i
|
| + // Feature tag name comes first |
| + if (token.type() != StringToken) |
| + return false; |
| + if (token.value().length() != tagNameLength) |
| + return false; |
| + AtomicString tag = token.value(); |
| + for (unsigned i = 0; i < tagNameLength; ++i) { |
| + // Limits the range of characters to 0x20-0x7E, following the tag name rules defiend in the OpenType specification. |
| + UChar character = tag[i]; |
| + if (character < 0x20 || character > 0x7E) |
| + return false; |
| + } |
| + |
| + int tagValue = 1; |
| + // Feature tag values could follow: <integer> | on | off |
| + range.consumeIncludingWhitespace(); |
| + if (!range.atEnd()) { |
|
Timothy Loh
2015/09/16 03:22:28
don't need this check, peek() will just return EOF
|
| + if (range.peek().type() == NumberToken && range.peek().numericValueType() == IntegerValueType && range.peek().numericValue() >= 0) { |
| + tagValue = clampTo<int>(range.peek().numericValue()); |
|
Timothy Loh
2015/09/16 03:22:28
range.consumeIncludingWhitespace().numericValue()?
|
| + if (tagValue < 0) |
| + return false; |
|
Timothy Loh
2015/09/16 03:22:28
does this ever get hit?
|
| + range.consumeIncludingWhitespace(); |
| + } else if (range.peek().id() == CSSValueOn || range.peek().id() == CSSValueOff) { |
| + tagValue = range.peek().id() == CSSValueOn; |
|
Timothy Loh
2015/09/16 03:22:28
range.consumeIncludingWhitespace().id()
|
| + range.consumeIncludingWhitespace(); |
| + } |
| + } |
| + settings->append(CSSFontFeatureValue::create(tag, tagValue)); |
| + return true; |
| +} |
| + |
| +static PassRefPtrWillBeRawPtr<CSSValue> consumeFontFeatureSettings(CSSParserTokenRange& range) |
|
Timothy Loh
2015/09/16 03:22:28
this one too, separate patch
|
| +{ |
| + if (range.peek().id() == CSSValueNormal) |
| + return consumeIdent(range); |
| + RefPtrWillBeRawPtr<CSSValueList> settings = CSSValueList::createCommaSeparated(); |
| + do { |
| + if (!consumeFontFeatureTag(range, settings.get())) |
| + return nullptr; |
| + if (range.atEnd()) |
|
Timothy Loh
2015/09/16 03:22:28
I think just return the value outside the loop and
|
| + return settings.release(); |
| + } while (consumeCommaIncludingWhitespace(range)); |
| + return nullptr; |
| +} |
| + |
| static PassRefPtrWillBeRawPtr<CSSPrimitiveValue> consumePage(CSSParserTokenRange& range) |
| { |
| if (range.peek().id() == CSSValueAuto) |
| @@ -173,6 +310,52 @@ static PassRefPtrWillBeRawPtr<CSSValue> consumeWebkitHighlight(CSSParserTokenRan |
| return consumeString(range); |
| } |
| +// normal | small-caps | inherit |
|
Timothy Loh
2015/09/16 03:22:28
Comment is wrong, let's just drop these (as well a
|
| +PassRefPtrWillBeRawPtr<CSSValue> CSSPropertyParser::consumeFontVariant() |
|
Timothy Loh
2015/09/16 03:22:28
static for consistency?
|
| +{ |
| + RefPtrWillBeRawPtr<CSSValueList> values = nullptr; |
|
Timothy Loh
2015/09/16 03:22:28
Can we rewrite this function so it isn't super com
|
| + bool expectComma = false; |
| + while (!m_range.atEnd()) { |
| + RefPtrWillBeRawPtr<CSSPrimitiveValue> parsedValue = nullptr; |
| + if (!expectComma) { |
| + expectComma = true; |
| + if (m_range.peek().id() == CSSValueNormal || m_range.peek().id() == CSSValueSmallCaps) { |
| + parsedValue = consumeIdent(m_range); |
| + } else if (m_range.peek().id() == CSSValueAll && !values) { |
| + // FIXME: CSSPropertyParser::parseFontVariant() implements |
| + // the old css3 draft: |
| + // http://www.w3.org/TR/2002/WD-css3-webfonts-20020802/#font-variant |
| + // '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 = consumeIdent(m_range); |
| + } |
| + } else if (consumeCommaIncludingWhitespace(m_range)) { |
| + expectComma = false; |
| + continue; |
| + } |
| + |
| + if (!parsedValue) |
| + return nullptr; |
| + |
| + if (isComma(m_range.peek())) |
| + values = CSSValueList::createCommaSeparated(); |
| + |
| + if (values) |
| + values->append(parsedValue.release()); |
| + else |
| + return parsedValue.release(); |
| + } |
| + |
| + if (values && values->length()) { |
| + if (m_ruleType != StyleRule::FontFace) |
| + return nullptr; |
| + return values.release(); |
| + } |
| + |
| + return nullptr; |
| +} |
| + |
| PassRefPtrWillBeRawPtr<CSSValue> CSSPropertyParser::parseSingleValue(CSSPropertyID propId) |
| { |
| m_range.consumeWhitespace(); |
| @@ -185,11 +368,246 @@ PassRefPtrWillBeRawPtr<CSSValue> CSSPropertyParser::parseSingleValue(CSSProperty |
| return consumeQuotes(m_range); |
| case CSSPropertyWebkitHighlight: |
| return consumeWebkitHighlight(m_range); |
| + case CSSPropertyFontVariantLigatures: |
| + return consumeFontVariantLigatures(m_range); |
| + case CSSPropertyWebkitFontFeatureSettings: |
| + return consumeFontFeatureSettings(m_range); |
| + case CSSPropertyFontVariant: |
| + return consumeFontVariant(); |
| + case CSSPropertyFontFamily: |
| + // [[ <family-name> | <generic-family> ],]* [<family-name> | <generic-family>] | inherit |
| + return consumeFontFamily(); |
| default: |
| return nullptr; |
| } |
| } |
| +static PassRefPtrWillBeRawPtr<CSSValueList> consumeFontFaceUnicodeRange(CSSParserTokenRange& range) |
| +{ |
| + RefPtrWillBeRawPtr<CSSValueList> values = CSSValueList::createCommaSeparated(); |
| + |
| + do { |
| + const CSSParserToken& token = range.consumeIncludingWhitespace(); |
| + if (token.type() != UnicodeRangeToken) |
| + return nullptr; |
| + |
| + UChar32 start = token.unicodeRangeStart(); |
| + UChar32 end = token.unicodeRangeEnd(); |
| + if (start > end) |
| + return nullptr; |
| + values->append(CSSUnicodeRangeValue::create(start, end)); |
| + } while (consumeCommaIncludingWhitespace(range)); |
| + |
| + return values.release(); |
| +} |
| + |
| +bool CSSPropertyParser::consumeFontFaceSrcURI(CSSValueList* valueList) |
|
Timothy Loh
2015/09/16 03:22:28
return a CSSValue instead of updating a CSSValueLi
|
| +{ |
| + String url = consumeUrl(m_range); |
| + if (url.isNull()) |
| + return false; |
| + RefPtrWillBeRawPtr<CSSFontFaceSrcValue> uriValue(CSSFontFaceSrcValue::create(completeURL(url), m_context.shouldCheckContentSecurityPolicy())); |
| + uriValue->setReferrer(m_context.referrer()); |
| + |
| + if (m_range.peek().functionId() != CSSValueFormat) { |
| + valueList->append(uriValue.release()); |
| + return true; |
| + } |
| + |
| + // FIXME: http://www.w3.org/TR/2011/WD-css3-fonts-20111004/ says that format() contains a comma-separated list of strings, |
|
Timothy Loh
2015/09/16 03:22:28
Should update the URL (https://drafts.csswg.org/cs
|
| + // but CSSFontFaceSrcValue stores only one format. Allowing one format for now. |
| + CSSParserTokenRange args = consumeFunction(m_range); |
| + if (args.peek().type() != StringToken && args.peek().type() != IdentToken) |
| + return false; |
| + uriValue->setFormat(args.consumeIncludingWhitespace().value()); |
|
Timothy Loh
2015/09/16 03:22:28
need to check for any tokens after the string?
|
| + valueList->append(uriValue.release()); |
| + return true; |
| +} |
| + |
| +bool CSSPropertyParser::consumeFontFaceSrcLocal(CSSValueList* valueList) |
|
Timothy Loh
2015/09/16 03:22:28
return a CSSValue
|
| +{ |
| + CSSParserTokenRange args = consumeFunction(m_range); |
| + if (args.atEnd()) |
| + return false; |
| + |
| + const CSSParserToken& arg = args.consumeIncludingWhitespace(); |
|
Timothy Loh
2015/09/16 03:22:28
This is just <family-name>, right? We should share
|
| + ContentSecurityPolicyDisposition shouldCheckContentSecurityPolicy = m_context.shouldCheckContentSecurityPolicy(); |
| + if (arg.type() == StringToken) { |
| + if (!args.atEnd()) |
| + return false; |
| + valueList->append(CSSFontFaceSrcValue::createLocal(arg.value(), shouldCheckContentSecurityPolicy)); |
| + } else if (arg.type() == IdentToken) { |
| + StringBuilder builder; |
| + builder.append(arg.value()); |
| + while (!args.atEnd()) { |
| + CSSParserToken localValue = args.consumeIncludingWhitespace(); |
| + if (localValue.type() != IdentToken) |
| + return false; |
| + if (!builder.isEmpty()) |
| + builder.append(' '); |
| + builder.append(localValue.value()); |
| + } |
| + valueList->append(CSSFontFaceSrcValue::createLocal(builder.toString(), shouldCheckContentSecurityPolicy)); |
| + } else { |
| + return false; |
| + } |
| + |
| + return true; |
| +} |
| + |
| +PassRefPtrWillBeRawPtr<CSSValueList> CSSPropertyParser::consumeFontFaceSrc() |
| +{ |
| + RefPtrWillBeRawPtr<CSSValueList> values(CSSValueList::createCommaSeparated()); |
| + |
| + do { |
| + const CSSParserToken& token = m_range.peek(); |
| + if (token.functionId() == CSSValueLocal) { |
| + if (!consumeFontFaceSrcLocal(values.get())) |
| + return nullptr; |
| + } else if (!consumeFontFaceSrcURI(values.get())) { |
| + return nullptr; |
| + } |
| + |
| + if (m_range.atEnd()) |
| + return values.release(); |
| + } while (consumeCommaIncludingWhitespace(m_range)); |
| + return nullptr; |
| +} |
| + |
| +static inline bool isCSSWideKeyword(const CSSParserToken& token) |
| +{ |
| + return token.id() == CSSValueInitial || token.id() == CSSValueInherit || token.id() == CSSValueUnset || token.id() == CSSValueDefault; |
|
Timothy Loh
2015/09/16 03:22:28
I know id() has a cache but can we pull the value
|
| +} |
| + |
| +PassRefPtrWillBeRawPtr<CSSValueList> CSSPropertyParser::consumeFontFamily() |
| +{ |
| + RefPtrWillBeRawPtr<CSSValueList> list = CSSValueList::createCommaSeparated(); |
| + CSSParserToken token = m_range.consumeIncludingWhitespace(); |
| + |
| + FontFamilyValueBuilder familyBuilder(list.get()); |
| + bool inFamily = false; |
| + |
| + while (token.type() != EOFToken) { |
| + const CSSParserToken& nextToken = m_range.consumeIncludingWhitespace(); |
| + bool nextValBreaksFont = nextToken.type() == EOFToken || isComma(nextToken); |
| + bool nextValIsFontName = ((nextToken.id() >= CSSValueSerif && nextToken.id() <= CSSValueWebkitBody) |
| + || (nextToken.type() == StringToken || nextToken.type() == IdentToken)); |
| + |
| + if (isCSSWideKeyword(token) && !inFamily) { |
| + if (nextValBreaksFont) |
| + return nullptr; |
| + if (nextValIsFontName) |
| + token = nextToken; |
| + continue; |
| + } |
| + |
| + if (token.id() >= CSSValueSerif && token.id() <= CSSValueWebkitBody) { |
| + if (inFamily) { |
| + familyBuilder.add(token.value()); |
| + } else if (nextValBreaksFont || !nextValIsFontName) { |
| + list->append(cssValuePool().createIdentifierValue(token.id())); |
| + } else { |
| + familyBuilder.commit(); |
| + familyBuilder.add(token.value()); |
| + inFamily = true; |
| + } |
| + } else if (token.type() == StringToken) { |
| + // Strings never share in a family name. |
| + inFamily = false; |
| + familyBuilder.commit(); |
| + list->append(cssValuePool().createFontFamilyValue(token.value())); |
| + } else if (token.type() == IdentToken) { |
| + if (inFamily) { |
| + familyBuilder.add(token.value()); |
| + } else if (nextValBreaksFont || !nextValIsFontName) { |
| + list->append(cssValuePool().createFontFamilyValue(token.value())); |
| + } else { |
| + familyBuilder.commit(); |
| + familyBuilder.add(token.value()); |
| + inFamily = true; |
| + } |
| + } else { |
| + break; |
| + } |
| + |
| + if (nextToken.type() == EOFToken) |
| + break; |
| + |
| + if (nextValBreaksFont) { |
| + token = m_range.consumeIncludingWhitespace(); |
| + familyBuilder.commit(); |
| + inFamily = false; |
| + } else if (nextValIsFontName) { |
| + token = nextToken; |
| + } else { |
| + break; |
| + } |
| + } |
| + familyBuilder.commit(); |
| + if (!list->length() || (m_ruleType == StyleRule::FontFace && list->length() > 1)) |
| + list = nullptr; |
| + return list.release(); |
| +} |
| + |
| +static PassRefPtrWillBeRawPtr<CSSValue> consumeFontWeight(CSSParserTokenRange& range) |
| +{ |
| + const CSSParserToken& token = range.peek(); |
| + if (token.id() >= CSSValueNormal && token.id() <= CSSValueLighter) |
| + return consumeIdent(range); |
| + if (token.type() != NumberToken) |
|
Timothy Loh
2015/09/16 03:22:28
|| token.numericValueType() != IntegerValueType?
|
| + return nullptr; |
| + int weight = static_cast<int>(token.numericValue()); |
| + if ((weight % 100) || weight < 100 || weight > 900) |
| + return nullptr; |
| + range.consumeIncludingWhitespace(); |
| + return cssValuePool().createIdentifierValue(static_cast<CSSValueID>(CSSValue100 + weight / 100 - 1)); |
| +} |
| + |
| +bool CSSPropertyParser::parseFontFaceDescriptor(CSSPropertyID propId) |
|
Timothy Loh
2015/09/16 03:22:28
I think we decided a while ago to split this into
|
| +{ |
| + RefPtrWillBeRawPtr<CSSValue> parsedValue = nullptr; |
| + |
| + m_range.consumeWhitespace(); |
| + switch (propId) { |
| + case CSSPropertyFontFamily: |
| + // <family-name> |
| + // TODO(rwlbuis): check there is only one family-name |
| + parsedValue = consumeFontFamily(); |
| + break; |
| + case CSSPropertySrc: // This is a list of urls or local references. |
| + parsedValue = consumeFontFaceSrc(); |
| + break; |
| + case CSSPropertyUnicodeRange: |
| + parsedValue = consumeFontFaceUnicodeRange(m_range); |
| + break; |
| + case CSSPropertyFontWeight: // normal | bold | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 |
| + parsedValue = consumeFontWeight(m_range); |
| + break; |
| + case CSSPropertyFontStretch: |
| + case CSSPropertyFontStyle: { |
| + CSSValueID id = m_range.consumeIncludingWhitespace().id(); |
| + if (!CSSParserFastPaths::isValidKeywordPropertyAndValue(propId, id)) |
| + return false; |
| + addProperty(propId, cssValuePool().createIdentifierValue(id), false); |
|
Timothy Loh
2015/09/16 03:22:28
parsedValue = ?
|
| + return true; |
| + } |
| + case CSSPropertyFontVariant: // normal | small-caps | inherit |
| + parsedValue = consumeFontVariant(); |
| + break; |
| + case CSSPropertyWebkitFontFeatureSettings: |
| + parsedValue = parseSingleValue(propId); |
| + break; |
| + default: |
| + break; |
| + } |
| + |
| + if (!parsedValue || !m_range.atEnd()) |
| + return false; |
| + |
| + addProperty(propId, parsedValue.release(), false); |
| + return true; |
| +} |
| + |
| bool CSSPropertyParser::parseShorthand(CSSPropertyID propId, bool important) |
| { |
| m_range.consumeWhitespace(); |