• 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 WebCore {
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         return NOPSEUDO;
261     case PseudoNotParsed:
262         ASSERT_NOT_REACHED();
263         return NOPSEUDO;
264     }
265 
266     ASSERT_NOT_REACHED();
267     return NOPSEUDO;
268 }
269 
270 // Could be made smaller and faster by replacing pointer with an
271 // offset into a string buffer and making the bit fields smaller but
272 // that could not be maintained by hand.
273 struct NameToPseudoStruct {
274     const char* string;
275     unsigned type:8;
276 };
277 
278 // This table should be kept sorted.
279 const static NameToPseudoStruct pseudoTypeMap[] = {
280 {"-webkit-any(",                  CSSSelector::PseudoAny},
281 {"-webkit-any-link",              CSSSelector::PseudoAnyLink},
282 {"-webkit-autofill",              CSSSelector::PseudoAutofill},
283 {"-webkit-drag",                  CSSSelector::PseudoDrag},
284 {"-webkit-full-page-media",       CSSSelector::PseudoFullPageMedia},
285 {"-webkit-full-screen",           CSSSelector::PseudoFullScreen},
286 {"-webkit-full-screen-ancestor",  CSSSelector::PseudoFullScreenAncestor},
287 {"-webkit-full-screen-document",  CSSSelector::PseudoFullScreenDocument},
288 {"-webkit-resizer",               CSSSelector::PseudoResizer},
289 {"-webkit-scrollbar",             CSSSelector::PseudoScrollbar},
290 {"-webkit-scrollbar-button",      CSSSelector::PseudoScrollbarButton},
291 {"-webkit-scrollbar-corner",      CSSSelector::PseudoScrollbarCorner},
292 {"-webkit-scrollbar-thumb",       CSSSelector::PseudoScrollbarThumb},
293 {"-webkit-scrollbar-track",       CSSSelector::PseudoScrollbarTrack},
294 {"-webkit-scrollbar-track-piece", CSSSelector::PseudoScrollbarTrackPiece},
295 {"active",                        CSSSelector::PseudoActive},
296 {"after",                         CSSSelector::PseudoAfter},
297 {"backdrop",                      CSSSelector::PseudoBackdrop},
298 {"before",                        CSSSelector::PseudoBefore},
299 {"checked",                       CSSSelector::PseudoChecked},
300 {"content",                       CSSSelector::PseudoContent},
301 {"corner-present",                CSSSelector::PseudoCornerPresent},
302 {"cue",                           CSSSelector::PseudoWebKitCustomElement},
303 {"cue(",                          CSSSelector::PseudoCue},
304 {"decrement",                     CSSSelector::PseudoDecrement},
305 {"default",                       CSSSelector::PseudoDefault},
306 {"disabled",                      CSSSelector::PseudoDisabled},
307 {"double-button",                 CSSSelector::PseudoDoubleButton},
308 {"empty",                         CSSSelector::PseudoEmpty},
309 {"enabled",                       CSSSelector::PseudoEnabled},
310 {"end",                           CSSSelector::PseudoEnd},
311 {"first",                         CSSSelector::PseudoFirstPage},
312 {"first-child",                   CSSSelector::PseudoFirstChild},
313 {"first-letter",                  CSSSelector::PseudoFirstLetter},
314 {"first-line",                    CSSSelector::PseudoFirstLine},
315 {"first-of-type",                 CSSSelector::PseudoFirstOfType},
316 {"focus",                         CSSSelector::PseudoFocus},
317 {"future",                        CSSSelector::PseudoFutureCue},
318 {"horizontal",                    CSSSelector::PseudoHorizontal},
319 {"host",                          CSSSelector::PseudoHost},
320 {"host(",                         CSSSelector::PseudoHost},
321 {"host-context(",                 CSSSelector::PseudoHostContext},
322 {"hover",                         CSSSelector::PseudoHover},
323 {"in-range",                      CSSSelector::PseudoInRange},
324 {"increment",                     CSSSelector::PseudoIncrement},
325 {"indeterminate",                 CSSSelector::PseudoIndeterminate},
326 {"invalid",                       CSSSelector::PseudoInvalid},
327 {"lang(",                         CSSSelector::PseudoLang},
328 {"last-child",                    CSSSelector::PseudoLastChild},
329 {"last-of-type",                  CSSSelector::PseudoLastOfType},
330 {"left",                          CSSSelector::PseudoLeftPage},
331 {"link",                          CSSSelector::PseudoLink},
332 {"no-button",                     CSSSelector::PseudoNoButton},
333 {"not(",                          CSSSelector::PseudoNot},
334 {"nth-child(",                    CSSSelector::PseudoNthChild},
335 {"nth-last-child(",               CSSSelector::PseudoNthLastChild},
336 {"nth-last-of-type(",             CSSSelector::PseudoNthLastOfType},
337 {"nth-of-type(",                  CSSSelector::PseudoNthOfType},
338 {"only-child",                    CSSSelector::PseudoOnlyChild},
339 {"only-of-type",                  CSSSelector::PseudoOnlyOfType},
340 {"optional",                      CSSSelector::PseudoOptional},
341 {"out-of-range",                  CSSSelector::PseudoOutOfRange},
342 {"past",                          CSSSelector::PseudoPastCue},
343 {"read-only",                     CSSSelector::PseudoReadOnly},
344 {"read-write",                    CSSSelector::PseudoReadWrite},
345 {"required",                      CSSSelector::PseudoRequired},
346 {"right",                         CSSSelector::PseudoRightPage},
347 {"root",                          CSSSelector::PseudoRoot},
348 {"scope",                         CSSSelector::PseudoScope},
349 {"selection",                     CSSSelector::PseudoSelection},
350 {"shadow",                        CSSSelector::PseudoShadow},
351 {"single-button",                 CSSSelector::PseudoSingleButton},
352 {"start",                         CSSSelector::PseudoStart},
353 {"target",                        CSSSelector::PseudoTarget},
354 {"unresolved",                    CSSSelector::PseudoUnresolved},
355 {"valid",                         CSSSelector::PseudoValid},
356 {"vertical",                      CSSSelector::PseudoVertical},
357 {"visited",                       CSSSelector::PseudoVisited},
358 {"window-inactive",               CSSSelector::PseudoWindowInactive},
359 };
360 
361 class NameToPseudoCompare {
362 public:
NameToPseudoCompare(const AtomicString & key)363     NameToPseudoCompare(const AtomicString& key) : m_key(key) { ASSERT(m_key.is8Bit()); }
364 
operator ()(const NameToPseudoStruct & entry,const NameToPseudoStruct &)365     bool operator()(const NameToPseudoStruct& entry, const NameToPseudoStruct&)
366     {
367         ASSERT(entry.string);
368         const char* key = reinterpret_cast<const char*>(m_key.characters8());
369         // If strncmp returns 0, then either the keys are equal, or |m_key| sorts before |entry|.
370         return strncmp(entry.string, key, m_key.length()) < 0;
371     }
372 
373 private:
374     const AtomicString& m_key;
375 };
376 
nameToPseudoType(const AtomicString & name)377 static CSSSelector::PseudoType nameToPseudoType(const AtomicString& name)
378 {
379     if (name.isNull() || !name.is8Bit())
380         return CSSSelector::PseudoUnknown;
381 
382     const NameToPseudoStruct* pseudoTypeMapEnd = pseudoTypeMap + WTF_ARRAY_LENGTH(pseudoTypeMap);
383     NameToPseudoStruct dummyKey = { 0, CSSSelector::PseudoUnknown };
384     const NameToPseudoStruct* match = std::lower_bound(pseudoTypeMap, pseudoTypeMapEnd, dummyKey, NameToPseudoCompare(name));
385     if (match == pseudoTypeMapEnd || match->string != name.string())
386         return CSSSelector::PseudoUnknown;
387 
388     return static_cast<CSSSelector::PseudoType>(match->type);
389 }
390 
391 #ifndef NDEBUG
show(int indent) const392 void CSSSelector::show(int indent) const
393 {
394     printf("%*sselectorText(): %s\n", indent, "", selectorText().ascii().data());
395     printf("%*sm_match: %d\n", indent, "", m_match);
396     printf("%*sisCustomPseudoElement(): %d\n", indent, "", isCustomPseudoElement());
397     if (m_match != Tag)
398         printf("%*svalue(): %s\n", indent, "", value().ascii().data());
399     printf("%*spseudoType(): %d\n", indent, "", pseudoType());
400     if (m_match == Tag)
401         printf("%*stagQName().localName: %s\n", indent, "", tagQName().localName().ascii().data());
402     printf("%*sisAttributeSelector(): %d\n", indent, "", isAttributeSelector());
403     if (isAttributeSelector())
404         printf("%*sattribute(): %s\n", indent, "", attribute().localName().ascii().data());
405     printf("%*sargument(): %s\n", indent, "", argument().ascii().data());
406     printf("%*sspecificity(): %u\n", indent, "", specificity());
407     if (tagHistory()) {
408         printf("\n%*s--> (relation == %d)\n", indent, "", relation());
409         tagHistory()->show(indent + 2);
410     } else {
411         printf("\n%*s--> (relation == %d)\n", indent, "", relation());
412     }
413 }
414 
show() const415 void CSSSelector::show() const
416 {
417     printf("\n******* CSSSelector::show(\"%s\") *******\n", selectorText().ascii().data());
418     show(2);
419     printf("******* end *******\n");
420 }
421 #endif
422 
parsePseudoType(const AtomicString & name)423 CSSSelector::PseudoType CSSSelector::parsePseudoType(const AtomicString& name)
424 {
425     CSSSelector::PseudoType pseudoType = nameToPseudoType(name);
426     if (pseudoType != PseudoUnknown)
427         return pseudoType;
428 
429     if (name.startsWith("-webkit-"))
430         return PseudoWebKitCustomElement;
431     if (name.startsWith("cue"))
432         return PseudoUserAgentCustomElement;
433 
434     return PseudoUnknown;
435 }
436 
extractPseudoType() const437 void CSSSelector::extractPseudoType() const
438 {
439     if (m_match != PseudoClass && m_match != PseudoElement && m_match != PagePseudoClass)
440         return;
441 
442     m_pseudoType = parsePseudoType(value());
443 
444     bool element = false; // pseudo-element
445     bool compat = false; // single colon compatbility mode
446     bool isPagePseudoClass = false; // Page pseudo-class
447 
448     switch (m_pseudoType) {
449     case PseudoAfter:
450     case PseudoBefore:
451     case PseudoCue:
452     case PseudoFirstLetter:
453     case PseudoFirstLine:
454         compat = true;
455     case PseudoBackdrop:
456     case PseudoResizer:
457     case PseudoScrollbar:
458     case PseudoScrollbarCorner:
459     case PseudoScrollbarButton:
460     case PseudoScrollbarThumb:
461     case PseudoScrollbarTrack:
462     case PseudoScrollbarTrackPiece:
463     case PseudoSelection:
464     case PseudoUserAgentCustomElement:
465     case PseudoWebKitCustomElement:
466     case PseudoContent:
467     case PseudoShadow:
468         element = true;
469         break;
470     case PseudoUnknown:
471     case PseudoEmpty:
472     case PseudoFirstChild:
473     case PseudoFirstOfType:
474     case PseudoLastChild:
475     case PseudoLastOfType:
476     case PseudoOnlyChild:
477     case PseudoOnlyOfType:
478     case PseudoNthChild:
479     case PseudoNthOfType:
480     case PseudoNthLastChild:
481     case PseudoNthLastOfType:
482     case PseudoLink:
483     case PseudoVisited:
484     case PseudoAny:
485     case PseudoAnyLink:
486     case PseudoAutofill:
487     case PseudoHover:
488     case PseudoDrag:
489     case PseudoFocus:
490     case PseudoActive:
491     case PseudoChecked:
492     case PseudoEnabled:
493     case PseudoFullPageMedia:
494     case PseudoDefault:
495     case PseudoDisabled:
496     case PseudoOptional:
497     case PseudoRequired:
498     case PseudoReadOnly:
499     case PseudoReadWrite:
500     case PseudoScope:
501     case PseudoValid:
502     case PseudoInvalid:
503     case PseudoIndeterminate:
504     case PseudoTarget:
505     case PseudoLang:
506     case PseudoNot:
507     case PseudoRoot:
508     case PseudoScrollbarBack:
509     case PseudoScrollbarForward:
510     case PseudoWindowInactive:
511     case PseudoCornerPresent:
512     case PseudoDecrement:
513     case PseudoIncrement:
514     case PseudoHorizontal:
515     case PseudoVertical:
516     case PseudoStart:
517     case PseudoEnd:
518     case PseudoDoubleButton:
519     case PseudoSingleButton:
520     case PseudoNoButton:
521     case PseudoNotParsed:
522     case PseudoFullScreen:
523     case PseudoFullScreenDocument:
524     case PseudoFullScreenAncestor:
525     case PseudoInRange:
526     case PseudoOutOfRange:
527     case PseudoFutureCue:
528     case PseudoPastCue:
529     case PseudoHost:
530     case PseudoHostContext:
531     case PseudoUnresolved:
532         break;
533     case PseudoFirstPage:
534     case PseudoLeftPage:
535     case PseudoRightPage:
536         isPagePseudoClass = true;
537         break;
538     }
539 
540     bool matchPagePseudoClass = (m_match == PagePseudoClass);
541     if (matchPagePseudoClass != isPagePseudoClass)
542         m_pseudoType = PseudoUnknown;
543     else if (m_match == PseudoClass && element) {
544         if (!compat)
545             m_pseudoType = PseudoUnknown;
546         else
547             m_match = PseudoElement;
548     } else if (m_match == PseudoElement && !element)
549         m_pseudoType = PseudoUnknown;
550 }
551 
operator ==(const CSSSelector & other) const552 bool CSSSelector::operator==(const CSSSelector& other) const
553 {
554     const CSSSelector* sel1 = this;
555     const CSSSelector* sel2 = &other;
556 
557     while (sel1 && sel2) {
558         if (sel1->attribute() != sel2->attribute()
559             || sel1->relation() != sel2->relation()
560             || sel1->m_match != sel2->m_match
561             || sel1->value() != sel2->value()
562             || sel1->pseudoType() != sel2->pseudoType()
563             || sel1->argument() != sel2->argument()) {
564             return false;
565         }
566         if (sel1->m_match == Tag) {
567             if (sel1->tagQName() != sel2->tagQName())
568                 return false;
569         }
570         sel1 = sel1->tagHistory();
571         sel2 = sel2->tagHistory();
572     }
573 
574     if (sel1 || sel2)
575         return false;
576 
577     return true;
578 }
579 
selectorText(const String & rightSide) const580 String CSSSelector::selectorText(const String& rightSide) const
581 {
582     StringBuilder str;
583 
584     if (m_match == CSSSelector::Tag && !m_tagIsForNamespaceRule) {
585         if (tagQName().prefix().isNull())
586             str.append(tagQName().localName());
587         else {
588             str.append(tagQName().prefix().string());
589             str.append('|');
590             str.append(tagQName().localName());
591         }
592     }
593 
594     const CSSSelector* cs = this;
595     while (true) {
596         if (cs->m_match == CSSSelector::Id) {
597             str.append('#');
598             serializeIdentifier(cs->value(), str);
599         } else if (cs->m_match == CSSSelector::Class) {
600             str.append('.');
601             serializeIdentifier(cs->value(), str);
602         } else if (cs->m_match == CSSSelector::PseudoClass || cs->m_match == CSSSelector::PagePseudoClass) {
603             str.append(':');
604             str.append(cs->value());
605 
606             switch (cs->pseudoType()) {
607             case PseudoNot:
608                 ASSERT(cs->selectorList());
609                 str.append(cs->selectorList()->first()->selectorText());
610                 str.append(')');
611                 break;
612             case PseudoLang:
613             case PseudoNthChild:
614             case PseudoNthLastChild:
615             case PseudoNthOfType:
616             case PseudoNthLastOfType:
617                 str.append(cs->argument());
618                 str.append(')');
619                 break;
620             case PseudoAny: {
621                 const CSSSelector* firstSubSelector = cs->selectorList()->first();
622                 for (const CSSSelector* subSelector = firstSubSelector; subSelector; subSelector = CSSSelectorList::next(*subSelector)) {
623                     if (subSelector != firstSubSelector)
624                         str.append(',');
625                     str.append(subSelector->selectorText());
626                 }
627                 str.append(')');
628                 break;
629             }
630             case PseudoHost:
631             case PseudoHostContext: {
632                 if (cs->selectorList()) {
633                     const CSSSelector* firstSubSelector = cs->selectorList()->first();
634                     for (const CSSSelector* subSelector = firstSubSelector; subSelector; subSelector = CSSSelectorList::next(*subSelector)) {
635                         if (subSelector != firstSubSelector)
636                             str.append(',');
637                         str.append(subSelector->selectorText());
638                     }
639                     str.append(')');
640                 }
641                 break;
642             }
643             default:
644                 break;
645             }
646         } else if (cs->m_match == CSSSelector::PseudoElement) {
647             str.appendLiteral("::");
648             str.append(cs->value());
649 
650             if (cs->pseudoType() == PseudoContent) {
651                 if (cs->relation() == CSSSelector::SubSelector && cs->tagHistory())
652                     return cs->tagHistory()->selectorText() + str.toString() + rightSide;
653             }
654         } else if (cs->isAttributeSelector()) {
655             str.append('[');
656             const AtomicString& prefix = cs->attribute().prefix();
657             if (!prefix.isNull()) {
658                 str.append(prefix);
659                 str.append("|");
660             }
661             str.append(cs->attribute().localName());
662             switch (cs->m_match) {
663                 case CSSSelector::Exact:
664                     str.append('=');
665                     break;
666                 case CSSSelector::Set:
667                     // set has no operator or value, just the attrName
668                     str.append(']');
669                     break;
670                 case CSSSelector::List:
671                     str.appendLiteral("~=");
672                     break;
673                 case CSSSelector::Hyphen:
674                     str.appendLiteral("|=");
675                     break;
676                 case CSSSelector::Begin:
677                     str.appendLiteral("^=");
678                     break;
679                 case CSSSelector::End:
680                     str.appendLiteral("$=");
681                     break;
682                 case CSSSelector::Contain:
683                     str.appendLiteral("*=");
684                     break;
685                 default:
686                     break;
687             }
688             if (cs->m_match != CSSSelector::Set) {
689                 serializeString(cs->value(), str);
690                 str.append(']');
691             }
692         }
693         if (cs->relation() != CSSSelector::SubSelector || !cs->tagHistory())
694             break;
695         cs = cs->tagHistory();
696     }
697 
698     if (const CSSSelector* tagHistory = cs->tagHistory()) {
699         switch (cs->relation()) {
700         case CSSSelector::Descendant:
701             return tagHistory->selectorText(" " + str.toString() + rightSide);
702         case CSSSelector::Child:
703             return tagHistory->selectorText(" > " + str.toString() + rightSide);
704         case CSSSelector::ShadowDeep:
705             return tagHistory->selectorText(" /deep/ " + str.toString() + rightSide);
706         case CSSSelector::DirectAdjacent:
707             return tagHistory->selectorText(" + " + str.toString() + rightSide);
708         case CSSSelector::IndirectAdjacent:
709             return tagHistory->selectorText(" ~ " + str.toString() + rightSide);
710         case CSSSelector::SubSelector:
711             ASSERT_NOT_REACHED();
712         case CSSSelector::ShadowPseudo:
713             return tagHistory->selectorText(str.toString() + rightSide);
714         }
715     }
716     return str.toString() + rightSide;
717 }
718 
setAttribute(const QualifiedName & value)719 void CSSSelector::setAttribute(const QualifiedName& value)
720 {
721     createRareData();
722     m_data.m_rareData->m_attribute = value;
723 }
724 
setArgument(const AtomicString & value)725 void CSSSelector::setArgument(const AtomicString& value)
726 {
727     createRareData();
728     m_data.m_rareData->m_argument = value;
729 }
730 
setSelectorList(PassOwnPtr<CSSSelectorList> selectorList)731 void CSSSelector::setSelectorList(PassOwnPtr<CSSSelectorList> selectorList)
732 {
733     createRareData();
734     m_data.m_rareData->m_selectorList = selectorList;
735 }
736 
validateSubSelector(const CSSSelector * selector)737 static bool validateSubSelector(const CSSSelector* selector)
738 {
739     switch (selector->match()) {
740     case CSSSelector::Tag:
741     case CSSSelector::Id:
742     case CSSSelector::Class:
743     case CSSSelector::Exact:
744     case CSSSelector::Set:
745     case CSSSelector::List:
746     case CSSSelector::Hyphen:
747     case CSSSelector::Contain:
748     case CSSSelector::Begin:
749     case CSSSelector::End:
750         return true;
751     case CSSSelector::PseudoElement:
752     case CSSSelector::Unknown:
753         return false;
754     case CSSSelector::PagePseudoClass:
755     case CSSSelector::PseudoClass:
756         break;
757     }
758 
759     switch (selector->pseudoType()) {
760     case CSSSelector::PseudoEmpty:
761     case CSSSelector::PseudoLink:
762     case CSSSelector::PseudoVisited:
763     case CSSSelector::PseudoTarget:
764     case CSSSelector::PseudoEnabled:
765     case CSSSelector::PseudoDisabled:
766     case CSSSelector::PseudoChecked:
767     case CSSSelector::PseudoIndeterminate:
768     case CSSSelector::PseudoNthChild:
769     case CSSSelector::PseudoNthLastChild:
770     case CSSSelector::PseudoNthOfType:
771     case CSSSelector::PseudoNthLastOfType:
772     case CSSSelector::PseudoFirstChild:
773     case CSSSelector::PseudoLastChild:
774     case CSSSelector::PseudoFirstOfType:
775     case CSSSelector::PseudoLastOfType:
776     case CSSSelector::PseudoOnlyOfType:
777     case CSSSelector::PseudoHost:
778     case CSSSelector::PseudoHostContext:
779     case CSSSelector::PseudoNot:
780         return true;
781     default:
782         return false;
783     }
784 }
785 
isCompound() const786 bool CSSSelector::isCompound() const
787 {
788     if (!validateSubSelector(this))
789         return false;
790 
791     const CSSSelector* prevSubSelector = this;
792     const CSSSelector* subSelector = tagHistory();
793 
794     while (subSelector) {
795         if (prevSubSelector->relation() != CSSSelector::SubSelector)
796             return false;
797         if (!validateSubSelector(subSelector))
798             return false;
799 
800         prevSubSelector = subSelector;
801         subSelector = subSelector->tagHistory();
802     }
803 
804     return true;
805 }
806 
parseNth() const807 bool CSSSelector::parseNth() const
808 {
809     if (!m_hasRareData)
810         return false;
811     if (m_parsedNth)
812         return true;
813     m_parsedNth = m_data.m_rareData->parseNth();
814     return m_parsedNth;
815 }
816 
matchNth(int count) const817 bool CSSSelector::matchNth(int count) const
818 {
819     ASSERT(m_hasRareData);
820     return m_data.m_rareData->matchNth(count);
821 }
822 
RareData(const AtomicString & value)823 CSSSelector::RareData::RareData(const AtomicString& value)
824     : m_value(value)
825     , m_a(0)
826     , m_b(0)
827     , m_attribute(anyQName())
828     , m_argument(nullAtom)
829 {
830 }
831 
~RareData()832 CSSSelector::RareData::~RareData()
833 {
834 }
835 
836 // a helper function for parsing nth-arguments
parseNth()837 bool CSSSelector::RareData::parseNth()
838 {
839     String argument = m_argument.lower();
840 
841     if (argument.isEmpty())
842         return false;
843 
844     m_a = 0;
845     m_b = 0;
846     if (argument == "odd") {
847         m_a = 2;
848         m_b = 1;
849     } else if (argument == "even") {
850         m_a = 2;
851         m_b = 0;
852     } else {
853         size_t n = argument.find('n');
854         if (n != kNotFound) {
855             if (argument[0] == '-') {
856                 if (n == 1)
857                     m_a = -1; // -n == -1n
858                 else
859                     m_a = argument.substring(0, n).toInt();
860             } else if (!n)
861                 m_a = 1; // n == 1n
862             else
863                 m_a = argument.substring(0, n).toInt();
864 
865             size_t p = argument.find('+', n);
866             if (p != kNotFound)
867                 m_b = argument.substring(p + 1, argument.length() - p - 1).toInt();
868             else {
869                 p = argument.find('-', n);
870                 if (p != kNotFound)
871                     m_b = -argument.substring(p + 1, argument.length() - p - 1).toInt();
872             }
873         } else
874             m_b = argument.toInt();
875     }
876     return true;
877 }
878 
879 // a helper function for checking nth-arguments
matchNth(int count)880 bool CSSSelector::RareData::matchNth(int count)
881 {
882     if (!m_a)
883         return count == m_b;
884     else if (m_a > 0) {
885         if (count < m_b)
886             return false;
887         return (count - m_b) % m_a == 0;
888     } else {
889         if (count > m_b)
890             return false;
891         return (m_b - count) % (-m_a) == 0;
892     }
893 }
894 
895 } // namespace WebCore
896