• 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  *               2001 Andreas Schlapbach (schlpbch@iam.unibe.ch)
5  *               2001-2003 Dirk Mueller (mueller@kde.org)
6  * Copyright (C) 2002, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved.
7  * Copyright (C) 2008 David Smith (catfish.man@gmail.com)
8  * Copyright (C) 2010 Google Inc. All rights reserved.
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Library General Public
12  * License as published by the Free Software Foundation; either
13  * version 2 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Library General Public License for more details.
19  *
20  * You should have received a copy of the GNU Library General Public License
21  * along with this library; see the file COPYING.LIB.  If not, write to
22  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23  * Boston, MA 02110-1301, USA.
24  */
25 
26 #include "config.h"
27 #include "core/css/CSSSelector.h"
28 
29 #include "core/HTMLNames.h"
30 #include "core/css/CSSOMUtils.h"
31 #include "core/css/CSSSelectorList.h"
32 #include "platform/RuntimeEnabledFeatures.h"
33 #include "wtf/Assertions.h"
34 #include "wtf/HashMap.h"
35 #include "wtf/StdLibExtras.h"
36 #include "wtf/text/StringBuilder.h"
37 
38 #ifndef NDEBUG
39 #include <stdio.h>
40 #endif
41 
42 namespace blink {
43 
44 using namespace HTMLNames;
45 
46 struct SameSizeAsCSSSelector {
47     unsigned bitfields;
48     void *pointers[1];
49 };
50 
51 COMPILE_ASSERT(sizeof(CSSSelector) == sizeof(SameSizeAsCSSSelector), CSSSelectorShouldStaySmall);
52 
createRareData()53 void CSSSelector::createRareData()
54 {
55     ASSERT(m_match != Tag);
56     if (m_hasRareData)
57         return;
58     AtomicString value(m_data.m_value);
59     if (m_data.m_value)
60         m_data.m_value->deref();
61     m_data.m_rareData = RareData::create(value).leakRef();
62     m_hasRareData = true;
63 }
64 
specificity() const65 unsigned CSSSelector::specificity() const
66 {
67     // make sure the result doesn't overflow
68     static const unsigned maxValueMask = 0xffffff;
69     static const unsigned idMask = 0xff0000;
70     static const unsigned classMask = 0xff00;
71     static const unsigned elementMask = 0xff;
72 
73     if (isForPage())
74         return specificityForPage() & maxValueMask;
75 
76     unsigned total = 0;
77     unsigned temp = 0;
78 
79     for (const CSSSelector* selector = this; selector; selector = selector->tagHistory()) {
80         temp = total + selector->specificityForOneSelector();
81         // Clamp each component to its max in the case of overflow.
82         if ((temp & idMask) < (total & idMask))
83             total |= idMask;
84         else if ((temp & classMask) < (total & classMask))
85             total |= classMask;
86         else if ((temp & elementMask) < (total & elementMask))
87             total |= elementMask;
88         else
89             total = temp;
90     }
91     return total;
92 }
93 
specificityForOneSelector() const94 inline unsigned CSSSelector::specificityForOneSelector() const
95 {
96     // FIXME: Pseudo-elements and pseudo-classes do not have the same specificity. This function
97     // isn't quite correct.
98     switch (m_match) {
99     case Id:
100         return 0x10000;
101     case PseudoClass:
102         if (pseudoType() == PseudoHost || pseudoType() == PseudoHostContext)
103             return 0;
104         // fall through.
105     case Exact:
106     case Class:
107     case Set:
108     case List:
109     case Hyphen:
110     case PseudoElement:
111     case Contain:
112     case Begin:
113     case End:
114         // FIXME: PseudoAny should base the specificity on the sub-selectors.
115         // See http://lists.w3.org/Archives/Public/www-style/2010Sep/0530.html
116         if (pseudoType() == PseudoNot) {
117             ASSERT(selectorList());
118             return selectorList()->first()->specificityForOneSelector();
119         }
120         return 0x100;
121     case Tag:
122         return (tagQName().localName() != starAtom) ? 1 : 0;
123     case Unknown:
124         return 0;
125     }
126     ASSERT_NOT_REACHED();
127     return 0;
128 }
129 
specificityForPage() const130 unsigned CSSSelector::specificityForPage() const
131 {
132     // See http://dev.w3.org/csswg/css3-page/#cascading-and-page-context
133     unsigned s = 0;
134 
135     for (const CSSSelector* component = this; component; component = component->tagHistory()) {
136         switch (component->m_match) {
137         case Tag:
138             s += tagQName().localName() == starAtom ? 0 : 4;
139             break;
140         case PagePseudoClass:
141             switch (component->pseudoType()) {
142             case PseudoFirstPage:
143                 s += 2;
144                 break;
145             case PseudoLeftPage:
146             case PseudoRightPage:
147                 s += 1;
148                 break;
149             case PseudoNotParsed:
150                 break;
151             default:
152                 ASSERT_NOT_REACHED();
153             }
154             break;
155         default:
156             break;
157         }
158     }
159     return s;
160 }
161 
pseudoId(PseudoType type)162 PseudoId CSSSelector::pseudoId(PseudoType type)
163 {
164     switch (type) {
165     case PseudoFirstLine:
166         return FIRST_LINE;
167     case PseudoFirstLetter:
168         return FIRST_LETTER;
169     case PseudoSelection:
170         return SELECTION;
171     case PseudoBefore:
172         return BEFORE;
173     case PseudoAfter:
174         return AFTER;
175     case PseudoBackdrop:
176         return BACKDROP;
177     case PseudoScrollbar:
178         return SCROLLBAR;
179     case PseudoScrollbarButton:
180         return SCROLLBAR_BUTTON;
181     case PseudoScrollbarCorner:
182         return SCROLLBAR_CORNER;
183     case PseudoScrollbarThumb:
184         return SCROLLBAR_THUMB;
185     case PseudoScrollbarTrack:
186         return SCROLLBAR_TRACK;
187     case PseudoScrollbarTrackPiece:
188         return SCROLLBAR_TRACK_PIECE;
189     case PseudoResizer:
190         return RESIZER;
191     case PseudoUnknown:
192     case PseudoEmpty:
193     case PseudoFirstChild:
194     case PseudoFirstOfType:
195     case PseudoLastChild:
196     case PseudoLastOfType:
197     case PseudoOnlyChild:
198     case PseudoOnlyOfType:
199     case PseudoNthChild:
200     case PseudoNthOfType:
201     case PseudoNthLastChild:
202     case PseudoNthLastOfType:
203     case PseudoLink:
204     case PseudoVisited:
205     case PseudoAny:
206     case PseudoAnyLink:
207     case PseudoAutofill:
208     case PseudoHover:
209     case PseudoDrag:
210     case PseudoFocus:
211     case PseudoActive:
212     case PseudoChecked:
213     case PseudoEnabled:
214     case PseudoFullPageMedia:
215     case PseudoDefault:
216     case PseudoDisabled:
217     case PseudoOptional:
218     case PseudoRequired:
219     case PseudoReadOnly:
220     case PseudoReadWrite:
221     case PseudoValid:
222     case PseudoInvalid:
223     case PseudoIndeterminate:
224     case PseudoTarget:
225     case PseudoLang:
226     case PseudoNot:
227     case PseudoRoot:
228     case PseudoScope:
229     case PseudoScrollbarBack:
230     case PseudoScrollbarForward:
231     case PseudoWindowInactive:
232     case PseudoCornerPresent:
233     case PseudoDecrement:
234     case PseudoIncrement:
235     case PseudoHorizontal:
236     case PseudoVertical:
237     case PseudoStart:
238     case PseudoEnd:
239     case PseudoDoubleButton:
240     case PseudoSingleButton:
241     case PseudoNoButton:
242     case PseudoFirstPage:
243     case PseudoLeftPage:
244     case PseudoRightPage:
245     case PseudoInRange:
246     case PseudoOutOfRange:
247     case PseudoUserAgentCustomElement:
248     case PseudoWebKitCustomElement:
249     case PseudoCue:
250     case PseudoFutureCue:
251     case PseudoPastCue:
252     case PseudoUnresolved:
253     case PseudoContent:
254     case PseudoHost:
255     case PseudoHostContext:
256     case PseudoShadow:
257     case PseudoFullScreen:
258     case PseudoFullScreenDocument:
259     case PseudoFullScreenAncestor:
260     case PseudoSpatialNavigationFocus:
261     case PseudoListBox:
262         return NOPSEUDO;
263     case PseudoNotParsed:
264         ASSERT_NOT_REACHED();
265         return NOPSEUDO;
266     }
267 
268     ASSERT_NOT_REACHED();
269     return NOPSEUDO;
270 }
271 
272 // Could be made smaller and faster by replacing pointer with an
273 // offset into a string buffer and making the bit fields smaller but
274 // that could not be maintained by hand.
275 struct NameToPseudoStruct {
276     const char* string;
277     unsigned type:8;
278 };
279 
280 // These tables should be kept sorted.
281 const static NameToPseudoStruct pseudoTypeWithoutArgumentsMap[] = {
282 {"-internal-list-box",            CSSSelector::PseudoListBox},
283 {"-internal-media-controls-cast-button", CSSSelector::PseudoWebKitCustomElement},
284 {"-internal-media-controls-overlay-cast-button", CSSSelector::PseudoWebKitCustomElement},
285 {"-internal-spatial-navigation-focus", CSSSelector::PseudoSpatialNavigationFocus},
286 {"-webkit-any-link",              CSSSelector::PseudoAnyLink},
287 {"-webkit-autofill",              CSSSelector::PseudoAutofill},
288 {"-webkit-drag",                  CSSSelector::PseudoDrag},
289 {"-webkit-full-page-media",       CSSSelector::PseudoFullPageMedia},
290 {"-webkit-full-screen",           CSSSelector::PseudoFullScreen},
291 {"-webkit-full-screen-ancestor",  CSSSelector::PseudoFullScreenAncestor},
292 {"-webkit-full-screen-document",  CSSSelector::PseudoFullScreenDocument},
293 {"-webkit-resizer",               CSSSelector::PseudoResizer},
294 {"-webkit-scrollbar",             CSSSelector::PseudoScrollbar},
295 {"-webkit-scrollbar-button",      CSSSelector::PseudoScrollbarButton},
296 {"-webkit-scrollbar-corner",      CSSSelector::PseudoScrollbarCorner},
297 {"-webkit-scrollbar-thumb",       CSSSelector::PseudoScrollbarThumb},
298 {"-webkit-scrollbar-track",       CSSSelector::PseudoScrollbarTrack},
299 {"-webkit-scrollbar-track-piece", CSSSelector::PseudoScrollbarTrackPiece},
300 {"active",                        CSSSelector::PseudoActive},
301 {"after",                         CSSSelector::PseudoAfter},
302 {"backdrop",                      CSSSelector::PseudoBackdrop},
303 {"before",                        CSSSelector::PseudoBefore},
304 {"checked",                       CSSSelector::PseudoChecked},
305 {"content",                       CSSSelector::PseudoContent},
306 {"corner-present",                CSSSelector::PseudoCornerPresent},
307 {"cue",                           CSSSelector::PseudoWebKitCustomElement},
308 {"decrement",                     CSSSelector::PseudoDecrement},
309 {"default",                       CSSSelector::PseudoDefault},
310 {"disabled",                      CSSSelector::PseudoDisabled},
311 {"double-button",                 CSSSelector::PseudoDoubleButton},
312 {"empty",                         CSSSelector::PseudoEmpty},
313 {"enabled",                       CSSSelector::PseudoEnabled},
314 {"end",                           CSSSelector::PseudoEnd},
315 {"first",                         CSSSelector::PseudoFirstPage},
316 {"first-child",                   CSSSelector::PseudoFirstChild},
317 {"first-letter",                  CSSSelector::PseudoFirstLetter},
318 {"first-line",                    CSSSelector::PseudoFirstLine},
319 {"first-of-type",                 CSSSelector::PseudoFirstOfType},
320 {"focus",                         CSSSelector::PseudoFocus},
321 {"future",                        CSSSelector::PseudoFutureCue},
322 {"horizontal",                    CSSSelector::PseudoHorizontal},
323 {"host",                          CSSSelector::PseudoHost},
324 {"hover",                         CSSSelector::PseudoHover},
325 {"in-range",                      CSSSelector::PseudoInRange},
326 {"increment",                     CSSSelector::PseudoIncrement},
327 {"indeterminate",                 CSSSelector::PseudoIndeterminate},
328 {"invalid",                       CSSSelector::PseudoInvalid},
329 {"last-child",                    CSSSelector::PseudoLastChild},
330 {"last-of-type",                  CSSSelector::PseudoLastOfType},
331 {"left",                          CSSSelector::PseudoLeftPage},
332 {"link",                          CSSSelector::PseudoLink},
333 {"no-button",                     CSSSelector::PseudoNoButton},
334 {"only-child",                    CSSSelector::PseudoOnlyChild},
335 {"only-of-type",                  CSSSelector::PseudoOnlyOfType},
336 {"optional",                      CSSSelector::PseudoOptional},
337 {"out-of-range",                  CSSSelector::PseudoOutOfRange},
338 {"past",                          CSSSelector::PseudoPastCue},
339 {"read-only",                     CSSSelector::PseudoReadOnly},
340 {"read-write",                    CSSSelector::PseudoReadWrite},
341 {"required",                      CSSSelector::PseudoRequired},
342 {"right",                         CSSSelector::PseudoRightPage},
343 {"root",                          CSSSelector::PseudoRoot},
344 {"scope",                         CSSSelector::PseudoScope},
345 {"selection",                     CSSSelector::PseudoSelection},
346 {"shadow",                        CSSSelector::PseudoShadow},
347 {"single-button",                 CSSSelector::PseudoSingleButton},
348 {"start",                         CSSSelector::PseudoStart},
349 {"target",                        CSSSelector::PseudoTarget},
350 {"unresolved",                    CSSSelector::PseudoUnresolved},
351 {"valid",                         CSSSelector::PseudoValid},
352 {"vertical",                      CSSSelector::PseudoVertical},
353 {"visited",                       CSSSelector::PseudoVisited},
354 {"window-inactive",               CSSSelector::PseudoWindowInactive},
355 };
356 
357 const static NameToPseudoStruct pseudoTypeWithArgumentsMap[] = {
358 {"-webkit-any",      CSSSelector::PseudoAny},
359 {"cue",              CSSSelector::PseudoCue},
360 {"host",             CSSSelector::PseudoHost},
361 {"host-context",     CSSSelector::PseudoHostContext},
362 {"lang",             CSSSelector::PseudoLang},
363 {"not",              CSSSelector::PseudoNot},
364 {"nth-child",        CSSSelector::PseudoNthChild},
365 {"nth-last-child",   CSSSelector::PseudoNthLastChild},
366 {"nth-last-of-type", CSSSelector::PseudoNthLastOfType},
367 {"nth-of-type",      CSSSelector::PseudoNthOfType},
368 };
369 
370 class NameToPseudoCompare {
371 public:
NameToPseudoCompare(const AtomicString & key)372     NameToPseudoCompare(const AtomicString& key) : m_key(key) { ASSERT(m_key.is8Bit()); }
373 
operator ()(const NameToPseudoStruct & entry,const NameToPseudoStruct &)374     bool operator()(const NameToPseudoStruct& entry, const NameToPseudoStruct&)
375     {
376         ASSERT(entry.string);
377         const char* key = reinterpret_cast<const char*>(m_key.characters8());
378         // If strncmp returns 0, then either the keys are equal, or |m_key| sorts before |entry|.
379         return strncmp(entry.string, key, m_key.length()) < 0;
380     }
381 
382 private:
383     const AtomicString& m_key;
384 };
385 
nameToPseudoType(const AtomicString & name,bool hasArguments)386 static CSSSelector::PseudoType nameToPseudoType(const AtomicString& name, bool hasArguments)
387 {
388     if (name.isNull() || !name.is8Bit())
389         return CSSSelector::PseudoUnknown;
390 
391     const NameToPseudoStruct* pseudoTypeMap;
392     const NameToPseudoStruct* pseudoTypeMapEnd;
393     if (hasArguments) {
394         pseudoTypeMap = pseudoTypeWithArgumentsMap;
395         pseudoTypeMapEnd = pseudoTypeWithArgumentsMap + WTF_ARRAY_LENGTH(pseudoTypeWithArgumentsMap);
396     } else {
397         pseudoTypeMap = pseudoTypeWithoutArgumentsMap;
398         pseudoTypeMapEnd = pseudoTypeWithoutArgumentsMap + WTF_ARRAY_LENGTH(pseudoTypeWithoutArgumentsMap);
399     }
400     NameToPseudoStruct dummyKey = { 0, CSSSelector::PseudoUnknown };
401     const NameToPseudoStruct* match = std::lower_bound(pseudoTypeMap, pseudoTypeMapEnd, dummyKey, NameToPseudoCompare(name));
402     if (match == pseudoTypeMapEnd || match->string != name.string())
403         return CSSSelector::PseudoUnknown;
404 
405     return static_cast<CSSSelector::PseudoType>(match->type);
406 }
407 
408 #ifndef NDEBUG
show(int indent) const409 void CSSSelector::show(int indent) const
410 {
411     printf("%*sselectorText(): %s\n", indent, "", selectorText().ascii().data());
412     printf("%*sm_match: %d\n", indent, "", m_match);
413     printf("%*sisCustomPseudoElement(): %d\n", indent, "", isCustomPseudoElement());
414     if (m_match != Tag)
415         printf("%*svalue(): %s\n", indent, "", value().ascii().data());
416     printf("%*spseudoType(): %d\n", indent, "", pseudoType());
417     if (m_match == Tag)
418         printf("%*stagQName().localName: %s\n", indent, "", tagQName().localName().ascii().data());
419     printf("%*sisAttributeSelector(): %d\n", indent, "", isAttributeSelector());
420     if (isAttributeSelector())
421         printf("%*sattribute(): %s\n", indent, "", attribute().localName().ascii().data());
422     printf("%*sargument(): %s\n", indent, "", argument().ascii().data());
423     printf("%*sspecificity(): %u\n", indent, "", specificity());
424     if (tagHistory()) {
425         printf("\n%*s--> (relation == %d)\n", indent, "", relation());
426         tagHistory()->show(indent + 2);
427     } else {
428         printf("\n%*s--> (relation == %d)\n", indent, "", relation());
429     }
430 }
431 
show() const432 void CSSSelector::show() const
433 {
434     printf("\n******* CSSSelector::show(\"%s\") *******\n", selectorText().ascii().data());
435     show(2);
436     printf("******* end *******\n");
437 }
438 #endif
439 
parsePseudoType(const AtomicString & name,bool hasArguments)440 CSSSelector::PseudoType CSSSelector::parsePseudoType(const AtomicString& name, bool hasArguments)
441 {
442     CSSSelector::PseudoType pseudoType = nameToPseudoType(name, hasArguments);
443     if (pseudoType != PseudoUnknown)
444         return pseudoType;
445 
446     if (name.startsWith("-webkit-"))
447         return PseudoWebKitCustomElement;
448     if (name.startsWith("cue"))
449         return PseudoUserAgentCustomElement;
450 
451     return PseudoUnknown;
452 }
453 
extractPseudoType() const454 void CSSSelector::extractPseudoType() const
455 {
456     if (m_match != PseudoClass && m_match != PseudoElement && m_match != PagePseudoClass)
457         return;
458 
459     m_pseudoType = parsePseudoType(value(), !argument().isNull() || selectorList());
460 
461     bool element = false; // pseudo-element
462     bool compat = false; // single colon compatbility mode
463     bool isPagePseudoClass = false; // Page pseudo-class
464 
465     switch (m_pseudoType) {
466     case PseudoAfter:
467     case PseudoBefore:
468     case PseudoCue:
469     case PseudoFirstLetter:
470     case PseudoFirstLine:
471         compat = true;
472     case PseudoBackdrop:
473     case PseudoResizer:
474     case PseudoScrollbar:
475     case PseudoScrollbarCorner:
476     case PseudoScrollbarButton:
477     case PseudoScrollbarThumb:
478     case PseudoScrollbarTrack:
479     case PseudoScrollbarTrackPiece:
480     case PseudoSelection:
481     case PseudoUserAgentCustomElement:
482     case PseudoWebKitCustomElement:
483     case PseudoContent:
484     case PseudoShadow:
485         element = true;
486         break;
487     case PseudoUnknown:
488     case PseudoEmpty:
489     case PseudoFirstChild:
490     case PseudoFirstOfType:
491     case PseudoLastChild:
492     case PseudoLastOfType:
493     case PseudoOnlyChild:
494     case PseudoOnlyOfType:
495     case PseudoNthChild:
496     case PseudoNthOfType:
497     case PseudoNthLastChild:
498     case PseudoNthLastOfType:
499     case PseudoLink:
500     case PseudoVisited:
501     case PseudoAny:
502     case PseudoAnyLink:
503     case PseudoAutofill:
504     case PseudoHover:
505     case PseudoDrag:
506     case PseudoFocus:
507     case PseudoActive:
508     case PseudoChecked:
509     case PseudoEnabled:
510     case PseudoFullPageMedia:
511     case PseudoDefault:
512     case PseudoDisabled:
513     case PseudoOptional:
514     case PseudoRequired:
515     case PseudoReadOnly:
516     case PseudoReadWrite:
517     case PseudoScope:
518     case PseudoValid:
519     case PseudoInvalid:
520     case PseudoIndeterminate:
521     case PseudoTarget:
522     case PseudoLang:
523     case PseudoNot:
524     case PseudoRoot:
525     case PseudoScrollbarBack:
526     case PseudoScrollbarForward:
527     case PseudoWindowInactive:
528     case PseudoCornerPresent:
529     case PseudoDecrement:
530     case PseudoIncrement:
531     case PseudoHorizontal:
532     case PseudoVertical:
533     case PseudoStart:
534     case PseudoEnd:
535     case PseudoDoubleButton:
536     case PseudoSingleButton:
537     case PseudoNoButton:
538     case PseudoNotParsed:
539     case PseudoFullScreen:
540     case PseudoFullScreenDocument:
541     case PseudoFullScreenAncestor:
542     case PseudoInRange:
543     case PseudoOutOfRange:
544     case PseudoFutureCue:
545     case PseudoPastCue:
546     case PseudoHost:
547     case PseudoHostContext:
548     case PseudoUnresolved:
549     case PseudoSpatialNavigationFocus:
550     case PseudoListBox:
551         break;
552     case PseudoFirstPage:
553     case PseudoLeftPage:
554     case PseudoRightPage:
555         isPagePseudoClass = true;
556         break;
557     }
558 
559     bool matchPagePseudoClass = (m_match == PagePseudoClass);
560     if (matchPagePseudoClass != isPagePseudoClass)
561         m_pseudoType = PseudoUnknown;
562     else if (m_match == PseudoClass && element) {
563         if (!compat)
564             m_pseudoType = PseudoUnknown;
565         else
566             m_match = PseudoElement;
567     } else if (m_match == PseudoElement && !element)
568         m_pseudoType = PseudoUnknown;
569 }
570 
operator ==(const CSSSelector & other) const571 bool CSSSelector::operator==(const CSSSelector& other) const
572 {
573     const CSSSelector* sel1 = this;
574     const CSSSelector* sel2 = &other;
575 
576     while (sel1 && sel2) {
577         if (sel1->attribute() != sel2->attribute()
578             || sel1->relation() != sel2->relation()
579             || sel1->m_match != sel2->m_match
580             || sel1->value() != sel2->value()
581             || sel1->pseudoType() != sel2->pseudoType()
582             || sel1->argument() != sel2->argument()) {
583             return false;
584         }
585         if (sel1->m_match == Tag) {
586             if (sel1->tagQName() != sel2->tagQName())
587                 return false;
588         }
589         sel1 = sel1->tagHistory();
590         sel2 = sel2->tagHistory();
591     }
592 
593     if (sel1 || sel2)
594         return false;
595 
596     return true;
597 }
598 
selectorText(const String & rightSide) const599 String CSSSelector::selectorText(const String& rightSide) const
600 {
601     StringBuilder str;
602 
603     if (m_match == CSSSelector::Tag && !m_tagIsForNamespaceRule) {
604         if (tagQName().prefix().isNull())
605             str.append(tagQName().localName());
606         else {
607             str.append(tagQName().prefix().string());
608             str.append('|');
609             str.append(tagQName().localName());
610         }
611     }
612 
613     const CSSSelector* cs = this;
614     while (true) {
615         if (cs->m_match == CSSSelector::Id) {
616             str.append('#');
617             serializeIdentifier(cs->value(), str);
618         } else if (cs->m_match == CSSSelector::Class) {
619             str.append('.');
620             serializeIdentifier(cs->value(), str);
621         } else if (cs->m_match == CSSSelector::PseudoClass || cs->m_match == CSSSelector::PagePseudoClass) {
622             str.append(':');
623             str.append(cs->value());
624 
625             switch (cs->pseudoType()) {
626             case PseudoNot:
627                 ASSERT(cs->selectorList());
628                 str.append('(');
629                 str.append(cs->selectorList()->first()->selectorText());
630                 str.append(')');
631                 break;
632             case PseudoLang:
633             case PseudoNthChild:
634             case PseudoNthLastChild:
635             case PseudoNthOfType:
636             case PseudoNthLastOfType:
637                 str.append('(');
638                 str.append(cs->argument());
639                 str.append(')');
640                 break;
641             case PseudoAny: {
642                 str.append('(');
643                 const CSSSelector* firstSubSelector = cs->selectorList()->first();
644                 for (const CSSSelector* subSelector = firstSubSelector; subSelector; subSelector = CSSSelectorList::next(*subSelector)) {
645                     if (subSelector != firstSubSelector)
646                         str.append(',');
647                     str.append(subSelector->selectorText());
648                 }
649                 str.append(')');
650                 break;
651             }
652             case PseudoHost:
653             case PseudoHostContext: {
654                 if (cs->selectorList()) {
655                     str.append('(');
656                     const CSSSelector* firstSubSelector = cs->selectorList()->first();
657                     for (const CSSSelector* subSelector = firstSubSelector; subSelector; subSelector = CSSSelectorList::next(*subSelector)) {
658                         if (subSelector != firstSubSelector)
659                             str.append(',');
660                         str.append(subSelector->selectorText());
661                     }
662                     str.append(')');
663                 }
664                 break;
665             }
666             default:
667                 break;
668             }
669         } else if (cs->m_match == CSSSelector::PseudoElement) {
670             str.appendLiteral("::");
671             str.append(cs->value());
672 
673             if (cs->pseudoType() == PseudoContent) {
674                 if (cs->relation() == CSSSelector::SubSelector && cs->tagHistory())
675                     return cs->tagHistory()->selectorText() + str.toString() + rightSide;
676             }
677         } else if (cs->isAttributeSelector()) {
678             str.append('[');
679             const AtomicString& prefix = cs->attribute().prefix();
680             if (!prefix.isNull()) {
681                 str.append(prefix);
682                 str.append('|');
683             }
684             str.append(cs->attribute().localName());
685             switch (cs->m_match) {
686                 case CSSSelector::Exact:
687                     str.append('=');
688                     break;
689                 case CSSSelector::Set:
690                     // set has no operator or value, just the attrName
691                     str.append(']');
692                     break;
693                 case CSSSelector::List:
694                     str.appendLiteral("~=");
695                     break;
696                 case CSSSelector::Hyphen:
697                     str.appendLiteral("|=");
698                     break;
699                 case CSSSelector::Begin:
700                     str.appendLiteral("^=");
701                     break;
702                 case CSSSelector::End:
703                     str.appendLiteral("$=");
704                     break;
705                 case CSSSelector::Contain:
706                     str.appendLiteral("*=");
707                     break;
708                 default:
709                     break;
710             }
711             if (cs->m_match != CSSSelector::Set) {
712                 serializeString(cs->value(), str);
713                 if (cs->attributeMatchType() == CaseInsensitive)
714                     str.appendLiteral(" i");
715                 str.append(']');
716             }
717         }
718         if (cs->relation() != CSSSelector::SubSelector || !cs->tagHistory())
719             break;
720         cs = cs->tagHistory();
721     }
722 
723     if (const CSSSelector* tagHistory = cs->tagHistory()) {
724         switch (cs->relation()) {
725         case CSSSelector::Descendant:
726             return tagHistory->selectorText(" " + str.toString() + rightSide);
727         case CSSSelector::Child:
728             return tagHistory->selectorText(" > " + str.toString() + rightSide);
729         case CSSSelector::ShadowDeep:
730             return tagHistory->selectorText(" /deep/ " + str.toString() + rightSide);
731         case CSSSelector::DirectAdjacent:
732             return tagHistory->selectorText(" + " + str.toString() + rightSide);
733         case CSSSelector::IndirectAdjacent:
734             return tagHistory->selectorText(" ~ " + str.toString() + rightSide);
735         case CSSSelector::SubSelector:
736             ASSERT_NOT_REACHED();
737         case CSSSelector::ShadowPseudo:
738             return tagHistory->selectorText(str.toString() + rightSide);
739         }
740     }
741     return str.toString() + rightSide;
742 }
743 
setAttribute(const QualifiedName & value,AttributeMatchType matchType)744 void CSSSelector::setAttribute(const QualifiedName& value, AttributeMatchType matchType)
745 {
746     createRareData();
747     m_data.m_rareData->m_attribute = value;
748     m_data.m_rareData->m_bits.m_attributeMatchType = matchType;
749 }
750 
setArgument(const AtomicString & value)751 void CSSSelector::setArgument(const AtomicString& value)
752 {
753     createRareData();
754     m_data.m_rareData->m_argument = value;
755 }
756 
setSelectorList(PassOwnPtr<CSSSelectorList> selectorList)757 void CSSSelector::setSelectorList(PassOwnPtr<CSSSelectorList> selectorList)
758 {
759     createRareData();
760     m_data.m_rareData->m_selectorList = selectorList;
761 }
762 
validateSubSelector(const CSSSelector * selector)763 static bool validateSubSelector(const CSSSelector* selector)
764 {
765     switch (selector->match()) {
766     case CSSSelector::Tag:
767     case CSSSelector::Id:
768     case CSSSelector::Class:
769     case CSSSelector::Exact:
770     case CSSSelector::Set:
771     case CSSSelector::List:
772     case CSSSelector::Hyphen:
773     case CSSSelector::Contain:
774     case CSSSelector::Begin:
775     case CSSSelector::End:
776         return true;
777     case CSSSelector::PseudoElement:
778     case CSSSelector::Unknown:
779         return false;
780     case CSSSelector::PagePseudoClass:
781     case CSSSelector::PseudoClass:
782         break;
783     }
784 
785     switch (selector->pseudoType()) {
786     case CSSSelector::PseudoEmpty:
787     case CSSSelector::PseudoLink:
788     case CSSSelector::PseudoVisited:
789     case CSSSelector::PseudoTarget:
790     case CSSSelector::PseudoEnabled:
791     case CSSSelector::PseudoDisabled:
792     case CSSSelector::PseudoChecked:
793     case CSSSelector::PseudoIndeterminate:
794     case CSSSelector::PseudoNthChild:
795     case CSSSelector::PseudoNthLastChild:
796     case CSSSelector::PseudoNthOfType:
797     case CSSSelector::PseudoNthLastOfType:
798     case CSSSelector::PseudoFirstChild:
799     case CSSSelector::PseudoLastChild:
800     case CSSSelector::PseudoFirstOfType:
801     case CSSSelector::PseudoLastOfType:
802     case CSSSelector::PseudoOnlyOfType:
803     case CSSSelector::PseudoHost:
804     case CSSSelector::PseudoHostContext:
805     case CSSSelector::PseudoNot:
806     case CSSSelector::PseudoSpatialNavigationFocus:
807     case CSSSelector::PseudoListBox:
808         return true;
809     default:
810         return false;
811     }
812 }
813 
isCompound() const814 bool CSSSelector::isCompound() const
815 {
816     if (!validateSubSelector(this))
817         return false;
818 
819     const CSSSelector* prevSubSelector = this;
820     const CSSSelector* subSelector = tagHistory();
821 
822     while (subSelector) {
823         if (prevSubSelector->relation() != CSSSelector::SubSelector)
824             return false;
825         if (!validateSubSelector(subSelector))
826             return false;
827 
828         prevSubSelector = subSelector;
829         subSelector = subSelector->tagHistory();
830     }
831 
832     return true;
833 }
834 
parseNth() const835 bool CSSSelector::parseNth() const
836 {
837     if (!m_hasRareData)
838         return false;
839     if (m_parsedNth)
840         return true;
841     m_parsedNth = m_data.m_rareData->parseNth();
842     return m_parsedNth;
843 }
844 
matchNth(int count) const845 bool CSSSelector::matchNth(int count) const
846 {
847     ASSERT(m_hasRareData);
848     return m_data.m_rareData->matchNth(count);
849 }
850 
RareData(const AtomicString & value)851 CSSSelector::RareData::RareData(const AtomicString& value)
852     : m_value(value)
853     , m_bits()
854     , m_attribute(anyQName())
855     , m_argument(nullAtom)
856 {
857 }
858 
~RareData()859 CSSSelector::RareData::~RareData()
860 {
861 }
862 
863 // a helper function for parsing nth-arguments
parseNth()864 bool CSSSelector::RareData::parseNth()
865 {
866     String argument = m_argument.lower();
867 
868     if (argument.isEmpty())
869         return false;
870 
871     int nthA = 0;
872     int nthB = 0;
873     if (argument == "odd") {
874         nthA = 2;
875         nthB = 1;
876     } else if (argument == "even") {
877         nthA = 2;
878         nthB = 0;
879     } else {
880         size_t n = argument.find('n');
881         if (n != kNotFound) {
882             if (argument[0] == '-') {
883                 if (n == 1)
884                     nthA = -1; // -n == -1n
885                 else
886                     nthA = argument.substring(0, n).toInt();
887             } else if (!n) {
888                 nthA = 1; // n == 1n
889             } else {
890                 nthA = argument.substring(0, n).toInt();
891             }
892 
893             size_t p = argument.find('+', n);
894             if (p != kNotFound) {
895                 nthB = argument.substring(p + 1, argument.length() - p - 1).toInt();
896             } else {
897                 p = argument.find('-', n);
898                 if (p != kNotFound)
899                     nthB = -argument.substring(p + 1, argument.length() - p - 1).toInt();
900             }
901         } else {
902             nthB = argument.toInt();
903         }
904     }
905     setNthAValue(nthA);
906     setNthBValue(nthB);
907     return true;
908 }
909 
910 // a helper function for checking nth-arguments
matchNth(int count)911 bool CSSSelector::RareData::matchNth(int count)
912 {
913     if (!nthAValue())
914         return count == nthBValue();
915     if (nthAValue() > 0) {
916         if (count < nthBValue())
917             return false;
918         return (count - nthBValue()) % nthAValue() == 0;
919     }
920     if (count > nthBValue())
921         return false;
922     return (nthBValue() - count) % (-nthAValue()) == 0;
923 }
924 
925 } // namespace blink
926