| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "core/css/parser/CSSParserImpl.h" |
| |
| #include "core/css/CSSCustomIdentValue.h" |
| #include "core/css/CSSCustomPropertyDeclaration.h" |
| #include "core/css/CSSKeyframesRule.h" |
| #include "core/css/CSSStyleSheet.h" |
| #include "core/css/StylePropertySet.h" |
| #include "core/css/StyleRuleImport.h" |
| #include "core/css/StyleRuleKeyframe.h" |
| #include "core/css/StyleRuleNamespace.h" |
| #include "core/css/StyleSheetContents.h" |
| #include "core/css/parser/CSSAtRuleID.h" |
| #include "core/css/parser/CSSParserObserver.h" |
| #include "core/css/parser/CSSParserObserverWrapper.h" |
| #include "core/css/parser/CSSParserSelector.h" |
| #include "core/css/parser/CSSPropertyParser.h" |
| #include "core/css/parser/CSSSelectorParser.h" |
| #include "core/css/parser/CSSSupportsParser.h" |
| #include "core/css/parser/CSSTokenizer.h" |
| #include "core/css/parser/CSSVariableParser.h" |
| #include "core/css/parser/MediaQueryParser.h" |
| #include "core/dom/Document.h" |
| #include "core/dom/Element.h" |
| #include "core/frame/Deprecation.h" |
| #include "core/frame/UseCounter.h" |
| #include "platform/tracing/TraceEvent.h" |
| #include "wtf/PtrUtil.h" |
| #include <bitset> |
| #include <memory> |
| |
| namespace blink { |
| |
| CSSParserImpl::CSSParserImpl(const CSSParserContext& context, |
| StyleSheetContents* styleSheet) |
| : m_context(context), |
| m_styleSheet(styleSheet), |
| m_observerWrapper(nullptr) {} |
| |
| bool CSSParserImpl::parseValue(MutableStylePropertySet* declaration, |
| CSSPropertyID unresolvedProperty, |
| const String& string, |
| bool important, |
| const CSSParserContext& context) { |
| CSSParserImpl parser(context); |
| StyleRule::RuleType ruleType = StyleRule::Style; |
| if (declaration->cssParserMode() == CSSViewportRuleMode) |
| ruleType = StyleRule::Viewport; |
| else if (declaration->cssParserMode() == CSSFontFaceRuleMode) |
| ruleType = StyleRule::FontFace; |
| CSSTokenizer::Scope scope(string); |
| parser.consumeDeclarationValue(scope.tokenRange(), unresolvedProperty, |
| important, ruleType); |
| if (parser.m_parsedProperties.isEmpty()) |
| return false; |
| return declaration->addParsedProperties(parser.m_parsedProperties); |
| } |
| |
| bool CSSParserImpl::parseVariableValue(MutableStylePropertySet* declaration, |
| const AtomicString& propertyName, |
| const String& value, |
| bool important, |
| const CSSParserContext& context, |
| bool isAnimationTainted) { |
| CSSParserImpl parser(context); |
| CSSTokenizer::Scope scope(value); |
| parser.consumeVariableValue(scope.tokenRange(), propertyName, important, |
| isAnimationTainted); |
| if (parser.m_parsedProperties.isEmpty()) |
| return false; |
| return declaration->addParsedProperties(parser.m_parsedProperties); |
| } |
| |
| static inline void filterProperties( |
| bool important, |
| const HeapVector<CSSProperty, 256>& input, |
| HeapVector<CSSProperty, 256>& output, |
| size_t& unusedEntries, |
| std::bitset<numCSSProperties>& seenProperties, |
| HashSet<AtomicString>& seenCustomProperties) { |
| // Add properties in reverse order so that highest priority definitions are |
| // reached first. Duplicate definitions can then be ignored when found. |
| for (size_t i = input.size(); i--;) { |
| const CSSProperty& property = input[i]; |
| if (property.isImportant() != important) |
| continue; |
| const unsigned propertyIDIndex = property.id() - firstCSSProperty; |
| |
| if (property.id() == CSSPropertyVariable) { |
| const AtomicString& name = |
| toCSSCustomPropertyDeclaration(property.value())->name(); |
| if (seenCustomProperties.contains(name)) |
| continue; |
| seenCustomProperties.add(name); |
| } else if (property.id() == CSSPropertyApplyAtRule) { |
| // TODO(timloh): Do we need to do anything here? |
| } else { |
| if (seenProperties.test(propertyIDIndex)) |
| continue; |
| seenProperties.set(propertyIDIndex); |
| } |
| output[--unusedEntries] = property; |
| } |
| } |
| |
| static ImmutableStylePropertySet* createStylePropertySet( |
| HeapVector<CSSProperty, 256>& parsedProperties, |
| CSSParserMode mode) { |
| std::bitset<numCSSProperties> seenProperties; |
| size_t unusedEntries = parsedProperties.size(); |
| HeapVector<CSSProperty, 256> results(unusedEntries); |
| HashSet<AtomicString> seenCustomProperties; |
| |
| filterProperties(true, parsedProperties, results, unusedEntries, |
| seenProperties, seenCustomProperties); |
| filterProperties(false, parsedProperties, results, unusedEntries, |
| seenProperties, seenCustomProperties); |
| |
| ImmutableStylePropertySet* result = ImmutableStylePropertySet::create( |
| results.data() + unusedEntries, results.size() - unusedEntries, mode); |
| parsedProperties.clear(); |
| return result; |
| } |
| |
| ImmutableStylePropertySet* CSSParserImpl::parseInlineStyleDeclaration( |
| const String& string, |
| Element* element) { |
| Document& document = element->document(); |
| CSSParserContext context = |
| CSSParserContext(document.elementSheet().contents()->parserContext(), |
| UseCounter::getFrom(&document)); |
| CSSParserMode mode = element->isHTMLElement() && !document.inQuirksMode() |
| ? HTMLStandardMode |
| : HTMLQuirksMode; |
| context.setMode(mode); |
| CSSParserImpl parser(context, document.elementSheet().contents()); |
| CSSTokenizer::Scope scope(string); |
| parser.consumeDeclarationList(scope.tokenRange(), StyleRule::Style); |
| return createStylePropertySet(parser.m_parsedProperties, mode); |
| } |
| |
| bool CSSParserImpl::parseDeclarationList(MutableStylePropertySet* declaration, |
| const String& string, |
| const CSSParserContext& context) { |
| CSSParserImpl parser(context); |
| StyleRule::RuleType ruleType = StyleRule::Style; |
| if (declaration->cssParserMode() == CSSViewportRuleMode) |
| ruleType = StyleRule::Viewport; |
| CSSTokenizer::Scope scope(string); |
| parser.consumeDeclarationList(scope.tokenRange(), ruleType); |
| if (parser.m_parsedProperties.isEmpty()) |
| return false; |
| |
| std::bitset<numCSSProperties> seenProperties; |
| size_t unusedEntries = parser.m_parsedProperties.size(); |
| HeapVector<CSSProperty, 256> results(unusedEntries); |
| HashSet<AtomicString> seenCustomProperties; |
| filterProperties(true, parser.m_parsedProperties, results, unusedEntries, |
| seenProperties, seenCustomProperties); |
| filterProperties(false, parser.m_parsedProperties, results, unusedEntries, |
| seenProperties, seenCustomProperties); |
| if (unusedEntries) |
| results.remove(0, unusedEntries); |
| return declaration->addParsedProperties(results); |
| } |
| |
| StyleRuleBase* CSSParserImpl::parseRule(const String& string, |
| const CSSParserContext& context, |
| StyleSheetContents* styleSheet, |
| AllowedRulesType allowedRules) { |
| CSSParserImpl parser(context, styleSheet); |
| CSSTokenizer::Scope scope(string); |
| CSSParserTokenRange range = scope.tokenRange(); |
| range.consumeWhitespace(); |
| if (range.atEnd()) |
| return nullptr; // Parse error, empty rule |
| StyleRuleBase* rule; |
| if (range.peek().type() == AtKeywordToken) |
| rule = parser.consumeAtRule(range, allowedRules); |
| else |
| rule = parser.consumeQualifiedRule(range, allowedRules); |
| if (!rule) |
| return nullptr; // Parse error, failed to consume rule |
| range.consumeWhitespace(); |
| if (!rule || !range.atEnd()) |
| return nullptr; // Parse error, trailing garbage |
| return rule; |
| } |
| |
| void CSSParserImpl::parseStyleSheet(const String& string, |
| const CSSParserContext& context, |
| StyleSheetContents* styleSheet) { |
| TRACE_EVENT_BEGIN2("blink,blink_style", "CSSParserImpl::parseStyleSheet", |
| "baseUrl", context.baseURL().getString().utf8(), "mode", |
| context.mode()); |
| |
| TRACE_EVENT_BEGIN0("blink,blink_style", |
| "CSSParserImpl::parseStyleSheet.tokenize"); |
| CSSTokenizer::Scope scope(string); |
| TRACE_EVENT_END0("blink,blink_style", |
| "CSSParserImpl::parseStyleSheet.tokenize"); |
| |
| TRACE_EVENT_BEGIN0("blink,blink_style", |
| "CSSParserImpl::parseStyleSheet.parse"); |
| CSSParserImpl parser(context, styleSheet); |
| bool firstRuleValid = parser.consumeRuleList( |
| scope.tokenRange(), TopLevelRuleList, [&styleSheet](StyleRuleBase* rule) { |
| if (rule->isCharsetRule()) |
| return; |
| styleSheet->parserAppendRule(rule); |
| }); |
| styleSheet->setHasSyntacticallyValidCSSHeader(firstRuleValid); |
| TRACE_EVENT_END0("blink,blink_style", "CSSParserImpl::parseStyleSheet.parse"); |
| |
| TRACE_EVENT_END2("blink,blink_style", "CSSParserImpl::parseStyleSheet", |
| "tokenCount", scope.tokenCount(), "length", string.length()); |
| } |
| |
| CSSSelectorList CSSParserImpl::parsePageSelector( |
| CSSParserTokenRange range, |
| StyleSheetContents* styleSheet) { |
| // We only support a small subset of the css-page spec. |
| range.consumeWhitespace(); |
| AtomicString typeSelector; |
| if (range.peek().type() == IdentToken) |
| typeSelector = range.consume().value().toAtomicString(); |
| |
| AtomicString pseudo; |
| if (range.peek().type() == ColonToken) { |
| range.consume(); |
| if (range.peek().type() != IdentToken) |
| return CSSSelectorList(); |
| pseudo = range.consume().value().toAtomicString(); |
| } |
| |
| range.consumeWhitespace(); |
| if (!range.atEnd()) |
| return CSSSelectorList(); // Parse error; extra tokens in @page selector |
| |
| std::unique_ptr<CSSParserSelector> selector; |
| if (!typeSelector.isNull() && pseudo.isNull()) { |
| selector = CSSParserSelector::create( |
| QualifiedName(nullAtom, typeSelector, styleSheet->defaultNamespace())); |
| } else { |
| selector = CSSParserSelector::create(); |
| if (!pseudo.isNull()) { |
| selector->setMatch(CSSSelector::PagePseudoClass); |
| selector->updatePseudoType(pseudo.lower()); |
| if (selector->pseudoType() == CSSSelector::PseudoUnknown) |
| return CSSSelectorList(); |
| } |
| if (!typeSelector.isNull()) { |
| selector->prependTagSelector(QualifiedName( |
| nullAtom, typeSelector, styleSheet->defaultNamespace())); |
| } |
| } |
| |
| selector->setForPage(); |
| Vector<std::unique_ptr<CSSParserSelector>> selectorVector; |
| selectorVector.append(std::move(selector)); |
| CSSSelectorList selectorList = |
| CSSSelectorList::adoptSelectorVector(selectorVector); |
| return selectorList; |
| } |
| |
| ImmutableStylePropertySet* CSSParserImpl::parseCustomPropertySet( |
| CSSParserTokenRange range) { |
| range.consumeWhitespace(); |
| if (range.peek().type() != LeftBraceToken) |
| return nullptr; |
| CSSParserTokenRange block = range.consumeBlock(); |
| range.consumeWhitespace(); |
| if (!range.atEnd()) |
| return nullptr; |
| CSSParserImpl parser(strictCSSParserContext()); |
| parser.consumeDeclarationList(block, StyleRule::Style); |
| |
| // Drop nested @apply rules. Seems nicer to do this here instead of making |
| // a different StyleRule type |
| for (size_t i = parser.m_parsedProperties.size(); i--;) { |
| if (parser.m_parsedProperties[i].id() == CSSPropertyApplyAtRule) |
| parser.m_parsedProperties.remove(i); |
| } |
| |
| return createStylePropertySet(parser.m_parsedProperties, HTMLStandardMode); |
| } |
| |
| std::unique_ptr<Vector<double>> CSSParserImpl::parseKeyframeKeyList( |
| const String& keyList) { |
| return consumeKeyframeKeyList(CSSTokenizer::Scope(keyList).tokenRange()); |
| } |
| |
| bool CSSParserImpl::supportsDeclaration(CSSParserTokenRange& range) { |
| ASSERT(m_parsedProperties.isEmpty()); |
| consumeDeclaration(range, StyleRule::Style); |
| bool result = !m_parsedProperties.isEmpty(); |
| m_parsedProperties.clear(); |
| return result; |
| } |
| |
| void CSSParserImpl::parseDeclarationListForInspector( |
| const String& declaration, |
| const CSSParserContext& context, |
| CSSParserObserver& observer) { |
| CSSParserImpl parser(context); |
| CSSParserObserverWrapper wrapper(observer); |
| parser.m_observerWrapper = &wrapper; |
| CSSTokenizer::Scope scope(declaration, wrapper); |
| observer.startRuleHeader(StyleRule::Style, 0); |
| observer.endRuleHeader(1); |
| parser.consumeDeclarationList(scope.tokenRange(), StyleRule::Style); |
| } |
| |
| void CSSParserImpl::parseStyleSheetForInspector(const String& string, |
| const CSSParserContext& context, |
| StyleSheetContents* styleSheet, |
| CSSParserObserver& observer) { |
| CSSParserImpl parser(context, styleSheet); |
| CSSParserObserverWrapper wrapper(observer); |
| parser.m_observerWrapper = &wrapper; |
| CSSTokenizer::Scope scope(string, wrapper); |
| bool firstRuleValid = parser.consumeRuleList( |
| scope.tokenRange(), TopLevelRuleList, [&styleSheet](StyleRuleBase* rule) { |
| if (rule->isCharsetRule()) |
| return; |
| styleSheet->parserAppendRule(rule); |
| }); |
| styleSheet->setHasSyntacticallyValidCSSHeader(firstRuleValid); |
| } |
| |
| static CSSParserImpl::AllowedRulesType computeNewAllowedRules( |
| CSSParserImpl::AllowedRulesType allowedRules, |
| StyleRuleBase* rule) { |
| if (!rule || allowedRules == CSSParserImpl::KeyframeRules || |
| allowedRules == CSSParserImpl::NoRules) |
| return allowedRules; |
| ASSERT(allowedRules <= CSSParserImpl::RegularRules); |
| if (rule->isCharsetRule() || rule->isImportRule()) |
| return CSSParserImpl::AllowImportRules; |
| if (rule->isNamespaceRule()) |
| return CSSParserImpl::AllowNamespaceRules; |
| return CSSParserImpl::RegularRules; |
| } |
| |
| template <typename T> |
| bool CSSParserImpl::consumeRuleList(CSSParserTokenRange range, |
| RuleListType ruleListType, |
| const T callback) { |
| AllowedRulesType allowedRules = RegularRules; |
| switch (ruleListType) { |
| case TopLevelRuleList: |
| allowedRules = AllowCharsetRules; |
| break; |
| case RegularRuleList: |
| allowedRules = RegularRules; |
| break; |
| case KeyframesRuleList: |
| allowedRules = KeyframeRules; |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| |
| bool seenRule = false; |
| bool firstRuleValid = false; |
| while (!range.atEnd()) { |
| StyleRuleBase* rule; |
| switch (range.peek().type()) { |
| case WhitespaceToken: |
| range.consumeWhitespace(); |
| continue; |
| case AtKeywordToken: |
| rule = consumeAtRule(range, allowedRules); |
| break; |
| case CDOToken: |
| case CDCToken: |
| if (ruleListType == TopLevelRuleList) { |
| range.consume(); |
| continue; |
| } |
| // fallthrough |
| default: |
| rule = consumeQualifiedRule(range, allowedRules); |
| break; |
| } |
| if (!seenRule) { |
| seenRule = true; |
| firstRuleValid = rule; |
| } |
| if (rule) { |
| allowedRules = computeNewAllowedRules(allowedRules, rule); |
| callback(rule); |
| } |
| } |
| |
| return firstRuleValid; |
| } |
| |
| StyleRuleBase* CSSParserImpl::consumeAtRule(CSSParserTokenRange& range, |
| AllowedRulesType allowedRules) { |
| ASSERT(range.peek().type() == AtKeywordToken); |
| const StringView name = range.consumeIncludingWhitespace().value(); |
| const CSSParserToken* preludeStart = &range.peek(); |
| while (!range.atEnd() && range.peek().type() != LeftBraceToken && |
| range.peek().type() != SemicolonToken) |
| range.consumeComponentValue(); |
| |
| CSSParserTokenRange prelude = range.makeSubRange(preludeStart, &range.peek()); |
| CSSAtRuleID id = cssAtRuleID(name); |
| if (id != CSSAtRuleInvalid && m_context.useCounter()) |
| countAtRule(m_context.useCounter(), id); |
| |
| if (range.atEnd() || range.peek().type() == SemicolonToken) { |
| range.consume(); |
| if (allowedRules == AllowCharsetRules && id == CSSAtRuleCharset) |
| return consumeCharsetRule(prelude); |
| if (allowedRules <= AllowImportRules && id == CSSAtRuleImport) |
| return consumeImportRule(prelude); |
| if (allowedRules <= AllowNamespaceRules && id == CSSAtRuleNamespace) |
| return consumeNamespaceRule(prelude); |
| if (allowedRules == ApplyRules && id == CSSAtRuleApply) { |
| consumeApplyRule(prelude); |
| return nullptr; // consumeApplyRule just updates m_parsedProperties |
| } |
| return nullptr; // Parse error, unrecognised at-rule without block |
| } |
| |
| CSSParserTokenRange block = range.consumeBlock(); |
| if (allowedRules == KeyframeRules) |
| return nullptr; // Parse error, no at-rules supported inside @keyframes |
| if (allowedRules == NoRules || allowedRules == ApplyRules) |
| return nullptr; // Parse error, no at-rules with blocks supported inside |
| // declaration lists |
| |
| ASSERT(allowedRules <= RegularRules); |
| |
| switch (id) { |
| case CSSAtRuleMedia: |
| return consumeMediaRule(prelude, block); |
| case CSSAtRuleSupports: |
| return consumeSupportsRule(prelude, block); |
| case CSSAtRuleViewport: |
| return consumeViewportRule(prelude, block); |
| case CSSAtRuleFontFace: |
| return consumeFontFaceRule(prelude, block); |
| case CSSAtRuleWebkitKeyframes: |
| return consumeKeyframesRule(true, prelude, block); |
| case CSSAtRuleKeyframes: |
| return consumeKeyframesRule(false, prelude, block); |
| case CSSAtRulePage: |
| return consumePageRule(prelude, block); |
| default: |
| return nullptr; // Parse error, unrecognised at-rule with block |
| } |
| } |
| |
| StyleRuleBase* CSSParserImpl::consumeQualifiedRule( |
| CSSParserTokenRange& range, |
| AllowedRulesType allowedRules) { |
| const CSSParserToken* preludeStart = &range.peek(); |
| while (!range.atEnd() && range.peek().type() != LeftBraceToken) |
| range.consumeComponentValue(); |
| |
| if (range.atEnd()) |
| return nullptr; // Parse error, EOF instead of qualified rule block |
| |
| CSSParserTokenRange prelude = range.makeSubRange(preludeStart, &range.peek()); |
| CSSParserTokenRange block = range.consumeBlock(); |
| |
| if (allowedRules <= RegularRules) |
| return consumeStyleRule(prelude, block); |
| if (allowedRules == KeyframeRules) |
| return consumeKeyframeStyleRule(prelude, block); |
| |
| ASSERT_NOT_REACHED(); |
| return nullptr; |
| } |
| |
| // This may still consume tokens if it fails |
| static AtomicString consumeStringOrURI(CSSParserTokenRange& range) { |
| const CSSParserToken& token = range.peek(); |
| |
| if (token.type() == StringToken || token.type() == UrlToken) |
| return range.consumeIncludingWhitespace().value().toAtomicString(); |
| |
| if (token.type() != FunctionToken || |
| !equalIgnoringASCIICase(token.value(), "url")) |
| return AtomicString(); |
| |
| CSSParserTokenRange contents = range.consumeBlock(); |
| const CSSParserToken& uri = contents.consumeIncludingWhitespace(); |
| if (uri.type() == BadStringToken || !contents.atEnd()) |
| return AtomicString(); |
| DCHECK_EQ(uri.type(), StringToken); |
| return uri.value().toAtomicString(); |
| } |
| |
| StyleRuleCharset* CSSParserImpl::consumeCharsetRule( |
| CSSParserTokenRange prelude) { |
| const CSSParserToken& string = prelude.consumeIncludingWhitespace(); |
| if (string.type() != StringToken || !prelude.atEnd()) |
| return nullptr; // Parse error, expected a single string |
| return StyleRuleCharset::create(); |
| } |
| |
| StyleRuleImport* CSSParserImpl::consumeImportRule(CSSParserTokenRange prelude) { |
| AtomicString uri(consumeStringOrURI(prelude)); |
| if (uri.isNull()) |
| return nullptr; // Parse error, expected string or URI |
| |
| if (m_observerWrapper) { |
| unsigned endOffset = m_observerWrapper->endOffset(prelude); |
| m_observerWrapper->observer().startRuleHeader( |
| StyleRule::Import, m_observerWrapper->startOffset(prelude)); |
| m_observerWrapper->observer().endRuleHeader(endOffset); |
| m_observerWrapper->observer().startRuleBody(endOffset); |
| m_observerWrapper->observer().endRuleBody(endOffset); |
| } |
| |
| return StyleRuleImport::create(uri, |
| MediaQueryParser::parseMediaQuerySet(prelude)); |
| } |
| |
| StyleRuleNamespace* CSSParserImpl::consumeNamespaceRule( |
| CSSParserTokenRange prelude) { |
| AtomicString namespacePrefix; |
| if (prelude.peek().type() == IdentToken) |
| namespacePrefix = |
| prelude.consumeIncludingWhitespace().value().toAtomicString(); |
| |
| AtomicString uri(consumeStringOrURI(prelude)); |
| if (uri.isNull() || !prelude.atEnd()) |
| return nullptr; // Parse error, expected string or URI |
| |
| return StyleRuleNamespace::create(namespacePrefix, uri); |
| } |
| |
| StyleRuleMedia* CSSParserImpl::consumeMediaRule(CSSParserTokenRange prelude, |
| CSSParserTokenRange block) { |
| HeapVector<Member<StyleRuleBase>> rules; |
| |
| if (m_observerWrapper) { |
| m_observerWrapper->observer().startRuleHeader( |
| StyleRule::Media, m_observerWrapper->startOffset(prelude)); |
| m_observerWrapper->observer().endRuleHeader( |
| m_observerWrapper->endOffset(prelude)); |
| m_observerWrapper->observer().startRuleBody( |
| m_observerWrapper->previousTokenStartOffset(block)); |
| } |
| |
| if (m_styleSheet) |
| m_styleSheet->setHasMediaQueries(); |
| |
| consumeRuleList(block, RegularRuleList, |
| [&rules](StyleRuleBase* rule) { rules.append(rule); }); |
| |
| if (m_observerWrapper) |
| m_observerWrapper->observer().endRuleBody( |
| m_observerWrapper->endOffset(block)); |
| |
| return StyleRuleMedia::create(MediaQueryParser::parseMediaQuerySet(prelude), |
| rules); |
| } |
| |
| StyleRuleSupports* CSSParserImpl::consumeSupportsRule( |
| CSSParserTokenRange prelude, |
| CSSParserTokenRange block) { |
| CSSSupportsParser::SupportsResult supported = |
| CSSSupportsParser::supportsCondition(prelude, *this); |
| if (supported == CSSSupportsParser::Invalid) |
| return nullptr; // Parse error, invalid @supports condition |
| |
| if (m_observerWrapper) { |
| m_observerWrapper->observer().startRuleHeader( |
| StyleRule::Supports, m_observerWrapper->startOffset(prelude)); |
| m_observerWrapper->observer().endRuleHeader( |
| m_observerWrapper->endOffset(prelude)); |
| m_observerWrapper->observer().startRuleBody( |
| m_observerWrapper->previousTokenStartOffset(block)); |
| } |
| |
| HeapVector<Member<StyleRuleBase>> rules; |
| consumeRuleList(block, RegularRuleList, |
| [&rules](StyleRuleBase* rule) { rules.append(rule); }); |
| |
| if (m_observerWrapper) |
| m_observerWrapper->observer().endRuleBody( |
| m_observerWrapper->endOffset(block)); |
| |
| return StyleRuleSupports::create(prelude.serialize().stripWhiteSpace(), |
| supported, rules); |
| } |
| |
| StyleRuleViewport* CSSParserImpl::consumeViewportRule( |
| CSSParserTokenRange prelude, |
| CSSParserTokenRange block) { |
| // Allow @viewport rules from UA stylesheets even if the feature is disabled. |
| if (!RuntimeEnabledFeatures::cssViewportEnabled() && |
| !isUASheetBehavior(m_context.mode())) |
| return nullptr; |
| |
| if (!prelude.atEnd()) |
| return nullptr; // Parser error; @viewport prelude should be empty |
| |
| if (m_observerWrapper) { |
| unsigned endOffset = m_observerWrapper->endOffset(prelude); |
| m_observerWrapper->observer().startRuleHeader( |
| StyleRule::Viewport, m_observerWrapper->startOffset(prelude)); |
| m_observerWrapper->observer().endRuleHeader(endOffset); |
| m_observerWrapper->observer().startRuleBody(endOffset); |
| m_observerWrapper->observer().endRuleBody(endOffset); |
| } |
| |
| consumeDeclarationList(block, StyleRule::Viewport); |
| return StyleRuleViewport::create( |
| createStylePropertySet(m_parsedProperties, CSSViewportRuleMode)); |
| } |
| |
| StyleRuleFontFace* CSSParserImpl::consumeFontFaceRule( |
| CSSParserTokenRange prelude, |
| CSSParserTokenRange block) { |
| if (!prelude.atEnd()) |
| return nullptr; // Parse error; @font-face prelude should be empty |
| |
| if (m_observerWrapper) { |
| unsigned endOffset = m_observerWrapper->endOffset(prelude); |
| m_observerWrapper->observer().startRuleHeader( |
| StyleRule::FontFace, m_observerWrapper->startOffset(prelude)); |
| m_observerWrapper->observer().endRuleHeader(endOffset); |
| m_observerWrapper->observer().startRuleBody(endOffset); |
| m_observerWrapper->observer().endRuleBody(endOffset); |
| } |
| |
| if (m_styleSheet) |
| m_styleSheet->setHasFontFaceRule(); |
| |
| consumeDeclarationList(block, StyleRule::FontFace); |
| return StyleRuleFontFace::create( |
| createStylePropertySet(m_parsedProperties, CSSFontFaceRuleMode)); |
| } |
| |
| StyleRuleKeyframes* CSSParserImpl::consumeKeyframesRule( |
| bool webkitPrefixed, |
| CSSParserTokenRange prelude, |
| CSSParserTokenRange block) { |
| CSSParserTokenRange rangeCopy = prelude; // For inspector callbacks |
| const CSSParserToken& nameToken = prelude.consumeIncludingWhitespace(); |
| if (!prelude.atEnd()) |
| return nullptr; // Parse error; expected single non-whitespace token in |
| // @keyframes header |
| |
| String name; |
| if (nameToken.type() == IdentToken) { |
| name = nameToken.value().toString(); |
| } else if (nameToken.type() == StringToken && webkitPrefixed) { |
| if (m_context.useCounter()) |
| m_context.useCounter()->count(UseCounter::QuotedKeyframesRule); |
| name = nameToken.value().toString(); |
| } else { |
| return nullptr; // Parse error; expected ident token in @keyframes header |
| } |
| |
| if (m_observerWrapper) { |
| m_observerWrapper->observer().startRuleHeader( |
| StyleRule::Keyframes, m_observerWrapper->startOffset(rangeCopy)); |
| m_observerWrapper->observer().endRuleHeader( |
| m_observerWrapper->endOffset(prelude)); |
| m_observerWrapper->observer().startRuleBody( |
| m_observerWrapper->previousTokenStartOffset(block)); |
| m_observerWrapper->observer().endRuleBody( |
| m_observerWrapper->endOffset(block)); |
| } |
| |
| StyleRuleKeyframes* keyframeRule = StyleRuleKeyframes::create(); |
| consumeRuleList( |
| block, KeyframesRuleList, [keyframeRule](StyleRuleBase* keyframe) { |
| keyframeRule->parserAppendKeyframe(toStyleRuleKeyframe(keyframe)); |
| }); |
| keyframeRule->setName(name); |
| keyframeRule->setVendorPrefixed(webkitPrefixed); |
| return keyframeRule; |
| } |
| |
| StyleRulePage* CSSParserImpl::consumePageRule(CSSParserTokenRange prelude, |
| CSSParserTokenRange block) { |
| CSSSelectorList selectorList = parsePageSelector(prelude, m_styleSheet); |
| if (!selectorList.isValid()) |
| return nullptr; // Parse error, invalid @page selector |
| |
| if (m_observerWrapper) { |
| unsigned endOffset = m_observerWrapper->endOffset(prelude); |
| m_observerWrapper->observer().startRuleHeader( |
| StyleRule::Page, m_observerWrapper->startOffset(prelude)); |
| m_observerWrapper->observer().endRuleHeader(endOffset); |
| } |
| |
| consumeDeclarationList(block, StyleRule::Style); |
| |
| return StyleRulePage::create( |
| std::move(selectorList), |
| createStylePropertySet(m_parsedProperties, m_context.mode())); |
| } |
| |
| void CSSParserImpl::consumeApplyRule(CSSParserTokenRange prelude) { |
| ASSERT(RuntimeEnabledFeatures::cssApplyAtRulesEnabled()); |
| |
| const CSSParserToken& ident = prelude.consumeIncludingWhitespace(); |
| if (!prelude.atEnd() || !CSSVariableParser::isValidVariableName(ident)) |
| return; // Parse error, expected a single custom property name |
| m_parsedProperties.append(CSSProperty( |
| CSSPropertyApplyAtRule, |
| *CSSCustomIdentValue::create(ident.value().toAtomicString()))); |
| } |
| |
| StyleRuleKeyframe* CSSParserImpl::consumeKeyframeStyleRule( |
| CSSParserTokenRange prelude, |
| CSSParserTokenRange block) { |
| std::unique_ptr<Vector<double>> keyList = consumeKeyframeKeyList(prelude); |
| if (!keyList) |
| return nullptr; |
| |
| if (m_observerWrapper) { |
| m_observerWrapper->observer().startRuleHeader( |
| StyleRule::Keyframe, m_observerWrapper->startOffset(prelude)); |
| m_observerWrapper->observer().endRuleHeader( |
| m_observerWrapper->endOffset(prelude)); |
| } |
| |
| consumeDeclarationList(block, StyleRule::Keyframe); |
| return StyleRuleKeyframe::create( |
| std::move(keyList), |
| createStylePropertySet(m_parsedProperties, m_context.mode())); |
| } |
| |
| static void observeSelectors(CSSParserObserverWrapper& wrapper, |
| CSSParserTokenRange selectors) { |
| // This is easier than hooking into the CSSSelectorParser |
| selectors.consumeWhitespace(); |
| CSSParserTokenRange originalRange = selectors; |
| wrapper.observer().startRuleHeader(StyleRule::Style, |
| wrapper.startOffset(originalRange)); |
| |
| while (!selectors.atEnd()) { |
| const CSSParserToken* selectorStart = &selectors.peek(); |
| while (!selectors.atEnd() && selectors.peek().type() != CommaToken) |
| selectors.consumeComponentValue(); |
| CSSParserTokenRange selector = |
| selectors.makeSubRange(selectorStart, &selectors.peek()); |
| selectors.consumeIncludingWhitespace(); |
| |
| wrapper.observer().observeSelector(wrapper.startOffset(selector), |
| wrapper.endOffset(selector)); |
| } |
| |
| wrapper.observer().endRuleHeader(wrapper.endOffset(originalRange)); |
| } |
| |
| StyleRule* CSSParserImpl::consumeStyleRule(CSSParserTokenRange prelude, |
| CSSParserTokenRange block) { |
| CSSSelectorList selectorList = |
| CSSSelectorParser::parseSelector(prelude, m_context, m_styleSheet); |
| if (!selectorList.isValid()) |
| return nullptr; // Parse error, invalid selector list |
| |
| if (m_observerWrapper) |
| observeSelectors(*m_observerWrapper, prelude); |
| |
| consumeDeclarationList(block, StyleRule::Style); |
| |
| return StyleRule::create( |
| std::move(selectorList), |
| createStylePropertySet(m_parsedProperties, m_context.mode())); |
| } |
| |
| void CSSParserImpl::consumeDeclarationList(CSSParserTokenRange range, |
| StyleRule::RuleType ruleType) { |
| ASSERT(m_parsedProperties.isEmpty()); |
| |
| bool useObserver = m_observerWrapper && (ruleType == StyleRule::Style || |
| ruleType == StyleRule::Keyframe); |
| if (useObserver) { |
| m_observerWrapper->observer().startRuleBody( |
| m_observerWrapper->previousTokenStartOffset(range)); |
| m_observerWrapper->skipCommentsBefore(range, true); |
| } |
| |
| while (!range.atEnd()) { |
| switch (range.peek().type()) { |
| case WhitespaceToken: |
| case SemicolonToken: |
| range.consume(); |
| break; |
| case IdentToken: { |
| const CSSParserToken* declarationStart = &range.peek(); |
| |
| if (useObserver) |
| m_observerWrapper->yieldCommentsBefore(range); |
| |
| while (!range.atEnd() && range.peek().type() != SemicolonToken) |
| range.consumeComponentValue(); |
| |
| consumeDeclaration(range.makeSubRange(declarationStart, &range.peek()), |
| ruleType); |
| |
| if (useObserver) |
| m_observerWrapper->skipCommentsBefore(range, false); |
| break; |
| } |
| case AtKeywordToken: { |
| AllowedRulesType allowedRules = |
| ruleType == StyleRule::Style && |
| RuntimeEnabledFeatures::cssApplyAtRulesEnabled() |
| ? ApplyRules |
| : NoRules; |
| StyleRuleBase* rule = consumeAtRule(range, allowedRules); |
| ASSERT_UNUSED(rule, !rule); |
| break; |
| } |
| default: // Parse error, unexpected token in declaration list |
| while (!range.atEnd() && range.peek().type() != SemicolonToken) |
| range.consumeComponentValue(); |
| break; |
| } |
| } |
| |
| // Yield remaining comments |
| if (useObserver) { |
| m_observerWrapper->yieldCommentsBefore(range); |
| m_observerWrapper->observer().endRuleBody( |
| m_observerWrapper->endOffset(range)); |
| } |
| } |
| |
| void CSSParserImpl::consumeDeclaration(CSSParserTokenRange range, |
| StyleRule::RuleType ruleType) { |
| CSSParserTokenRange rangeCopy = range; // For inspector callbacks |
| |
| ASSERT(range.peek().type() == IdentToken); |
| const CSSParserToken& token = range.consumeIncludingWhitespace(); |
| CSSPropertyID unresolvedProperty = token.parseAsUnresolvedCSSPropertyID(); |
| if (range.consume().type() != ColonToken) |
| return; // Parse error |
| |
| bool important = false; |
| const CSSParserToken* declarationValueEnd = range.end(); |
| const CSSParserToken* last = range.end() - 1; |
| while (last->type() == WhitespaceToken) |
| --last; |
| if (last->type() == IdentToken && |
| equalIgnoringASCIICase(last->value(), "important")) { |
| --last; |
| while (last->type() == WhitespaceToken) |
| --last; |
| if (last->type() == DelimiterToken && last->delimiter() == '!') { |
| important = true; |
| declarationValueEnd = last; |
| } |
| } |
| |
| size_t propertiesCount = m_parsedProperties.size(); |
| // TODO(timloh): This should only be for StyleRule::Style/Keyframe, |
| // crbug.com/641873. |
| if (unresolvedProperty == CSSPropertyVariable) { |
| AtomicString variableName = token.value().toAtomicString(); |
| bool isAnimationTainted = ruleType == StyleRule::Keyframe; |
| consumeVariableValue(range.makeSubRange(&range.peek(), declarationValueEnd), |
| variableName, important, isAnimationTainted); |
| } |
| |
| // TODO(timloh): Should this check occur before the call to |
| // consumeVariableValue()? |
| if (important && |
| (ruleType == StyleRule::FontFace || ruleType == StyleRule::Keyframe)) |
| return; |
| |
| if (unresolvedProperty != CSSPropertyInvalid && |
| unresolvedProperty != CSSPropertyVariable) { |
| if (m_styleSheet && m_styleSheet->singleOwnerDocument()) |
| Deprecation::warnOnDeprecatedProperties( |
| m_styleSheet->singleOwnerDocument()->frame(), unresolvedProperty); |
| consumeDeclarationValue( |
| range.makeSubRange(&range.peek(), declarationValueEnd), |
| unresolvedProperty, important, ruleType); |
| } |
| |
| if (m_observerWrapper && |
| (ruleType == StyleRule::Style || ruleType == StyleRule::Keyframe)) { |
| m_observerWrapper->observer().observeProperty( |
| m_observerWrapper->startOffset(rangeCopy), |
| m_observerWrapper->endOffset(rangeCopy), important, |
| m_parsedProperties.size() != propertiesCount); |
| } |
| } |
| |
| void CSSParserImpl::consumeVariableValue(CSSParserTokenRange range, |
| const AtomicString& variableName, |
| bool important, |
| bool isAnimationTainted) { |
| if (CSSCustomPropertyDeclaration* value = |
| CSSVariableParser::parseDeclarationValue(variableName, range, |
| isAnimationTainted)) |
| m_parsedProperties.append( |
| CSSProperty(CSSPropertyVariable, *value, important)); |
| } |
| |
| void CSSParserImpl::consumeDeclarationValue(CSSParserTokenRange range, |
| CSSPropertyID unresolvedProperty, |
| bool important, |
| StyleRule::RuleType ruleType) { |
| CSSPropertyParser::parseValue(unresolvedProperty, important, range, m_context, |
| m_parsedProperties, ruleType); |
| } |
| |
| std::unique_ptr<Vector<double>> CSSParserImpl::consumeKeyframeKeyList( |
| CSSParserTokenRange range) { |
| std::unique_ptr<Vector<double>> result = wrapUnique(new Vector<double>); |
| while (true) { |
| range.consumeWhitespace(); |
| const CSSParserToken& token = range.consumeIncludingWhitespace(); |
| if (token.type() == PercentageToken && token.numericValue() >= 0 && |
| token.numericValue() <= 100) |
| result->append(token.numericValue() / 100); |
| else if (token.type() == IdentToken && |
| equalIgnoringASCIICase(token.value(), "from")) |
| result->append(0); |
| else if (token.type() == IdentToken && |
| equalIgnoringASCIICase(token.value(), "to")) |
| result->append(1); |
| else |
| return nullptr; // Parser error, invalid value in keyframe selector |
| if (range.atEnd()) |
| return result; |
| if (range.consume().type() != CommaToken) |
| return nullptr; // Parser error |
| } |
| } |
| |
| } // namespace blink |