| /* |
| * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
| * (C) 2004-2005 Allan Sandfeld Jensen (kde@carewolf.com) |
| * Copyright (C) 2006, 2007 Nicholas Shanks (webkit@nickshanks.com) |
| * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Apple Inc. |
| * All rights reserved. |
| * Copyright (C) 2007 Alexey Proskuryakov <ap@webkit.org> |
| * Copyright (C) 2007, 2008 Eric Seidel <eric@webkit.org> |
| * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. |
| * (http://www.torchmobile.com/) |
| * Copyright (c) 2011, Code Aurora Forum. All rights reserved. |
| * Copyright (C) Research In Motion Limited 2011. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "core/css/SelectorChecker.h" |
| |
| #include "core/HTMLNames.h" |
| #include "core/css/CSSSelectorList.h" |
| #include "core/dom/Document.h" |
| #include "core/dom/Element.h" |
| #include "core/dom/ElementTraversal.h" |
| #include "core/dom/Fullscreen.h" |
| #include "core/dom/NodeComputedStyle.h" |
| #include "core/dom/NthIndexCache.h" |
| #include "core/dom/StyleEngine.h" |
| #include "core/dom/Text.h" |
| #include "core/dom/shadow/FlatTreeTraversal.h" |
| #include "core/dom/shadow/InsertionPoint.h" |
| #include "core/dom/shadow/ShadowRoot.h" |
| #include "core/editing/FrameSelection.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/html/HTMLDocument.h" |
| #include "core/html/HTMLFrameElementBase.h" |
| #include "core/html/HTMLInputElement.h" |
| #include "core/html/HTMLOptionElement.h" |
| #include "core/html/HTMLSelectElement.h" |
| #include "core/html/HTMLSlotElement.h" |
| #include "core/html/HTMLVideoElement.h" |
| #include "core/html/parser/HTMLParserIdioms.h" |
| #include "core/html/track/vtt/VTTElement.h" |
| #include "core/page/FocusController.h" |
| #include "core/page/Page.h" |
| #include "core/probe/CoreProbes.h" |
| #include "core/style/ComputedStyle.h" |
| #include "platform/scroll/ScrollableArea.h" |
| #include "platform/scroll/ScrollbarTheme.h" |
| #include "platform/wtf/AutoReset.h" |
| |
| namespace blink { |
| |
| using namespace HTMLNames; |
| |
| static bool IsFrameFocused(const Element& element) { |
| return element.GetDocument().GetFrame() && |
| element.GetDocument().GetFrame()->Selection().IsFocusedAndActive(); |
| } |
| |
| static bool MatchesSpatialNavigationFocusPseudoClass(const Element& element) { |
| return isHTMLOptionElement(element) && |
| toHTMLOptionElement(element).SpatialNavigationFocused() && |
| IsFrameFocused(element); |
| } |
| |
| static bool MatchesListBoxPseudoClass(const Element& element) { |
| return isHTMLSelectElement(element) && |
| !toHTMLSelectElement(element).UsesMenuList(); |
| } |
| |
| static bool MatchesTagName(const Element& element, |
| const QualifiedName& tag_q_name) { |
| if (tag_q_name == AnyQName()) |
| return true; |
| const AtomicString& local_name = tag_q_name.LocalName(); |
| if (local_name != g_star_atom && local_name != element.localName()) { |
| if (element.IsHTMLElement() || !element.GetDocument().IsHTMLDocument()) |
| return false; |
| // Non-html elements in html documents are normalized to their camel-cased |
| // version during parsing if applicable. Yet, type selectors are lower-cased |
| // for selectors in html documents. Compare the upper case converted names |
| // instead to allow matching SVG elements like foreignObject. |
| if (element.TagQName().LocalNameUpper() != tag_q_name.LocalNameUpper()) |
| return false; |
| } |
| const AtomicString& namespace_uri = tag_q_name.NamespaceURI(); |
| return namespace_uri == g_star_atom || |
| namespace_uri == element.namespaceURI(); |
| } |
| |
| static Element* ParentElement( |
| const SelectorChecker::SelectorCheckingContext& context) { |
| // - If context.scope is a shadow root, we should walk up to its shadow host. |
| // - 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 == context.element->ContainingShadowRoot() || |
| context.scope->GetTreeScope() == context.element->GetTreeScope())) |
| return context.element->ParentOrShadowHostElement(); |
| return context.element->parentElement(); |
| } |
| |
| // If context has scope, return slot that matches the scope, otherwise return |
| // the assigned slot for scope-less matching of ::slotted pseudo element. |
| static const HTMLSlotElement* FindSlotElementInScope( |
| const SelectorChecker::SelectorCheckingContext& context) { |
| if (!context.scope) |
| return context.element->AssignedSlot(); |
| |
| for (const HTMLSlotElement* slot = context.element->AssignedSlot(); slot; |
| slot = slot->AssignedSlot()) { |
| if (slot->GetTreeScope() == context.scope->GetTreeScope()) |
| return slot; |
| } |
| return nullptr; |
| } |
| |
| static bool ScopeContainsLastMatchedElement( |
| const SelectorChecker::SelectorCheckingContext& context) { |
| // If this context isn't scoped, skip checking. |
| if (!context.scope) |
| return true; |
| |
| if (context.scope->GetTreeScope() == context.element->GetTreeScope()) |
| return true; |
| |
| // Because Blink treats a shadow host's TreeScope as a separate one from its |
| // descendent shadow roots, if the last matched element is a shadow host, the |
| // condition above isn't met, even though it should be. |
| return context.element == context.scope->OwnerShadowHost() && |
| (!context.previous_element || |
| context.previous_element->IsInDescendantTreeOf(context.element)); |
| } |
| |
| static inline bool NextSelectorExceedsScope( |
| const SelectorChecker::SelectorCheckingContext& context) { |
| if (context.scope && context.scope->IsInShadowTree()) |
| return context.element == context.scope->OwnerShadowHost(); |
| |
| return false; |
| } |
| |
| static bool ShouldMatchHoverOrActive( |
| const SelectorChecker::SelectorCheckingContext& context) { |
| // If we're in quirks mode, then :hover and :active should never match anchors |
| // with no href and *:hover and *:active should not match anything. This is |
| // specified in https://quirks.spec.whatwg.org/#the-:active-and-:hover-quirk |
| if (!context.element->GetDocument().InQuirksMode()) |
| return true; |
| if (context.is_sub_selector) |
| return true; |
| if (context.element->IsLink()) |
| return true; |
| const CSSSelector* selector = context.selector; |
| while (selector->Relation() == CSSSelector::kSubSelector && |
| selector->TagHistory()) { |
| selector = selector->TagHistory(); |
| if (selector->Match() != CSSSelector::kPseudoClass) |
| return true; |
| if (selector->GetPseudoType() != CSSSelector::kPseudoHover && |
| selector->GetPseudoType() != CSSSelector::kPseudoActive) |
| return true; |
| } |
| return false; |
| } |
| |
| static bool IsFirstChild(Element& element) { |
| return !ElementTraversal::PreviousSibling(element); |
| } |
| |
| static bool IsLastChild(Element& element) { |
| return !ElementTraversal::NextSibling(element); |
| } |
| |
| static bool IsFirstOfType(Element& element, const QualifiedName& type) { |
| return !ElementTraversal::PreviousSibling(element, HasTagName(type)); |
| } |
| |
| static bool IsLastOfType(Element& element, const QualifiedName& type) { |
| return !ElementTraversal::NextSibling(element, HasTagName(type)); |
| } |
| |
| // Recursive check of selectors and combinators |
| // It can return 4 different values: |
| // * SelectorMatches - the selector matches the element e |
| // * SelectorFailsLocally - the selector fails for 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 |
| SelectorChecker::MatchStatus SelectorChecker::MatchSelector( |
| const SelectorCheckingContext& context, |
| MatchResult& result) const { |
| MatchResult sub_result; |
| if (!CheckOne(context, sub_result)) |
| return kSelectorFailsLocally; |
| |
| if (sub_result.dynamic_pseudo != kPseudoIdNone) |
| result.dynamic_pseudo = sub_result.dynamic_pseudo; |
| |
| if (context.selector->IsLastInTagHistory()) { |
| if (ScopeContainsLastMatchedElement(context)) { |
| result.specificity += sub_result.specificity; |
| return kSelectorMatches; |
| } |
| return kSelectorFailsLocally; |
| } |
| |
| MatchStatus match; |
| if (context.selector->Relation() != CSSSelector::kSubSelector) { |
| if (NextSelectorExceedsScope(context)) |
| return kSelectorFailsCompletely; |
| |
| if (context.pseudo_id != kPseudoIdNone && |
| context.pseudo_id != result.dynamic_pseudo) |
| return kSelectorFailsCompletely; |
| |
| AutoReset<PseudoId> dynamic_pseudo_scope(&result.dynamic_pseudo, |
| kPseudoIdNone); |
| match = MatchForRelation(context, result); |
| } else { |
| match = MatchForSubSelector(context, result); |
| } |
| if (match == kSelectorMatches) |
| result.specificity += sub_result.specificity; |
| return match; |
| } |
| |
| static inline SelectorChecker::SelectorCheckingContext |
| PrepareNextContextForRelation( |
| const SelectorChecker::SelectorCheckingContext& context) { |
| SelectorChecker::SelectorCheckingContext next_context(context); |
| DCHECK(context.selector->TagHistory()); |
| next_context.selector = context.selector->TagHistory(); |
| return next_context; |
| } |
| |
| SelectorChecker::MatchStatus SelectorChecker::MatchForSubSelector( |
| const SelectorCheckingContext& context, |
| MatchResult& result) const { |
| SelectorCheckingContext next_context = PrepareNextContextForRelation(context); |
| |
| PseudoId dynamic_pseudo = result.dynamic_pseudo; |
| next_context.has_scrollbar_pseudo = |
| dynamic_pseudo != kPseudoIdNone && |
| (scrollbar_ || dynamic_pseudo == kPseudoIdScrollbarCorner || |
| dynamic_pseudo == kPseudoIdResizer); |
| next_context.has_selection_pseudo = dynamic_pseudo == kPseudoIdSelection; |
| next_context.is_sub_selector = true; |
| return MatchSelector(next_context, result); |
| } |
| |
| static inline bool IsV0ShadowRoot(const Node* node) { |
| return node && node->IsShadowRoot() && |
| ToShadowRoot(node)->GetType() == ShadowRootType::V0; |
| } |
| |
| SelectorChecker::MatchStatus SelectorChecker::MatchForPseudoShadow( |
| const SelectorCheckingContext& context, |
| const ContainerNode* node, |
| MatchResult& result) const { |
| if (!IsV0ShadowRoot(node)) |
| return kSelectorFailsCompletely; |
| if (!context.previous_element) |
| return kSelectorFailsCompletely; |
| return MatchSelector(context, result); |
| } |
| |
| static inline Element* ParentOrV0ShadowHostElement(const Element& element) { |
| if (element.parentNode() && element.parentNode()->IsShadowRoot()) { |
| if (ToShadowRoot(element.parentNode())->GetType() != ShadowRootType::V0) |
| return nullptr; |
| } |
| return element.ParentOrShadowHostElement(); |
| } |
| |
| static inline Element* ParentOrOpenShadowHostElement(const Element& element) { |
| if (element.parentNode() && element.parentNode()->IsShadowRoot()) { |
| if (ToShadowRoot(element.parentNode())->GetType() != ShadowRootType::kOpen) |
| return nullptr; |
| } |
| return element.ParentOrShadowHostElement(); |
| } |
| |
| SelectorChecker::MatchStatus SelectorChecker::MatchForRelation( |
| const SelectorCheckingContext& context, |
| MatchResult& result) const { |
| SelectorCheckingContext next_context = PrepareNextContextForRelation(context); |
| |
| CSSSelector::RelationType relation = context.selector->Relation(); |
| |
| // Disable :visited matching when we see the first link or try to match |
| // anything else than an ancestors. |
| if (!context.is_sub_selector && |
| (context.element->IsLink() || (relation != CSSSelector::kDescendant && |
| relation != CSSSelector::kChild))) |
| next_context.visited_match_type = kVisitedMatchDisabled; |
| |
| next_context.in_rightmost_compound = false; |
| next_context.is_sub_selector = false; |
| next_context.previous_element = context.element; |
| next_context.pseudo_id = kPseudoIdNone; |
| |
| switch (relation) { |
| case CSSSelector::kDescendant: |
| if (context.selector->RelationIsAffectedByPseudoContent()) { |
| for (Element* element = context.element; element; |
| element = element->parentElement()) { |
| if (MatchForPseudoContent(next_context, *element, result) == |
| kSelectorMatches) |
| return kSelectorMatches; |
| } |
| return kSelectorFailsCompletely; |
| } |
| |
| if (next_context.selector->GetPseudoType() == CSSSelector::kPseudoShadow) |
| return MatchForPseudoShadow( |
| next_context, context.element->ContainingShadowRoot(), result); |
| |
| for (next_context.element = ParentElement(context); next_context.element; |
| next_context.element = ParentElement(next_context)) { |
| MatchStatus match = MatchSelector(next_context, result); |
| if (match == kSelectorMatches || match == kSelectorFailsCompletely) |
| return match; |
| if (NextSelectorExceedsScope(next_context)) |
| return kSelectorFailsCompletely; |
| } |
| return kSelectorFailsCompletely; |
| case CSSSelector::kChild: { |
| if (context.selector->RelationIsAffectedByPseudoContent()) |
| return MatchForPseudoContent(next_context, *context.element, result); |
| |
| if (next_context.selector->GetPseudoType() == CSSSelector::kPseudoShadow) |
| return MatchForPseudoShadow(next_context, context.element->parentNode(), |
| result); |
| |
| next_context.element = ParentElement(context); |
| if (!next_context.element) |
| return kSelectorFailsCompletely; |
| return MatchSelector(next_context, result); |
| } |
| case CSSSelector::kDirectAdjacent: |
| // Shadow roots can't have sibling elements |
| if (next_context.selector->GetPseudoType() == CSSSelector::kPseudoShadow) |
| return kSelectorFailsCompletely; |
| |
| if (mode_ == kResolvingStyle) { |
| if (ContainerNode* parent = |
| context.element->ParentElementOrShadowRoot()) |
| parent->SetChildrenAffectedByDirectAdjacentRules(); |
| } |
| next_context.element = |
| ElementTraversal::PreviousSibling(*context.element); |
| if (!next_context.element) |
| return kSelectorFailsAllSiblings; |
| return MatchSelector(next_context, result); |
| |
| case CSSSelector::kIndirectAdjacent: |
| // Shadow roots can't have sibling elements |
| if (next_context.selector->GetPseudoType() == CSSSelector::kPseudoShadow) |
| return kSelectorFailsCompletely; |
| |
| if (mode_ == kResolvingStyle) { |
| if (ContainerNode* parent = |
| context.element->ParentElementOrShadowRoot()) |
| parent->SetChildrenAffectedByIndirectAdjacentRules(); |
| } |
| next_context.element = |
| ElementTraversal::PreviousSibling(*context.element); |
| for (; next_context.element; |
| next_context.element = |
| ElementTraversal::PreviousSibling(*next_context.element)) { |
| MatchStatus match = MatchSelector(next_context, result); |
| if (match == kSelectorMatches || match == kSelectorFailsAllSiblings || |
| match == kSelectorFailsCompletely) |
| return match; |
| } |
| return kSelectorFailsAllSiblings; |
| |
| case CSSSelector::kShadowPseudo: { |
| if (!is_ua_rule_ && mode_ != kQueryingRules && |
| context.selector->GetPseudoType() == CSSSelector::kPseudoShadow) |
| Deprecation::CountDeprecation(context.element->GetDocument(), |
| UseCounter::kCSSSelectorPseudoShadow); |
| // 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->OwnerShadowHost() && |
| context.scope->OwnerShadowHost()->GetTreeScope() == |
| context.element->GetTreeScope()) |
| return kSelectorFailsCompletely; |
| |
| Element* shadow_host = context.element->OwnerShadowHost(); |
| if (!shadow_host) |
| return kSelectorFailsCompletely; |
| next_context.element = shadow_host; |
| return MatchSelector(next_context, result); |
| } |
| |
| case CSSSelector::kShadowDeep: { |
| if (!is_ua_rule_ && mode_ != kQueryingRules) |
| Deprecation::CountDeprecation(context.element->GetDocument(), |
| UseCounter::kCSSDeepCombinator); |
| if (ShadowRoot* root = context.element->ContainingShadowRoot()) { |
| if (root->GetType() == ShadowRootType::kUserAgent) |
| return kSelectorFailsCompletely; |
| } |
| |
| if (context.selector->RelationIsAffectedByPseudoContent()) { |
| // TODO(kochi): closed mode tree should be handled as well for |
| // ::content. |
| for (Element* element = context.element; element; |
| element = element->ParentOrShadowHostElement()) { |
| if (MatchForPseudoContent(next_context, *element, result) == |
| kSelectorMatches) { |
| if (context.element->IsInShadowTree()) |
| UseCounter::Count(context.element->GetDocument(), |
| UseCounter::kCSSDeepCombinatorAndShadow); |
| return kSelectorMatches; |
| } |
| } |
| return kSelectorFailsCompletely; |
| } |
| |
| for (next_context.element = ParentOrV0ShadowHostElement(*context.element); |
| next_context.element; |
| next_context.element = |
| ParentOrV0ShadowHostElement(*next_context.element)) { |
| MatchStatus match = MatchSelector(next_context, result); |
| if (match == kSelectorMatches && context.element->IsInShadowTree()) |
| UseCounter::Count(context.element->GetDocument(), |
| UseCounter::kCSSDeepCombinatorAndShadow); |
| if (match == kSelectorMatches || match == kSelectorFailsCompletely) |
| return match; |
| if (NextSelectorExceedsScope(next_context)) |
| return kSelectorFailsCompletely; |
| } |
| return kSelectorFailsCompletely; |
| } |
| |
| case CSSSelector::kShadowPiercingDescendant: { |
| DCHECK_EQ(mode_, kQueryingRules); |
| UseCounter::Count(context.element->GetDocument(), |
| UseCounter::kCSSShadowPiercingDescendantCombinator); |
| // TODO(kochi): parentOrOpenShadowHostElement() is necessary because |
| // SelectorQuery can pass V0 shadow roots. All closed shadow roots are |
| // already filtered out, thus once V0 is removed this logic can use |
| // parentOrShadowHostElement() instead. |
| for (next_context.element = |
| ParentOrOpenShadowHostElement(*context.element); |
| next_context.element; |
| next_context.element = |
| ParentOrOpenShadowHostElement(*next_context.element)) { |
| MatchStatus match = MatchSelector(next_context, result); |
| if (match == kSelectorMatches || match == kSelectorFailsCompletely) |
| return match; |
| if (NextSelectorExceedsScope(next_context)) |
| break; |
| } |
| return kSelectorFailsCompletely; |
| } |
| |
| case CSSSelector::kShadowSlot: { |
| const HTMLSlotElement* slot = FindSlotElementInScope(context); |
| if (!slot) |
| return kSelectorFailsCompletely; |
| |
| next_context.element = const_cast<HTMLSlotElement*>(slot); |
| return MatchSelector(next_context, result); |
| } |
| |
| case CSSSelector::kSubSelector: |
| break; |
| } |
| NOTREACHED(); |
| return kSelectorFailsCompletely; |
| } |
| |
| SelectorChecker::MatchStatus SelectorChecker::MatchForPseudoContent( |
| const SelectorCheckingContext& context, |
| const Element& element, |
| MatchResult& result) const { |
| HeapVector<Member<InsertionPoint>, 8> insertion_points; |
| CollectDestinationInsertionPoints(element, insertion_points); |
| SelectorCheckingContext next_context(context); |
| for (const auto& insertion_point : insertion_points) { |
| next_context.element = insertion_point; |
| // TODO(esprehn): Why does kSharingRules have a special case? |
| if (mode_ == kSharingRules) |
| next_context.scope = insertion_point->ContainingShadowRoot(); |
| if (Match(next_context, result)) |
| return kSelectorMatches; |
| } |
| return kSelectorFailsLocally; |
| } |
| |
| static bool AttributeValueMatches(const Attribute& attribute_item, |
| CSSSelector::MatchType match, |
| const AtomicString& selector_value, |
| TextCaseSensitivity case_sensitivity) { |
| // TODO(esprehn): How do we get here with a null value? |
| const AtomicString& value = attribute_item.Value(); |
| if (value.IsNull()) |
| return false; |
| |
| switch (match) { |
| case CSSSelector::kAttributeExact: |
| if (case_sensitivity == kTextCaseSensitive) |
| return selector_value == value; |
| return EqualIgnoringASCIICase(selector_value, value); |
| case CSSSelector::kAttributeSet: |
| return true; |
| case CSSSelector::kAttributeList: { |
| // Ignore empty selectors or selectors containing HTML spaces |
| if (selector_value.IsEmpty() || |
| selector_value.Find(&IsHTMLSpace<UChar>) != kNotFound) |
| return false; |
| |
| unsigned start_search_at = 0; |
| while (true) { |
| size_t found_pos = |
| value.Find(selector_value, start_search_at, case_sensitivity); |
| if (found_pos == kNotFound) |
| return false; |
| if (!found_pos || IsHTMLSpace<UChar>(value[found_pos - 1])) { |
| unsigned end_str = found_pos + selector_value.length(); |
| if (end_str == value.length() || IsHTMLSpace<UChar>(value[end_str])) |
| break; // We found a match. |
| } |
| |
| // No match. Keep looking. |
| start_search_at = found_pos + 1; |
| } |
| return true; |
| } |
| case CSSSelector::kAttributeContain: |
| if (selector_value.IsEmpty()) |
| return false; |
| return value.Contains(selector_value, case_sensitivity); |
| case CSSSelector::kAttributeBegin: |
| if (selector_value.IsEmpty()) |
| return false; |
| return value.StartsWith(selector_value, case_sensitivity); |
| case CSSSelector::kAttributeEnd: |
| if (selector_value.IsEmpty()) |
| return false; |
| return value.EndsWith(selector_value, case_sensitivity); |
| case CSSSelector::kAttributeHyphen: |
| if (value.length() < selector_value.length()) |
| return false; |
| if (!value.StartsWith(selector_value, case_sensitivity)) |
| return false; |
| // It they start the same, check for exact match or following '-': |
| if (value.length() != selector_value.length() && |
| value[selector_value.length()] != '-') |
| return false; |
| return true; |
| default: |
| NOTREACHED(); |
| return false; |
| } |
| } |
| |
| static bool AnyAttributeMatches(Element& element, |
| CSSSelector::MatchType match, |
| const CSSSelector& selector) { |
| const QualifiedName& selector_attr = selector.Attribute(); |
| // Should not be possible from the CSS grammar. |
| DCHECK_NE(selector_attr.LocalName(), g_star_atom); |
| |
| // Synchronize the attribute in case it is lazy-computed. |
| // Currently all lazy properties have a null namespace, so only pass |
| // localName(). |
| element.SynchronizeAttribute(selector_attr.LocalName()); |
| |
| const AtomicString& selector_value = selector.Value(); |
| TextCaseSensitivity case_sensitivity = |
| (selector.AttributeMatch() == CSSSelector::kCaseInsensitive) |
| ? kTextCaseASCIIInsensitive |
| : kTextCaseSensitive; |
| |
| AttributeCollection attributes = element.AttributesWithoutUpdate(); |
| for (const auto& attribute_item : attributes) { |
| if (!attribute_item.Matches(selector_attr)) { |
| if (element.IsHTMLElement() || !element.GetDocument().IsHTMLDocument()) |
| continue; |
| // Non-html attributes in html documents are normalized to their camel- |
| // cased version during parsing if applicable. Yet, attribute selectors |
| // are lower-cased for selectors in html documents. Compare the selector |
| // and the attribute local name insensitively to e.g. allow matching SVG |
| // attributes like viewBox. |
| if (!attribute_item.MatchesCaseInsensitive(selector_attr)) |
| continue; |
| } |
| |
| if (AttributeValueMatches(attribute_item, match, selector_value, |
| case_sensitivity)) |
| return true; |
| |
| if (case_sensitivity == kTextCaseASCIIInsensitive) { |
| if (selector_attr.NamespaceURI() != g_star_atom) |
| return false; |
| continue; |
| } |
| |
| // Legacy dictates that values of some attributes should be compared in |
| // a case-insensitive manner regardless of whether the case insensitive |
| // flag is set or not. |
| bool legacy_case_insensitive = |
| element.GetDocument().IsHTMLDocument() && |
| !HTMLDocument::IsCaseSensitiveAttribute(selector_attr); |
| |
| // If case-insensitive, re-check, and count if result differs. |
| // See http://code.google.com/p/chromium/issues/detail?id=327060 |
| if (legacy_case_insensitive && |
| AttributeValueMatches(attribute_item, match, selector_value, |
| kTextCaseASCIIInsensitive)) { |
| UseCounter::Count(element.GetDocument(), |
| UseCounter::kCaseInsensitiveAttrSelectorMatch); |
| return true; |
| } |
| if (selector_attr.NamespaceURI() != g_star_atom) |
| return false; |
| } |
| |
| return false; |
| } |
| |
| bool SelectorChecker::CheckOne(const SelectorCheckingContext& context, |
| MatchResult& result) const { |
| DCHECK(context.element); |
| Element& element = *context.element; |
| DCHECK(context.selector); |
| const CSSSelector& selector = *context.selector; |
| |
| // Only :host and :host-context() should match the host: |
| // http://drafts.csswg.org/css-scoping/#host-element |
| if (context.scope && context.scope->OwnerShadowHost() == element && |
| (!selector.IsHostPseudoClass() && |
| !context.treat_shadow_host_as_normal_scope && |
| selector.Match() != CSSSelector::kPseudoElement)) |
| return false; |
| |
| switch (selector.Match()) { |
| case CSSSelector::kTag: |
| return MatchesTagName(element, selector.TagQName()); |
| case CSSSelector::kClass: |
| return element.HasClass() && |
| element.ClassNames().Contains(selector.Value()); |
| case CSSSelector::kId: |
| return element.HasID() && |
| element.IdForStyleResolution() == selector.Value(); |
| |
| // Attribute selectors |
| case CSSSelector::kAttributeExact: |
| case CSSSelector::kAttributeSet: |
| case CSSSelector::kAttributeHyphen: |
| case CSSSelector::kAttributeList: |
| case CSSSelector::kAttributeContain: |
| case CSSSelector::kAttributeBegin: |
| case CSSSelector::kAttributeEnd: |
| return AnyAttributeMatches(element, selector.Match(), selector); |
| |
| case CSSSelector::kPseudoClass: |
| return CheckPseudoClass(context, result); |
| case CSSSelector::kPseudoElement: |
| return CheckPseudoElement(context, result); |
| |
| default: |
| NOTREACHED(); |
| return false; |
| } |
| } |
| |
| bool SelectorChecker::CheckPseudoNot(const SelectorCheckingContext& context, |
| MatchResult& result) const { |
| const CSSSelector& selector = *context.selector; |
| |
| SelectorCheckingContext sub_context(context); |
| sub_context.is_sub_selector = true; |
| DCHECK(selector.SelectorList()); |
| for (sub_context.selector = selector.SelectorList()->First(); |
| sub_context.selector; |
| sub_context.selector = sub_context.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 |
| DCHECK_NE(sub_context.selector->GetPseudoType(), CSSSelector::kPseudoNot); |
| // We select between :visited and :link when applying. We don't know which |
| // one applied (or not) yet. |
| if (sub_context.selector->GetPseudoType() == CSSSelector::kPseudoVisited || |
| (sub_context.selector->GetPseudoType() == CSSSelector::kPseudoLink && |
| sub_context.visited_match_type == kVisitedMatchEnabled)) |
| return true; |
| if (mode_ == kSharingRules) { |
| // context.scope is not available if mode_ == kSharingRules. |
| // We cannot determine whether :host or :scope matches a given element or |
| // not. |
| if (sub_context.selector->IsHostPseudoClass() || |
| sub_context.selector->GetPseudoType() == CSSSelector::kPseudoScope) |
| return true; |
| // :hover, :active, :focus, :-webkit-drag relies on setting flags on |
| // ComputedStyle even if the whole selector may not match. That |
| // means we cannot share style between elements which may fail |
| // matching the same selector for different reasons. An example is |
| // [attr]:hover which both fail for :hover, but an element without |
| // attr won't reach the :hover selector, hence not setting the bit. |
| if (sub_context.selector->IsUserActionPseudoClass()) |
| return true; |
| } |
| if (!CheckOne(sub_context, result)) |
| return true; |
| } |
| return false; |
| } |
| |
| bool SelectorChecker::CheckPseudoClass(const SelectorCheckingContext& context, |
| MatchResult& result) const { |
| Element& element = *context.element; |
| const CSSSelector& selector = *context.selector; |
| bool force_pseudo_state = false; |
| |
| if (context.has_scrollbar_pseudo) { |
| // CSS scrollbars match a specific subset of pseudo classes, and they have |
| // specialized rules for each |
| // (since there are no elements involved). |
| return CheckScrollbarPseudoClass(context, result); |
| } |
| |
| switch (selector.GetPseudoType()) { |
| case CSSSelector::kPseudoNot: |
| return CheckPseudoNot(context, result); |
| case CSSSelector::kPseudoEmpty: { |
| bool result = true; |
| for (Node* n = element.FirstChild(); n; n = n->nextSibling()) { |
| if (n->IsElementNode()) { |
| result = false; |
| break; |
| } |
| if (n->IsTextNode()) { |
| Text* text_node = ToText(n); |
| if (!text_node->data().IsEmpty()) { |
| result = false; |
| break; |
| } |
| } |
| } |
| if (mode_ == kResolvingStyle) { |
| element.SetStyleAffectedByEmpty(); |
| if (context.in_rightmost_compound) |
| element_style_->SetEmptyState(result); |
| else if (element.GetComputedStyle() && |
| (element.GetDocument().GetStyleEngine().UsesSiblingRules() || |
| element.GetComputedStyle()->Unique())) |
| element.MutableComputedStyle()->SetEmptyState(result); |
| } |
| return result; |
| } |
| case CSSSelector::kPseudoFirstChild: |
| if (mode_ == kResolvingStyle) { |
| if (ContainerNode* parent = element.ParentElementOrDocumentFragment()) |
| parent->SetChildrenAffectedByFirstChildRules(); |
| element.SetAffectedByFirstChildRules(); |
| } |
| return IsFirstChild(element); |
| case CSSSelector::kPseudoFirstOfType: |
| if (mode_ == kResolvingStyle) { |
| if (ContainerNode* parent = element.ParentElementOrDocumentFragment()) |
| parent->SetChildrenAffectedByForwardPositionalRules(); |
| } |
| return IsFirstOfType(element, element.TagQName()); |
| case CSSSelector::kPseudoLastChild: { |
| ContainerNode* parent = element.ParentElementOrDocumentFragment(); |
| if (mode_ == kResolvingStyle) { |
| if (parent) |
| parent->SetChildrenAffectedByLastChildRules(); |
| element.SetAffectedByLastChildRules(); |
| } |
| if (mode_ != kQueryingRules && parent && |
| !parent->IsFinishedParsingChildren()) |
| return false; |
| return IsLastChild(element); |
| } |
| case CSSSelector::kPseudoLastOfType: { |
| ContainerNode* parent = element.ParentElementOrDocumentFragment(); |
| if (mode_ == kResolvingStyle) { |
| if (parent) |
| parent->SetChildrenAffectedByBackwardPositionalRules(); |
| } |
| if (mode_ != kQueryingRules && parent && |
| !parent->IsFinishedParsingChildren()) |
| return false; |
| return IsLastOfType(element, element.TagQName()); |
| } |
| case CSSSelector::kPseudoOnlyChild: { |
| ContainerNode* parent = element.ParentElementOrDocumentFragment(); |
| if (mode_ == kResolvingStyle) { |
| if (parent) { |
| parent->SetChildrenAffectedByFirstChildRules(); |
| parent->SetChildrenAffectedByLastChildRules(); |
| } |
| element.SetAffectedByFirstChildRules(); |
| element.SetAffectedByLastChildRules(); |
| } |
| if (mode_ != kQueryingRules && parent && |
| !parent->IsFinishedParsingChildren()) |
| return false; |
| return IsFirstChild(element) && IsLastChild(element); |
| } |
| case CSSSelector::kPseudoOnlyOfType: { |
| // FIXME: This selector is very slow. |
| ContainerNode* parent = element.ParentElementOrDocumentFragment(); |
| if (mode_ == kResolvingStyle && parent) { |
| parent->SetChildrenAffectedByForwardPositionalRules(); |
| parent->SetChildrenAffectedByBackwardPositionalRules(); |
| } |
| if (mode_ != kQueryingRules && parent && |
| !parent->IsFinishedParsingChildren()) |
| return false; |
| return IsFirstOfType(element, element.TagQName()) && |
| IsLastOfType(element, element.TagQName()); |
| } |
| case CSSSelector::kPseudoPlaceholderShown: |
| if (IsTextControlElement(element)) |
| return ToTextControlElement(element).IsPlaceholderVisible(); |
| break; |
| case CSSSelector::kPseudoNthChild: |
| if (mode_ == kResolvingStyle) { |
| if (ContainerNode* parent = element.ParentElementOrDocumentFragment()) |
| parent->SetChildrenAffectedByForwardPositionalRules(); |
| } |
| return selector.MatchNth(NthIndexCache::NthChildIndex(element)); |
| case CSSSelector::kPseudoNthOfType: |
| if (mode_ == kResolvingStyle) { |
| if (ContainerNode* parent = element.ParentElementOrDocumentFragment()) |
| parent->SetChildrenAffectedByForwardPositionalRules(); |
| } |
| return selector.MatchNth(NthIndexCache::NthOfTypeIndex(element)); |
| case CSSSelector::kPseudoNthLastChild: { |
| ContainerNode* parent = element.ParentElementOrDocumentFragment(); |
| if (mode_ == kResolvingStyle && parent) |
| parent->SetChildrenAffectedByBackwardPositionalRules(); |
| if (mode_ != kQueryingRules && parent && |
| !parent->IsFinishedParsingChildren()) |
| return false; |
| return selector.MatchNth(NthIndexCache::NthLastChildIndex(element)); |
| } |
| case CSSSelector::kPseudoNthLastOfType: { |
| ContainerNode* parent = element.ParentElementOrDocumentFragment(); |
| if (mode_ == kResolvingStyle && parent) |
| parent->SetChildrenAffectedByBackwardPositionalRules(); |
| if (mode_ != kQueryingRules && parent && |
| !parent->IsFinishedParsingChildren()) |
| return false; |
| return selector.MatchNth(NthIndexCache::NthLastOfTypeIndex(element)); |
| } |
| case CSSSelector::kPseudoTarget: |
| return element == element.GetDocument().CssTarget(); |
| case CSSSelector::kPseudoAny: { |
| SelectorCheckingContext sub_context(context); |
| sub_context.is_sub_selector = true; |
| DCHECK(selector.SelectorList()); |
| for (sub_context.selector = selector.SelectorList()->First(); |
| sub_context.selector; sub_context.selector = CSSSelectorList::Next( |
| *sub_context.selector)) { |
| if (Match(sub_context)) |
| return true; |
| } |
| } break; |
| case CSSSelector::kPseudoAutofill: |
| return element.IsFormControlElement() && |
| ToHTMLFormControlElement(element).IsAutofilled(); |
| case CSSSelector::kPseudoAnyLink: |
| case CSSSelector::kPseudoLink: |
| return element.IsLink(); |
| case CSSSelector::kPseudoVisited: |
| return element.IsLink() && |
| context.visited_match_type == kVisitedMatchEnabled; |
| case CSSSelector::kPseudoDrag: |
| if (mode_ == kSharingRules) |
| return true; |
| if (mode_ == kResolvingStyle) { |
| if (context.in_rightmost_compound) { |
| element_style_->SetAffectedByDrag(); |
| } else { |
| element_style_->SetUnique(); |
| element.SetChildrenOrSiblingsAffectedByDrag(); |
| } |
| } |
| return element.IsDragged(); |
| case CSSSelector::kPseudoFocus: |
| if (mode_ == kSharingRules) |
| return true; |
| if (mode_ == kResolvingStyle) { |
| if (context.in_rightmost_compound) { |
| element_style_->SetAffectedByFocus(); |
| } else { |
| element_style_->SetUnique(); |
| element.SetChildrenOrSiblingsAffectedByFocus(); |
| } |
| } |
| return MatchesFocusPseudoClass(element); |
| case CSSSelector::kPseudoFocusWithin: |
| if (mode_ == kSharingRules) |
| return true; |
| if (mode_ == kResolvingStyle) { |
| if (context.in_rightmost_compound) { |
| element_style_->SetAffectedByFocusWithin(); |
| } else { |
| element_style_->SetUnique(); |
| element.SetChildrenOrSiblingsAffectedByFocusWithin(); |
| } |
| } |
| probe::forcePseudoState(&element, CSSSelector::kPseudoFocusWithin, |
| &force_pseudo_state); |
| if (force_pseudo_state) |
| return true; |
| return element.HasFocusWithin(); |
| case CSSSelector::kPseudoHover: |
| if (mode_ == kSharingRules) |
| return true; |
| if (mode_ == kResolvingStyle) { |
| if (context.in_rightmost_compound) { |
| element_style_->SetAffectedByHover(); |
| } else { |
| element_style_->SetUnique(); |
| element.SetChildrenOrSiblingsAffectedByHover(); |
| } |
| } |
| if (!ShouldMatchHoverOrActive(context)) |
| return false; |
| probe::forcePseudoState(&element, CSSSelector::kPseudoHover, |
| &force_pseudo_state); |
| if (force_pseudo_state) |
| return true; |
| return element.IsHovered(); |
| case CSSSelector::kPseudoActive: |
| if (mode_ == kSharingRules) |
| return true; |
| if (mode_ == kResolvingStyle) { |
| if (context.in_rightmost_compound) { |
| element_style_->SetAffectedByActive(); |
| } else { |
| element_style_->SetUnique(); |
| element.SetChildrenOrSiblingsAffectedByActive(); |
| } |
| } |
| if (!ShouldMatchHoverOrActive(context)) |
| return false; |
| probe::forcePseudoState(&element, CSSSelector::kPseudoActive, |
| &force_pseudo_state); |
| if (force_pseudo_state) |
| return true; |
| return element.IsActive(); |
| case CSSSelector::kPseudoEnabled: |
| return element.MatchesEnabledPseudoClass(); |
| case CSSSelector::kPseudoFullPageMedia: |
| return element.GetDocument().IsMediaDocument(); |
| case CSSSelector::kPseudoDefault: |
| return element.MatchesDefaultPseudoClass(); |
| case CSSSelector::kPseudoDisabled: |
| return element.IsDisabledFormControl(); |
| case CSSSelector::kPseudoReadOnly: |
| return element.MatchesReadOnlyPseudoClass(); |
| case CSSSelector::kPseudoReadWrite: |
| return element.MatchesReadWritePseudoClass(); |
| case CSSSelector::kPseudoOptional: |
| return element.IsOptionalFormControl(); |
| case CSSSelector::kPseudoRequired: |
| return element.IsRequiredFormControl(); |
| case CSSSelector::kPseudoValid: |
| if (mode_ == kResolvingStyle) |
| element.GetDocument().SetContainsValidityStyleRules(); |
| return element.MatchesValidityPseudoClasses() && element.IsValidElement(); |
| case CSSSelector::kPseudoInvalid: |
| if (mode_ == kResolvingStyle) |
| element.GetDocument().SetContainsValidityStyleRules(); |
| return element.MatchesValidityPseudoClasses() && |
| !element.IsValidElement(); |
| case CSSSelector::kPseudoChecked: { |
| if (isHTMLInputElement(element)) { |
| HTMLInputElement& input_element = toHTMLInputElement(element); |
| // Even though WinIE allows checked and indeterminate to |
| // co-exist, the CSS selector spec says that you can't be |
| // both checked and indeterminate. We will behave like WinIE |
| // behind the scenes and just obey the CSS spec here in the |
| // test for matching the pseudo. |
| if (input_element.ShouldAppearChecked() && |
| !input_element.ShouldAppearIndeterminate()) |
| return true; |
| } else if (isHTMLOptionElement(element) && |
| toHTMLOptionElement(element).Selected()) { |
| return true; |
| } |
| break; |
| } |
| case CSSSelector::kPseudoIndeterminate: |
| return element.ShouldAppearIndeterminate(); |
| case CSSSelector::kPseudoRoot: |
| return element == element.GetDocument().documentElement(); |
| case CSSSelector::kPseudoLang: { |
| AtomicString value; |
| if (element.IsVTTElement()) |
| value = ToVTTElement(element).Language(); |
| else |
| value = element.ComputeInheritedLanguage(); |
| const AtomicString& argument = selector.Argument(); |
| if (value.IsEmpty() || |
| !value.StartsWith(argument, kTextCaseASCIIInsensitive)) |
| break; |
| if (value.length() != argument.length() && |
| value[argument.length()] != '-') |
| break; |
| return true; |
| } |
| case CSSSelector::kPseudoFullScreen: |
| // While a Document is in the fullscreen state, and the document's current |
| // fullscreen 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 (IsHTMLFrameElementBase(element) && |
| element.ContainsFullScreenElement()) |
| return true; |
| return Fullscreen::IsCurrentFullScreenElement(element); |
| case CSSSelector::kPseudoFullScreenAncestor: |
| return element.ContainsFullScreenElement(); |
| case CSSSelector::kPseudoVideoPersistent: |
| if (!is_ua_rule_ || !isHTMLVideoElement(element)) |
| return false; |
| return toHTMLVideoElement(element).IsPersistent(); |
| case CSSSelector::kPseudoVideoPersistentAncestor: |
| return is_ua_rule_ && element.ContainsPersistentVideo(); |
| case CSSSelector::kPseudoInRange: |
| if (mode_ == kResolvingStyle) |
| element.GetDocument().SetContainsValidityStyleRules(); |
| return element.IsInRange(); |
| case CSSSelector::kPseudoOutOfRange: |
| if (mode_ == kResolvingStyle) |
| element.GetDocument().SetContainsValidityStyleRules(); |
| return element.IsOutOfRange(); |
| case CSSSelector::kPseudoFutureCue: |
| return element.IsVTTElement() && !ToVTTElement(element).IsPastNode(); |
| case CSSSelector::kPseudoPastCue: |
| return element.IsVTTElement() && ToVTTElement(element).IsPastNode(); |
| case CSSSelector::kPseudoScope: |
| if (mode_ == kSharingRules) |
| return true; |
| if (context.scope == &element.GetDocument()) |
| return element == element.GetDocument().documentElement(); |
| return context.scope == &element; |
| case CSSSelector::kPseudoUnresolved: |
| return element.IsUnresolvedV0CustomElement(); |
| case CSSSelector::kPseudoDefined: |
| DCHECK(RuntimeEnabledFeatures::customElementsV1Enabled()); |
| return element.IsDefined(); |
| case CSSSelector::kPseudoHost: |
| case CSSSelector::kPseudoHostContext: |
| return CheckPseudoHost(context, result); |
| case CSSSelector::kPseudoSpatialNavigationFocus: |
| return is_ua_rule_ && MatchesSpatialNavigationFocusPseudoClass(element); |
| case CSSSelector::kPseudoListBox: |
| return is_ua_rule_ && MatchesListBoxPseudoClass(element); |
| case CSSSelector::kPseudoHostHasAppearance: |
| if (!is_ua_rule_) |
| return false; |
| if (ShadowRoot* root = element.ContainingShadowRoot()) { |
| if (root->GetType() != ShadowRootType::kUserAgent) |
| return false; |
| const ComputedStyle* style = root->host().GetComputedStyle(); |
| return style && style->HasAppearance(); |
| } |
| return false; |
| case CSSSelector::kPseudoWindowInactive: |
| if (!context.has_selection_pseudo) |
| return false; |
| return !element.GetDocument().GetPage()->GetFocusController().IsActive(); |
| case CSSSelector::kPseudoHorizontal: |
| case CSSSelector::kPseudoVertical: |
| case CSSSelector::kPseudoDecrement: |
| case CSSSelector::kPseudoIncrement: |
| case CSSSelector::kPseudoStart: |
| case CSSSelector::kPseudoEnd: |
| case CSSSelector::kPseudoDoubleButton: |
| case CSSSelector::kPseudoSingleButton: |
| case CSSSelector::kPseudoNoButton: |
| case CSSSelector::kPseudoCornerPresent: |
| return false; |
| case CSSSelector::kPseudoUnknown: |
| default: |
| NOTREACHED(); |
| break; |
| } |
| return false; |
| } |
| |
| bool SelectorChecker::CheckPseudoElement(const SelectorCheckingContext& context, |
| MatchResult& result) const { |
| const CSSSelector& selector = *context.selector; |
| Element& element = *context.element; |
| |
| switch (selector.GetPseudoType()) { |
| case CSSSelector::kPseudoCue: { |
| SelectorCheckingContext sub_context(context); |
| sub_context.is_sub_selector = true; |
| sub_context.scope = nullptr; |
| sub_context.treat_shadow_host_as_normal_scope = false; |
| |
| for (sub_context.selector = selector.SelectorList()->First(); |
| sub_context.selector; sub_context.selector = CSSSelectorList::Next( |
| *sub_context.selector)) { |
| if (Match(sub_context)) |
| return true; |
| } |
| return false; |
| } |
| case CSSSelector::kPseudoPlaceholder: |
| if (ShadowRoot* root = element.ContainingShadowRoot()) { |
| return root->GetType() == ShadowRootType::kUserAgent && |
| element.ShadowPseudoId() == "-webkit-input-placeholder"; |
| } |
| return false; |
| case CSSSelector::kPseudoWebKitCustomElement: { |
| if (ShadowRoot* root = element.ContainingShadowRoot()) |
| return root->GetType() == ShadowRootType::kUserAgent && |
| element.ShadowPseudoId() == selector.Value(); |
| return false; |
| } |
| case CSSSelector::kPseudoBlinkInternalElement: |
| if (!is_ua_rule_) |
| return false; |
| if (ShadowRoot* root = element.ContainingShadowRoot()) |
| return root->GetType() == ShadowRootType::kUserAgent && |
| element.ShadowPseudoId() == selector.Value(); |
| return false; |
| case CSSSelector::kPseudoSlotted: { |
| SelectorCheckingContext sub_context(context); |
| sub_context.is_sub_selector = true; |
| sub_context.scope = nullptr; |
| sub_context.treat_shadow_host_as_normal_scope = false; |
| |
| // ::slotted() only allows one compound selector. |
| DCHECK(selector.SelectorList()->First()); |
| DCHECK(!CSSSelectorList::Next(*selector.SelectorList()->First())); |
| sub_context.selector = selector.SelectorList()->First(); |
| return Match(sub_context); |
| } |
| case CSSSelector::kPseudoContent: |
| return element.IsInShadowTree() && element.IsInsertionPoint(); |
| case CSSSelector::kPseudoShadow: |
| return element.IsInShadowTree() && context.previous_element; |
| default: |
| if (mode_ == kSharingRules) |
| return true; |
| DCHECK_NE(mode_, kQueryingRules); |
| result.dynamic_pseudo = |
| CSSSelector::GetPseudoId(selector.GetPseudoType()); |
| DCHECK_NE(result.dynamic_pseudo, kPseudoIdNone); |
| return true; |
| } |
| } |
| |
| bool SelectorChecker::CheckPseudoHost(const SelectorCheckingContext& context, |
| MatchResult& result) const { |
| const CSSSelector& selector = *context.selector; |
| Element& element = *context.element; |
| |
| if (mode_ == kSharingRules) |
| return true; |
| // :host only matches a shadow host when :host is in a shadow tree of the |
| // shadow host. |
| if (!context.scope) |
| return false; |
| const ContainerNode* shadow_host = context.scope->OwnerShadowHost(); |
| if (!shadow_host || shadow_host != element) |
| return false; |
| DCHECK(element.Shadow()); |
| |
| // For the case with no parameters, i.e. just :host. |
| if (!selector.SelectorList()) |
| return true; |
| |
| SelectorCheckingContext sub_context(context); |
| sub_context.is_sub_selector = true; |
| |
| bool matched = false; |
| unsigned max_specificity = 0; |
| |
| // If one of simple selectors matches an element, returns SelectorMatches. |
| // Just "OR". |
| for (sub_context.selector = selector.SelectorList()->First(); |
| sub_context.selector; |
| sub_context.selector = CSSSelectorList::Next(*sub_context.selector)) { |
| sub_context.treat_shadow_host_as_normal_scope = true; |
| sub_context.scope = context.scope; |
| // Use FlatTreeTraversal to traverse a composed ancestor list of a given |
| // element. |
| Element* next_element = &element; |
| SelectorCheckingContext host_context(sub_context); |
| do { |
| MatchResult sub_result; |
| host_context.element = next_element; |
| if (Match(host_context, sub_result)) { |
| matched = true; |
| // Consider div:host(div:host(div:host(div:host...))). |
| max_specificity = |
| std::max(max_specificity, host_context.selector->Specificity() + |
| sub_result.specificity); |
| break; |
| } |
| host_context.treat_shadow_host_as_normal_scope = false; |
| host_context.scope = nullptr; |
| |
| if (selector.GetPseudoType() == CSSSelector::kPseudoHost) |
| break; |
| |
| host_context.in_rightmost_compound = false; |
| next_element = FlatTreeTraversal::ParentElement(*next_element); |
| } while (next_element); |
| } |
| if (matched) { |
| result.specificity += max_specificity; |
| return true; |
| } |
| |
| // FIXME: this was a fallthrough condition. |
| return false; |
| } |
| |
| bool SelectorChecker::CheckScrollbarPseudoClass( |
| const SelectorCheckingContext& context, |
| MatchResult& result) const { |
| const CSSSelector& selector = *context.selector; |
| |
| if (selector.GetPseudoType() == CSSSelector::kPseudoNot) |
| return CheckPseudoNot(context, result); |
| |
| // 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.GetPseudoType() == CSSSelector::kPseudoWindowInactive) |
| return !context.element->GetDocument() |
| .GetPage() |
| ->GetFocusController() |
| .IsActive(); |
| |
| if (!scrollbar_) |
| return false; |
| |
| switch (selector.GetPseudoType()) { |
| case CSSSelector::kPseudoEnabled: |
| return scrollbar_->Enabled(); |
| case CSSSelector::kPseudoDisabled: |
| return !scrollbar_->Enabled(); |
| case CSSSelector::kPseudoHover: { |
| ScrollbarPart hovered_part = scrollbar_->HoveredPart(); |
| if (scrollbar_part_ == kScrollbarBGPart) |
| return hovered_part != kNoPart; |
| if (scrollbar_part_ == kTrackBGPart) |
| return hovered_part == kBackTrackPart || |
| hovered_part == kForwardTrackPart || hovered_part == kThumbPart; |
| return scrollbar_part_ == hovered_part; |
| } |
| case CSSSelector::kPseudoActive: { |
| ScrollbarPart pressed_part = scrollbar_->PressedPart(); |
| if (scrollbar_part_ == kScrollbarBGPart) |
| return pressed_part != kNoPart; |
| if (scrollbar_part_ == kTrackBGPart) |
| return pressed_part == kBackTrackPart || |
| pressed_part == kForwardTrackPart || pressed_part == kThumbPart; |
| return scrollbar_part_ == pressed_part; |
| } |
| case CSSSelector::kPseudoHorizontal: |
| return scrollbar_->Orientation() == kHorizontalScrollbar; |
| case CSSSelector::kPseudoVertical: |
| return scrollbar_->Orientation() == kVerticalScrollbar; |
| case CSSSelector::kPseudoDecrement: |
| return scrollbar_part_ == kBackButtonStartPart || |
| scrollbar_part_ == kBackButtonEndPart || |
| scrollbar_part_ == kBackTrackPart; |
| case CSSSelector::kPseudoIncrement: |
| return scrollbar_part_ == kForwardButtonStartPart || |
| scrollbar_part_ == kForwardButtonEndPart || |
| scrollbar_part_ == kForwardTrackPart; |
| case CSSSelector::kPseudoStart: |
| return scrollbar_part_ == kBackButtonStartPart || |
| scrollbar_part_ == kForwardButtonStartPart || |
| scrollbar_part_ == kBackTrackPart; |
| case CSSSelector::kPseudoEnd: |
| return scrollbar_part_ == kBackButtonEndPart || |
| scrollbar_part_ == kForwardButtonEndPart || |
| scrollbar_part_ == kForwardTrackPart; |
| case CSSSelector::kPseudoDoubleButton: { |
| WebScrollbarButtonsPlacement buttons_placement = |
| scrollbar_->GetTheme().ButtonsPlacement(); |
| if (scrollbar_part_ == kBackButtonStartPart || |
| scrollbar_part_ == kForwardButtonStartPart || |
| scrollbar_part_ == kBackTrackPart) |
| return buttons_placement == kWebScrollbarButtonsPlacementDoubleStart || |
| buttons_placement == kWebScrollbarButtonsPlacementDoubleBoth; |
| if (scrollbar_part_ == kBackButtonEndPart || |
| scrollbar_part_ == kForwardButtonEndPart || |
| scrollbar_part_ == kForwardTrackPart) |
| return buttons_placement == kWebScrollbarButtonsPlacementDoubleEnd || |
| buttons_placement == kWebScrollbarButtonsPlacementDoubleBoth; |
| return false; |
| } |
| case CSSSelector::kPseudoSingleButton: { |
| WebScrollbarButtonsPlacement buttons_placement = |
| scrollbar_->GetTheme().ButtonsPlacement(); |
| if (scrollbar_part_ == kBackButtonStartPart || |
| scrollbar_part_ == kForwardButtonEndPart || |
| scrollbar_part_ == kBackTrackPart || |
| scrollbar_part_ == kForwardTrackPart) |
| return buttons_placement == kWebScrollbarButtonsPlacementSingle; |
| return false; |
| } |
| case CSSSelector::kPseudoNoButton: { |
| WebScrollbarButtonsPlacement buttons_placement = |
| scrollbar_->GetTheme().ButtonsPlacement(); |
| if (scrollbar_part_ == kBackTrackPart) |
| return buttons_placement == kWebScrollbarButtonsPlacementNone || |
| buttons_placement == kWebScrollbarButtonsPlacementDoubleEnd; |
| if (scrollbar_part_ == kForwardTrackPart) |
| return buttons_placement == kWebScrollbarButtonsPlacementNone || |
| buttons_placement == kWebScrollbarButtonsPlacementDoubleStart; |
| return false; |
| } |
| case CSSSelector::kPseudoCornerPresent: |
| return scrollbar_->GetScrollableArea() && |
| scrollbar_->GetScrollableArea()->IsScrollCornerVisible(); |
| default: |
| return false; |
| } |
| } |
| |
| bool SelectorChecker::MatchesFocusPseudoClass(const Element& element) { |
| bool force_pseudo_state = false; |
| probe::forcePseudoState(const_cast<Element*>(&element), |
| CSSSelector::kPseudoFocus, &force_pseudo_state); |
| if (force_pseudo_state) |
| return true; |
| return element.IsFocused() && IsFrameFocused(element); |
| } |
| |
| } // namespace blink |