blob: a676948be4eac567e1adb06d3f446e49fecc17c2 [file] [log] [blame]
// 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/CSSSelectorParser.h"
#include "core/css/CSSSelectorList.h"
#include "core/css/StyleSheetContents.h"
#include "core/css/parser/CSSParserContext.h"
#include "core/frame/Deprecation.h"
#include "core/frame/UseCounter.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "wtf/PtrUtil.h"
#include <memory>
namespace blink {
// static
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();
parser.recordUsageAndDeprecations(result);
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.push_back(std::move(selector));
while (!range.atEnd() && range.peek().type() == CommaToken) {
range.consumeIncludingWhitespace();
selector = consumeComplexSelector(range);
if (!selector)
return CSSSelectorList();
selectorList.push_back(std::move(selector));
}
if (m_failedParsing)
return CSSSelectorList();
return CSSSelectorList::adoptSelectorVector(selectorList);
}
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.push_back(std::move(selector));
while (!range.atEnd() && range.peek().type() == CommaToken) {
range.consumeIncludingWhitespace();
selector = consumeCompoundSelector(range);
range.consumeWhitespace();
if (!selector)
return CSSSelectorList();
selectorList.push_back(std::move(selector));
}
if (m_failedParsing)
return CSSSelectorList();
return CSSSelectorList::adoptSelectorVector(selectorList);
}
namespace {
enum CompoundSelectorFlags {
HasPseudoElementForRightmostCompound = 1 << 0,
HasContentPseudoElement = 1 << 1
};
unsigned extractCompoundFlags(const CSSParserSelector& simpleSelector,
CSSParserMode parserMode) {
if (simpleSelector.match() != CSSSelector::PseudoElement)
return 0;
if (simpleSelector.pseudoType() == CSSSelector::PseudoContent)
return HasContentPseudoElement;
if (simpleSelector.pseudoType() == CSSSelector::PseudoShadow)
return 0;
// TODO(rune@opera.com): crbug.com/578131
// 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.pseudoType() == CSSSelector::PseudoWebKitCustomElement)
return 0;
return HasPseudoElementForRightmostCompound;
}
} // namespace
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 (CSSSelector::RelationType combinator = consumeCombinator(range)) {
std::unique_ptr<CSSParserSelector> nextSelector =
consumeCompoundSelector(range);
if (!nextSelector)
return combinator == CSSSelector::Descendant ? std::move(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);
if (previousCompoundFlags & HasContentPseudoElement)
end->setRelationIsAffectedByPseudoContent();
previousCompoundFlags = compoundFlags;
end->setTagHistory(std::move(selector));
selector = std::move(nextSelector);
}
return selector;
}
namespace {
bool isScrollbarPseudoClass(CSSSelector::PseudoType pseudo) {
switch (pseudo) {
case CSSSelector::PseudoEnabled:
case CSSSelector::PseudoDisabled:
case CSSSelector::PseudoHover:
case CSSSelector::PseudoActive:
case CSSSelector::PseudoHorizontal:
case CSSSelector::PseudoVertical:
case CSSSelector::PseudoDecrement:
case CSSSelector::PseudoIncrement:
case CSSSelector::PseudoStart:
case CSSSelector::PseudoEnd:
case CSSSelector::PseudoDoubleButton:
case CSSSelector::PseudoSingleButton:
case CSSSelector::PseudoNoButton:
case CSSSelector::PseudoCornerPresent:
case CSSSelector::PseudoWindowInactive:
return true;
default:
return false;
}
}
bool isUserActionPseudoClass(CSSSelector::PseudoType pseudo) {
switch (pseudo) {
case CSSSelector::PseudoHover:
case CSSSelector::PseudoFocus:
case CSSSelector::PseudoActive:
return true;
default:
return false;
}
}
bool isPseudoClassValidAfterPseudoElement(
CSSSelector::PseudoType pseudoClass,
CSSSelector::PseudoType compoundPseudoElement) {
switch (compoundPseudoElement) {
case CSSSelector::PseudoResizer:
case CSSSelector::PseudoScrollbar:
case CSSSelector::PseudoScrollbarCorner:
case CSSSelector::PseudoScrollbarButton:
case CSSSelector::PseudoScrollbarThumb:
case CSSSelector::PseudoScrollbarTrack:
case CSSSelector::PseudoScrollbarTrackPiece:
return isScrollbarPseudoClass(pseudoClass);
case CSSSelector::PseudoSelection:
return pseudoClass == CSSSelector::PseudoWindowInactive;
case CSSSelector::PseudoWebKitCustomElement:
case CSSSelector::PseudoBlinkInternalElement:
return isUserActionPseudoClass(pseudoClass);
default:
return false;
}
}
bool isSimpleSelectorValidAfterPseudoElement(
const CSSParserSelector& simpleSelector,
CSSSelector::PseudoType compoundPseudoElement) {
if (compoundPseudoElement == CSSSelector::PseudoUnknown)
return true;
if (compoundPseudoElement == CSSSelector::PseudoContent)
return simpleSelector.match() != CSSSelector::PseudoElement;
if (simpleSelector.match() != CSSSelector::PseudoClass)
return false;
CSSSelector::PseudoType pseudo = simpleSelector.pseudoType();
if (pseudo == CSSSelector::PseudoNot) {
ASSERT(simpleSelector.selectorList());
ASSERT(simpleSelector.selectorList()->first());
ASSERT(!simpleSelector.selectorList()->first()->tagHistory());
pseudo = simpleSelector.selectorList()->first()->getPseudoType();
}
return isPseudoClassValidAfterPseudoElement(pseudo, compoundPseudoElement);
}
} // namespace
std::unique_ptr<CSSParserSelector> CSSSelectorParser::consumeCompoundSelector(
CSSParserTokenRange& range) {
std::unique_ptr<CSSParserSelector> compoundSelector;
AtomicString namespacePrefix;
AtomicString elementName;
CSSSelector::PseudoType compoundPseudoElement = CSSSelector::PseudoUnknown;
if (!consumeName(range, elementName, namespacePrefix)) {
compoundSelector = consumeSimpleSelector(range);
if (!compoundSelector)
return nullptr;
if (compoundSelector->match() == CSSSelector::PseudoElement)
compoundPseudoElement = compoundSelector->pseudoType();
}
if (m_context->isHTMLDocument())
elementName = elementName.lower();
while (std::unique_ptr<CSSParserSelector> simpleSelector =
consumeSimpleSelector(range)) {
// TODO(rune@opera.com): crbug.com/578131
// 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->pseudoType();
if (compoundSelector)
compoundSelector = addSimpleSelectorToCompound(
std::move(compoundSelector), std::move(simpleSelector));
else
compoundSelector = std::move(simpleSelector);
}
if (!compoundSelector) {
AtomicString namespaceURI = determineNamespace(namespacePrefix);
if (namespaceURI.isNull()) {
m_failedParsing = true;
return nullptr;
}
if (namespaceURI == defaultNamespace())
namespacePrefix = nullAtom;
return CSSParserSelector::create(
QualifiedName(namespacePrefix, elementName, namespaceURI));
}
prependTypeSelectorIfNeeded(namespacePrefix, elementName,
compoundSelector.get());
return splitCompoundAtImplicitShadowCrossingCombinator(
std::move(compoundSelector));
}
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 = CSSParserSelector::create();
selector->setMatch(CSSSelector::Id);
AtomicString value = range.consume().value().toAtomicString();
selector->setValue(value, isQuirksModeBehavior(m_context->matchMode()));
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 = CSSParserSelector::create();
selector->setMatch(CSSSelector::Class);
AtomicString value = range.consume().value().toAtomicString();
selector->setValue(value, isQuirksModeBehavior(m_context->matchMode()));
return selector;
}
std::unique_ptr<CSSParserSelector> CSSSelectorParser::consumeAttribute(
CSSParserTokenRange& range) {
ASSERT(range.peek().type() == LeftBracketToken);
CSSParserTokenRange block = range.consumeBlock();
block.consumeWhitespace();
AtomicString namespacePrefix;
AtomicString attributeName;
if (!consumeName(block, attributeName, namespacePrefix))
return nullptr;
if (attributeName == starAtom)
return nullptr;
block.consumeWhitespace();
if (m_context->isHTMLDocument())
attributeName = attributeName.lower();
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 = CSSParserSelector::create();
if (block.atEnd()) {
selector->setAttribute(qualifiedName, CSSSelector::CaseSensitive);
selector->setMatch(CSSSelector::AttributeSet);
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, consumeAttributeFlags(block));
if (!block.atEnd())
return nullptr;
return selector;
}
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 = CSSParserSelector::create();
selector->setMatch(colons == 1 ? CSSSelector::PseudoClass
: CSSSelector::PseudoElement);
AtomicString value = token.value().toAtomicString().lowerASCII();
bool hasArguments = token.type() == FunctionToken;
selector->updatePseudoType(value, hasArguments);
if (selector->match() == CSSSelector::PseudoElement &&
m_disallowPseudoElements)
return nullptr;
if (token.type() == IdentToken) {
range.consume();
if (selector->pseudoType() == CSSSelector::PseudoUnknown)
return nullptr;
return selector;
}
CSSParserTokenRange block = range.consumeBlock();
block.consumeWhitespace();
if (selector->pseudoType() == CSSSelector::PseudoUnknown)
return nullptr;
switch (selector->pseudoType()) {
case CSSSelector::PseudoHost:
case CSSSelector::PseudoHostContext:
case CSSSelector::PseudoAny:
case CSSSelector::PseudoCue: {
DisallowPseudoElementsScope scope(this);
std::unique_ptr<CSSSelectorList> selectorList =
WTF::makeUnique<CSSSelectorList>();
*selectorList = consumeCompoundSelectorList(block);
if (!selectorList->isValid() || !block.atEnd())
return nullptr;
selector->setSelectorList(std::move(selectorList));
return selector;
}
case CSSSelector::PseudoNot: {
std::unique_ptr<CSSParserSelector> innerSelector =
consumeCompoundSelector(block);
block.consumeWhitespace();
if (!innerSelector || !innerSelector->isSimple() || !block.atEnd())
return nullptr;
Vector<std::unique_ptr<CSSParserSelector>> selectorVector;
selectorVector.push_back(std::move(innerSelector));
selector->adoptSelectorVector(selectorVector);
return selector;
}
case CSSSelector::PseudoSlotted: {
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.push_back(std::move(innerSelector));
selector->adoptSelectorVector(selectorVector);
return selector;
}
case CSSSelector::PseudoLang: {
// FIXME: CSS Selectors Level 4 allows :lang(*-foo)
const CSSParserToken& ident = block.consumeIncludingWhitespace();
if (ident.type() != IdentToken || !block.atEnd())
return nullptr;
selector->setArgument(ident.value().toAtomicString());
return selector;
}
case CSSSelector::PseudoNthChild:
case CSSSelector::PseudoNthLastChild:
case CSSSelector::PseudoNthOfType:
case CSSSelector::PseudoNthLastOfType: {
std::pair<int, int> ab;
if (!consumeANPlusB(block, ab))
return nullptr;
block.consumeWhitespace();
if (!block.atEnd())
return nullptr;
selector->setNth(ab.first, ab.second);
return selector;
}
default:
break;
}
return nullptr;
}
CSSSelector::RelationType CSSSelectorParser::consumeCombinator(
CSSParserTokenRange& range) {
CSSSelector::RelationType fallbackResult = CSSSelector::SubSelector;
while (range.peek().type() == WhitespaceToken) {
range.consume();
fallbackResult = CSSSelector::Descendant;
}
if (range.peek().type() != DelimiterToken)
return fallbackResult;
switch (range.peek().delimiter()) {
case '+':
range.consumeIncludingWhitespace();
return CSSSelector::DirectAdjacent;
case '~':
range.consumeIncludingWhitespace();
return CSSSelector::IndirectAdjacent;
case '>':
if (!RuntimeEnabledFeatures::
shadowPiercingDescendantCombinatorEnabled() ||
m_context->isDynamicProfile() ||
range.peek(1).type() != DelimiterToken ||
range.peek(1).delimiter() != '>') {
range.consumeIncludingWhitespace();
return CSSSelector::Child;
}
range.consume();
// Check the 3rd '>'.
if (range.peek(1).type() != DelimiterToken ||
range.peek(1).delimiter() != '>') {
// TODO: Treat '>>' as a CSSSelector::Descendant here.
return CSSSelector::Child;
}
range.consume();
range.consumeIncludingWhitespace();
return CSSSelector::ShadowPiercingDescendant;
case '/': {
// Match /deep/
range.consume();
const CSSParserToken& ident = range.consume();
if (ident.type() != IdentToken ||
!equalIgnoringASCIICase(ident.value(), "deep"))
m_failedParsing = true;
const CSSParserToken& slash = range.consumeIncludingWhitespace();
if (slash.type() != DelimiterToken || slash.delimiter() != '/')
m_failedParsing = true;
return CSSSelector::ShadowDeep;
}
default:
break;
}
return fallbackResult;
}
CSSSelector::MatchType CSSSelectorParser::consumeAttributeMatch(
CSSParserTokenRange& range) {
const CSSParserToken& token = range.consumeIncludingWhitespace();
switch (token.type()) {
case IncludeMatchToken:
return CSSSelector::AttributeList;
case DashMatchToken:
return CSSSelector::AttributeHyphen;
case PrefixMatchToken:
return CSSSelector::AttributeBegin;
case SuffixMatchToken:
return CSSSelector::AttributeEnd;
case SubstringMatchToken:
return CSSSelector::AttributeContain;
case DelimiterToken:
if (token.delimiter() == '=')
return CSSSelector::AttributeExact;
default:
m_failedParsing = true;
return CSSSelector::AttributeExact;
}
}
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) {
if (elementName.isNull() && defaultNamespace() == starAtom &&
!compoundSelector->needsImplicitShadowCombinatorForMatching())
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/*:host-context 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, ::shadow), 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 ||
compoundSelector->needsImplicitShadowCombinatorForMatching())
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,
std::move(simpleSelector));
return compoundSelector;
}
std::unique_ptr<CSSParserSelector>
CSSSelectorParser::splitCompoundAtImplicitShadowCrossingCombinator(
std::unique_ptr<CSSParserSelector> compoundSelector) {
// 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 ]
//
// Likewise, ::slotted() pseudo element has an implicit ShadowSlot combinator
// to its left for finding matching slot element in other TreeScope.
//
// Example:
//
// slot[name=foo]::slotted(div) -> [ ::slotted(div), slot, [name=foo] ]
CSSParserSelector* splitAfter = compoundSelector.get();
while (splitAfter->tagHistory() &&
!splitAfter->tagHistory()->needsImplicitShadowCombinatorForMatching())
splitAfter = splitAfter->tagHistory();
if (!splitAfter || !splitAfter->tagHistory())
return compoundSelector;
std::unique_ptr<CSSParserSelector> secondCompound =
splitAfter->releaseTagHistory();
secondCompound->appendTagHistory(
secondCompound->pseudoType() == CSSSelector::PseudoSlotted
? CSSSelector::ShadowSlot
: CSSSelector::ShadowPseudo,
std::move(compoundSelector));
return secondCompound;
}
void CSSSelectorParser::recordUsageAndDeprecations(
const CSSSelectorList& selectorList) {
if (!m_context->isUseCounterRecordingEnabled())
return;
for (const CSSSelector* selector = selectorList.first(); selector;
selector = CSSSelectorList::next(*selector)) {
for (const CSSSelector* current = selector; current;
current = current->tagHistory()) {
UseCounter::Feature feature = UseCounter::NumberOfFeatures;
switch (current->getPseudoType()) {
case CSSSelector::PseudoAny:
feature = UseCounter::CSSSelectorPseudoAny;
break;
case CSSSelector::PseudoUnresolved:
feature = UseCounter::CSSSelectorPseudoUnresolved;
break;
case CSSSelector::PseudoDefined:
feature = UseCounter::CSSSelectorPseudoDefined;
break;
case CSSSelector::PseudoSlotted:
feature = UseCounter::CSSSelectorPseudoSlotted;
break;
case CSSSelector::PseudoContent:
feature = UseCounter::CSSSelectorPseudoContent;
break;
case CSSSelector::PseudoHost:
feature = UseCounter::CSSSelectorPseudoHost;
break;
case CSSSelector::PseudoHostContext:
feature = UseCounter::CSSSelectorPseudoHostContext;
break;
case CSSSelector::PseudoFullScreenAncestor:
feature = UseCounter::CSSSelectorPseudoFullScreenAncestor;
break;
case CSSSelector::PseudoFullScreen:
feature = UseCounter::CSSSelectorPseudoFullScreen;
break;
case CSSSelector::PseudoListBox:
if (m_context->mode() != UASheetMode)
feature = UseCounter::CSSSelectorInternalPseudoListBox;
break;
case CSSSelector::PseudoWebKitCustomElement:
if (m_context->mode() != UASheetMode) {
if (current->value() ==
"-internal-media-controls-overlay-cast-button") {
feature =
UseCounter::CSSSelectorInternalMediaControlsOverlayCastButton;
} else if (current->value() ==
"-internal-media-controls-text-track-list") {
feature =
UseCounter::CSSSelectorInternalMediaControlsTextTrackList;
} else if (current->value() ==
"-internal-media-controls-text-track-list-item") {
feature =
UseCounter::CSSSelectorInternalMediaControlsTextTrackListItem;
} else if (current->value() ==
"-internal-media-controls-text-track-list-item-input") {
feature = UseCounter::
CSSSelectorInternalMediaControlsTextTrackListItemInput;
} else if (current->value() ==
"-internal-media-controls-text-track-list-kind-"
"captions") {
feature = UseCounter::
CSSSelectorInternalMediaControlsTextTrackListKindCaptions;
} else if (current->value() ==
"-internal-media-controls-text-track-list-kind-"
"subtitles") {
feature = UseCounter::
CSSSelectorInternalMediaControlsTextTrackListKindSubtitles;
}
}
break;
case CSSSelector::PseudoSpatialNavigationFocus:
if (m_context->mode() != UASheetMode) {
feature =
UseCounter::CSSSelectorInternalPseudoSpatialNavigationFocus;
}
break;
case CSSSelector::PseudoReadOnly:
if (m_context->mode() != UASheetMode)
feature = UseCounter::CSSSelectorPseudoReadOnly;
break;
case CSSSelector::PseudoReadWrite:
if (m_context->mode() != UASheetMode)
feature = UseCounter::CSSSelectorPseudoReadWrite;
break;
default:
break;
}
if (feature != UseCounter::NumberOfFeatures) {
if (!Deprecation::deprecationMessage(feature).isEmpty() &&
m_styleSheet->anyOwnerDocument()) {
Deprecation::countDeprecation(*m_styleSheet->anyOwnerDocument(),
feature);
} else {
m_context->count(feature);
}
}
if (current->relation() == CSSSelector::IndirectAdjacent)
m_context->count(UseCounter::CSSSelectorIndirectAdjacent);
if (current->selectorList())
recordUsageAndDeprecations(*current->selectorList());
}
}
}
} // namespace blink