// 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 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> selectorList; std::unique_ptr 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> selectorList; std::unique_ptr 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>& 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 CSSSelectorParser::consumeComplexSelector(CSSParserTokenRange& range) { std::unique_ptr 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 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 CSSSelectorParser::consumeCompoundSelector(CSSParserTokenRange& range) { std::unique_ptr 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 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 selector = std::unique_ptr(rawSelector); return selector; } prependTypeSelectorIfNeeded(namespacePrefix, elementName, compoundSelector.get()); return splitCompoundAtImplicitShadowCrossingCombinator(WTFMove(compoundSelector), m_context); } std::unique_ptr CSSSelectorParser::consumeSimpleSelector(CSSParserTokenRange& range) { const CSSParserToken& token = range.peek(); std::unique_ptr 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 CSSSelectorParser::consumeId(CSSParserTokenRange& range) { ASSERT(range.peek().type() == HashToken); if (range.peek().getHashTokenType() != HashTokenId) return nullptr; std::unique_ptr selector = std::unique_ptr(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 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 selector = std::unique_ptr(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 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 selector = std::unique_ptr(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 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 selector; auto lowercasedValue = token.value().toString().convertToASCIILowercase(); auto value = StringView { lowercasedValue }; if (colons == 1) selector = std::unique_ptr(CSSParserSelector::parsePseudoClassSelectorFromStringView(value)); else { selector = std::unique_ptr(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 selectorList = std::unique_ptr(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 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 selectorList = std::unique_ptr(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>(); if (!consumeLangArgumentList(argumentList, block)) return nullptr; selector->setLangArgumentList(WTFMove(argumentList)); return selector; } case CSSSelector::PseudoClassMatches: { std::unique_ptr selectorList = std::unique_ptr(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 selectorList = std::unique_ptr(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 selectorList = std::unique_ptr(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 innerSelector = consumeCompoundSelector(block); block.consumeWhitespace(); if (!innerSelector || !block.atEnd()) return nullptr; Vector> 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& result) { const CSSParserToken& token = range.consume(); if (token.type() == NumberToken && token.numericValueType() == IntegerValueType) { result = std::make_pair(0, static_cast(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 , // 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 CSSSelectorParser::addSimpleSelectorToCompound(std::unique_ptr compoundSelector, std::unique_ptr simpleSelector) { compoundSelector->appendTagHistory(CSSSelector::Subselector, WTFMove(simpleSelector)); return compoundSelector; } std::unique_ptr CSSSelectorParser::splitCompoundAtImplicitShadowCrossingCombinator(std::unique_ptr 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 secondCompound = context.mode != UASheetMode ? splitAfter->releaseTagHistory() : splitCompoundAtImplicitShadowCrossingCombinator(splitAfter->releaseTagHistory(), context); secondCompound->appendTagHistory(CSSSelector::ShadowDescendant, WTFMove(compoundSelector)); return secondCompound; } } // namespace WebCore