| Index: Source/core/css/parser/CSSSelectorParser.cpp
|
| diff --git a/Source/core/css/parser/CSSSelectorParser.cpp b/Source/core/css/parser/CSSSelectorParser.cpp
|
| index 653a8d80d8e4bb1e7705ba8e84b8eafaba9b74a6..cc7d7b28e231e95f76ce4ddabc486ea386bc2331 100644
|
| --- a/Source/core/css/parser/CSSSelectorParser.cpp
|
| +++ b/Source/core/css/parser/CSSSelectorParser.cpp
|
| @@ -101,36 +101,33 @@ PassOwnPtr<CSSParserSelector> CSSSelectorParser::consumeComplexSelector(CSSParse
|
|
|
| PassOwnPtr<CSSParserSelector> CSSSelectorParser::consumeCompoundSelector(CSSParserTokenRange& range)
|
| {
|
| - OwnPtr<CSSParserSelector> selector;
|
| + OwnPtr<CSSParserSelector> compoundSelector;
|
|
|
| AtomicString namespacePrefix;
|
| AtomicString elementName;
|
| bool hasNamespace;
|
| if (!consumeName(range, elementName, namespacePrefix, hasNamespace)) {
|
| - selector = consumeSimpleSelector(range);
|
| - if (!selector)
|
| + compoundSelector = consumeSimpleSelector(range);
|
| + if (!compoundSelector)
|
| return nullptr;
|
| }
|
| if (m_context.isHTMLDocument())
|
| elementName = elementName.lower();
|
|
|
| - while (OwnPtr<CSSParserSelector> nextSelector = consumeSimpleSelector(range)) {
|
| - if (selector)
|
| - selector = rewriteSpecifiers(selector.release(), nextSelector.release());
|
| + while (OwnPtr<CSSParserSelector> simpleSelector = consumeSimpleSelector(range)) {
|
| + if (compoundSelector)
|
| + compoundSelector = addSimpleSelectorToCompound(compoundSelector.release(), simpleSelector.release());
|
| else
|
| - selector = nextSelector.release();
|
| + compoundSelector = simpleSelector.release();
|
| }
|
|
|
| - if (!selector) {
|
| + if (!compoundSelector) {
|
| 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();
|
| + prependTypeSelectorIfNeeded(namespacePrefix, elementName, compoundSelector.get());
|
| + return compoundSelector.release();
|
| }
|
|
|
| PassOwnPtr<CSSParserSelector> CSSSelectorParser::consumeSimpleSelector(CSSParserTokenRange& range)
|
| @@ -521,32 +518,29 @@ QualifiedName CSSSelectorParser::determineNameInNamespace(const AtomicString& pr
|
| return QualifiedName(prefix, localName, m_styleSheet->determineNamespace(prefix));
|
| }
|
|
|
| -void CSSSelectorParser::rewriteSpecifiersWithNamespaceIfNeeded(CSSParserSelector* specifiers)
|
| +void CSSSelectorParser::prependTypeSelectorIfNeeded(const AtomicString& namespacePrefix, const AtomicString& elementName, CSSParserSelector* compoundSelector)
|
| {
|
| - if (m_defaultNamespace != starAtom || specifiers->crossesTreeScopes())
|
| - rewriteSpecifiersWithElementName(nullAtom, starAtom, specifiers, /*tagIsForNamespaceRule*/true);
|
| -}
|
| + if (elementName.isNull() && m_defaultNamespace == starAtom && !compoundSelector->crossesTreeScopes())
|
| + return;
|
|
|
| -void CSSSelectorParser::rewriteSpecifiersWithElementName(const AtomicString& namespacePrefix, const AtomicString& elementName, CSSParserSelector* specifiers, bool tagIsForNamespaceRule)
|
| -{
|
| + AtomicString determinedElementName = elementName.isNull() ? starAtom : elementName;
|
| AtomicString determinedNamespace = namespacePrefix != nullAtom && m_styleSheet ? m_styleSheet->determineNamespace(namespacePrefix) : m_defaultNamespace;
|
| - QualifiedName tag(namespacePrefix, elementName, determinedNamespace);
|
| + QualifiedName tag(namespacePrefix, determinedElementName, determinedNamespace);
|
|
|
| - if (specifiers->crossesTreeScopes())
|
| - return rewriteSpecifiersWithElementNameForCustomPseudoElement(tag, specifiers, tagIsForNamespaceRule);
|
| + if (compoundSelector->crossesTreeScopes())
|
| + return rewriteSpecifiersWithElementNameForCustomPseudoElement(tag, compoundSelector, elementName.isNull());
|
|
|
| - if (specifiers->isContentPseudoElement())
|
| - return rewriteSpecifiersWithElementNameForContentPseudoElement(tag, specifiers, tagIsForNamespaceRule);
|
| + if (compoundSelector->isContentPseudoElement())
|
| + return rewriteSpecifiersWithElementNameForContentPseudoElement(tag, compoundSelector, elementName.isNull());
|
|
|
| // *: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())
|
| + if (tag == anyQName() && !compoundSelector->hasHostPseudoSelector())
|
| return;
|
| - if (specifiers->pseudoType() != CSSSelector::PseudoCue)
|
| - specifiers->prependTagSelector(tag, tagIsForNamespaceRule);
|
| + compoundSelector->prependTagSelector(tag, elementName.isNull());
|
| }
|
|
|
| -void CSSSelectorParser::rewriteSpecifiersWithElementNameForCustomPseudoElement(const QualifiedName& tag, CSSParserSelector* specifiers, bool tagIsForNamespaceRule)
|
| +void CSSSelectorParser::rewriteSpecifiersWithElementNameForCustomPseudoElement(const QualifiedName& tag, CSSParserSelector* specifiers, bool tagIsImplicit)
|
| {
|
| CSSParserSelector* lastShadowPseudo = specifiers;
|
| CSSParserSelector* history = specifiers;
|
| @@ -558,18 +552,18 @@ void CSSSelectorParser::rewriteSpecifiersWithElementNameForCustomPseudoElement(c
|
|
|
| if (lastShadowPseudo->tagHistory()) {
|
| if (tag != anyQName())
|
| - lastShadowPseudo->tagHistory()->prependTagSelector(tag, tagIsForNamespaceRule);
|
| + lastShadowPseudo->tagHistory()->prependTagSelector(tag, tagIsImplicit);
|
| 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));
|
| + OwnPtr<CSSParserSelector> elementNameSelector = CSSParserSelector::create(tag);
|
| lastShadowPseudo->setTagHistory(elementNameSelector.release());
|
| lastShadowPseudo->setRelation(CSSSelector::ShadowPseudo);
|
| }
|
|
|
| -void CSSSelectorParser::rewriteSpecifiersWithElementNameForContentPseudoElement(const QualifiedName& tag, CSSParserSelector* specifiers, bool tagIsForNamespaceRule)
|
| +void CSSSelectorParser::rewriteSpecifiersWithElementNameForContentPseudoElement(const QualifiedName& tag, CSSParserSelector* specifiers, bool tagIsImplicit)
|
| {
|
| CSSParserSelector* last = specifiers;
|
| CSSParserSelector* history = specifiers;
|
| @@ -581,38 +575,64 @@ void CSSSelectorParser::rewriteSpecifiersWithElementNameForContentPseudoElement(
|
|
|
| if (last->tagHistory()) {
|
| if (tag != anyQName())
|
| - last->tagHistory()->prependTagSelector(tag, tagIsForNamespaceRule);
|
| + last->tagHistory()->prependTagSelector(tag, tagIsImplicit);
|
| 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));
|
| + OwnPtr<CSSParserSelector> elementNameSelector = CSSParserSelector::create(tag);
|
| last->setTagHistory(elementNameSelector.release());
|
| }
|
|
|
| -PassOwnPtr<CSSParserSelector> CSSSelectorParser::rewriteSpecifiers(PassOwnPtr<CSSParserSelector> specifiers, PassOwnPtr<CSSParserSelector> newSpecifier)
|
| +PassOwnPtr<CSSParserSelector> CSSSelectorParser::addSimpleSelectorToCompound(PassOwnPtr<CSSParserSelector> compoundSelector, PassOwnPtr<CSSParserSelector> simpleSelector)
|
| {
|
| - 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;
|
| + // 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 ]
|
| + //
|
| + // ::content is kept at the end of the compound in order easily know when to call
|
| + // setRelationIsAffectedByPseudoContent.
|
| + //
|
| + // We are currently not dropping selectors containing multiple instances of ::content,
|
| + // ::shadow, ::cue, and custom pseudo elements in arbitrary order. There are known
|
| + // issues like crbug.com/478563
|
| + //
|
| + // TODO(rune@opera.com): We should try to remove the need for the re-ordering tricks
|
| + // below and in the remaining rewrite* methods by using a more suitable storage
|
| + // structure in CSSSelectorParser.
|
| + //
|
| + // The code below is to keep ::content at the end of the compound, and to keep the
|
| + // tagHistory order correct for implicit ShadowPseudo and juggling multiple (two?)
|
| + // compounds.
|
| +
|
| + CSSSelector::Relation relation = CSSSelector::SubSelector;
|
| +
|
| + if (simpleSelector->crossesTreeScopes() || simpleSelector->isContentPseudoElement()) {
|
| + if (simpleSelector->crossesTreeScopes())
|
| + relation = CSSSelector::ShadowPseudo;
|
| + simpleSelector->appendTagHistory(relation, compoundSelector);
|
| + return simpleSelector;
|
| + }
|
| + if (compoundSelector->crossesTreeScopes() || compoundSelector->isContentPseudoElement()) {
|
| + if (compoundSelector->crossesTreeScopes())
|
| + relation = CSSSelector::ShadowPseudo;
|
| + compoundSelector->insertTagHistory(CSSSelector::SubSelector, simpleSelector, relation);
|
| + return compoundSelector;
|
| + }
|
| +
|
| + // All other simple selectors are added to the end of the compound.
|
| + compoundSelector->appendTagHistory(CSSSelector::SubSelector, simpleSelector);
|
| + return compoundSelector;
|
| }
|
|
|
| void CSSSelectorParser::recordSelectorStats(const CSSParserContext& context, const CSSSelectorList& selectorList)
|
|
|