#include "core/css/SelectorChecker.h"
#include "HTMLNames.h"
-#include "core/css/CSSSelector.h"
#include "core/css/CSSSelectorList.h"
#include "core/css/SiblingTraversalStrategies.h"
#include "core/dom/Document.h"
-#include "core/dom/Element.h"
+#include "core/dom/ElementTraversal.h"
#include "core/dom/FullscreenElementStack.h"
#include "core/dom/NodeRenderStyle.h"
#include "core/dom/Text.h"
#include "core/dom/shadow/InsertionPoint.h"
#include "core/dom/shadow/ShadowRoot.h"
#include "core/editing/FrameSelection.h"
-#include "core/html/HTMLAnchorElement.h"
+#include "core/frame/LocalFrame.h"
#include "core/html/HTMLDocument.h"
#include "core/html/HTMLFrameElementBase.h"
#include "core/html/HTMLInputElement.h"
-#include "core/html/HTMLOptGroupElement.h"
#include "core/html/HTMLOptionElement.h"
#include "core/html/parser/HTMLParserIdioms.h"
+#include "core/html/track/vtt/VTTElement.h"
#include "core/inspector/InspectorInstrumentation.h"
#include "core/page/FocusController.h"
-#include "core/frame/Frame.h"
-#include "core/platform/ScrollableArea.h"
-#include "core/platform/ScrollbarTheme.h"
#include "core/rendering/RenderObject.h"
#include "core/rendering/RenderScrollbar.h"
#include "core/rendering/style/RenderStyle.h"
-
-#include "core/html/track/WebVTTElement.h"
+#include "platform/scroll/ScrollableArea.h"
+#include "platform/scroll/ScrollbarTheme.h"
namespace WebCore {
{
}
-static bool matchesCustomPseudoElement(const Element* element, const CSSSelector* selector)
+static bool matchesCustomPseudoElement(const Element* element, const CSSSelector& selector)
{
ShadowRoot* root = element->containingShadowRoot();
- if (!root)
+ if (!root || root->type() != ShadowRoot::UserAgentShadowRoot)
return false;
- if (selector->pseudoType() != CSSSelector::PseudoPart) {
- const AtomicString& pseudoId = selector->pseudoType() == CSSSelector::PseudoWebKitCustomElement ? element->shadowPseudoId() : element->pseudo();
- if (pseudoId != selector->value())
- return false;
- if (selector->pseudoType() == CSSSelector::PseudoWebKitCustomElement && root->type() != ShadowRoot::UserAgentShadowRoot)
- return false;
- return true;
- }
-
- if (element->part() != selector->argument())
- return false;
- if (selector->isMatchUserAgentOnly() && root->type() != ShadowRoot::UserAgentShadowRoot)
+ if (element->shadowPseudoId() != selector.value())
return false;
+
return true;
}
-Element* SelectorChecker::parentElement(const SelectorCheckingContext& context) const
+Element* SelectorChecker::parentElement(const SelectorCheckingContext& context, bool allowToCrossBoundary) const
{
// CrossesBoundary means we don't care any context.scope. So we can walk up from a shadow root to its shadow host.
- if ((context.behaviorAtBoundary & SelectorChecker::BoundaryBehaviorMask) == SelectorChecker::CrossesBoundary)
+ if (allowToCrossBoundary)
return context.element->parentOrShadowHostElement();
- // If context.scope is not a shadow host, we cannot walk up from a shadow root to its shadow host.
- if (!(context.behaviorAtBoundary & SelectorChecker::ScopeIsShadowHost))
+ // If context.scope is a shadow host, we should walk up from a shadow root to its shadow host.
+ if ((context.behaviorAtBoundary & SelectorChecker::ScopeIsShadowHost) && context.scope == context.element->shadowHost())
+ return context.element->parentOrShadowHostElement();
+
+ if ((context.behaviorAtBoundary & SelectorChecker::BoundaryBehaviorMask) != SelectorChecker::StaysWithinTreeScope)
return context.element->parentElement();
- // If behaviorAtBoundary is StaysWithInTreeScope, we cannot walk up from a shadow root to its shadow host.
- return (context.behaviorAtBoundary & SelectorChecker::BoundaryBehaviorMask) != SelectorChecker::StaysWithinTreeScope ? context.element->parentOrShadowHostElement() : context.element->parentElement();
+ // If context.scope is some element in some shadow tree and querySelector initialized the context,
+ // e.g. shadowRoot.querySelector(':host *'),
+ // (a) context.element has the same treescope as context.scope, need to walk up to its shadow host.
+ // (b) Otherwise, should not walk up from a shadow root to a shadow host.
+ if (context.scope && context.scope->treeScope() == context.element->treeScope())
+ return context.element->parentOrShadowHostElement();
+
+ return context.element->parentElement();
}
bool SelectorChecker::scopeContainsLastMatchedElement(const SelectorCheckingContext& context) const
return context.scope->contains(context.element);
// If a given element is scope, i.e. shadow host, matches.
- if (context.element == context.scope)
+ if (context.element == context.scope && (!context.previousElement || context.previousElement->isInDescendantTreeOf(context.element)))
return true;
ShadowRoot* root = context.element->containingShadowRoot();
return root->host() == context.scope;
}
+static inline bool nextSelectorExceedsScope(const SelectorChecker::SelectorCheckingContext& context)
+{
+ if ((context.behaviorAtBoundary & SelectorChecker::BoundaryBehaviorMask) != SelectorChecker::StaysWithinTreeScope)
+ return context.element == context.scope;
+
+ if (context.scope && context.scope->isInShadowTree())
+ return context.element == context.scope->containingShadowRoot()->host();
+ return false;
+}
+
// Recursive check of selectors and combinators
// It can return 4 different values:
// * SelectorMatches - the selector matches the element e
// * SelectorFailsAllSiblings - the selector fails for e and any sibling of e
// * SelectorFailsCompletely - the selector fails for e and any sibling or ancestor of e
template<typename SiblingTraversalStrategy>
-SelectorChecker::Match SelectorChecker::match(const SelectorCheckingContext& context, PseudoId& dynamicPseudo, const SiblingTraversalStrategy& siblingTraversalStrategy) const
+SelectorChecker::Match SelectorChecker::match(const SelectorCheckingContext& context, const SiblingTraversalStrategy& siblingTraversalStrategy, MatchResult* result) const
{
// first selector has to match
- if (!checkOne(context, siblingTraversalStrategy))
+ unsigned specificity = 0;
+ if (!checkOne(context, siblingTraversalStrategy, &specificity))
return SelectorFailsLocally;
if (context.selector->m_match == CSSSelector::PseudoElement) {
if (context.selector->isCustomPseudoElement()) {
- if (!matchesCustomPseudoElement(context.element, context.selector))
+ if (!matchesCustomPseudoElement(context.element, *context.selector))
return SelectorFailsLocally;
} else if (context.selector->isContentPseudoElement()) {
if (!context.element->isInShadowTree() || !context.element->isInsertionPoint())
return SelectorFailsLocally;
+ } else if (context.selector->isShadowPseudoElement()) {
+ if (!context.element->isInShadowTree() || !context.previousElement)
+ return SelectorFailsCompletely;
} else {
if ((!context.elementStyle && m_mode == ResolvingStyle) || m_mode == QueryingRules)
return SelectorFailsLocally;
PseudoId pseudoId = CSSSelector::pseudoId(context.selector->pseudoType());
if (pseudoId == FIRST_LETTER)
context.element->document().styleEngine()->setUsesFirstLetterRules(true);
- if (pseudoId != NOPSEUDO && m_mode != SharingRules)
- dynamicPseudo = pseudoId;
+ if (pseudoId != NOPSEUDO && m_mode != SharingRules && result)
+ result->dynamicPseudo = pseudoId;
}
}
- // The rest of the selectors has to match
- CSSSelector::Relation relation = context.selector->relation();
-
// Prepare next selector
const CSSSelector* historySelector = context.selector->tagHistory();
- if (!historySelector)
- return scopeContainsLastMatchedElement(context) ? SelectorMatches : SelectorFailsLocally;
-
- SelectorCheckingContext nextContext(context);
- nextContext.selector = historySelector;
+ if (!historySelector) {
+ if (scopeContainsLastMatchedElement(context)) {
+ if (result)
+ result->specificity += specificity;
+ return SelectorMatches;
+ }
+ return SelectorFailsLocally;
+ }
- PseudoId ignoreDynamicPseudo = NOPSEUDO;
- if (relation != CSSSelector::SubSelector) {
+ Match match;
+ if (context.selector->relation() != CSSSelector::SubSelector) {
// Abort if the next selector would exceed the scope.
- if (context.element == context.scope && (context.behaviorAtBoundary & BoundaryBehaviorMask) != StaysWithinTreeScope)
+ if (nextSelectorExceedsScope(context))
return SelectorFailsCompletely;
// Bail-out if this selector is irrelevant for the pseudoId
- if (context.pseudoId != NOPSEUDO && context.pseudoId != dynamicPseudo)
+ if (context.pseudoId != NOPSEUDO && (!result || context.pseudoId != result->dynamicPseudo))
return SelectorFailsCompletely;
- // Disable :visited matching when we see the first link or try to match anything else than an ancestors.
- if (!context.isSubSelector && (context.element->isLink() || (relation != CSSSelector::Descendant && relation != CSSSelector::Child)))
- nextContext.visitedMatchType = VisitedMatchDisabled;
-
- nextContext.pseudoId = NOPSEUDO;
+ if (result) {
+ TemporaryChange<PseudoId> dynamicPseudoScope(result->dynamicPseudo, NOPSEUDO);
+ match = matchForRelation(context, siblingTraversalStrategy, result);
+ } else {
+ return matchForRelation(context, siblingTraversalStrategy, 0);
+ }
+ } else {
+ match = matchForSubSelector(context, siblingTraversalStrategy, result);
}
+ if (match != SelectorMatches || !result)
+ return match;
+
+ result->specificity += specificity;
+ return SelectorMatches;
+}
+
+static inline SelectorChecker::SelectorCheckingContext prepareNextContextForRelation(const SelectorChecker::SelectorCheckingContext& context)
+{
+ SelectorChecker::SelectorCheckingContext nextContext(context);
+ ASSERT(context.selector->tagHistory());
+ nextContext.selector = context.selector->tagHistory();
+ return nextContext;
+}
+
+static inline bool isAuthorShadowRoot(const Node* node)
+{
+ return node && node->isShadowRoot() && toShadowRoot(node)->type() == ShadowRoot::AuthorShadowRoot;
+}
+
+template<typename SiblingTraversalStrategy>
+SelectorChecker::Match SelectorChecker::matchForSubSelector(const SelectorCheckingContext& context, const SiblingTraversalStrategy& siblingTraversalStrategy, MatchResult* result) const
+{
+ SelectorCheckingContext nextContext = prepareNextContextForRelation(context);
+
+ PseudoId dynamicPseudo = result ? result->dynamicPseudo : NOPSEUDO;
+ // a selector is invalid if something follows a pseudo-element
+ // We make an exception for scrollbar pseudo elements and allow a set of pseudo classes (but nothing else)
+ // to follow the pseudo elements.
+ nextContext.hasScrollbarPseudo = dynamicPseudo != NOPSEUDO && (context.scrollbar || dynamicPseudo == SCROLLBAR_CORNER || dynamicPseudo == RESIZER);
+ nextContext.hasSelectionPseudo = dynamicPseudo == SELECTION;
+ if ((context.elementStyle || m_mode == CollectingCSSRules || m_mode == CollectingStyleRules || m_mode == QueryingRules) && dynamicPseudo != NOPSEUDO
+ && !nextContext.hasSelectionPseudo
+ && !(nextContext.hasScrollbarPseudo && nextContext.selector->m_match == CSSSelector::PseudoClass))
+ return SelectorFailsCompletely;
+
+ nextContext.isSubSelector = true;
+ return match(nextContext, siblingTraversalStrategy, result);
+}
+
+static bool selectorMatchesShadowRoot(const CSSSelector* selector)
+{
+ return selector && selector->isShadowPseudoElement();
+}
+
+template<typename SiblingTraversalStrategy>
+SelectorChecker::Match SelectorChecker::matchForPseudoShadow(const ContainerNode* node, const SelectorCheckingContext& context, const SiblingTraversalStrategy& siblingTraversalStrategy, MatchResult* result) const
+{
+ if (!isAuthorShadowRoot(node))
+ return SelectorFailsCompletely;
+ return match(context, siblingTraversalStrategy, result);
+}
+
+template<typename SiblingTraversalStrategy>
+SelectorChecker::Match SelectorChecker::matchForRelation(const SelectorCheckingContext& context, const SiblingTraversalStrategy& siblingTraversalStrategy, MatchResult* result) const
+{
+ SelectorCheckingContext nextContext = prepareNextContextForRelation(context);
+ nextContext.previousElement = context.element;
+
+ CSSSelector::Relation relation = context.selector->relation();
+
+ // Disable :visited matching when we see the first link or try to match anything else than an ancestors.
+ if (!context.isSubSelector && (context.element->isLink() || (relation != CSSSelector::Descendant && relation != CSSSelector::Child)))
+ nextContext.visitedMatchType = VisitedMatchDisabled;
+
+ nextContext.pseudoId = NOPSEUDO;
switch (relation) {
case CSSSelector::Descendant:
if (context.selector->relationIsAffectedByPseudoContent()) {
for (Element* element = context.element; element; element = element->parentElement()) {
- if (matchForShadowDistributed(element, siblingTraversalStrategy, ignoreDynamicPseudo, nextContext) == SelectorMatches)
+ if (matchForShadowDistributed(element, siblingTraversalStrategy, nextContext, result) == SelectorMatches)
return SelectorMatches;
}
return SelectorFailsCompletely;
}
nextContext.isSubSelector = false;
nextContext.elementStyle = 0;
+
+ if (selectorMatchesShadowRoot(nextContext.selector))
+ return matchForPseudoShadow(context.element->containingShadowRoot(), nextContext, siblingTraversalStrategy, result);
+
for (nextContext.element = parentElement(context); nextContext.element; nextContext.element = parentElement(nextContext)) {
- Match match = this->match(nextContext, ignoreDynamicPseudo, siblingTraversalStrategy);
+ Match match = this->match(nextContext, siblingTraversalStrategy, result);
if (match == SelectorMatches || match == SelectorFailsCompletely)
return match;
- if (nextContext.element == nextContext.scope && (nextContext.behaviorAtBoundary & BoundaryBehaviorMask) != StaysWithinTreeScope)
+ if (nextSelectorExceedsScope(nextContext))
return SelectorFailsCompletely;
}
return SelectorFailsCompletely;
case CSSSelector::Child:
{
if (context.selector->relationIsAffectedByPseudoContent())
- return matchForShadowDistributed(context.element, siblingTraversalStrategy, ignoreDynamicPseudo, nextContext);
+ return matchForShadowDistributed(context.element, siblingTraversalStrategy, nextContext, result);
+
+ nextContext.isSubSelector = false;
+ nextContext.elementStyle = 0;
+
+ if (selectorMatchesShadowRoot(nextContext.selector))
+ return matchForPseudoShadow(context.element->parentNode(), nextContext, siblingTraversalStrategy, result);
nextContext.element = parentElement(context);
if (!nextContext.element)
return SelectorFailsCompletely;
-
- nextContext.isSubSelector = false;
- nextContext.elementStyle = 0;
- return match(nextContext, ignoreDynamicPseudo, siblingTraversalStrategy);
+ return match(nextContext, siblingTraversalStrategy, result);
}
case CSSSelector::DirectAdjacent:
+ // Shadow roots can't have sibling elements
+ if (selectorMatchesShadowRoot(nextContext.selector))
+ return SelectorFailsCompletely;
+
if (m_mode == ResolvingStyle) {
- if (Element* parent = parentElement(context))
+ if (ContainerNode* parent = context.element->parentElementOrShadowRoot())
parent->setChildrenAffectedByDirectAdjacentRules();
}
- nextContext.element = context.element->previousElementSibling();
+ nextContext.element = ElementTraversal::previousSibling(*context.element);
if (!nextContext.element)
return SelectorFailsAllSiblings;
nextContext.isSubSelector = false;
nextContext.elementStyle = 0;
- return match(nextContext, ignoreDynamicPseudo, siblingTraversalStrategy);
+ return match(nextContext, siblingTraversalStrategy, result);
case CSSSelector::IndirectAdjacent:
+ // Shadow roots can't have sibling elements
+ if (selectorMatchesShadowRoot(nextContext.selector))
+ return SelectorFailsCompletely;
+
if (m_mode == ResolvingStyle) {
- if (Element* parent = parentElement(context))
- parent->setChildrenAffectedByForwardPositionalRules();
+ if (ContainerNode* parent = context.element->parentElementOrShadowRoot())
+ parent->setChildrenAffectedByIndirectAdjacentRules();
}
- nextContext.element = context.element->previousElementSibling();
+ nextContext.element = ElementTraversal::previousSibling(*context.element);
nextContext.isSubSelector = false;
nextContext.elementStyle = 0;
- for (; nextContext.element; nextContext.element = nextContext.element->previousElementSibling()) {
- Match match = this->match(nextContext, ignoreDynamicPseudo, siblingTraversalStrategy);
+ for (; nextContext.element; nextContext.element = ElementTraversal::previousSibling(*nextContext.element)) {
+ Match match = this->match(nextContext, siblingTraversalStrategy, result);
if (match == SelectorMatches || match == SelectorFailsAllSiblings || match == SelectorFailsCompletely)
return match;
};
return SelectorFailsAllSiblings;
- case CSSSelector::SubSelector:
- // a selector is invalid if something follows a pseudo-element
- // We make an exception for scrollbar pseudo elements and allow a set of pseudo classes (but nothing else)
- // to follow the pseudo elements.
- nextContext.hasScrollbarPseudo = dynamicPseudo != NOPSEUDO && (context.scrollbar || dynamicPseudo == SCROLLBAR_CORNER || dynamicPseudo == RESIZER);
- nextContext.hasSelectionPseudo = dynamicPseudo == SELECTION;
- if ((context.elementStyle || m_mode == CollectingCSSRules || m_mode == CollectingStyleRules || m_mode == QueryingRules) && dynamicPseudo != NOPSEUDO
- && !nextContext.hasSelectionPseudo
- && !(nextContext.hasScrollbarPseudo && nextContext.selector->m_match == CSSSelector::PseudoClass))
- return SelectorFailsCompletely;
- nextContext.isSubSelector = true;
- return match(nextContext, dynamicPseudo, siblingTraversalStrategy);
-
case CSSSelector::ShadowPseudo:
- case CSSSelector::ChildTree:
{
// If we're in the same tree-scope as the scoping element, then following a shadow descendant combinator would escape that and thus the scope.
if (context.scope && context.scope->treeScope() == context.element->treeScope() && (context.behaviorAtBoundary & BoundaryBehaviorMask) != StaysWithinTreeScope)
nextContext.element = shadowHost;
nextContext.isSubSelector = false;
nextContext.elementStyle = 0;
- return this->match(nextContext, ignoreDynamicPseudo, siblingTraversalStrategy);
+ return this->match(nextContext, siblingTraversalStrategy, result);
}
- case CSSSelector::DescendantTree:
+ case CSSSelector::ShadowDeep:
{
nextContext.isSubSelector = false;
nextContext.elementStyle = 0;
- for (nextContext.element = parentElement(context); nextContext.element; nextContext.element = parentElement(nextContext)) {
- Match match = this->match(nextContext, ignoreDynamicPseudo, siblingTraversalStrategy);
+ for (nextContext.element = parentElement(context, true); nextContext.element; nextContext.element = parentElement(nextContext, true)) {
+ Match match = this->match(nextContext, siblingTraversalStrategy, result);
if (match == SelectorMatches || match == SelectorFailsCompletely)
return match;
- if (nextContext.element == nextContext.scope && (nextContext.behaviorAtBoundary & BoundaryBehaviorMask) != StaysWithinTreeScope)
+ if (nextSelectorExceedsScope(nextContext))
return SelectorFailsCompletely;
}
return SelectorFailsCompletely;
}
+
+ case CSSSelector::SubSelector:
+ ASSERT_NOT_REACHED();
}
ASSERT_NOT_REACHED();
}
template<typename SiblingTraversalStrategy>
-SelectorChecker::Match SelectorChecker::matchForShadowDistributed(const Element* element, const SiblingTraversalStrategy& siblingTraversalStrategy, PseudoId& dynamicPseudo, SelectorCheckingContext& nextContext) const
+SelectorChecker::Match SelectorChecker::matchForShadowDistributed(const Element* element, const SiblingTraversalStrategy& siblingTraversalStrategy, SelectorCheckingContext& nextContext, MatchResult* result) const
{
ASSERT(element);
Vector<InsertionPoint*, 8> insertionPoints;
+
+ const ContainerNode* scope = nextContext.scope;
+ BehaviorAtBoundary behaviorAtBoundary = nextContext.behaviorAtBoundary;
+
collectDestinationInsertionPoints(*element, insertionPoints);
for (size_t i = 0; i < insertionPoints.size(); ++i) {
nextContext.element = insertionPoints[i];
+
+ // If a given scope is a shadow host of an insertion point but behaviorAtBoundary doesn't have ScopeIsShadowHost,
+ // we need to update behaviorAtBoundary to make selectors like ":host > ::content" work correctly.
+ if (m_mode == SharingRules) {
+ nextContext.behaviorAtBoundary = static_cast<BehaviorAtBoundary>(behaviorAtBoundary | ScopeIsShadowHost);
+ nextContext.scope = insertionPoints[i]->containingShadowRoot()->shadowHost();
+ } else if (scope == insertionPoints[i]->containingShadowRoot()->shadowHost() && !(behaviorAtBoundary & ScopeIsShadowHost))
+ nextContext.behaviorAtBoundary = static_cast<BehaviorAtBoundary>(behaviorAtBoundary | ScopeIsShadowHost);
+ else
+ nextContext.behaviorAtBoundary = behaviorAtBoundary;
+
nextContext.isSubSelector = false;
nextContext.elementStyle = 0;
- if (match(nextContext, dynamicPseudo, siblingTraversalStrategy) == SelectorMatches)
+ if (match(nextContext, siblingTraversalStrategy, result) == SelectorMatches)
return SelectorMatches;
}
- return SelectorFailsCompletely;
+ return SelectorFailsLocally;
}
static inline bool containsHTMLSpace(const AtomicString& string)
return false;
}
-static bool attributeValueMatches(const Attribute* attributeItem, CSSSelector::Match match, const AtomicString& selectorValue, bool caseSensitive)
+static bool attributeValueMatches(const Attribute& attributeItem, CSSSelector::Match match, const AtomicString& selectorValue, bool caseSensitive)
{
- const AtomicString& value = attributeItem->value();
+ const AtomicString& value = attributeItem.value();
if (value.isNull())
return false;
return true;
}
-static bool anyAttributeMatches(Element& element, CSSSelector::Match match, const QualifiedName& selectorAttr, const AtomicString& selectorValue, bool caseSensitive)
+static bool anyAttributeMatches(Element& element, CSSSelector::Match match, const CSSSelector& selector)
{
- ASSERT(element.hasAttributesWithoutUpdate());
- for (size_t i = 0; i < element.attributeCount(); ++i) {
- const Attribute* attributeItem = element.attributeItem(i);
+ const QualifiedName& selectorAttr = selector.attribute();
+ ASSERT(selectorAttr.localName() != starAtom); // Should not be possible from the CSS grammar.
+
+ // Synchronize the attribute in case it is lazy-computed.
+ // Currently all lazy properties have a null namespace, so only pass localName().
+ element.synchronizeAttribute(selectorAttr.localName());
+
+ if (!element.hasAttributesWithoutUpdate())
+ return false;
+
+ const AtomicString& selectorValue = selector.value();
+
+ unsigned attributeCount = element.attributeCount();
+ for (size_t i = 0; i < attributeCount; ++i) {
+ const Attribute& attributeItem = element.attributeItem(i);
- if (!attributeItem->matches(selectorAttr))
+ if (!attributeItem.matches(selectorAttr))
continue;
- if (attributeValueMatches(attributeItem, match, selectorValue, caseSensitive))
+ if (attributeValueMatches(attributeItem, match, selectorValue, true))
return true;
+
+ // Case sensitivity for attribute matching is looser than hasAttribute or
+ // Element::shouldIgnoreAttributeCase() for now. Unclear if that's correct.
+ bool caseSensitive = !element.document().isHTMLDocument() || HTMLDocument::isCaseSensitiveAttribute(selectorAttr);
+
+ // If case-insensitive, re-check, and count if result differs.
+ // See http://code.google.com/p/chromium/issues/detail?id=327060
+ if (!caseSensitive && attributeValueMatches(attributeItem, match, selectorValue, false)) {
+ UseCounter::count(element.document(), UseCounter::CaseInsensitiveAttrSelectorMatch);
+ return true;
+ }
}
return false;
}
template<typename SiblingTraversalStrategy>
-bool SelectorChecker::checkOne(const SelectorCheckingContext& context, const SiblingTraversalStrategy& siblingTraversalStrategy) const
+bool SelectorChecker::checkOne(const SelectorCheckingContext& context, const SiblingTraversalStrategy& siblingTraversalStrategy, unsigned* specificity) const
{
ASSERT(context.element);
Element& element = *context.element;
- const CSSSelector* const & selector = context.selector;
- ASSERT(selector);
- bool elementIsHostInItsShadowTree = isHostInItsShadowTree(element, context.behaviorAtBoundary, context.scope);
-
- if (selector->m_match == CSSSelector::Tag)
- return SelectorChecker::tagMatches(element, selector->tagQName(), elementIsHostInItsShadowTree ? MatchingHostInItsShadowTree : MatchingElement);
+ ASSERT(context.selector);
+ const CSSSelector& selector = *context.selector;
- if (selector->m_match == CSSSelector::Class)
- return element.hasClass() && element.classNames().contains(selector->value()) && !elementIsHostInItsShadowTree;
+ bool elementIsHostInItsShadowTree = isHostInItsShadowTree(element, context.behaviorAtBoundary, context.scope);
- if (selector->m_match == CSSSelector::Id)
- return element.hasID() && element.idForStyleResolution() == selector->value() && !elementIsHostInItsShadowTree;
+ // Only :host and :ancestor should match the host: http://drafts.csswg.org/css-scoping/#host-element
+ if (elementIsHostInItsShadowTree && !selector.isHostPseudoClass())
+ return false;
- if (selector->isAttributeSelector()) {
- const QualifiedName& attr = selector->attribute();
+ if (selector.m_match == CSSSelector::Tag)
+ return SelectorChecker::tagMatches(element, selector.tagQName());
- if (!element.hasAttributes() || elementIsHostInItsShadowTree)
- return false;
+ if (selector.m_match == CSSSelector::Class)
+ return element.hasClass() && element.classNames().contains(selector.value());
- bool caseSensitive = !m_documentIsHTML || HTMLDocument::isCaseSensitiveAttribute(attr);
+ if (selector.m_match == CSSSelector::Id)
+ return element.hasID() && element.idForStyleResolution() == selector.value();
- if (!anyAttributeMatches(element, static_cast<CSSSelector::Match>(selector->m_match), attr, selector->value(), caseSensitive))
+ if (selector.isAttributeSelector()) {
+ if (!anyAttributeMatches(element, static_cast<CSSSelector::Match>(selector.m_match), selector))
return false;
}
- if (selector->m_match == CSSSelector::PseudoClass) {
+ if (selector.m_match == CSSSelector::PseudoClass) {
// Handle :not up front.
- if (selector->pseudoType() == CSSSelector::PseudoNot) {
+ if (selector.pseudoType() == CSSSelector::PseudoNot) {
SelectorCheckingContext subContext(context);
subContext.isSubSelector = true;
- ASSERT(selector->selectorList());
- for (subContext.selector = selector->selectorList()->first(); subContext.selector; subContext.selector = subContext.selector->tagHistory()) {
+ ASSERT(selector.selectorList());
+ for (subContext.selector = selector.selectorList()->first(); subContext.selector; subContext.selector = subContext.selector->tagHistory()) {
// :not cannot nest. I don't really know why this is a
// restriction in CSS3, but it is, so let's honor it.
// the parser enforces that this never occurs
// We select between :visited and :link when applying. We don't know which one applied (or not) yet.
if (subContext.selector->pseudoType() == CSSSelector::PseudoVisited || (subContext.selector->pseudoType() == CSSSelector::PseudoLink && subContext.visitedMatchType == VisitedMatchEnabled))
return true;
+ // context.scope is not available if m_mode == SharingRules.
+ // We cannot determine whether :host or :scope matches a given element or not.
+ if (m_mode == SharingRules && (subContext.selector->isHostPseudoClass() || subContext.selector->pseudoType() == CSSSelector::PseudoScope))
+ return true;
if (!checkOne(subContext, DOMSiblingTraversalStrategy()))
return true;
}
// (since there are no elements involved).
return checkScrollbarPseudoClass(context, &element.document(), selector);
} else if (context.hasSelectionPseudo) {
- if (selector->pseudoType() == CSSSelector::PseudoWindowInactive)
+ if (selector.pseudoType() == CSSSelector::PseudoWindowInactive)
return !element.document().page()->focusController().isActive();
}
// Normal element pseudo class checking.
- switch (selector->pseudoType()) {
+ switch (selector.pseudoType()) {
// Pseudo classes:
case CSSSelector::PseudoNot:
break; // Already handled up above.
}
case CSSSelector::PseudoFirstChild:
// first-child matches the first child that is an element
- if (Element* parent = element.parentElement()) {
- bool result = siblingTraversalStrategy.isFirstChild(&element);
+ if (ContainerNode* parent = element.parentElementOrShadowRoot()) {
+ bool result = siblingTraversalStrategy.isFirstChild(element);
if (m_mode == ResolvingStyle) {
RenderStyle* childStyle = context.elementStyle ? context.elementStyle : element.renderStyle();
parent->setChildrenAffectedByFirstChildRules();
break;
case CSSSelector::PseudoFirstOfType:
// first-of-type matches the first element of its type
- if (Element* parent = element.parentElement()) {
- bool result = siblingTraversalStrategy.isFirstOfType(&element, element.tagQName());
+ if (ContainerNode* parent = element.parentElementOrShadowRoot()) {
+ bool result = siblingTraversalStrategy.isFirstOfType(element, element.tagQName());
if (m_mode == ResolvingStyle)
parent->setChildrenAffectedByForwardPositionalRules();
return result;
break;
case CSSSelector::PseudoLastChild:
// last-child matches the last child that is an element
- if (Element* parent = element.parentElement()) {
- bool result = parent->isFinishedParsingChildren() && siblingTraversalStrategy.isLastChild(&element);
+ if (ContainerNode* parent = element.parentElementOrShadowRoot()) {
+ bool result = parent->isFinishedParsingChildren() && siblingTraversalStrategy.isLastChild(element);
if (m_mode == ResolvingStyle) {
RenderStyle* childStyle = context.elementStyle ? context.elementStyle : element.renderStyle();
parent->setChildrenAffectedByLastChildRules();
break;
case CSSSelector::PseudoLastOfType:
// last-of-type matches the last element of its type
- if (Element* parent = element.parentElement()) {
+ if (ContainerNode* parent = element.parentElementOrShadowRoot()) {
if (m_mode == ResolvingStyle)
parent->setChildrenAffectedByBackwardPositionalRules();
if (!parent->isFinishedParsingChildren())
return false;
- return siblingTraversalStrategy.isLastOfType(&element, element.tagQName());
+ return siblingTraversalStrategy.isLastOfType(element, element.tagQName());
}
break;
case CSSSelector::PseudoOnlyChild:
- if (Element* parent = element.parentElement()) {
- bool firstChild = siblingTraversalStrategy.isFirstChild(&element);
- bool onlyChild = firstChild && parent->isFinishedParsingChildren() && siblingTraversalStrategy.isLastChild(&element);
+ if (ContainerNode* parent = element.parentElementOrShadowRoot()) {
+ bool firstChild = siblingTraversalStrategy.isFirstChild(element);
+ bool onlyChild = firstChild && parent->isFinishedParsingChildren() && siblingTraversalStrategy.isLastChild(element);
if (m_mode == ResolvingStyle) {
RenderStyle* childStyle = context.elementStyle ? context.elementStyle : element.renderStyle();
parent->setChildrenAffectedByFirstChildRules();
break;
case CSSSelector::PseudoOnlyOfType:
// FIXME: This selector is very slow.
- if (Element* parent = element.parentElement()) {
+ if (ContainerNode* parent = element.parentElementOrShadowRoot()) {
if (m_mode == ResolvingStyle) {
parent->setChildrenAffectedByForwardPositionalRules();
parent->setChildrenAffectedByBackwardPositionalRules();
}
if (!parent->isFinishedParsingChildren())
return false;
- return siblingTraversalStrategy.isFirstOfType(&element, element.tagQName()) && siblingTraversalStrategy.isLastOfType(&element, element.tagQName());
+ return siblingTraversalStrategy.isFirstOfType(element, element.tagQName()) && siblingTraversalStrategy.isLastOfType(element, element.tagQName());
}
break;
case CSSSelector::PseudoNthChild:
- if (!selector->parseNth())
+ if (!selector.parseNth())
break;
- if (Element* parent = element.parentElement()) {
- int count = 1 + siblingTraversalStrategy.countElementsBefore(&element);
+ if (ContainerNode* parent = element.parentElementOrShadowRoot()) {
+ int count = 1 + siblingTraversalStrategy.countElementsBefore(element);
if (m_mode == ResolvingStyle) {
RenderStyle* childStyle = context.elementStyle ? context.elementStyle : element.renderStyle();
- element.setChildIndex(count);
if (childStyle)
childStyle->setUnique();
parent->setChildrenAffectedByForwardPositionalRules();
}
- if (selector->matchNth(count))
+ if (selector.matchNth(count))
return true;
}
break;
case CSSSelector::PseudoNthOfType:
- if (!selector->parseNth())
+ if (!selector.parseNth())
break;
- if (Element* parent = element.parentElement()) {
- int count = 1 + siblingTraversalStrategy.countElementsOfTypeBefore(&element, element.tagQName());
+ if (ContainerNode* parent = element.parentElementOrShadowRoot()) {
+ int count = 1 + siblingTraversalStrategy.countElementsOfTypeBefore(element, element.tagQName());
if (m_mode == ResolvingStyle)
parent->setChildrenAffectedByForwardPositionalRules();
- if (selector->matchNth(count))
+ if (selector.matchNth(count))
return true;
}
break;
case CSSSelector::PseudoNthLastChild:
- if (!selector->parseNth())
+ if (!selector.parseNth())
break;
- if (Element* parent = element.parentElement()) {
+ if (ContainerNode* parent = element.parentElementOrShadowRoot()) {
if (m_mode == ResolvingStyle)
parent->setChildrenAffectedByBackwardPositionalRules();
if (!parent->isFinishedParsingChildren())
return false;
- int count = 1 + siblingTraversalStrategy.countElementsAfter(&element);
- if (selector->matchNth(count))
+ int count = 1 + siblingTraversalStrategy.countElementsAfter(element);
+ if (selector.matchNth(count))
return true;
}
break;
case CSSSelector::PseudoNthLastOfType:
- if (!selector->parseNth())
+ if (!selector.parseNth())
break;
- if (Element* parent = element.parentElement()) {
+ if (ContainerNode* parent = element.parentElementOrShadowRoot()) {
if (m_mode == ResolvingStyle)
parent->setChildrenAffectedByBackwardPositionalRules();
if (!parent->isFinishedParsingChildren())
return false;
- int count = 1 + siblingTraversalStrategy.countElementsOfTypeAfter(&element, element.tagQName());
- if (selector->matchNth(count))
+ int count = 1 + siblingTraversalStrategy.countElementsOfTypeAfter(element, element.tagQName());
+ if (selector.matchNth(count))
return true;
}
break;
{
SelectorCheckingContext subContext(context);
subContext.isSubSelector = true;
- PseudoId ignoreDynamicPseudo = NOPSEUDO;
- ASSERT(selector->selectorList());
- for (subContext.selector = selector->selectorList()->first(); subContext.selector; subContext.selector = CSSSelectorList::next(subContext.selector)) {
- if (match(subContext, ignoreDynamicPseudo, siblingTraversalStrategy) == SelectorMatches)
+ ASSERT(selector.selectorList());
+ for (subContext.selector = selector.selectorList()->first(); subContext.selector; subContext.selector = CSSSelectorList::next(*subContext.selector)) {
+ if (match(subContext, siblingTraversalStrategy) == SelectorMatches)
return true;
}
}
if (context.elementStyle)
context.elementStyle->setAffectedByDrag();
else
- element.setChildrenAffectedByDrag(true);
+ element.setChildrenAffectedByDrag();
}
if (element.renderer() && element.renderer()->isDragging())
return true;
break;
case CSSSelector::PseudoFocus:
+ if (m_mode == ResolvingStyle) {
+ if (context.elementStyle)
+ context.elementStyle->setAffectedByFocus();
+ else
+ element.setChildrenAffectedByFocus();
+ }
return matchesFocusPseudoClass(element);
case CSSSelector::PseudoHover:
// If we're in quirks mode, then hover should never match anchors with no
// href and *:hover should not match anything. This is important for sites like wsj.com.
- if (m_strictParsing || context.isSubSelector || (selector->m_match == CSSSelector::Tag && selector->tagQName() != anyQName() && !isHTMLAnchorElement(element)) || element.isLink()) {
+ if (m_strictParsing || context.isSubSelector || (selector.m_match == CSSSelector::Tag && selector.tagQName() != anyQName() && !isHTMLAnchorElement(element)) || element.isLink()) {
if (m_mode == ResolvingStyle) {
if (context.elementStyle)
context.elementStyle->setAffectedByHover();
else
- element.setChildrenAffectedByHover(true);
+ element.setChildrenAffectedByHover();
}
if (element.hovered() || InspectorInstrumentation::forcePseudoState(&element, CSSSelector::PseudoHover))
return true;
case CSSSelector::PseudoActive:
// If we're in quirks mode, then :active should never match anchors with no
// href and *:active should not match anything.
- if (m_strictParsing || context.isSubSelector || (selector->m_match == CSSSelector::Tag && selector->tagQName() != anyQName() && !isHTMLAnchorElement(element)) || element.isLink()) {
+ if (m_strictParsing || context.isSubSelector || (selector.m_match == CSSSelector::Tag && selector.tagQName() != anyQName() && !isHTMLAnchorElement(element)) || element.isLink()) {
if (m_mode == ResolvingStyle) {
if (context.elementStyle)
context.elementStyle->setAffectedByActive();
else
- element.setChildrenAffectedByActive(true);
+ element.setChildrenAffectedByActive();
}
if (element.active() || InspectorInstrumentation::forcePseudoState(&element, CSSSelector::PseudoActive))
return true;
}
break;
case CSSSelector::PseudoEnabled:
- if (element.isFormControlElement() || element.hasTagName(optionTag) || isHTMLOptGroupElement(element))
+ if (element.isFormControlElement() || isHTMLOptionElement(element) || isHTMLOptGroupElement(element))
return !element.isDisabledFormControl();
break;
case CSSSelector::PseudoFullPageMedia:
case CSSSelector::PseudoDefault:
return element.isDefaultButtonForForm();
case CSSSelector::PseudoDisabled:
- if (element.isFormControlElement() || element.hasTagName(optionTag) || isHTMLOptGroupElement(element))
+ if (element.isFormControlElement() || isHTMLOptionElement(element) || isHTMLOptGroupElement(element))
return element.isDisabledFormControl();
break;
case CSSSelector::PseudoReadOnly:
return element.willValidate() && !element.isValidFormControlElement();
case CSSSelector::PseudoChecked:
{
- if (element.hasTagName(inputTag)) {
+ if (isHTMLInputElement(element)) {
HTMLInputElement& inputElement = toHTMLInputElement(element);
// Even though WinIE allows checked and indeterminate to
// co-exist, the CSS selector spec says that you can't be
// test for matching the pseudo.
if (inputElement.shouldAppearChecked() && !inputElement.shouldAppearIndeterminate())
return true;
- } else if (element.hasTagName(optionTag) && toHTMLOptionElement(element).selected())
+ } else if (isHTMLOptionElement(element) && toHTMLOptionElement(element).selected())
return true;
break;
}
case CSSSelector::PseudoLang:
{
AtomicString value;
- if (element.isWebVTTElement())
- value = toWebVTTElement(element).language();
+ if (element.isVTTElement())
+ value = toVTTElement(element).language();
else
value = element.computeInheritedLanguage();
- const AtomicString& argument = selector->argument();
+ const AtomicString& argument = selector.argument();
if (value.isEmpty() || !value.startsWith(argument, false))
break;
if (value.length() != argument.length() && value[argument.length()] != '-')
// element is an element in the document, the 'full-screen' pseudoclass applies to
// that element. Also, an <iframe>, <object> or <embed> element whose child browsing
// context's Document is in the fullscreen state has the 'full-screen' pseudoclass applied.
- if (element.isFrameElementBase() && element.containsFullScreenElement())
+ if (isHTMLFrameElementBase(element) && element.containsFullScreenElement())
return true;
- if (FullscreenElementStack* fullscreen = FullscreenElementStack::fromIfExists(&element.document())) {
+ if (FullscreenElementStack* fullscreen = FullscreenElementStack::fromIfExists(element.document())) {
if (!fullscreen->webkitIsFullScreen())
return false;
return element == fullscreen->webkitCurrentFullScreenElement();
case CSSSelector::PseudoFullScreenDocument:
// While a Document is in the fullscreen state, the 'full-screen-document' pseudoclass applies
// to all elements of that Document.
- if (!FullscreenElementStack::isFullScreen(&element.document()))
+ if (!FullscreenElementStack::isFullScreen(element.document()))
return false;
return true;
- case CSSSelector::PseudoSeamlessDocument:
- // While a document is rendered in a seamless iframe, the 'seamless-document' pseudoclass applies
- // to all elements of that Document.
- return element.document().shouldDisplaySeamlesslyWithParent();
case CSSSelector::PseudoInRange:
element.document().setContainsValidityStyleRules();
return element.isInRange();
element.document().setContainsValidityStyleRules();
return element.isOutOfRange();
case CSSSelector::PseudoFutureCue:
- return (element.isWebVTTElement() && !toWebVTTElement(element).isPastNode());
+ return (element.isVTTElement() && !toVTTElement(element).isPastNode());
case CSSSelector::PseudoPastCue:
- return (element.isWebVTTElement() && toWebVTTElement(element).isPastNode());
+ return (element.isVTTElement() && toVTTElement(element).isPastNode());
case CSSSelector::PseudoScope:
{
- const Node* contextualReferenceNode = !context.scope || (context.behaviorAtBoundary & BoundaryBehaviorMask) == CrossesBoundary ? element.document().documentElement() : context.scope;
+ if (m_mode == SharingRules)
+ return true;
+ const Node* contextualReferenceNode = !context.scope ? element.document().documentElement() : context.scope;
if (element == contextualReferenceNode)
return true;
break;
break;
case CSSSelector::PseudoHost:
+ case CSSSelector::PseudoHostContext:
{
+ if (m_mode == SharingRules)
+ return true;
// :host only matches a shadow host when :host is in a shadow tree of the shadow host.
- if (!context.scope || !(context.behaviorAtBoundary & ScopeIsShadowHost) || context.scope != element)
+ if (!context.scope)
+ return false;
+ const ContainerNode* shadowHost = (context.behaviorAtBoundary & ScopeIsShadowHost) ? context.scope : (context.scope->isInShadowTree() ? context.scope->shadowHost() : 0);
+ if (!shadowHost || shadowHost != element)
return false;
ASSERT(element.shadow());
// For empty parameter case, i.e. just :host or :host().
- if (!selector->selectorList())
+ if (!selector.selectorList()) // Use *'s specificity. So just 0.
return true;
SelectorCheckingContext subContext(context);
subContext.isSubSelector = true;
- subContext.behaviorAtBoundary = CrossesBoundary;
- subContext.scope = 0;
- // Use NodeRenderingTraversal to traverse a composed ancestor list of a given element.
- for (Element* nextElement = &element; nextElement; nextElement = NodeRenderingTraversal::parentElement(nextElement)) {
- // If one of simple selectors matches an element, returns SelectorMatches. Just "OR".
- for (subContext.selector = selector->selectorList()->first(); subContext.selector; subContext.selector = CSSSelectorList::next(subContext.selector)) {
- PseudoId ignoreDynamicPseudo = NOPSEUDO;
- subContext.element = nextElement;
- if (match(subContext, ignoreDynamicPseudo, siblingTraversalStrategy) == SelectorMatches)
- return true;
- }
+
+ bool matched = false;
+ unsigned maxSpecificity = 0;
+
+ // If one of simple selectors matches an element, returns SelectorMatches. Just "OR".
+ for (subContext.selector = selector.selectorList()->first(); subContext.selector; subContext.selector = CSSSelectorList::next(*subContext.selector)) {
+ subContext.behaviorAtBoundary = ScopeIsShadowHostInPseudoHostParameter;
+ subContext.scope = shadowHost;
+ // Use NodeRenderingTraversal to traverse a composed ancestor list of a given element.
+ Element* nextElement = &element;
+ SelectorCheckingContext hostContext(subContext);
+ do {
+ MatchResult subResult;
+ hostContext.element = nextElement;
+ if (match(hostContext, siblingTraversalStrategy, &subResult) == SelectorMatches) {
+ matched = true;
+ // Consider div:host(div:host(div:host(div:host...))).
+ maxSpecificity = std::max(maxSpecificity, hostContext.selector->specificity() + subResult.specificity);
+ break;
+ }
+ hostContext.behaviorAtBoundary = DoesNotCrossBoundary;
+ hostContext.scope = 0;
+
+ if (selector.pseudoType() == CSSSelector::PseudoHost)
+ break;
+
+ hostContext.elementStyle = 0;
+ nextElement = NodeRenderingTraversal::parentElement(nextElement);
+ } while (nextElement);
+ }
+ if (matched) {
+ if (specificity)
+ *specificity = maxSpecificity;
+ return true;
}
}
break;
break;
}
return false;
- }
- else if (selector->m_match == CSSSelector::PseudoElement && selector->pseudoType() == CSSSelector::PseudoCue) {
+ } else if (selector.m_match == CSSSelector::PseudoElement && selector.pseudoType() == CSSSelector::PseudoCue) {
SelectorCheckingContext subContext(context);
subContext.isSubSelector = true;
subContext.behaviorAtBoundary = StaysWithinTreeScope;
- PseudoId ignoreDynamicPseudo = NOPSEUDO;
- const CSSSelector* const & selector = context.selector;
- for (subContext.selector = selector->selectorList()->first(); subContext.selector; subContext.selector = CSSSelectorList::next(subContext.selector)) {
- if (match(subContext, ignoreDynamicPseudo, siblingTraversalStrategy) == SelectorMatches)
+ const CSSSelector* contextSelector = context.selector;
+ ASSERT(contextSelector);
+ for (subContext.selector = contextSelector->selectorList()->first(); subContext.selector; subContext.selector = CSSSelectorList::next(*subContext.selector)) {
+ if (match(subContext, siblingTraversalStrategy) == SelectorMatches)
return true;
}
return false;
return true;
}
-bool SelectorChecker::checkScrollbarPseudoClass(const SelectorCheckingContext& context, Document* document, const CSSSelector* selector) const
+bool SelectorChecker::checkScrollbarPseudoClass(const SelectorCheckingContext& context, Document* document, const CSSSelector& selector) const
{
RenderScrollbar* scrollbar = context.scrollbar;
ScrollbarPart part = context.scrollbarPart;
// FIXME: This is a temporary hack for resizers and scrollbar corners. Eventually :window-inactive should become a real
// pseudo class and just apply to everything.
- if (selector->pseudoType() == CSSSelector::PseudoWindowInactive)
+ if (selector.pseudoType() == CSSSelector::PseudoWindowInactive)
return !document->page()->focusController().isActive();
if (!scrollbar)
return false;
- ASSERT(selector->m_match == CSSSelector::PseudoClass);
- switch (selector->pseudoType()) {
+ ASSERT(selector.m_match == CSSSelector::PseudoClass);
+ switch (selector.pseudoType()) {
case CSSSelector::PseudoEnabled:
return scrollbar->enabled();
case CSSSelector::PseudoDisabled:
}
}
-unsigned SelectorChecker::determineLinkMatchType(const CSSSelector* selector)
+unsigned SelectorChecker::determineLinkMatchType(const CSSSelector& selector)
{
unsigned linkMatchType = MatchAll;
// Statically determine if this selector will match a link in visited, unvisited or any state, or never.
// :visited never matches other elements than the innermost link element.
- for (; selector; selector = selector->tagHistory()) {
- switch (selector->pseudoType()) {
+ for (const CSSSelector* current = &selector; current; current = current->tagHistory()) {
+ switch (current->pseudoType()) {
case CSSSelector::PseudoNot:
{
// :not(:visited) is equivalent to :link. Parser enforces that :not can't nest.
- ASSERT(selector->selectorList());
- for (const CSSSelector* subSelector = selector->selectorList()->first(); subSelector; subSelector = subSelector->tagHistory()) {
+ ASSERT(current->selectorList());
+ for (const CSSSelector* subSelector = current->selectorList()->first(); subSelector; subSelector = subSelector->tagHistory()) {
CSSSelector::PseudoType subType = subSelector->pseudoType();
if (subType == CSSSelector::PseudoVisited)
linkMatchType &= ~SelectorChecker::MatchVisited;
// We don't support :link and :visited inside :-webkit-any.
break;
}
- CSSSelector::Relation relation = selector->relation();
+ CSSSelector::Relation relation = current->relation();
if (relation == CSSSelector::SubSelector)
continue;
if (relation != CSSSelector::Descendant && relation != CSSSelector::Child)
}
template
-SelectorChecker::Match SelectorChecker::match(const SelectorCheckingContext&, PseudoId&, const DOMSiblingTraversalStrategy&) const;
+SelectorChecker::Match SelectorChecker::match(const SelectorCheckingContext&, const DOMSiblingTraversalStrategy&, MatchResult*) const;
template
-SelectorChecker::Match SelectorChecker::match(const SelectorCheckingContext&, PseudoId&, const ShadowDOMSiblingTraversalStrategy&) const;
+SelectorChecker::Match SelectorChecker::match(const SelectorCheckingContext&, const ShadowDOMSiblingTraversalStrategy&, MatchResult*) const;
}