| /* |
| * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) |
| * 1999 Waldo Bastian (bastian@kde.org) |
| * Copyright (C) 2004, 2006, 2007, 2008, 2009, 2010, 2013 Apple Inc. 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. |
| */ |
| |
| #ifndef CSSSelector_h |
| #define CSSSelector_h |
| |
| #include <memory> |
| #include "core/CoreExport.h" |
| #include "core/css/parser/CSSParserMode.h" |
| #include "core/dom/QualifiedName.h" |
| #include "core/style/ComputedStyleConstants.h" |
| #include "platform/wtf/RefCounted.h" |
| |
| namespace blink { |
| class CSSSelectorList; |
| |
| // This class represents a simple selector for a StyleRule. |
| |
| // CSS selector representation is somewhat complicated and subtle. A |
| // representative list of selectors is in CSSSelectorTest; run it in a debug |
| // build to see useful debugging output. |
| // |
| // ** TagHistory() and Relation(): |
| // |
| // Selectors are represented as an array of simple selectors (defined more |
| // or less according to |
| // http://www.w3.org/TR/css3-selectors/#simple-selectors-dfn). The tagHistory() |
| // method returns the next simple selector in the list. The relation() method |
| // returns the relationship of the current simple selector to the one in |
| // tagHistory(). For example, the CSS selector .a.b #c is represented as: |
| // |
| // SelectorText(): .a.b #c |
| // --> (relation == kDescendant) |
| // SelectorText(): .a.b |
| // --> (relation == kSubSelector) |
| // SelectorText(): .b |
| // |
| // The order of tagHistory() varies depending on the situation. |
| // * Relations using combinators |
| // (http://www.w3.org/TR/css3-selectors/#combinators), such as descendant, |
| // sibling, etc., are parsed right-to-left (in the example above, this is why |
| // #c is earlier in the tagHistory() chain than .a.b). |
| // * SubSelector relations are parsed left-to-right in most cases (such as the |
| // .a.b example above); a counter-example is the |
| // ::content pseudo-element. Most (all?) other pseudo elements and pseudo |
| // classes are parsed left-to-right. |
| // * ShadowPseudo relations are parsed right-to-left. Example: |
| // summary::-webkit-details-marker is parsed as: selectorText(): |
| // summary::-webkit-details-marker --> (relation == ShadowPseudo) |
| // selectorText(): summary |
| // |
| // ** match(): |
| // |
| // The match of the current simple selector tells us the type of selector, such |
| // as class, id, tagname, or pseudo-class. Inline comments in the Match enum |
| // give examples of when each type would occur. |
| // |
| // ** value(), attribute(): |
| // |
| // value() tells you the value of the simple selector. For example, for class |
| // selectors, value() will tell you the class string, and for id selectors it |
| // will tell you the id(). See below for the special case of attribute |
| // selectors. |
| // |
| // ** Attribute selectors. |
| // |
| // Attribute selectors return the attribute name in the attribute() method. The |
| // value() method returns the value matched against in case of selectors like |
| // [attr="value"]. |
| // |
| class CORE_EXPORT CSSSelector { |
| USING_FAST_MALLOC_WITH_TYPE_NAME(blink::CSSSelector); |
| |
| public: |
| CSSSelector(); |
| CSSSelector(const CSSSelector&); |
| explicit CSSSelector(const QualifiedName&, bool tag_is_implicit = false); |
| |
| ~CSSSelector(); |
| |
| String SelectorText() const; |
| |
| bool operator==(const CSSSelector&) const; |
| |
| // http://www.w3.org/TR/css3-selectors/#specificity |
| // We use 256 as the base of the specificity number system. |
| unsigned Specificity() const; |
| |
| /* how the attribute value has to match.... Default is Exact */ |
| enum MatchType { |
| kUnknown, |
| kTag, // Example: div |
| kId, // Example: #id |
| kClass, // example: .class |
| kPseudoClass, // Example: :nth-child(2) |
| kPseudoElement, // Example: ::first-line |
| kPagePseudoClass, // ?? |
| kAttributeExact, // Example: E[foo="bar"] |
| kAttributeSet, // Example: E[foo] |
| kAttributeHyphen, // Example: E[foo|="bar"] |
| kAttributeList, // Example: E[foo~="bar"] |
| kAttributeContain, // css3: E[foo*="bar"] |
| kAttributeBegin, // css3: E[foo^="bar"] |
| kAttributeEnd, // css3: E[foo$="bar"] |
| kFirstAttributeSelectorMatch = kAttributeExact, |
| }; |
| |
| enum RelationType { |
| kSubSelector, // No combinator |
| kDescendant, // "Space" combinator |
| kChild, // > combinator |
| kDirectAdjacent, // + combinator |
| kIndirectAdjacent, // ~ combinator |
| // Special cases for shadow DOM related selectors. |
| kShadowPiercingDescendant, // >>> combinator |
| kShadowDeep, // /deep/ combinator |
| kShadowPseudo, // ::shadow pseudo element |
| kShadowSlot // ::slotted() pseudo element |
| }; |
| |
| enum PseudoType { |
| kPseudoUnknown, |
| kPseudoEmpty, |
| kPseudoFirstChild, |
| kPseudoFirstOfType, |
| kPseudoLastChild, |
| kPseudoLastOfType, |
| kPseudoOnlyChild, |
| kPseudoOnlyOfType, |
| kPseudoFirstLine, |
| kPseudoFirstLetter, |
| kPseudoNthChild, |
| kPseudoNthOfType, |
| kPseudoNthLastChild, |
| kPseudoNthLastOfType, |
| kPseudoLink, |
| kPseudoVisited, |
| kPseudoAny, |
| kPseudoAnyLink, |
| kPseudoAutofill, |
| kPseudoHover, |
| kPseudoDrag, |
| kPseudoFocus, |
| kPseudoFocusWithin, |
| kPseudoActive, |
| kPseudoChecked, |
| kPseudoEnabled, |
| kPseudoFullPageMedia, |
| kPseudoDefault, |
| kPseudoDisabled, |
| kPseudoOptional, |
| kPseudoPlaceholderShown, |
| kPseudoRequired, |
| kPseudoReadOnly, |
| kPseudoReadWrite, |
| kPseudoValid, |
| kPseudoInvalid, |
| kPseudoIndeterminate, |
| kPseudoTarget, |
| kPseudoBefore, |
| kPseudoAfter, |
| kPseudoBackdrop, |
| kPseudoLang, |
| kPseudoNot, |
| kPseudoPlaceholder, |
| kPseudoResizer, |
| kPseudoRoot, |
| kPseudoScope, |
| kPseudoScrollbar, |
| kPseudoScrollbarButton, |
| kPseudoScrollbarCorner, |
| kPseudoScrollbarThumb, |
| kPseudoScrollbarTrack, |
| kPseudoScrollbarTrackPiece, |
| kPseudoWindowInactive, |
| kPseudoCornerPresent, |
| kPseudoDecrement, |
| kPseudoIncrement, |
| kPseudoHorizontal, |
| kPseudoVertical, |
| kPseudoStart, |
| kPseudoEnd, |
| kPseudoDoubleButton, |
| kPseudoSingleButton, |
| kPseudoNoButton, |
| kPseudoSelection, |
| kPseudoLeftPage, |
| kPseudoRightPage, |
| kPseudoFirstPage, |
| kPseudoFullScreen, |
| kPseudoFullScreenAncestor, |
| kPseudoInRange, |
| kPseudoOutOfRange, |
| // Pseudo elements in UA ShadowRoots. Available in any stylesheets. |
| kPseudoWebKitCustomElement, |
| // Pseudo elements in UA ShadowRoots. Availble only in UA stylesheets. |
| kPseudoBlinkInternalElement, |
| kPseudoCue, |
| kPseudoFutureCue, |
| kPseudoPastCue, |
| kPseudoUnresolved, |
| kPseudoDefined, |
| kPseudoContent, |
| kPseudoHost, |
| kPseudoHostContext, |
| kPseudoShadow, |
| kPseudoSpatialNavigationFocus, |
| kPseudoListBox, |
| kPseudoHostHasAppearance, |
| kPseudoSlotted, |
| kPseudoVideoPersistent, |
| kPseudoVideoPersistentAncestor, |
| }; |
| |
| enum AttributeMatchType { |
| kCaseSensitive, |
| kCaseInsensitive, |
| }; |
| |
| PseudoType GetPseudoType() const { |
| return static_cast<PseudoType>(pseudo_type_); |
| } |
| void UpdatePseudoType(const AtomicString&, bool has_arguments, CSSParserMode); |
| void UpdatePseudoPage(const AtomicString&); |
| |
| static PseudoType ParsePseudoType(const AtomicString&, bool has_arguments); |
| static PseudoId ParsePseudoId(const String&); |
| static PseudoId GetPseudoId(PseudoType); |
| |
| // Selectors are kept in an array by CSSSelectorList. The next component of |
| // the selector is the next item in the array. |
| const CSSSelector* TagHistory() const { |
| return is_last_in_tag_history_ ? 0 : const_cast<CSSSelector*>(this + 1); |
| } |
| |
| const QualifiedName& TagQName() const; |
| const AtomicString& Value() const; |
| const AtomicString& SerializingValue() const; |
| |
| // WARNING: Use of QualifiedName by attribute() is a lie. |
| // attribute() will return a QualifiedName with prefix and namespaceURI |
| // set to starAtom to mean "matches any namespace". Be very careful |
| // how you use the returned QualifiedName. |
| // http://www.w3.org/TR/css3-selectors/#attrnmsp |
| const QualifiedName& Attribute() const; |
| AttributeMatchType AttributeMatch() const; |
| // Returns the argument of a parameterized selector. For example, :lang(en-US) |
| // would have an argument of en-US. |
| // Note that :nth-* selectors don't store an argument and just store the |
| // numbers. |
| const AtomicString& Argument() const { |
| return has_rare_data_ ? data_.rare_data_->argument_ : g_null_atom; |
| } |
| const CSSSelectorList* SelectorList() const { |
| return has_rare_data_ ? data_.rare_data_->selector_list_.get() : nullptr; |
| } |
| |
| #ifndef NDEBUG |
| void Show() const; |
| void Show(int indent) const; |
| #endif |
| |
| bool IsASCIILower(const AtomicString& value); |
| void SetValue(const AtomicString&, bool match_lower_case); |
| void SetAttribute(const QualifiedName&, AttributeMatchType); |
| void SetArgument(const AtomicString&); |
| void SetSelectorList(std::unique_ptr<CSSSelectorList>); |
| |
| void SetNth(int a, int b); |
| bool MatchNth(unsigned count) const; |
| |
| bool IsAdjacentSelector() const { |
| return relation_ == kDirectAdjacent || relation_ == kIndirectAdjacent; |
| } |
| bool IsShadowSelector() const { |
| return relation_ == kShadowPseudo || relation_ == kShadowDeep; |
| } |
| bool IsAttributeSelector() const { |
| return match_ >= kFirstAttributeSelectorMatch; |
| } |
| bool IsHostPseudoClass() const { |
| return pseudo_type_ == kPseudoHost || pseudo_type_ == kPseudoHostContext; |
| } |
| bool IsUserActionPseudoClass() const; |
| bool IsV0InsertionPointCrossing() const { |
| return pseudo_type_ == kPseudoHostContext || pseudo_type_ == kPseudoContent; |
| } |
| bool IsIdClassOrAttributeSelector() const; |
| |
| RelationType Relation() const { return static_cast<RelationType>(relation_); } |
| void SetRelation(RelationType relation) { |
| relation_ = relation; |
| DCHECK_EQ(static_cast<RelationType>(relation_), |
| relation); // using a bitfield. |
| } |
| |
| MatchType Match() const { return static_cast<MatchType>(match_); } |
| void SetMatch(MatchType match) { |
| match_ = match; |
| DCHECK_EQ(static_cast<MatchType>(match_), match); // using a bitfield. |
| } |
| |
| bool IsLastInSelectorList() const { return is_last_in_selector_list_; } |
| void SetLastInSelectorList() { is_last_in_selector_list_ = true; } |
| bool IsLastInTagHistory() const { return is_last_in_tag_history_; } |
| void SetNotLastInTagHistory() { is_last_in_tag_history_ = false; } |
| |
| // http://dev.w3.org/csswg/selectors4/#compound |
| bool IsCompound() const; |
| |
| enum LinkMatchMask { |
| kMatchLink = 1, |
| kMatchVisited = 2, |
| kMatchAll = kMatchLink | kMatchVisited |
| }; |
| unsigned ComputeLinkMatchType() const; |
| |
| bool IsForPage() const { return is_for_page_; } |
| void SetForPage() { is_for_page_ = true; } |
| |
| bool RelationIsAffectedByPseudoContent() const { |
| return relation_is_affected_by_pseudo_content_; |
| } |
| void SetRelationIsAffectedByPseudoContent() { |
| relation_is_affected_by_pseudo_content_ = true; |
| } |
| |
| bool MatchesPseudoElement() const; |
| |
| bool HasContentPseudo() const; |
| bool HasSlottedPseudo() const; |
| bool HasDeepCombinatorOrShadowPseudo() const; |
| bool NeedsUpdatedDistribution() const; |
| |
| private: |
| unsigned relation_ : 4; // enum RelationType |
| unsigned match_ : 4; // enum MatchType |
| unsigned pseudo_type_ : 8; // enum PseudoType |
| unsigned is_last_in_selector_list_ : 1; |
| unsigned is_last_in_tag_history_ : 1; |
| unsigned has_rare_data_ : 1; |
| unsigned is_for_page_ : 1; |
| unsigned tag_is_implicit_ : 1; |
| unsigned relation_is_affected_by_pseudo_content_ : 1; |
| |
| void SetPseudoType(PseudoType pseudo_type) { |
| pseudo_type_ = pseudo_type; |
| DCHECK_EQ(static_cast<PseudoType>(pseudo_type_), |
| pseudo_type); // using a bitfield. |
| } |
| |
| unsigned SpecificityForOneSelector() const; |
| unsigned SpecificityForPage() const; |
| const CSSSelector* SerializeCompound(StringBuilder&) const; |
| |
| // Hide. |
| CSSSelector& operator=(const CSSSelector&); |
| |
| struct RareData : public RefCounted<RareData> { |
| static RefPtr<RareData> Create(const AtomicString& value) { |
| return AdoptRef(new RareData(value)); |
| } |
| ~RareData(); |
| |
| bool MatchNth(unsigned count); |
| int NthAValue() const { return bits_.nth_.a_; } |
| int NthBValue() const { return bits_.nth_.b_; } |
| |
| AtomicString matching_value_; |
| AtomicString serializing_value_; |
| union { |
| struct { |
| int a_; // Used for :nth-* |
| int b_; // Used for :nth-* |
| } nth_; |
| AttributeMatchType |
| attribute_match_; // used for attribute selector (with value) |
| } bits_; |
| QualifiedName attribute_; // used for attribute selector |
| AtomicString argument_; // Used for :contains, :lang, :nth-* |
| std::unique_ptr<CSSSelectorList> |
| selector_list_; // Used for :-webkit-any and :not |
| |
| private: |
| RareData(const AtomicString& value); |
| }; |
| void CreateRareData(); |
| |
| union DataUnion { |
| DataUnion() : value_(nullptr) {} |
| StringImpl* value_; |
| QualifiedName::QualifiedNameImpl* tag_q_name_; |
| RareData* rare_data_; |
| } data_; |
| }; |
| |
| inline const QualifiedName& CSSSelector::Attribute() const { |
| DCHECK(IsAttributeSelector()); |
| DCHECK(has_rare_data_); |
| return data_.rare_data_->attribute_; |
| } |
| |
| inline CSSSelector::AttributeMatchType CSSSelector::AttributeMatch() const { |
| DCHECK(IsAttributeSelector()); |
| DCHECK(has_rare_data_); |
| return data_.rare_data_->bits_.attribute_match_; |
| } |
| |
| inline bool CSSSelector::IsASCIILower(const AtomicString& value) { |
| for (size_t i = 0; i < value.length(); ++i) { |
| if (IsASCIIUpper(value[i])) |
| return false; |
| } |
| return true; |
| } |
| |
| inline void CSSSelector::SetValue(const AtomicString& value, |
| bool match_lower_case = false) { |
| DCHECK_NE(match_, static_cast<unsigned>(kTag)); |
| if (match_lower_case && !has_rare_data_ && !IsASCIILower(value)) { |
| CreateRareData(); |
| } |
| // Need to do ref counting manually for the union. |
| if (!has_rare_data_) { |
| if (data_.value_) |
| data_.value_->Deref(); |
| data_.value_ = value.Impl(); |
| data_.value_->Ref(); |
| return; |
| } |
| data_.rare_data_->matching_value_ = |
| match_lower_case ? value.LowerASCII() : value; |
| data_.rare_data_->serializing_value_ = value; |
| } |
| |
| inline CSSSelector::CSSSelector() |
| : relation_(kSubSelector), |
| match_(kUnknown), |
| pseudo_type_(kPseudoUnknown), |
| is_last_in_selector_list_(false), |
| is_last_in_tag_history_(true), |
| has_rare_data_(false), |
| is_for_page_(false), |
| tag_is_implicit_(false), |
| relation_is_affected_by_pseudo_content_(false) {} |
| |
| inline CSSSelector::CSSSelector(const QualifiedName& tag_q_name, |
| bool tag_is_implicit) |
| : relation_(kSubSelector), |
| match_(kTag), |
| pseudo_type_(kPseudoUnknown), |
| is_last_in_selector_list_(false), |
| is_last_in_tag_history_(true), |
| has_rare_data_(false), |
| is_for_page_(false), |
| tag_is_implicit_(tag_is_implicit), |
| relation_is_affected_by_pseudo_content_(false) { |
| data_.tag_q_name_ = tag_q_name.Impl(); |
| data_.tag_q_name_->Ref(); |
| } |
| |
| inline CSSSelector::CSSSelector(const CSSSelector& o) |
| : relation_(o.relation_), |
| match_(o.match_), |
| pseudo_type_(o.pseudo_type_), |
| is_last_in_selector_list_(o.is_last_in_selector_list_), |
| is_last_in_tag_history_(o.is_last_in_tag_history_), |
| has_rare_data_(o.has_rare_data_), |
| is_for_page_(o.is_for_page_), |
| tag_is_implicit_(o.tag_is_implicit_), |
| relation_is_affected_by_pseudo_content_( |
| o.relation_is_affected_by_pseudo_content_) { |
| if (o.match_ == kTag) { |
| data_.tag_q_name_ = o.data_.tag_q_name_; |
| data_.tag_q_name_->Ref(); |
| } else if (o.has_rare_data_) { |
| data_.rare_data_ = o.data_.rare_data_; |
| data_.rare_data_->Ref(); |
| } else if (o.data_.value_) { |
| data_.value_ = o.data_.value_; |
| data_.value_->Ref(); |
| } |
| } |
| |
| inline CSSSelector::~CSSSelector() { |
| if (match_ == kTag) |
| data_.tag_q_name_->Deref(); |
| else if (has_rare_data_) |
| data_.rare_data_->Deref(); |
| else if (data_.value_) |
| data_.value_->Deref(); |
| } |
| |
| inline const QualifiedName& CSSSelector::TagQName() const { |
| DCHECK_EQ(match_, static_cast<unsigned>(kTag)); |
| return *reinterpret_cast<const QualifiedName*>(&data_.tag_q_name_); |
| } |
| |
| inline const AtomicString& CSSSelector::Value() const { |
| DCHECK_NE(match_, static_cast<unsigned>(kTag)); |
| if (has_rare_data_) |
| return data_.rare_data_->matching_value_; |
| // AtomicString is really just a StringImpl* so the cast below is safe. |
| // FIXME: Perhaps call sites could be changed to accept StringImpl? |
| return *reinterpret_cast<const AtomicString*>(&data_.value_); |
| } |
| |
| inline const AtomicString& CSSSelector::SerializingValue() const { |
| DCHECK_NE(match_, static_cast<unsigned>(kTag)); |
| if (has_rare_data_) |
| return data_.rare_data_->serializing_value_; |
| // AtomicString is really just a StringImpl* so the cast below is safe. |
| // FIXME: Perhaps call sites could be changed to accept StringImpl? |
| return *reinterpret_cast<const AtomicString*>(&data_.value_); |
| } |
| |
| inline bool CSSSelector::IsUserActionPseudoClass() const { |
| return pseudo_type_ == kPseudoHover || pseudo_type_ == kPseudoActive || |
| pseudo_type_ == kPseudoFocus || pseudo_type_ == kPseudoDrag || |
| pseudo_type_ == kPseudoFocusWithin; |
| } |
| |
| inline bool CSSSelector::IsIdClassOrAttributeSelector() const { |
| return IsAttributeSelector() || Match() == CSSSelector::kId || |
| Match() == CSSSelector::kClass; |
| } |
| |
| } // namespace blink |
| |
| #endif // CSSSelector_h |