• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org)
3  *               1999 Waldo Bastian (bastian@kde.org)
4  * Copyright (C) 2004, 2006, 2007, 2008, 2009, 2010, 2013 Apple Inc. All rights reserved.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public License
17  * along with this library; see the file COPYING.LIB.  If not, write to
18  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21 
22 #ifndef CSSSelector_h
23 #define CSSSelector_h
24 
25 #include "core/dom/QualifiedName.h"
26 #include "core/rendering/style/RenderStyleConstants.h"
27 #include "wtf/OwnPtr.h"
28 #include "wtf/PassOwnPtr.h"
29 
30 namespace blink {
31     class CSSSelectorList;
32 
33     // This class represents a selector for a StyleRule.
34 
35     // CSS selector representation is somewhat complicated and subtle. A representative list of selectors is
36     // in CSSSelectorTest; run it in a debug build to see useful debugging output.
37     //
38     // ** tagHistory() and relation():
39     //
40     // Selectors are represented as a linked list of simple selectors (defined more or less according to
41     // http://www.w3.org/TR/css3-selectors/#simple-selectors-dfn). The tagHistory() method returns the next
42     // simple selector in the list. The relation() method returns the relationship of the current simple selector to
43     // the one in tagHistory(). For example, the CSS selector .a.b #c is represented as:
44     //
45     // selectorText(): .a.b #c
46     // --> (relation == Descendant)
47     //   selectorText(): .a.b
48     //   --> (relation == SubSelector)
49     //     selectorText(): .b
50     //
51     // Note that currently a bare selector such as ".a" has a relation() of Descendant. This is a bug - instead the relation should be
52     // "None".
53     //
54     // The order of tagHistory() varies depending on the situation.
55     // * Relations using combinators (http://www.w3.org/TR/css3-selectors/#combinators), such as descendant, sibling, etc., are parsed
56     //   right-to-left (in the example above, this is why .c is earlier in the tagHistory() chain than .a.b).
57     // * SubSelector relations are parsed left-to-right in most cases (such as the .a.b example above); a counter-example is the
58     //   ::content pseudo-element. Most (all?) other pseudo elements and pseudo classes are parsed left-to-right.
59     // * ShadowPseudo relations are parsed right-to-left. Example: summary::-webkit-details-marker is parsed as:
60     //   selectorText(): summary::-webkit-details-marker
61     //    --> (relation == ShadowPseudo)
62     //     selectorText(): summary
63     //
64     // ** match():
65     //
66     // The match of the current simple selector tells us the type of selector, such as class, id, tagname, or pseudo-class.
67     // Inline comments in the Match enum give examples of when each type would occur.
68     //
69     // ** value(), attribute():
70     //
71     // value() tells you the value of the simple selector. For example, for class selectors, value() will tell you the class string,
72     // and for id selectors it will tell you the id(). See below for the special case of attribute selectors.
73     //
74     // ** Attribute selectors.
75     //
76     // Attribute selectors return the attribute name in the attribute() method. The value() method returns the value matched against
77     // in case of selectors like [attr="value"].
78     //
79     // ** isCustomPseudoElement():
80     //
81     // It appears this is used only for pseudo elements that appear in user-agent shadow DOM. They are not exposed to author-created
82     // shadow DOM.
83 
84     class CSSSelector {
85         WTF_MAKE_FAST_ALLOCATED;
86     public:
87         CSSSelector();
88         CSSSelector(const CSSSelector&);
89         explicit CSSSelector(const QualifiedName&, bool tagIsForNamespaceRule = false);
90 
91         ~CSSSelector();
92 
93         /**
94          * Re-create selector text from selector's data
95          */
96         String selectorText(const String& = "") const;
97 
98         // checks if the 2 selectors (including sub selectors) agree.
99         bool operator==(const CSSSelector&) const;
100 
101         // tag == -1 means apply to all elements (Selector = *)
102 
103         // http://www.w3.org/TR/css3-selectors/#specificity
104         // We use 256 as the base of the specificity number system.
105         unsigned specificity() const;
106 
107         /* how the attribute value has to match.... Default is Exact */
108         enum Match {
109             Unknown = 0,
110             Tag, // Example: div
111             Id, // Example: #id
112             Class, // example: .class
113             PseudoClass, // Example:  :nth-child(2)
114             PseudoElement, // Example: ::first-line
115             PagePseudoClass, // ??
116             Exact, // Example: E[foo="bar"]
117             Set, // Example: E[foo]
118             Hyphen, // Example: E[foo|="bar"]
119             List, // Example: E[foo~="bar"]
120             Contain, // css3: E[foo*="bar"]
121             Begin, // css3: E[foo^="bar"]
122             End, // css3: E[foo$="bar"]
123             FirstAttributeSelectorMatch = Exact,
124         };
125 
126         enum Relation {
127             Descendant = 0, // "Space" combinator
128             Child, // > combinator
129             DirectAdjacent, // + combinator
130             IndirectAdjacent, // ~ combinator
131             SubSelector, // "No space" combinator
132             ShadowPseudo, // Special case of shadow DOM pseudo elements / shadow pseudo element
133             ShadowDeep // /deep/ combinator
134         };
135 
136         enum PseudoType {
137             PseudoNotParsed = 0,
138             PseudoUnknown,
139             PseudoEmpty,
140             PseudoFirstChild,
141             PseudoFirstOfType,
142             PseudoLastChild,
143             PseudoLastOfType,
144             PseudoOnlyChild,
145             PseudoOnlyOfType,
146             PseudoFirstLine,
147             PseudoFirstLetter,
148             PseudoNthChild,
149             PseudoNthOfType,
150             PseudoNthLastChild,
151             PseudoNthLastOfType,
152             PseudoLink,
153             PseudoVisited,
154             PseudoAny,
155             PseudoAnyLink,
156             PseudoAutofill,
157             PseudoHover,
158             PseudoDrag,
159             PseudoFocus,
160             PseudoActive,
161             PseudoChecked,
162             PseudoEnabled,
163             PseudoFullPageMedia,
164             PseudoDefault,
165             PseudoDisabled,
166             PseudoOptional,
167             PseudoRequired,
168             PseudoReadOnly,
169             PseudoReadWrite,
170             PseudoValid,
171             PseudoInvalid,
172             PseudoIndeterminate,
173             PseudoTarget,
174             PseudoBefore,
175             PseudoAfter,
176             PseudoBackdrop,
177             PseudoLang,
178             PseudoNot,
179             PseudoResizer,
180             PseudoRoot,
181             PseudoScope,
182             PseudoScrollbar,
183             PseudoScrollbarBack,
184             PseudoScrollbarButton,
185             PseudoScrollbarCorner,
186             PseudoScrollbarForward,
187             PseudoScrollbarThumb,
188             PseudoScrollbarTrack,
189             PseudoScrollbarTrackPiece,
190             PseudoWindowInactive,
191             PseudoCornerPresent,
192             PseudoDecrement,
193             PseudoIncrement,
194             PseudoHorizontal,
195             PseudoVertical,
196             PseudoStart,
197             PseudoEnd,
198             PseudoDoubleButton,
199             PseudoSingleButton,
200             PseudoNoButton,
201             PseudoSelection,
202             PseudoLeftPage,
203             PseudoRightPage,
204             PseudoFirstPage,
205             PseudoFullScreen,
206             PseudoFullScreenDocument,
207             PseudoFullScreenAncestor,
208             PseudoInRange,
209             PseudoOutOfRange,
210             PseudoUserAgentCustomElement,
211             PseudoWebKitCustomElement,
212             PseudoCue,
213             PseudoFutureCue,
214             PseudoPastCue,
215             PseudoUnresolved,
216             PseudoContent,
217             PseudoHost,
218             PseudoHostContext,
219             PseudoShadow,
220             PseudoSpatialNavigationFocus,
221             PseudoListBox
222         };
223 
224         enum MarginBoxType {
225             TopLeftCornerMarginBox,
226             TopLeftMarginBox,
227             TopCenterMarginBox,
228             TopRightMarginBox,
229             TopRightCornerMarginBox,
230             BottomLeftCornerMarginBox,
231             BottomLeftMarginBox,
232             BottomCenterMarginBox,
233             BottomRightMarginBox,
234             BottomRightCornerMarginBox,
235             LeftTopMarginBox,
236             LeftMiddleMarginBox,
237             LeftBottomMarginBox,
238             RightTopMarginBox,
239             RightMiddleMarginBox,
240             RightBottomMarginBox,
241         };
242 
243         enum AttributeMatchType {
244             CaseSensitive,
245             CaseInsensitive,
246         };
247 
pseudoType()248         PseudoType pseudoType() const
249         {
250             if (m_pseudoType == PseudoNotParsed)
251                 extractPseudoType();
252             return static_cast<PseudoType>(m_pseudoType);
253         }
254 
255         static PseudoType parsePseudoType(const AtomicString&, bool hasArguments);
256         static PseudoId pseudoId(PseudoType);
257 
258         // Selectors are kept in an array by CSSSelectorList. The next component of the selector is
259         // the next item in the array.
tagHistory()260         const CSSSelector* tagHistory() const { return m_isLastInTagHistory ? 0 : const_cast<CSSSelector*>(this + 1); }
261 
262         const QualifiedName& tagQName() const;
263         const AtomicString& value() const;
264 
265         // WARNING: Use of QualifiedName by attribute() is a lie.
266         // attribute() will return a QualifiedName with prefix and namespaceURI
267         // set to starAtom to mean "matches any namespace". Be very careful
268         // how you use the returned QualifiedName.
269         // http://www.w3.org/TR/css3-selectors/#attrnmsp
270         const QualifiedName& attribute() const;
271         AttributeMatchType attributeMatchType() const;
272         // Returns the argument of a parameterized selector. For example, nth-child(2) would have an argument of 2.
argument()273         const AtomicString& argument() const { return m_hasRareData ? m_data.m_rareData->m_argument : nullAtom; }
selectorList()274         const CSSSelectorList* selectorList() const { return m_hasRareData ? m_data.m_rareData->m_selectorList.get() : 0; }
275 
276 #ifndef NDEBUG
277         void show() const;
278         void show(int indent) const;
279 #endif
280 
281         void setValue(const AtomicString&);
282         void setAttribute(const QualifiedName&, AttributeMatchType);
283         void setArgument(const AtomicString&);
284         void setSelectorList(PassOwnPtr<CSSSelectorList>);
285         void setMatchUserAgentOnly();
286 
287         bool parseNth() const;
288         bool matchNth(int count) const;
289 
290         bool matchesPseudoElement() const;
291         bool isCustomPseudoElement() const;
isDirectAdjacentSelector()292         bool isDirectAdjacentSelector() const { return m_relation == DirectAdjacent; }
293         bool isSiblingSelector() const;
294         bool isAttributeSelector() const;
295         bool isContentPseudoElement() const;
296         bool isShadowPseudoElement() const;
297         bool isHostPseudoClass() const;
298 
299         // FIXME: selectors with no tagHistory() get a relation() of Descendant (and sometimes even SubSelector). It should instead be
300         // None.
relation()301         Relation relation() const { return static_cast<Relation>(m_relation); }
setRelation(Relation relation)302         void setRelation(Relation relation)
303         {
304             m_relation = relation;
305             ASSERT(static_cast<Relation>(m_relation) == relation); // using a bitfield.
306         }
307 
match()308         Match match() const { return static_cast<Match>(m_match); }
setMatch(Match match)309         void setMatch(Match match)
310         {
311             m_match = match;
312             ASSERT(static_cast<Match>(m_match) == match); // using a bitfield.
313         }
314 
isLastInSelectorList()315         bool isLastInSelectorList() const { return m_isLastInSelectorList; }
setLastInSelectorList()316         void setLastInSelectorList() { m_isLastInSelectorList = true; }
isLastInTagHistory()317         bool isLastInTagHistory() const { return m_isLastInTagHistory; }
setNotLastInTagHistory()318         void setNotLastInTagHistory() { m_isLastInTagHistory = false; }
319 
320         // http://dev.w3.org/csswg/selectors4/#compound
321         bool isCompound() const;
322 
isForPage()323         bool isForPage() const { return m_isForPage; }
setForPage()324         void setForPage() { m_isForPage = true; }
325 
relationIsAffectedByPseudoContent()326         bool relationIsAffectedByPseudoContent() const { return m_relationIsAffectedByPseudoContent; }
setRelationIsAffectedByPseudoContent()327         void setRelationIsAffectedByPseudoContent() { m_relationIsAffectedByPseudoContent = true; }
328 
329     private:
330         unsigned m_relation           : 3; // enum Relation
331         mutable unsigned m_match      : 4; // enum Match
332         mutable unsigned m_pseudoType : 8; // PseudoType
333         mutable unsigned m_parsedNth      : 1; // Used for :nth-*
334         unsigned m_isLastInSelectorList   : 1;
335         unsigned m_isLastInTagHistory     : 1;
336         unsigned m_hasRareData            : 1;
337         unsigned m_isForPage              : 1;
338         unsigned m_tagIsForNamespaceRule  : 1;
339         unsigned m_relationIsAffectedByPseudoContent  : 1;
340 
341         unsigned specificityForOneSelector() const;
342         unsigned specificityForPage() const;
343         void extractPseudoType() const;
344 
345         // Hide.
346         CSSSelector& operator=(const CSSSelector&);
347 
348         struct RareData : public RefCounted<RareData> {
createRareData349             static PassRefPtr<RareData> create(const AtomicString& value) { return adoptRef(new RareData(value)); }
350             ~RareData();
351 
352             bool parseNth();
353             bool matchNth(int count);
nthAValueRareData354             int nthAValue() const { return m_bits.m_nth.m_a; }
setNthAValueRareData355             void setNthAValue(int nthA) { m_bits.m_nth.m_a = nthA; }
nthBValueRareData356             int nthBValue() const { return m_bits.m_nth.m_b; }
setNthBValueRareData357             void setNthBValue(int nthB) { m_bits.m_nth.m_b = nthB; }
358 
359             AtomicString m_value;
360             union {
361                 struct {
362                     int m_a; // Used for :nth-*
363                     int m_b; // Used for :nth-*
364                 } m_nth;
365                 AttributeMatchType m_attributeMatchType; // used for attribute selector (with value)
366             } m_bits;
367             QualifiedName m_attribute; // used for attribute selector
368             AtomicString m_argument; // Used for :contains, :lang, :nth-*
369             OwnPtr<CSSSelectorList> m_selectorList; // Used for :-webkit-any and :not
370 
371         private:
372             RareData(const AtomicString& value);
373         };
374         void createRareData();
375 
376         union DataUnion {
DataUnion()377             DataUnion() : m_value(0) { }
378             StringImpl* m_value;
379             QualifiedName::QualifiedNameImpl* m_tagQName;
380             RareData* m_rareData;
381         } m_data;
382     };
383 
attribute()384 inline const QualifiedName& CSSSelector::attribute() const
385 {
386     ASSERT(isAttributeSelector());
387     ASSERT(m_hasRareData);
388     return m_data.m_rareData->m_attribute;
389 }
390 
attributeMatchType()391 inline CSSSelector::AttributeMatchType CSSSelector::attributeMatchType() const
392 {
393     ASSERT(isAttributeSelector());
394     ASSERT(m_hasRareData);
395     return m_data.m_rareData->m_bits.m_attributeMatchType;
396 }
397 
matchesPseudoElement()398 inline bool CSSSelector::matchesPseudoElement() const
399 {
400     if (m_pseudoType == PseudoUnknown)
401         extractPseudoType();
402     return m_match == PseudoElement;
403 }
404 
isCustomPseudoElement()405 inline bool CSSSelector::isCustomPseudoElement() const
406 {
407     return m_match == PseudoElement && (m_pseudoType == PseudoUserAgentCustomElement || m_pseudoType == PseudoWebKitCustomElement);
408 }
409 
isHostPseudoClass()410 inline bool CSSSelector::isHostPseudoClass() const
411 {
412     return m_match == PseudoClass && (m_pseudoType == PseudoHost || m_pseudoType == PseudoHostContext);
413 }
414 
isSiblingSelector()415 inline bool CSSSelector::isSiblingSelector() const
416 {
417     PseudoType type = pseudoType();
418     return m_relation == DirectAdjacent
419         || m_relation == IndirectAdjacent
420         || type == PseudoEmpty
421         || type == PseudoFirstChild
422         || type == PseudoFirstOfType
423         || type == PseudoLastChild
424         || type == PseudoLastOfType
425         || type == PseudoOnlyChild
426         || type == PseudoOnlyOfType
427         || type == PseudoNthChild
428         || type == PseudoNthOfType
429         || type == PseudoNthLastChild
430         || type == PseudoNthLastOfType;
431 }
432 
isAttributeSelector()433 inline bool CSSSelector::isAttributeSelector() const
434 {
435     return m_match >= FirstAttributeSelectorMatch;
436 }
437 
isContentPseudoElement()438 inline bool CSSSelector::isContentPseudoElement() const
439 {
440     return m_match == PseudoElement && pseudoType() == PseudoContent;
441 }
442 
isShadowPseudoElement()443 inline bool CSSSelector::isShadowPseudoElement() const
444 {
445     return m_match == PseudoElement && pseudoType() == PseudoShadow;
446 }
447 
setValue(const AtomicString & value)448 inline void CSSSelector::setValue(const AtomicString& value)
449 {
450     ASSERT(m_match != Tag);
451     ASSERT(m_pseudoType == PseudoNotParsed);
452     // Need to do ref counting manually for the union.
453     if (m_hasRareData) {
454         m_data.m_rareData->m_value = value;
455         return;
456     }
457     if (m_data.m_value)
458         m_data.m_value->deref();
459     m_data.m_value = value.impl();
460     m_data.m_value->ref();
461 }
462 
CSSSelector()463 inline CSSSelector::CSSSelector()
464     : m_relation(Descendant)
465     , m_match(Unknown)
466     , m_pseudoType(PseudoNotParsed)
467     , m_parsedNth(false)
468     , m_isLastInSelectorList(false)
469     , m_isLastInTagHistory(true)
470     , m_hasRareData(false)
471     , m_isForPage(false)
472     , m_tagIsForNamespaceRule(false)
473     , m_relationIsAffectedByPseudoContent(false)
474 {
475 }
476 
CSSSelector(const QualifiedName & tagQName,bool tagIsForNamespaceRule)477 inline CSSSelector::CSSSelector(const QualifiedName& tagQName, bool tagIsForNamespaceRule)
478     : m_relation(Descendant)
479     , m_match(Tag)
480     , m_pseudoType(PseudoNotParsed)
481     , m_parsedNth(false)
482     , m_isLastInSelectorList(false)
483     , m_isLastInTagHistory(true)
484     , m_hasRareData(false)
485     , m_isForPage(false)
486     , m_tagIsForNamespaceRule(tagIsForNamespaceRule)
487     , m_relationIsAffectedByPseudoContent(false)
488 {
489     m_data.m_tagQName = tagQName.impl();
490     m_data.m_tagQName->ref();
491 }
492 
CSSSelector(const CSSSelector & o)493 inline CSSSelector::CSSSelector(const CSSSelector& o)
494     : m_relation(o.m_relation)
495     , m_match(o.m_match)
496     , m_pseudoType(o.m_pseudoType)
497     , m_parsedNth(o.m_parsedNth)
498     , m_isLastInSelectorList(o.m_isLastInSelectorList)
499     , m_isLastInTagHistory(o.m_isLastInTagHistory)
500     , m_hasRareData(o.m_hasRareData)
501     , m_isForPage(o.m_isForPage)
502     , m_tagIsForNamespaceRule(o.m_tagIsForNamespaceRule)
503     , m_relationIsAffectedByPseudoContent(o.m_relationIsAffectedByPseudoContent)
504 {
505     if (o.m_match == Tag) {
506         m_data.m_tagQName = o.m_data.m_tagQName;
507         m_data.m_tagQName->ref();
508     } else if (o.m_hasRareData) {
509         m_data.m_rareData = o.m_data.m_rareData;
510         m_data.m_rareData->ref();
511     } else if (o.m_data.m_value) {
512         m_data.m_value = o.m_data.m_value;
513         m_data.m_value->ref();
514     }
515 }
516 
~CSSSelector()517 inline CSSSelector::~CSSSelector()
518 {
519     if (m_match == Tag)
520         m_data.m_tagQName->deref();
521     else if (m_hasRareData)
522         m_data.m_rareData->deref();
523     else if (m_data.m_value)
524         m_data.m_value->deref();
525 }
526 
tagQName()527 inline const QualifiedName& CSSSelector::tagQName() const
528 {
529     ASSERT(m_match == Tag);
530     return *reinterpret_cast<const QualifiedName*>(&m_data.m_tagQName);
531 }
532 
value()533 inline const AtomicString& CSSSelector::value() const
534 {
535     ASSERT(m_match != Tag);
536     if (m_hasRareData)
537         return m_data.m_rareData->m_value;
538     // AtomicString is really just a StringImpl* so the cast below is safe.
539     // FIXME: Perhaps call sites could be changed to accept StringImpl?
540     return *reinterpret_cast<const AtomicString*>(&m_data.m_value);
541 }
542 
543 } // namespace blink
544 
545 #endif // CSSSelector_h
546