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(); |