| Index: Source/core/css/parser/CSSSelectorParser.cpp
|
| diff --git a/Source/core/css/parser/CSSSelectorParser.cpp b/Source/core/css/parser/CSSSelectorParser.cpp
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..10bd535c30739e85d444e7132a5070447255f615
|
| --- /dev/null
|
| +++ b/Source/core/css/parser/CSSSelectorParser.cpp
|
| @@ -0,0 +1,348 @@
|
| +// 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 "config.h"
|
| +#include "core/css/parser/CSSSelectorParser.h"
|
| +
|
| +#include "core/css/CSSSelectorList.h"
|
| +#include "core/css/StyleSheetContents.h"
|
| +#include "platform/RuntimeEnabledFeatures.h"
|
| +
|
| +namespace blink {
|
| +
|
| +void CSSSelectorParser::parseSelector(CSSParserTokenRange tokenRange, const CSSParserContext& context, CSSSelectorList& output)
|
| +{
|
| + CSSSelectorParser parser(tokenRange, context);
|
| + parser.m_tokenRange.consumeWhitespaceAndComments();
|
| + CSSSelectorList result;
|
| + parser.consumeComplexSelectorList(result);
|
| + if (parser.m_tokenRange.atEnd())
|
| + output.adopt(result);
|
| + ASSERT(!(output.isValid() && parser.m_failedParsing));
|
| +}
|
| +
|
| +CSSSelectorParser::CSSSelectorParser(CSSParserTokenRange tokenRange, const CSSParserContext& context)
|
| +: m_tokenRange(tokenRange)
|
| +, m_context(context)
|
| +, m_defaultNamespace(starAtom)
|
| +, m_styleSheet(nullptr)
|
| +, m_failedParsing(false)
|
| +{
|
| +}
|
| +
|
| +void CSSSelectorParser::consumeComplexSelectorList(CSSSelectorList& output)
|
| +{
|
| + Vector<OwnPtr<CSSParserSelector>> selectorList;
|
| + OwnPtr<CSSParserSelector> selector = consumeComplexSelector();
|
| + if (!selector)
|
| + return;
|
| + selectorList.append(selector.release());
|
| + while (!m_tokenRange.atEnd() && m_tokenRange.peek().type() == CommaToken) {
|
| + m_tokenRange.consumeIncludingWhitespaceAndComments();
|
| + selector = consumeComplexSelector();
|
| + if (!selector)
|
| + return;
|
| + selectorList.append(selector.release());
|
| + }
|
| +
|
| + if (!m_failedParsing)
|
| + output.adoptSelectorVector(selectorList);
|
| +}
|
| +
|
| +PassOwnPtr<CSSParserSelector> CSSSelectorParser::consumeComplexSelector()
|
| +{
|
| + OwnPtr<CSSParserSelector> selector = consumeCompoundSelector();
|
| + if (!selector)
|
| + return nullptr;
|
| + while (CSSSelector::Relation combinator = consumeCombinator()) {
|
| + OwnPtr<CSSParserSelector> nextSelector = consumeCompoundSelector();
|
| + if (!nextSelector)
|
| + return combinator == CSSSelector::Descendant ? selector.release() : nullptr;
|
| + CSSParserSelector* end = nextSelector.get();
|
| + while (end->tagHistory())
|
| + end = end->tagHistory();
|
| + end->setRelation(combinator);
|
| + if (selector->isContentPseudoElement())
|
| + end->setRelationIsAffectedByPseudoContent();
|
| + end->setTagHistory(selector.release());
|
| +
|
| + selector = nextSelector.release();
|
| + }
|
| +
|
| + return selector.release();
|
| +}
|
| +
|
| +PassOwnPtr<CSSParserSelector> CSSSelectorParser::consumeCompoundSelector()
|
| +{
|
| + OwnPtr<CSSParserSelector> selector;
|
| +
|
| + AtomicString namespacePrefix;
|
| + AtomicString elementName;
|
| + bool hasNamespace;
|
| + if (!consumeName(elementName, namespacePrefix, hasNamespace)) {
|
| + selector = consumeSimpleSelector();
|
| + if (!selector)
|
| + return nullptr;
|
| + }
|
| + if (m_context.isHTMLDocument())
|
| + elementName = elementName.lower();
|
| +
|
| + while (OwnPtr<CSSParserSelector> nextSelector = consumeSimpleSelector()) {
|
| + if (selector)
|
| + selector = rewriteSpecifiers(selector.release(), nextSelector.release());
|
| + else
|
| + selector = nextSelector.release();
|
| + }
|
| +
|
| + if (!selector) {
|
| + if (hasNamespace)
|
| + return CSSParserSelector::create(determineNameInNamespace(namespacePrefix, elementName));
|
| + return CSSParserSelector::create(QualifiedName(nullAtom, elementName, m_defaultNamespace));
|
| + }
|
| + if (elementName.isNull())
|
| + rewriteSpecifiersWithNamespaceIfNeeded(selector.get());
|
| + else
|
| + rewriteSpecifiersWithElementName(namespacePrefix, elementName, selector.get());
|
| + return selector.release();
|
| +}
|
| +
|
| +PassOwnPtr<CSSParserSelector> CSSSelectorParser::consumeSimpleSelector()
|
| +{
|
| + const CSSParserToken& token = m_tokenRange.peek();
|
| + OwnPtr<CSSParserSelector> selector;
|
| + if (token.type() == HashToken)
|
| + selector = consumeId();
|
| + else if (token.type() == DelimiterToken && token.delimiter() == '.')
|
| + selector = consumeClass();
|
| + else if (token.type() == LeftBracketToken)
|
| + selector = consumeAttribute();
|
| + else if (token.type() == ColonToken)
|
| + selector = consumePseudo();
|
| + else
|
| + return nullptr;
|
| + if (!selector)
|
| + m_failedParsing = true;
|
| + return selector.release();
|
| +}
|
| +
|
| +bool CSSSelectorParser::consumeName(AtomicString& name, AtomicString& namespacePrefix, bool& hasNamespace)
|
| +{
|
| + name = nullAtom;
|
| + namespacePrefix = nullAtom;
|
| + hasNamespace = false;
|
| +
|
| + const CSSParserToken& firstToken = m_tokenRange.peek();
|
| + if (firstToken.type() == IdentToken) {
|
| + name = AtomicString(firstToken.value());
|
| + m_tokenRange.consumeIncludingComments();
|
| + } else if (firstToken.type() == DelimiterToken && firstToken.delimiter() == '*') {
|
| + name = starAtom;
|
| + m_tokenRange.consumeIncludingComments();
|
| + } else if (firstToken.type() == DelimiterToken && firstToken.delimiter() == '|') {
|
| + // No namespace
|
| + } else {
|
| + return false;
|
| + }
|
| +
|
| + if (m_tokenRange.peek().type() != DelimiterToken || m_tokenRange.peek().delimiter() != '|')
|
| + return true;
|
| + m_tokenRange.consumeIncludingComments();
|
| +
|
| + hasNamespace = true;
|
| + namespacePrefix = name;
|
| + const CSSParserToken& nameToken = m_tokenRange.consumeIncludingComments();
|
| + if (nameToken.type() == IdentToken) {
|
| + name = AtomicString(nameToken.value());
|
| + } else if (nameToken.type() == DelimiterToken && nameToken.delimiter() == '*') {
|
| + name = starAtom;
|
| + } else {
|
| + name = nullAtom;
|
| + namespacePrefix = nullAtom;
|
| + return false;
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +PassOwnPtr<CSSParserSelector> CSSSelectorParser::consumeId()
|
| +{
|
| + ASSERT(m_tokenRange.peek().type() == HashToken);
|
| + if (m_tokenRange.peek().hashTokenType() != HashTokenId)
|
| + return nullptr;
|
| + OwnPtr<CSSParserSelector> selector = CSSParserSelector::create();
|
| + selector->setMatch(CSSSelector::Id);
|
| + const String& value = m_tokenRange.consumeIncludingComments().value();
|
| + if (isQuirksModeBehavior(m_context.mode()))
|
| + selector->setValue(AtomicString(value.lower()));
|
| + else
|
| + selector->setValue(AtomicString(value));
|
| + return selector.release();
|
| +}
|
| +
|
| +PassOwnPtr<CSSParserSelector> CSSSelectorParser::consumeClass()
|
| +{
|
| + ASSERT(m_tokenRange.peek().type() == DelimiterToken);
|
| + ASSERT(m_tokenRange.peek().delimiter() == '.');
|
| + m_tokenRange.consumeIncludingComments();
|
| + if (m_tokenRange.peek().type() != IdentToken)
|
| + return nullptr;
|
| + OwnPtr<CSSParserSelector> selector = CSSParserSelector::create();
|
| + selector->setMatch(CSSSelector::Class);
|
| + const String& value = m_tokenRange.consumeIncludingComments().value();
|
| + if (isQuirksModeBehavior(m_context.mode()))
|
| + selector->setValue(AtomicString(value.lower()));
|
| + else
|
| + selector->setValue(AtomicString(value));
|
| + return selector.release();
|
| +}
|
| +
|
| +PassOwnPtr<CSSParserSelector> CSSSelectorParser::consumeAttribute()
|
| +{
|
| + // FIXME: Implement attribute parsing
|
| + return nullptr;
|
| +}
|
| +
|
| +PassOwnPtr<CSSParserSelector> CSSSelectorParser::consumePseudo()
|
| +{
|
| + // FIXME: Implement pseudo-element and pseudo-class parsing
|
| + return nullptr;
|
| +}
|
| +
|
| +CSSSelector::Relation CSSSelectorParser::consumeCombinator()
|
| +{
|
| + CSSSelector::Relation fallbackResult = CSSSelector::SubSelector;
|
| + while (m_tokenRange.peek().type() == WhitespaceToken || m_tokenRange.peek().type() == CommentToken) {
|
| + if (m_tokenRange.consume().type() == WhitespaceToken)
|
| + fallbackResult = CSSSelector::Descendant;
|
| + }
|
| +
|
| + if (m_tokenRange.peek().type() != DelimiterToken)
|
| + return fallbackResult;
|
| +
|
| + UChar delimiter = m_tokenRange.peek().delimiter();
|
| +
|
| + if (delimiter == '+' || delimiter == '~' || delimiter == '>') {
|
| + m_tokenRange.consumeIncludingWhitespaceAndComments();
|
| + if (delimiter == '+')
|
| + return CSSSelector::DirectAdjacent;
|
| + if (delimiter == '~')
|
| + return CSSSelector::IndirectAdjacent;
|
| + return CSSSelector::Child;
|
| + }
|
| +
|
| + // Match /deep/
|
| + if (delimiter != '/')
|
| + return fallbackResult;
|
| + m_tokenRange.consumeIncludingComments();
|
| + const CSSParserToken& ident = m_tokenRange.consumeIncludingComments();
|
| + if (ident.type() != IdentToken || !equalIgnoringCase(ident.value(), "deep"))
|
| + m_failedParsing = true;
|
| + const CSSParserToken& slash = m_tokenRange.consumeIncludingWhitespaceAndComments();
|
| + if (slash.type() != DelimiterToken || slash.delimiter() != '/')
|
| + m_failedParsing = true;
|
| + return CSSSelector::ShadowDeep;
|
| +}
|
| +
|
| +QualifiedName CSSSelectorParser::determineNameInNamespace(const AtomicString& prefix, const AtomicString& localName)
|
| +{
|
| + if (!m_styleSheet)
|
| + return QualifiedName(prefix, localName, m_defaultNamespace);
|
| + return QualifiedName(prefix, localName, m_styleSheet->determineNamespace(prefix));
|
| +}
|
| +
|
| +void CSSSelectorParser::rewriteSpecifiersWithNamespaceIfNeeded(CSSParserSelector* specifiers)
|
| +{
|
| + if (m_defaultNamespace != starAtom || specifiers->crossesTreeScopes())
|
| + rewriteSpecifiersWithElementName(nullAtom, starAtom, specifiers, /*tagIsForNamespaceRule*/true);
|
| +}
|
| +
|
| +void CSSSelectorParser::rewriteSpecifiersWithElementName(const AtomicString& namespacePrefix, const AtomicString& elementName, CSSParserSelector* specifiers, bool tagIsForNamespaceRule)
|
| +{
|
| + AtomicString determinedNamespace = namespacePrefix != nullAtom && m_styleSheet ? m_styleSheet->determineNamespace(namespacePrefix) : m_defaultNamespace;
|
| + QualifiedName tag(namespacePrefix, elementName, determinedNamespace);
|
| +
|
| + if (specifiers->crossesTreeScopes())
|
| + return rewriteSpecifiersWithElementNameForCustomPseudoElement(tag, elementName, specifiers, tagIsForNamespaceRule);
|
| +
|
| + if (specifiers->isContentPseudoElement())
|
| + return rewriteSpecifiersWithElementNameForContentPseudoElement(tag, elementName, specifiers, tagIsForNamespaceRule);
|
| +
|
| + // *:host never matches, so we can't discard the * otherwise we can't tell the
|
| + // difference between *:host and just :host.
|
| + if (tag == anyQName() && !specifiers->hasHostPseudoSelector())
|
| + return;
|
| + if (specifiers->pseudoType() != CSSSelector::PseudoCue)
|
| + specifiers->prependTagSelector(tag, tagIsForNamespaceRule);
|
| +}
|
| +
|
| +void CSSSelectorParser::rewriteSpecifiersWithElementNameForCustomPseudoElement(const QualifiedName& tag, const AtomicString& elementName, CSSParserSelector* specifiers, bool tagIsForNamespaceRule)
|
| +{
|
| + CSSParserSelector* lastShadowPseudo = specifiers;
|
| + CSSParserSelector* history = specifiers;
|
| + while (history->tagHistory()) {
|
| + history = history->tagHistory();
|
| + if (history->crossesTreeScopes() || history->hasShadowPseudo())
|
| + lastShadowPseudo = history;
|
| + }
|
| +
|
| + if (lastShadowPseudo->tagHistory()) {
|
| + if (tag != anyQName())
|
| + lastShadowPseudo->tagHistory()->prependTagSelector(tag, tagIsForNamespaceRule);
|
| + return;
|
| + }
|
| +
|
| + // For shadow-ID pseudo-elements to be correctly matched, the ShadowPseudo combinator has to be used.
|
| + // We therefore create a new Selector with that combinator here in any case, even if matching any (host) element in any namespace (i.e. '*').
|
| + OwnPtr<CSSParserSelector> elementNameSelector = adoptPtr(new CSSParserSelector(tag));
|
| + lastShadowPseudo->setTagHistory(elementNameSelector.release());
|
| + lastShadowPseudo->setRelation(CSSSelector::ShadowPseudo);
|
| +}
|
| +
|
| +void CSSSelectorParser::rewriteSpecifiersWithElementNameForContentPseudoElement(const QualifiedName& tag, const AtomicString& elementName, CSSParserSelector* specifiers, bool tagIsForNamespaceRule)
|
| +{
|
| + CSSParserSelector* last = specifiers;
|
| + CSSParserSelector* history = specifiers;
|
| + while (history->tagHistory()) {
|
| + history = history->tagHistory();
|
| + if (history->isContentPseudoElement() || history->relationIsAffectedByPseudoContent())
|
| + last = history;
|
| + }
|
| +
|
| + if (last->tagHistory()) {
|
| + if (tag != anyQName())
|
| + last->tagHistory()->prependTagSelector(tag, tagIsForNamespaceRule);
|
| + return;
|
| + }
|
| +
|
| + // For shadow-ID pseudo-elements to be correctly matched, the ShadowPseudo combinator has to be used.
|
| + // We therefore create a new Selector with that combinator here in any case, even if matching any (host) element in any namespace (i.e. '*').
|
| + OwnPtr<CSSParserSelector> elementNameSelector = adoptPtr(new CSSParserSelector(tag));
|
| + last->setTagHistory(elementNameSelector.release());
|
| +}
|
| +
|
| +PassOwnPtr<CSSParserSelector> CSSSelectorParser::rewriteSpecifiers(PassOwnPtr<CSSParserSelector> specifiers, PassOwnPtr<CSSParserSelector> newSpecifier)
|
| +{
|
| + if (newSpecifier->crossesTreeScopes()) {
|
| + // Unknown pseudo element always goes at the top of selector chain.
|
| + newSpecifier->appendTagHistory(CSSSelector::ShadowPseudo, specifiers);
|
| + return newSpecifier;
|
| + }
|
| + if (newSpecifier->isContentPseudoElement()) {
|
| + newSpecifier->appendTagHistory(CSSSelector::SubSelector, specifiers);
|
| + return newSpecifier;
|
| + }
|
| + if (specifiers->crossesTreeScopes()) {
|
| + // Specifiers for unknown pseudo element go right behind it in the chain.
|
| + specifiers->insertTagHistory(CSSSelector::SubSelector, newSpecifier, CSSSelector::ShadowPseudo);
|
| + return specifiers;
|
| + }
|
| + if (specifiers->isContentPseudoElement()) {
|
| + specifiers->insertTagHistory(CSSSelector::SubSelector, newSpecifier, CSSSelector::SubSelector);
|
| + return specifiers;
|
| + }
|
| + specifiers->appendTagHistory(CSSSelector::SubSelector, newSpecifier);
|
| + return specifiers;
|
| +}
|
| +
|
| +} // namespace blink
|
|
|