summaryrefslogtreecommitdiff
path: root/Source/WebCore/css/parser/CSSSelectorParser.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/css/parser/CSSSelectorParser.cpp')
-rw-r--r--Source/WebCore/css/parser/CSSSelectorParser.cpp891
1 files changed, 891 insertions, 0 deletions
diff --git a/Source/WebCore/css/parser/CSSSelectorParser.cpp b/Source/WebCore/css/parser/CSSSelectorParser.cpp
new file mode 100644
index 000000000..08892c955
--- /dev/null
+++ b/Source/WebCore/css/parser/CSSSelectorParser.cpp
@@ -0,0 +1,891 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright (C) 2016 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "config.h"
+#include "CSSSelectorParser.h"
+
+#include "CSSParserIdioms.h"
+#include "CSSParserMode.h"
+#include "CSSSelectorList.h"
+#include "StyleSheetContents.h"
+#include <memory>
+
+namespace WebCore {
+
+CSSSelectorList CSSSelectorParser::parseSelector(CSSParserTokenRange range, const CSSParserContext& context, StyleSheetContents* styleSheet)
+{
+ CSSSelectorParser parser(context, styleSheet);
+ range.consumeWhitespace();
+ CSSSelectorList result = parser.consumeComplexSelectorList(range);
+ if (!range.atEnd())
+ return CSSSelectorList();
+ return result;
+}
+
+CSSSelectorParser::CSSSelectorParser(const CSSParserContext& context, StyleSheetContents* styleSheet)
+ : m_context(context)
+ , m_styleSheet(styleSheet)
+{
+}
+
+CSSSelectorList CSSSelectorParser::consumeComplexSelectorList(CSSParserTokenRange& range)
+{
+ Vector<std::unique_ptr<CSSParserSelector>> selectorList;
+ std::unique_ptr<CSSParserSelector> selector = consumeComplexSelector(range);
+ if (!selector)
+ return CSSSelectorList();
+ selectorList.append(WTFMove(selector));
+ while (!range.atEnd() && range.peek().type() == CommaToken) {
+ range.consumeIncludingWhitespace();
+ selector = consumeComplexSelector(range);
+ if (!selector)
+ return CSSSelectorList();
+ selectorList.append(WTFMove(selector));
+ }
+
+ CSSSelectorList list;
+ if (m_failedParsing)
+ return list;
+ list.adoptSelectorVector(selectorList);
+ return list;
+}
+
+CSSSelectorList CSSSelectorParser::consumeCompoundSelectorList(CSSParserTokenRange& range)
+{
+ Vector<std::unique_ptr<CSSParserSelector>> selectorList;
+ std::unique_ptr<CSSParserSelector> selector = consumeCompoundSelector(range);
+ range.consumeWhitespace();
+ if (!selector)
+ return CSSSelectorList();
+ selectorList.append(WTFMove(selector));
+ while (!range.atEnd() && range.peek().type() == CommaToken) {
+ range.consumeIncludingWhitespace();
+ selector = consumeCompoundSelector(range);
+ range.consumeWhitespace();
+ if (!selector)
+ return CSSSelectorList();
+ selectorList.append(WTFMove(selector));
+ }
+
+ CSSSelectorList list;
+ if (m_failedParsing)
+ return list;
+ list.adoptSelectorVector(selectorList);
+ return list;
+}
+
+static bool consumeLangArgumentList(std::unique_ptr<Vector<AtomicString>>& argumentList, CSSParserTokenRange& range)
+{
+ const CSSParserToken& ident = range.consumeIncludingWhitespace();
+ if (ident.type() != IdentToken && ident.type() != StringToken)
+ return false;
+ StringView string = ident.value();
+ if (string.startsWith("--"))
+ return false;
+ argumentList->append(string.toAtomicString());
+ while (!range.atEnd() && range.peek().type() == CommaToken) {
+ range.consumeIncludingWhitespace();
+ const CSSParserToken& ident = range.consumeIncludingWhitespace();
+ if (ident.type() != IdentToken && ident.type() != StringToken)
+ return false;
+ StringView string = ident.value();
+ if (string.startsWith("--"))
+ return false;
+ argumentList->append(string.toAtomicString());
+ }
+ return range.atEnd();
+}
+
+namespace {
+
+enum CompoundSelectorFlags {
+ HasPseudoElementForRightmostCompound = 1 << 0,
+ HasContentPseudoElement = 1 << 1
+};
+
+unsigned extractCompoundFlags(const CSSParserSelector& simpleSelector, CSSParserMode parserMode)
+{
+ if (simpleSelector.match() != CSSSelector::PseudoElement)
+ return 0;
+
+ // FIXME: https://bugs.webkit.org/show_bug.cgi?id=161747
+ // The UASheetMode check is a work-around to allow this selector in mediaControls(New).css:
+ // input[type="range" i]::-webkit-media-slider-container > div {
+ if (parserMode == UASheetMode && simpleSelector.pseudoElementType() == CSSSelector::PseudoElementWebKitCustom)
+ return 0;
+ return HasPseudoElementForRightmostCompound;
+}
+
+} // namespace
+
+static bool isDescendantCombinator(CSSSelector::RelationType relation)
+{
+#if ENABLE(CSS_SELECTORS_LEVEL4)
+ return relation == CSSSelector::DescendantSpace || relation == CSSSelector::DescendantDoubleChild;
+#else
+ return relation == CSSSelector::DescendantSpace;
+#endif
+}
+std::unique_ptr<CSSParserSelector> CSSSelectorParser::consumeComplexSelector(CSSParserTokenRange& range)
+{
+ std::unique_ptr<CSSParserSelector> selector = consumeCompoundSelector(range);
+ if (!selector)
+ return nullptr;
+
+ unsigned previousCompoundFlags = 0;
+
+ for (CSSParserSelector* simple = selector.get(); simple && !previousCompoundFlags; simple = simple->tagHistory())
+ previousCompoundFlags |= extractCompoundFlags(*simple, m_context.mode);
+
+ while (auto combinator = consumeCombinator(range)) {
+ std::unique_ptr<CSSParserSelector> nextSelector = consumeCompoundSelector(range);
+ if (!nextSelector)
+ return isDescendantCombinator(combinator) ? WTFMove(selector) : nullptr;
+ if (previousCompoundFlags & HasPseudoElementForRightmostCompound)
+ return nullptr;
+ CSSParserSelector* end = nextSelector.get();
+ unsigned compoundFlags = extractCompoundFlags(*end, m_context.mode);
+ while (end->tagHistory()) {
+ end = end->tagHistory();
+ compoundFlags |= extractCompoundFlags(*end, m_context.mode);
+ }
+ end->setRelation(combinator);
+ previousCompoundFlags = compoundFlags;
+ end->setTagHistory(WTFMove(selector));
+
+ selector = WTFMove(nextSelector);
+ }
+
+ return selector;
+}
+
+namespace {
+
+bool isScrollbarPseudoClass(CSSSelector::PseudoClassType pseudo)
+{
+ switch (pseudo) {
+ case CSSSelector::PseudoClassEnabled:
+ case CSSSelector::PseudoClassDisabled:
+ case CSSSelector::PseudoClassHover:
+ case CSSSelector::PseudoClassActive:
+ case CSSSelector::PseudoClassHorizontal:
+ case CSSSelector::PseudoClassVertical:
+ case CSSSelector::PseudoClassDecrement:
+ case CSSSelector::PseudoClassIncrement:
+ case CSSSelector::PseudoClassStart:
+ case CSSSelector::PseudoClassEnd:
+ case CSSSelector::PseudoClassDoubleButton:
+ case CSSSelector::PseudoClassSingleButton:
+ case CSSSelector::PseudoClassNoButton:
+ case CSSSelector::PseudoClassCornerPresent:
+ case CSSSelector::PseudoClassWindowInactive:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool isUserActionPseudoClass(CSSSelector::PseudoClassType pseudo)
+{
+ switch (pseudo) {
+ case CSSSelector::PseudoClassHover:
+ case CSSSelector::PseudoClassFocus:
+ case CSSSelector::PseudoClassActive:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool isPseudoClassValidAfterPseudoElement(CSSSelector::PseudoClassType pseudoClass, CSSSelector::PseudoElementType compoundPseudoElement)
+{
+ switch (compoundPseudoElement) {
+ case CSSSelector::PseudoElementResizer:
+ case CSSSelector::PseudoElementScrollbar:
+ case CSSSelector::PseudoElementScrollbarCorner:
+ case CSSSelector::PseudoElementScrollbarButton:
+ case CSSSelector::PseudoElementScrollbarThumb:
+ case CSSSelector::PseudoElementScrollbarTrack:
+ case CSSSelector::PseudoElementScrollbarTrackPiece:
+ return isScrollbarPseudoClass(pseudoClass);
+ case CSSSelector::PseudoElementSelection:
+ return pseudoClass == CSSSelector::PseudoClassWindowInactive;
+ case CSSSelector::PseudoElementWebKitCustom:
+ case CSSSelector::PseudoElementWebKitCustomLegacyPrefixed:
+ return isUserActionPseudoClass(pseudoClass);
+ default:
+ return false;
+ }
+}
+
+bool isSimpleSelectorValidAfterPseudoElement(const CSSParserSelector& simpleSelector, CSSSelector::PseudoElementType compoundPseudoElement)
+{
+ if (compoundPseudoElement == CSSSelector::PseudoElementUnknown)
+ return true;
+ // FIXME-NEWPARSER: This doesn't exist for us.
+ // if (compoundPseudoElement == CSSSelector::PseudoElementContent)
+ // return simpleSelector.match() != CSSSelector::PseudoElement;
+ if (simpleSelector.match() != CSSSelector::PseudoClass)
+ return false;
+ CSSSelector::PseudoClassType pseudo = simpleSelector.pseudoClassType();
+ if (pseudo == CSSSelector::PseudoClassNot) {
+ ASSERT(simpleSelector.selectorList());
+ ASSERT(simpleSelector.selectorList()->first());
+ pseudo = simpleSelector.selectorList()->first()->pseudoClassType();
+ }
+ return isPseudoClassValidAfterPseudoElement(pseudo, compoundPseudoElement);
+}
+
+} // namespace
+
+std::unique_ptr<CSSParserSelector> CSSSelectorParser::consumeCompoundSelector(CSSParserTokenRange& range)
+{
+ std::unique_ptr<CSSParserSelector> compoundSelector;
+
+ AtomicString namespacePrefix;
+ AtomicString elementName;
+ CSSSelector::PseudoElementType compoundPseudoElement = CSSSelector::PseudoElementUnknown;
+ if (!consumeName(range, elementName, namespacePrefix)) {
+ compoundSelector = consumeSimpleSelector(range);
+ if (!compoundSelector)
+ return nullptr;
+ if (compoundSelector->match() == CSSSelector::PseudoElement)
+ compoundPseudoElement = compoundSelector->pseudoElementType();
+ }
+
+ while (std::unique_ptr<CSSParserSelector> simpleSelector = consumeSimpleSelector(range)) {
+ // FIXME: https://bugs.webkit.org/show_bug.cgi?id=161747
+ // The UASheetMode check is a work-around to allow this selector in mediaControls(New).css:
+ // video::-webkit-media-text-track-region-container.scrolling
+ if (m_context.mode != UASheetMode && !isSimpleSelectorValidAfterPseudoElement(*simpleSelector.get(), compoundPseudoElement)) {
+ m_failedParsing = true;
+ return nullptr;
+ }
+ if (simpleSelector->match() == CSSSelector::PseudoElement)
+ compoundPseudoElement = simpleSelector->pseudoElementType();
+
+ if (compoundSelector)
+ compoundSelector = addSimpleSelectorToCompound(WTFMove(compoundSelector), WTFMove(simpleSelector));
+ else
+ compoundSelector = WTFMove(simpleSelector);
+ }
+
+ if (!compoundSelector) {
+ AtomicString namespaceURI = determineNamespace(namespacePrefix);
+ if (namespaceURI.isNull()) {
+ m_failedParsing = true;
+ return nullptr;
+ }
+ if (namespaceURI == defaultNamespace())
+ namespacePrefix = nullAtom;
+
+ CSSParserSelector* rawSelector = new CSSParserSelector(QualifiedName(namespacePrefix, elementName, namespaceURI));
+ std::unique_ptr<CSSParserSelector> selector = std::unique_ptr<CSSParserSelector>(rawSelector);
+ return selector;
+ }
+ prependTypeSelectorIfNeeded(namespacePrefix, elementName, compoundSelector.get());
+ return splitCompoundAtImplicitShadowCrossingCombinator(WTFMove(compoundSelector), m_context);
+}
+
+std::unique_ptr<CSSParserSelector> CSSSelectorParser::consumeSimpleSelector(CSSParserTokenRange& range)
+{
+ const CSSParserToken& token = range.peek();
+ std::unique_ptr<CSSParserSelector> selector;
+ if (token.type() == HashToken)
+ selector = consumeId(range);
+ else if (token.type() == DelimiterToken && token.delimiter() == '.')
+ selector = consumeClass(range);
+ else if (token.type() == LeftBracketToken)
+ selector = consumeAttribute(range);
+ else if (token.type() == ColonToken)
+ selector = consumePseudo(range);
+ else
+ return nullptr;
+ if (!selector)
+ m_failedParsing = true;
+ return selector;
+}
+
+bool CSSSelectorParser::consumeName(CSSParserTokenRange& range, AtomicString& name, AtomicString& namespacePrefix)
+{
+ name = nullAtom;
+ namespacePrefix = nullAtom;
+
+ const CSSParserToken& firstToken = range.peek();
+ if (firstToken.type() == IdentToken) {
+ name = firstToken.value().toAtomicString();
+ range.consume();
+ } else if (firstToken.type() == DelimiterToken && firstToken.delimiter() == '*') {
+ name = starAtom;
+ range.consume();
+ } else if (firstToken.type() == DelimiterToken && firstToken.delimiter() == '|') {
+ // This is an empty namespace, which'll get assigned this value below
+ name = emptyAtom;
+ } else
+ return false;
+
+ if (range.peek().type() != DelimiterToken || range.peek().delimiter() != '|')
+ return true;
+ range.consume();
+
+ namespacePrefix = name;
+ const CSSParserToken& nameToken = range.consume();
+ if (nameToken.type() == IdentToken) {
+ name = nameToken.value().toAtomicString();
+ } else if (nameToken.type() == DelimiterToken && nameToken.delimiter() == '*')
+ name = starAtom;
+ else {
+ name = nullAtom;
+ namespacePrefix = nullAtom;
+ return false;
+ }
+
+ return true;
+}
+
+std::unique_ptr<CSSParserSelector> CSSSelectorParser::consumeId(CSSParserTokenRange& range)
+{
+ ASSERT(range.peek().type() == HashToken);
+ if (range.peek().getHashTokenType() != HashTokenId)
+ return nullptr;
+ std::unique_ptr<CSSParserSelector> selector = std::unique_ptr<CSSParserSelector>(new CSSParserSelector());
+ selector->setMatch(CSSSelector::Id);
+
+ // FIXME-NEWPARSER: Avoid having to do this, but the old parser does and we need
+ // to be compatible for now.
+ CSSParserToken token = range.consume();
+ selector->setValue(token.value().toAtomicString(), m_context.mode == HTMLQuirksMode);
+ return selector;
+}
+
+std::unique_ptr<CSSParserSelector> CSSSelectorParser::consumeClass(CSSParserTokenRange& range)
+{
+ ASSERT(range.peek().type() == DelimiterToken);
+ ASSERT(range.peek().delimiter() == '.');
+ range.consume();
+ if (range.peek().type() != IdentToken)
+ return nullptr;
+ std::unique_ptr<CSSParserSelector> selector = std::unique_ptr<CSSParserSelector>(new CSSParserSelector());
+ selector->setMatch(CSSSelector::Class);
+
+ // FIXME-NEWPARSER: Avoid having to do this, but the old parser does and we need
+ // to be compatible for now.
+ CSSParserToken token = range.consume();
+ selector->setValue(token.value().toAtomicString(), m_context.mode == HTMLQuirksMode);
+
+ return selector;
+}
+
+std::unique_ptr<CSSParserSelector> CSSSelectorParser::consumeAttribute(CSSParserTokenRange& range)
+{
+ ASSERT(range.peek().type() == LeftBracketToken);
+ CSSParserTokenRange block = range.consumeBlock();
+ if (block.end() == range.end())
+ return nullptr; // No ] was found. Be strict about this.
+
+ block.consumeWhitespace();
+
+ AtomicString namespacePrefix;
+ AtomicString attributeName;
+ if (!consumeName(block, attributeName, namespacePrefix))
+ return nullptr;
+ block.consumeWhitespace();
+
+ AtomicString namespaceURI = determineNamespace(namespacePrefix);
+ if (namespaceURI.isNull())
+ return nullptr;
+
+ QualifiedName qualifiedName = namespacePrefix.isNull()
+ ? QualifiedName(nullAtom, attributeName, nullAtom)
+ : QualifiedName(namespacePrefix, attributeName, namespaceURI);
+
+ std::unique_ptr<CSSParserSelector> selector = std::unique_ptr<CSSParserSelector>(new CSSParserSelector());
+
+ if (block.atEnd()) {
+ selector->setAttribute(qualifiedName, m_context.isHTMLDocument, CSSSelector::CaseSensitive);
+ selector->setMatch(CSSSelector::Set);
+ return selector;
+ }
+
+ selector->setMatch(consumeAttributeMatch(block));
+
+ const CSSParserToken& attributeValue = block.consumeIncludingWhitespace();
+ if (attributeValue.type() != IdentToken && attributeValue.type() != StringToken)
+ return nullptr;
+ selector->setValue(attributeValue.value().toAtomicString());
+
+ selector->setAttribute(qualifiedName, m_context.isHTMLDocument, consumeAttributeFlags(block));
+
+ if (!block.atEnd())
+ return nullptr;
+ return selector;
+}
+
+static bool isOnlyPseudoClassFunction(CSSSelector::PseudoClassType pseudoClassType)
+{
+ switch (pseudoClassType) {
+ case CSSSelector::PseudoClassNot:
+ case CSSSelector::PseudoClassMatches:
+ case CSSSelector::PseudoClassNthChild:
+ case CSSSelector::PseudoClassNthLastChild:
+ case CSSSelector::PseudoClassNthOfType:
+ case CSSSelector::PseudoClassNthLastOfType:
+ case CSSSelector::PseudoClassLang:
+ case CSSSelector::PseudoClassAny:
+#if ENABLE(CSS_SELECTORS_LEVEL4)
+ case CSSSelector::PseudoClassDir:
+ case CSSSelector::PseudoClassRole:
+#endif
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+static bool isOnlyPseudoElementFunction(CSSSelector::PseudoElementType pseudoElementType)
+{
+ // Note that we omit cue since it can be both an ident or a function.
+ switch (pseudoElementType) {
+ case CSSSelector::PseudoElementSlotted:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+std::unique_ptr<CSSParserSelector> CSSSelectorParser::consumePseudo(CSSParserTokenRange& range)
+{
+ ASSERT(range.peek().type() == ColonToken);
+ range.consume();
+
+ int colons = 1;
+ if (range.peek().type() == ColonToken) {
+ range.consume();
+ colons++;
+ }
+
+ const CSSParserToken& token = range.peek();
+ if (token.type() != IdentToken && token.type() != FunctionToken)
+ return nullptr;
+
+ std::unique_ptr<CSSParserSelector> selector;
+
+ auto lowercasedValue = token.value().toString().convertToASCIILowercase();
+ auto value = StringView { lowercasedValue };
+
+ if (colons == 1)
+ selector = std::unique_ptr<CSSParserSelector>(CSSParserSelector::parsePseudoClassSelectorFromStringView(value));
+ else {
+ selector = std::unique_ptr<CSSParserSelector>(CSSParserSelector::parsePseudoElementSelectorFromStringView(value));
+#if ENABLE(VIDEO_TRACK)
+ // Treat the ident version of cue as PseudoElementWebkitCustom.
+ if (token.type() == IdentToken && selector && selector->match() == CSSSelector::PseudoElement && selector->pseudoElementType() == CSSSelector::PseudoElementCue)
+ selector->setPseudoElementType(CSSSelector::PseudoElementWebKitCustom);
+#endif
+ }
+
+ if (!selector || (selector->match() == CSSSelector::PseudoElement && m_disallowPseudoElements))
+ return nullptr;
+
+ if (token.type() == IdentToken) {
+ range.consume();
+ if ((selector->match() == CSSSelector::PseudoElement && (selector->pseudoElementType() == CSSSelector::PseudoElementUnknown || isOnlyPseudoElementFunction(selector->pseudoElementType())))
+ || (selector->match() == CSSSelector::PseudoClass && (selector->pseudoClassType() == CSSSelector::PseudoClassUnknown || isOnlyPseudoClassFunction(selector->pseudoClassType()))))
+ return nullptr;
+ return selector;
+ }
+
+ CSSParserTokenRange block = range.consumeBlock();
+ if (block.end() == range.end())
+ return nullptr; // No ) was found. Be strict about this.
+ block.consumeWhitespace();
+ if (token.type() != FunctionToken)
+ return nullptr;
+
+ const auto& argumentStart = block.peek();
+
+ if (selector->match() == CSSSelector::PseudoClass) {
+ switch (selector->pseudoClassType()) {
+ case CSSSelector::PseudoClassNot: {
+ DisallowPseudoElementsScope scope(this);
+ std::unique_ptr<CSSSelectorList> selectorList = std::unique_ptr<CSSSelectorList>(new CSSSelectorList());
+ *selectorList = consumeComplexSelectorList(block);
+ if (!selectorList->componentCount() || !block.atEnd())
+ return nullptr;
+ selector->setSelectorList(WTFMove(selectorList));
+ return selector;
+ }
+ case CSSSelector::PseudoClassNthChild:
+ case CSSSelector::PseudoClassNthLastChild:
+ case CSSSelector::PseudoClassNthOfType:
+ case CSSSelector::PseudoClassNthLastOfType: {
+ std::pair<int, int> ab;
+ if (!consumeANPlusB(block, ab))
+ return nullptr;
+ block.consumeWhitespace();
+ const auto& argumentEnd = block.peek();
+ auto rangeOfANPlusB = block.makeSubRange(&argumentStart, &argumentEnd);
+ auto argument = rangeOfANPlusB.serialize();
+ selector->setArgument(argument.stripWhiteSpace());
+ if (!block.atEnd()) {
+ if (block.peek().type() != IdentToken)
+ return nullptr;
+ const CSSParserToken& ident = block.consume();
+ if (!equalIgnoringASCIICase(ident.value(), "of"))
+ return nullptr;
+ if (block.peek().type() != WhitespaceToken)
+ return nullptr;
+ DisallowPseudoElementsScope scope(this);
+ block.consumeWhitespace();
+ std::unique_ptr<CSSSelectorList> selectorList = std::unique_ptr<CSSSelectorList>(new CSSSelectorList());
+ *selectorList = consumeComplexSelectorList(block);
+ if (!selectorList->componentCount() || !block.atEnd())
+ return nullptr;
+ selector->setSelectorList(WTFMove(selectorList));
+ }
+ selector->setNth(ab.first, ab.second);
+ return selector;
+ }
+ case CSSSelector::PseudoClassLang: {
+ // FIXME: CSS Selectors Level 4 allows :lang(*-foo)
+ auto argumentList = std::make_unique<Vector<AtomicString>>();
+ if (!consumeLangArgumentList(argumentList, block))
+ return nullptr;
+ selector->setLangArgumentList(WTFMove(argumentList));
+ return selector;
+ }
+ case CSSSelector::PseudoClassMatches: {
+ std::unique_ptr<CSSSelectorList> selectorList = std::unique_ptr<CSSSelectorList>(new CSSSelectorList());
+ *selectorList = consumeComplexSelectorList(block);
+ if (!selectorList->componentCount() || !block.atEnd())
+ return nullptr;
+ selector->setSelectorList(WTFMove(selectorList));
+ return selector;
+ }
+ case CSSSelector::PseudoClassAny:
+ case CSSSelector::PseudoClassHost: {
+ std::unique_ptr<CSSSelectorList> selectorList = std::unique_ptr<CSSSelectorList>(new CSSSelectorList());
+ *selectorList = consumeCompoundSelectorList(block);
+ if (!selectorList->componentCount() || !block.atEnd())
+ return nullptr;
+ selector->setSelectorList(WTFMove(selectorList));
+ return selector;
+ }
+#if ENABLE(CSS_SELECTORS_LEVEL4)
+ case CSSSelector::PseudoClassDir:
+ case CSSSelector::PseudoClassRole: {
+ const CSSParserToken& ident = block.consumeIncludingWhitespace();
+ if (ident.type() != IdentToken || !block.atEnd())
+ return nullptr;
+ selector->setArgument(ident.value().toAtomicString());
+ return selector;
+ }
+#endif
+ default:
+ break;
+ }
+
+ }
+
+ if (selector->match() == CSSSelector::PseudoElement) {
+ switch (selector->pseudoElementType()) {
+#if ENABLE(VIDEO_TRACK)
+ case CSSSelector::PseudoElementCue: {
+ DisallowPseudoElementsScope scope(this);
+ std::unique_ptr<CSSSelectorList> selectorList = std::unique_ptr<CSSSelectorList>(new CSSSelectorList());
+ *selectorList = consumeCompoundSelectorList(block);
+ if (!selectorList->isValid() || !block.atEnd())
+ return nullptr;
+ selector->setSelectorList(WTFMove(selectorList));
+ return selector;
+ }
+#endif
+ case CSSSelector::PseudoElementSlotted: {
+ DisallowPseudoElementsScope scope(this);
+
+ std::unique_ptr<CSSParserSelector> innerSelector = consumeCompoundSelector(block);
+ block.consumeWhitespace();
+ if (!innerSelector || !block.atEnd())
+ return nullptr;
+ Vector<std::unique_ptr<CSSParserSelector>> selectorVector;
+ selectorVector.append(WTFMove(innerSelector));
+ selector->adoptSelectorVector(selectorVector);
+ return selector;
+ }
+ default:
+ break;
+ }
+ }
+
+ return nullptr;
+}
+
+CSSSelector::RelationType CSSSelectorParser::consumeCombinator(CSSParserTokenRange& range)
+{
+ auto fallbackResult = CSSSelector::Subselector;
+ while (range.peek().type() == WhitespaceToken) {
+ range.consume();
+ fallbackResult = CSSSelector::DescendantSpace;
+ }
+
+ if (range.peek().type() != DelimiterToken)
+ return fallbackResult;
+
+ UChar delimiter = range.peek().delimiter();
+
+ if (delimiter == '+' || delimiter == '~' || delimiter == '>') {
+ if (delimiter == '+') {
+ range.consumeIncludingWhitespace();
+ return CSSSelector::DirectAdjacent;
+ }
+
+ if (delimiter == '~') {
+ range.consumeIncludingWhitespace();
+ return CSSSelector::IndirectAdjacent;
+ }
+
+#if ENABLE(CSS_SELECTORS_LEVEL4)
+ range.consume();
+ if (range.peek().type() == DelimiterToken && range.peek().delimiter() == '>') {
+ range.consumeIncludingWhitespace();
+ return CSSSelector::DescendantDoubleChild;
+ }
+ range.consumeWhitespace();
+#else
+ range.consumeIncludingWhitespace();
+#endif
+ return CSSSelector::Child;
+ }
+
+ return fallbackResult;
+}
+
+CSSSelector::Match CSSSelectorParser::consumeAttributeMatch(CSSParserTokenRange& range)
+{
+ const CSSParserToken& token = range.consumeIncludingWhitespace();
+ switch (token.type()) {
+ case IncludeMatchToken:
+ return CSSSelector::List;
+ case DashMatchToken:
+ return CSSSelector::Hyphen;
+ case PrefixMatchToken:
+ return CSSSelector::Begin;
+ case SuffixMatchToken:
+ return CSSSelector::End;
+ case SubstringMatchToken:
+ return CSSSelector::Contain;
+ case DelimiterToken:
+ if (token.delimiter() == '=')
+ return CSSSelector::Exact;
+ FALLTHROUGH;
+ default:
+ m_failedParsing = true;
+ return CSSSelector::Exact;
+ }
+}
+
+CSSSelector::AttributeMatchType CSSSelectorParser::consumeAttributeFlags(CSSParserTokenRange& range)
+{
+ if (range.peek().type() != IdentToken)
+ return CSSSelector::CaseSensitive;
+ const CSSParserToken& flag = range.consumeIncludingWhitespace();
+ if (equalIgnoringASCIICase(flag.value(), "i"))
+ return CSSSelector::CaseInsensitive;
+ m_failedParsing = true;
+ return CSSSelector::CaseSensitive;
+}
+
+bool CSSSelectorParser::consumeANPlusB(CSSParserTokenRange& range, std::pair<int, int>& result)
+{
+ const CSSParserToken& token = range.consume();
+ if (token.type() == NumberToken && token.numericValueType() == IntegerValueType) {
+ result = std::make_pair(0, static_cast<int>(token.numericValue()));
+ return true;
+ }
+ if (token.type() == IdentToken) {
+ if (equalIgnoringASCIICase(token.value(), "odd")) {
+ result = std::make_pair(2, 1);
+ return true;
+ }
+ if (equalIgnoringASCIICase(token.value(), "even")) {
+ result = std::make_pair(2, 0);
+ return true;
+ }
+ }
+
+ // The 'n' will end up as part of an ident or dimension. For a valid <an+b>,
+ // this will store a string of the form 'n', 'n-', or 'n-123'.
+ String nString;
+
+ if (token.type() == DelimiterToken && token.delimiter() == '+' && range.peek().type() == IdentToken) {
+ result.first = 1;
+ nString = range.consume().value().toString();
+ } else if (token.type() == DimensionToken && token.numericValueType() == IntegerValueType) {
+ result.first = token.numericValue();
+ nString = token.value().toString();
+ } else if (token.type() == IdentToken) {
+ if (token.value()[0] == '-') {
+ result.first = -1;
+ nString = token.value().toString().substring(1);
+ } else {
+ result.first = 1;
+ nString = token.value().toString();
+ }
+ }
+
+ range.consumeWhitespace();
+
+ if (nString.isEmpty() || !isASCIIAlphaCaselessEqual(nString[0], 'n'))
+ return false;
+ if (nString.length() > 1 && nString[1] != '-')
+ return false;
+
+ if (nString.length() > 2) {
+ bool valid;
+ result.second = nString.substring(1).toIntStrict(&valid);
+ return valid;
+ }
+
+ NumericSign sign = nString.length() == 1 ? NoSign : MinusSign;
+ if (sign == NoSign && range.peek().type() == DelimiterToken) {
+ char delimiterSign = range.consumeIncludingWhitespace().delimiter();
+ if (delimiterSign == '+')
+ sign = PlusSign;
+ else if (delimiterSign == '-')
+ sign = MinusSign;
+ else
+ return false;
+ }
+
+ if (sign == NoSign && range.peek().type() != NumberToken) {
+ result.second = 0;
+ return true;
+ }
+
+ const CSSParserToken& b = range.consume();
+ if (b.type() != NumberToken || b.numericValueType() != IntegerValueType)
+ return false;
+ if ((b.numericSign() == NoSign) == (sign == NoSign))
+ return false;
+ result.second = b.numericValue();
+ if (sign == MinusSign)
+ result.second = -result.second;
+ return true;
+}
+
+const AtomicString& CSSSelectorParser::defaultNamespace() const
+{
+ if (!m_styleSheet)
+ return starAtom;
+ return m_styleSheet->defaultNamespace();
+}
+
+const AtomicString& CSSSelectorParser::determineNamespace(const AtomicString& prefix)
+{
+ if (prefix.isNull())
+ return defaultNamespace();
+ if (prefix.isEmpty())
+ return emptyAtom; // No namespace. If an element/attribute has a namespace, we won't match it.
+ if (prefix == starAtom)
+ return starAtom; // We'll match any namespace.
+ if (!m_styleSheet)
+ return nullAtom; // Cannot resolve prefix to namespace without a stylesheet, syntax error.
+ return m_styleSheet->namespaceURIFromPrefix(prefix);
+}
+
+void CSSSelectorParser::prependTypeSelectorIfNeeded(const AtomicString& namespacePrefix, const AtomicString& elementName, CSSParserSelector* compoundSelector)
+{
+ bool isShadowDOM = compoundSelector->needsImplicitShadowCombinatorForMatching();
+
+ if (elementName.isNull() && defaultNamespace() == starAtom && !isShadowDOM)
+ return;
+
+ AtomicString determinedElementName = elementName.isNull() ? starAtom : elementName;
+ AtomicString namespaceURI = determineNamespace(namespacePrefix);
+ if (namespaceURI.isNull()) {
+ m_failedParsing = true;
+ return;
+ }
+ AtomicString determinedPrefix = namespacePrefix;
+ if (namespaceURI == defaultNamespace())
+ determinedPrefix = nullAtom;
+ QualifiedName tag = QualifiedName(determinedPrefix, determinedElementName, namespaceURI);
+
+ // *:host never matches, so we can't discard the *,
+ // otherwise we can't tell the difference between *:host and just :host.
+ //
+ // Also, selectors where we use a ShadowPseudo combinator between the
+ // element and the pseudo element for matching (custom pseudo elements,
+ // ::cue), we need a universal selector to set the combinator
+ // (relation) on in the cases where there are no simple selectors preceding
+ // the pseudo element.
+ bool explicitForHost = compoundSelector->isHostPseudoSelector() && !elementName.isNull();
+ if (tag != anyQName() || explicitForHost || isShadowDOM)
+ compoundSelector->prependTagSelector(tag, determinedPrefix == nullAtom && determinedElementName == starAtom && !explicitForHost);
+}
+
+std::unique_ptr<CSSParserSelector> CSSSelectorParser::addSimpleSelectorToCompound(std::unique_ptr<CSSParserSelector> compoundSelector, std::unique_ptr<CSSParserSelector> simpleSelector)
+{
+ compoundSelector->appendTagHistory(CSSSelector::Subselector, WTFMove(simpleSelector));
+ return compoundSelector;
+}
+
+std::unique_ptr<CSSParserSelector> CSSSelectorParser::splitCompoundAtImplicitShadowCrossingCombinator(std::unique_ptr<CSSParserSelector> compoundSelector, const CSSParserContext& context)
+{
+ // The tagHistory is a linked list that stores combinator separated compound selectors
+ // from right-to-left. Yet, within a single compound selector, stores the simple selectors
+ // from left-to-right.
+ //
+ // ".a.b > div#id" is stored in a tagHistory as [div, #id, .a, .b], each element in the
+ // list stored with an associated relation (combinator or Subselector).
+ //
+ // ::cue, ::shadow, and custom pseudo elements have an implicit ShadowPseudo combinator
+ // to their left, which really makes for a new compound selector, yet it's consumed by
+ // the selector parser as a single compound selector.
+ //
+ // Example: input#x::-webkit-clear-button -> [ ::-webkit-clear-button, input, #x ]
+ //
+ CSSParserSelector* splitAfter = compoundSelector.get();
+ while (splitAfter->tagHistory() && !splitAfter->tagHistory()->needsImplicitShadowCombinatorForMatching())
+ splitAfter = splitAfter->tagHistory();
+
+ if (!splitAfter || !splitAfter->tagHistory())
+ return compoundSelector;
+
+ // FIXME: https://bugs.webkit.org/show_bug.cgi?id=161747
+ // We have to recur, since we have rules in media controls like video::a::b. This should not be allowed, and
+ // we should remove this recursion once those rules are gone.
+ std::unique_ptr<CSSParserSelector> secondCompound = context.mode != UASheetMode ? splitAfter->releaseTagHistory() : splitCompoundAtImplicitShadowCrossingCombinator(splitAfter->releaseTagHistory(), context);
+ secondCompound->appendTagHistory(CSSSelector::ShadowDescendant, WTFMove(compoundSelector));
+ return secondCompound;
+}
+
+} // namespace WebCore