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..974a0b660bc3532a5d439263b2a0560786cbf3e8 100644 |
| --- a/Source/core/css/parser/CSSPropertyParser.cpp |
| +++ b/Source/core/css/parser/CSSPropertyParser.cpp |
| @@ -7,10 +7,14 @@ |
| #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" |
| #include "core/frame/UseCounter.h" |
| +#include "wtf/text/StringBuilder.h" |
| namespace blink { |
| @@ -91,6 +95,44 @@ static PassRefPtrWillBeRawPtr<CSSPrimitiveValue> consumeString(CSSParserTokenRan |
| return cssValuePool().createValue(range.consumeIncludingWhitespace().value(), CSSPrimitiveValue::UnitType::String); |
| } |
| +static bool tryConsumeUrl(CSSParserTokenRange& valueList, String& urlValue) |
|
Timothy Loh
2015/09/15 02:01:05
"consumeUrl" is fine I think. Also maybe we can re
|
| +{ |
| + const CSSParserToken& token = valueList.peek(); |
| + if (token.type() == FunctionToken) { |
| + if (!token.valueEqualsIgnoringCase("url")) |
|
Timothy Loh
2015/09/15 02:01:05
functionId?
|
| + return false; |
| + |
| + CSSParserTokenRange urlArgs = valueList.consumeBlock(); |
| + const CSSParserToken& next = urlArgs.consumeIncludingWhitespace(); |
| + if (next.type() == BadStringToken) |
|
Timothy Loh
2015/09/15 02:01:05
|| EOFToken I think? (i.e. "url()")
Timothy Loh
2015/09/15 03:05:04
Whoops, url() makes a UrlToken.
|
| + return false; |
| + ASSERT(next.type() == StringToken); |
| + urlValue = next.value(); |
| + valueList.consumeWhitespace(); |
|
Timothy Loh
2015/09/15 02:01:05
Should check whether the inner range is atEnd()
|
| + return true; |
| + } |
| + if (token.type() == UrlToken) { |
|
Timothy Loh
2015/09/15 02:01:05
I think it'd be nicer if this check was before the
|
| + urlValue = token.value(); |
| + valueList.consumeIncludingWhitespace(); |
| + return true; |
| + } |
| + |
| + return false; |
| +} |
| + |
| +static CSSParserTokenRange consumeFunction(CSSParserTokenRange& valueList) |
| +{ |
| + ASSERT(valueList.peek().type() == FunctionToken); |
| + CSSParserTokenRange ret = valueList.consumeBlock(); |
|
Timothy Loh
2015/09/15 02:01:05
ret -> result? contents?
|
| + valueList.consumeWhitespace(); |
| + return ret; |
| +} |
| + |
| +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 +184,114 @@ static PassRefPtrWillBeRawPtr<CSSValue> consumeWillChange(CSSParserTokenRange& r |
| return values.release(); |
| } |
| +static PassRefPtrWillBeRawPtr<CSSValue> consumeFontVariantLigatures(CSSParserTokenRange& range) |
| +{ |
| + 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; |
| + |
| + while (!range.atEnd()) { |
|
Timothy Loh
2015/09/15 02:01:05
do {
} while (!range.atEnd());
return ligatureVa
|
| + if (range.peek().type() != IdentToken) |
|
Timothy Loh
2015/09/15 02:01:05
No need to check this since id() will just return
|
| + return nullptr; |
| + |
| + CSSValueID id = range.peek().id(); |
| + switch (id) { |
| + case CSSValueNoCommonLigatures: |
| + case CSSValueCommonLigatures: |
| + if (sawCommonLigaturesValue) |
| + return nullptr; |
| + sawCommonLigaturesValue = true; |
| + ligatureValues->append(consumeIdent(range)); |
|
Timothy Loh
2015/09/15 02:01:05
Probably nicer outside of the switch statement so
|
| + break; |
| + case CSSValueNoDiscretionaryLigatures: |
| + case CSSValueDiscretionaryLigatures: |
| + if (sawDiscretionaryLigaturesValue) |
| + return nullptr; |
| + sawDiscretionaryLigaturesValue = true; |
| + ligatureValues->append(consumeIdent(range)); |
| + break; |
| + case CSSValueNoHistoricalLigatures: |
| + case CSSValueHistoricalLigatures: |
| + if (sawHistoricalLigaturesValue) |
| + return nullptr; |
| + sawHistoricalLigaturesValue = true; |
| + ligatureValues->append(consumeIdent(range)); |
| + break; |
| + case CSSValueNoContextual: |
| + case CSSValueContextual: |
| + if (sawContextualLigaturesValue) |
| + return nullptr; |
| + sawContextualLigaturesValue = true; |
| + ligatureValues->append(consumeIdent(range)); |
| + break; |
| + default: |
| + return nullptr; |
| + } |
| + } |
| + |
| + if (!ligatureValues->length()) |
| + return nullptr; |
| + |
| + return ligatureValues.release(); |
| +} |
| + |
| +static bool consumeFontFeatureTag(CSSParserTokenRange& range, CSSValueList* settings) |
| +{ |
| + // Feature tag name consists of 4-letter characters. |
| + static const unsigned tagNameLength = 4; |
| + |
| + CSSParserToken token = range.peek(); |
| + // 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()) { |
| + if (range.peek().type() == NumberToken && range.peek().numericValueType() == IntegerValueType && range.peek().numericValue() >= 0) { |
| + tagValue = clampTo<int>(range.peek().numericValue()); |
| + if (tagValue < 0) |
| + return false; |
| + range.consumeIncludingWhitespace(); |
| + } else if (range.peek().id() == CSSValueOn || range.peek().id() == CSSValueOff) { |
| + tagValue = range.peek().id() == CSSValueOn; |
| + range.consumeIncludingWhitespace(); |
| + } |
| + } |
| + settings->append(CSSFontFeatureValue::create(tag, tagValue)); |
| + return true; |
| +} |
| + |
| +static PassRefPtrWillBeRawPtr<CSSValue> consumeFontFeatureSettings(CSSParserTokenRange& range) |
| +{ |
| + if (range.peek().id() == CSSValueNormal) |
| + return consumeIdent(range); |
| + RefPtrWillBeRawPtr<CSSValueList> settings = CSSValueList::createCommaSeparated(); |
| + while (true) { |
| + if (range.atEnd() || !consumeFontFeatureTag(range, settings.get())) |
| + return nullptr; |
| + if (range.atEnd()) |
| + break; |
| + if (!consumeCommaIncludingWhitespace(range)) |
| + return nullptr; |
| + } |
| + return settings.release(); |
| +} |
| + |
| static PassRefPtrWillBeRawPtr<CSSPrimitiveValue> consumePage(CSSParserTokenRange& range) |
| { |
| if (range.peek().id() == CSSValueAuto) |
| @@ -173,6 +323,82 @@ static PassRefPtrWillBeRawPtr<CSSValue> consumeWebkitHighlight(CSSParserTokenRan |
| return consumeString(range); |
| } |
| +// normal | small-caps | inherit |
| +PassRefPtrWillBeRawPtr<CSSValue> CSSPropertyParser::consumeFontVariant() |
| +{ |
| + RefPtrWillBeRawPtr<CSSValueList> values = nullptr; |
| + 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; |
| +} |
| + |
| +static PassRefPtrWillBeRawPtr<CSSValue> consumeWebkitLocale(CSSParserTokenRange& range) |
| +{ |
| + if (range.peek().id() == CSSValueAuto) |
| + return consumeIdent(range); |
| + return consumeString(range); |
| +} |
| + |
| +// none | all | 1 (will be dropped in the unprefixed property) |
| +static PassRefPtrWillBeRawPtr<CSSValue> consumeWebkitColumnSpan(CSSParserTokenRange& range) |
| +{ |
| + if (range.peek().id() == CSSValueAll || range.peek().id() == CSSValueNone) |
| + return consumeIdent(range); |
| + if (range.peek().type() == NumberToken && range.peek().numericValue() == 1) { |
| + const CSSParserToken& columnSpan = range.consumeIncludingWhitespace(); |
| + return cssValuePool().createValue(columnSpan.numericValue(), columnSpan.unitType()); |
| + } |
| + return nullptr; |
| +} |
| + |
| +static PassRefPtrWillBeRawPtr<CSSValue> consumeTextAlign(CSSParserTokenRange& range) |
| +{ |
| + // left | right | center | justify | -webkit-left | -webkit-right | -webkit-center | -webkit-match-parent |
| + // | start | end | <string> | inherit | -webkit-auto (converted to start) |
| + // FIXME: <string> not supported right now |
| + CSSValueID id = range.peek().id(); |
| + if ((id >= CSSValueWebkitAuto && id <= CSSValueWebkitMatchParent) || id == CSSValueStart || id == CSSValueEnd) |
| + return consumeIdent(range); |
| + return nullptr; |
| +} |
| + |
| PassRefPtrWillBeRawPtr<CSSValue> CSSPropertyParser::parseSingleValue(CSSPropertyID propId) |
| { |
| m_range.consumeWhitespace(); |
| @@ -185,11 +411,177 @@ 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 CSSPropertyWebkitHyphenateCharacter: |
| + case CSSPropertyWebkitLocale: |
| + return consumeWebkitLocale(m_range); |
| + case CSSPropertyWebkitColumnSpan: |
|
Timothy Loh
2015/09/15 02:01:05
let's leave the properties which aren't font-face
|
| + return consumeWebkitColumnSpan(m_range); |
| + case CSSPropertyTextAlign: |
| + return consumeTextAlign(m_range); |
| 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)); |
|
Timothy Loh
2015/09/15 02:01:05
I like this comma-separated-list pattern, can we d
|
| + |
| + return values.release(); |
| +} |
| + |
| +bool CSSPropertyParser::consumeFontFaceSrcURI(CSSValueList* valueList) |
| +{ |
| + String url; |
| + if (!tryConsumeUrl(m_range, url)) |
| + return false; |
| + RefPtrWillBeRawPtr<CSSFontFaceSrcValue> uriValue(CSSFontFaceSrcValue::create(completeURL(url), m_context.shouldCheckContentSecurityPolicy())); |
| + uriValue->setReferrer(m_context.referrer()); |
| + |
| + if (m_range.atEnd() || m_range.peek().type() != FunctionToken || m_range.peek().functionId() != CSSValueFormat) { |
|
Timothy Loh
2015/09/15 02:01:05
No need to check atEnd() or type() here, functionI
|
| + 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, |
| + // but CSSFontFaceSrcValue stores only one format. Allowing one format for now. |
| + CSSParserTokenRange args = consumeFunction(m_range); |
| + if (args.atEnd() || (args.peek().type() != StringToken && args.peek().type() != IdentToken)) |
|
Timothy Loh
2015/09/15 02:01:05
No need for atEnd() here.
|
| + return false; |
| + uriValue->setFormat(args.consumeIncludingWhitespace().value()); |
| + valueList->append(uriValue.release()); |
| + return true; |
| +} |
| + |
| +bool CSSPropertyParser::consumeFontFaceSrcLocal(CSSValueList* valueList) |
| +{ |
| + CSSParserTokenRange args = consumeFunction(m_range); |
| + if (args.atEnd()) |
| + return false; |
| + |
| + const CSSParserToken& arg = args.consumeIncludingWhitespace(); |
| + 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()); |
| + |
| + while (true) { |
| + const CSSParserToken& token = m_range.peek(); |
| + if (token.type() == FunctionToken && token.functionId() == CSSValueLocal) { |
| + if (!consumeFontFaceSrcLocal(values.get())) |
| + return nullptr; |
| + } else if (!consumeFontFaceSrcURI(values.get())) { |
| + return nullptr; |
| + } |
| + |
| + if (m_range.atEnd()) |
| + return values.release(); |
| + if (!consumeCommaIncludingWhitespace(m_range)) |
| + return nullptr; |
| + } |
| +} |
| + |
| +bool CSSPropertyParser::parseFontFaceDescriptor(CSSPropertyID propId) |
| +{ |
| + RefPtrWillBeRawPtr<CSSValue> parsedValue = nullptr; |
| + |
| + m_range.consumeWhitespace(); |
| + switch (propId) { |
| + case CSSPropertyFontFamily: |
| + // <family-name> |
| + // TODO(rwlbuis): check there is only one family-name |
| + parsedValue = parseFontFamily(); |
| + break; |
| + case CSSPropertySrc: // This is a list of urls or local references. |
| + parsedValue = consumeFontFaceSrc(); |
| + if (parsedValue && m_range.atEnd()) { |
| + addProperty(CSSPropertySrc, parsedValue.release(), false); |
| + return true; |
| + } |
| + break; |
| + case CSSPropertyUnicodeRange: |
| + parsedValue = consumeFontFaceUnicodeRange(m_range); |
| + if (parsedValue && m_range.atEnd()) { |
| + addProperty(CSSPropertyUnicodeRange, parsedValue.release(), false); |
| + return true; |
| + } |
| + break; |
| + case CSSPropertyFontWeight: // normal | bold | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 |
| + return parseFontWeight(false) && !m_valueList->next(); |
| + case CSSPropertyFontStretch: |
| + case CSSPropertyFontStyle: { |
| + CSSValueID id = m_range.consumeIncludingWhitespace().id(); |
| + if (!CSSParserFastPaths::isValidKeywordPropertyAndValue(propId, id)) |
| + return false; |
| + addProperty(propId, cssValuePool().createIdentifierValue(id), false); |
| + return true; |
| + } |
| + case CSSPropertyFontVariant: // normal | small-caps | inherit |
| + parsedValue = consumeFontVariant(); |
| + if (parsedValue && m_range.atEnd()) { |
| + addProperty(CSSPropertyFontVariant, parsedValue.release(), false); |
| + return true; |
| + } |
| + break; |
| + case CSSPropertyWebkitFontFeatureSettings: |
| + parsedValue = parseSingleValue(propId); |
| + if (parsedValue && m_range.atEnd()) { |
| + addProperty(CSSPropertyWebkitFontFeatureSettings, parsedValue.release(), false); |
| + return true; |
| + } |
| + break; |
| + default: |
| + break; |
| + } |
| + |
| + if (!parsedValue || m_valueList->current()) |
| + return false; |
| + |
| + addProperty(propId, parsedValue.release(), false); |
| + return true; |
| +} |
| + |
| bool CSSPropertyParser::parseShorthand(CSSPropertyID propId, bool important) |
| { |
| m_range.consumeWhitespace(); |