e9140bbc5dd80573bfdeb577cadbede546b1ca6a
[WebKit-https.git] / Source / WebCore / css / CSSSelector.cpp
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, 2013 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 "CSSSelector.h"
28
29 #include "CSSOMUtils.h"
30 #include "CSSSelectorList.h"
31 #include "HTMLNames.h"
32 #include <wtf/Assertions.h>
33 #include <wtf/HashMap.h>
34 #include <wtf/NeverDestroyed.h>
35 #include <wtf/StdLibExtras.h>
36 #include <wtf/Vector.h>
37 #include <wtf/text/AtomicStringHash.h>
38 #include <wtf/text/StringBuilder.h>
39
40 namespace WebCore {
41
42 using namespace HTMLNames;
43
44 void CSSSelector::createRareData()
45 {
46     ASSERT(m_match != Tag);
47     if (m_hasRareData)
48         return;
49     // Move the value to the rare data stucture.
50     m_data.m_rareData = RareData::create(adoptRef(m_data.m_value)).leakRef();
51     m_hasRareData = true;
52 }
53
54 unsigned CSSSelector::specificity() const
55 {
56     // make sure the result doesn't overflow
57     static const unsigned maxValueMask = 0xffffff;
58     static const unsigned idMask = 0xff0000;
59     static const unsigned classMask = 0xff00;
60     static const unsigned elementMask = 0xff;
61
62     if (isForPage())
63         return specificityForPage() & maxValueMask;
64
65     unsigned total = 0;
66     unsigned temp = 0;
67
68     for (const CSSSelector* selector = this; selector; selector = selector->tagHistory()) {
69         temp = total + selector->specificityForOneSelector();
70         // Clamp each component to its max in the case of overflow.
71         if ((temp & idMask) < (total & idMask))
72             total |= idMask;
73         else if ((temp & classMask) < (total & classMask))
74             total |= classMask;
75         else if ((temp & elementMask) < (total & elementMask))
76             total |= elementMask;
77         else
78             total = temp;
79     }
80     return total;
81 }
82
83 inline unsigned CSSSelector::specificityForOneSelector() const
84 {
85     // FIXME: Pseudo-elements and pseudo-classes do not have the same specificity. This function
86     // isn't quite correct.
87     switch (m_match) {
88     case Id:
89         return 0x10000;
90     case Exact:
91     case Class:
92     case Set:
93     case List:
94     case Hyphen:
95     case PseudoClass:
96     case PseudoElement:
97     case Contain:
98     case Begin:
99     case End:
100         // FIXME: PsuedoAny should base the specificity on the sub-selectors.
101         // See http://lists.w3.org/Archives/Public/www-style/2010Sep/0530.html
102         if (pseudoType() == PseudoNot && selectorList())
103             return selectorList()->first()->specificityForOneSelector();
104         return 0x100;
105     case Tag:
106         return (tagQName().localName() != starAtom) ? 1 : 0;
107     case Unknown:
108         return 0;
109     }
110     ASSERT_NOT_REACHED();
111     return 0;
112 }
113
114 unsigned CSSSelector::specificityForPage() const
115 {
116     // See http://dev.w3.org/csswg/css3-page/#cascading-and-page-context
117     unsigned s = 0;
118
119     for (const CSSSelector* component = this; component; component = component->tagHistory()) {
120         switch (component->m_match) {
121         case Tag:
122             s += tagQName().localName() == starAtom ? 0 : 4;
123             break;
124         case PseudoClass:
125             switch (component->pseudoType()) {
126             case PseudoFirstPage:
127                 s += 2;
128                 break;
129             case PseudoLeftPage:
130             case PseudoRightPage:
131                 s += 1;
132                 break;
133             case PseudoNotParsed:
134                 break;
135             default:
136                 ASSERT_NOT_REACHED();
137             }
138             break;
139         default:
140             break;
141         }
142     }
143     return s;
144 }
145
146 PseudoId CSSSelector::pseudoId(PseudoType type)
147 {
148     switch (type) {
149     case PseudoFirstLine:
150         return FIRST_LINE;
151     case PseudoFirstLetter:
152         return FIRST_LETTER;
153     case PseudoSelection:
154         return SELECTION;
155     case PseudoBefore:
156         return BEFORE;
157     case PseudoAfter:
158         return AFTER;
159     case PseudoScrollbar:
160         return SCROLLBAR;
161     case PseudoScrollbarButton:
162         return SCROLLBAR_BUTTON;
163     case PseudoScrollbarCorner:
164         return SCROLLBAR_CORNER;
165     case PseudoScrollbarThumb:
166         return SCROLLBAR_THUMB;
167     case PseudoScrollbarTrack:
168         return SCROLLBAR_TRACK;
169     case PseudoScrollbarTrackPiece:
170         return SCROLLBAR_TRACK_PIECE;
171     case PseudoResizer:
172         return RESIZER;
173 #if ENABLE(FULLSCREEN_API)
174     case PseudoFullScreen:
175         return FULL_SCREEN;
176     case PseudoFullScreenDocument:
177         return FULL_SCREEN_DOCUMENT;
178     case PseudoFullScreenAncestor:
179         return FULL_SCREEN_ANCESTOR;
180     case PseudoAnimatingFullScreenTransition:
181         return ANIMATING_FULL_SCREEN_TRANSITION;
182 #endif
183     case PseudoUnknown:
184     case PseudoEmpty:
185     case PseudoFirstChild:
186     case PseudoFirstOfType:
187     case PseudoLastChild:
188     case PseudoLastOfType:
189     case PseudoOnlyChild:
190     case PseudoOnlyOfType:
191     case PseudoNthChild:
192     case PseudoNthOfType:
193     case PseudoNthLastChild:
194     case PseudoNthLastOfType:
195     case PseudoLink:
196     case PseudoVisited:
197     case PseudoAny:
198     case PseudoAnyLink:
199     case PseudoAutofill:
200     case PseudoHover:
201     case PseudoDrag:
202     case PseudoFocus:
203     case PseudoActive:
204     case PseudoChecked:
205     case PseudoEnabled:
206     case PseudoFullPageMedia:
207     case PseudoDefault:
208     case PseudoDisabled:
209     case PseudoOptional:
210     case PseudoRequired:
211     case PseudoReadOnly:
212     case PseudoReadWrite:
213     case PseudoValid:
214     case PseudoInvalid:
215     case PseudoIndeterminate:
216     case PseudoTarget:
217     case PseudoLang:
218     case PseudoNot:
219     case PseudoRoot:
220     case PseudoScope:
221     case PseudoScrollbarBack:
222     case PseudoScrollbarForward:
223     case PseudoWindowInactive:
224     case PseudoCornerPresent:
225     case PseudoDecrement:
226     case PseudoIncrement:
227     case PseudoHorizontal:
228     case PseudoVertical:
229     case PseudoStart:
230     case PseudoEnd:
231     case PseudoDoubleButton:
232     case PseudoSingleButton:
233     case PseudoNoButton:
234     case PseudoFirstPage:
235     case PseudoLeftPage:
236     case PseudoRightPage:
237     case PseudoInRange:
238     case PseudoOutOfRange:
239     case PseudoUserAgentCustomElement:
240     case PseudoWebKitCustomElement:
241 #if ENABLE(VIDEO_TRACK)
242     case PseudoCue:
243     case PseudoFutureCue:
244     case PseudoPastCue:
245 #endif
246 #if ENABLE(IFRAME_SEAMLESS)
247     case PseudoSeamlessDocument:
248 #endif
249         return NOPSEUDO;
250     case PseudoNotParsed:
251         ASSERT_NOT_REACHED();
252         return NOPSEUDO;
253     }
254
255     ASSERT_NOT_REACHED();
256     return NOPSEUDO;
257 }
258
259 static void populatePseudoTypeByNameMap(HashMap<AtomicString, CSSSelector::PseudoType>& map)
260 {
261     struct TableEntry {
262         const char* name;
263         unsigned nameLength;
264         CSSSelector::PseudoType type;
265     };
266
267     // Could use strlen in this macro but not all compilers can constant-fold it.
268 #define TABLE_ENTRY(name, type) { name, sizeof(name) - 1, CSSSelector::type },
269
270     const TableEntry table[] = {
271         TABLE_ENTRY("-khtml-drag", PseudoDrag)
272         TABLE_ENTRY("-webkit-any(", PseudoAny)
273         TABLE_ENTRY("-webkit-any-link", PseudoAnyLink)
274         TABLE_ENTRY("-webkit-autofill", PseudoAutofill)
275         TABLE_ENTRY("-webkit-drag", PseudoDrag)
276         TABLE_ENTRY("-webkit-full-page-media", PseudoFullPageMedia)
277         TABLE_ENTRY("-webkit-resizer", PseudoResizer)
278         TABLE_ENTRY("-webkit-scrollbar", PseudoScrollbar)
279         TABLE_ENTRY("-webkit-scrollbar-button", PseudoScrollbarButton)
280         TABLE_ENTRY("-webkit-scrollbar-corner", PseudoScrollbarCorner)
281         TABLE_ENTRY("-webkit-scrollbar-thumb", PseudoScrollbarThumb)
282         TABLE_ENTRY("-webkit-scrollbar-track", PseudoScrollbarTrack)
283         TABLE_ENTRY("-webkit-scrollbar-track-piece", PseudoScrollbarTrackPiece)
284         TABLE_ENTRY("active", PseudoActive)
285         TABLE_ENTRY("after", PseudoAfter)
286         TABLE_ENTRY("before", PseudoBefore)
287         TABLE_ENTRY("checked", PseudoChecked)
288         TABLE_ENTRY("corner-present", PseudoCornerPresent)
289         TABLE_ENTRY("decrement", PseudoDecrement)
290         TABLE_ENTRY("default", PseudoDefault)
291         TABLE_ENTRY("disabled", PseudoDisabled)
292         TABLE_ENTRY("double-button", PseudoDoubleButton)
293         TABLE_ENTRY("empty", PseudoEmpty)
294         TABLE_ENTRY("enabled", PseudoEnabled)
295         TABLE_ENTRY("end", PseudoEnd)
296         TABLE_ENTRY("first", PseudoFirstPage)
297         TABLE_ENTRY("first-child", PseudoFirstChild)
298         TABLE_ENTRY("first-letter", PseudoFirstLetter)
299         TABLE_ENTRY("first-line", PseudoFirstLine)
300         TABLE_ENTRY("first-of-type", PseudoFirstOfType)
301         TABLE_ENTRY("focus", PseudoFocus)
302         TABLE_ENTRY("horizontal", PseudoHorizontal)
303         TABLE_ENTRY("hover", PseudoHover)
304         TABLE_ENTRY("in-range", PseudoInRange)
305         TABLE_ENTRY("increment", PseudoIncrement)
306         TABLE_ENTRY("indeterminate", PseudoIndeterminate)
307         TABLE_ENTRY("invalid", PseudoInvalid)
308         TABLE_ENTRY("lang(", PseudoLang)
309         TABLE_ENTRY("last-child", PseudoLastChild)
310         TABLE_ENTRY("last-of-type", PseudoLastOfType)
311         TABLE_ENTRY("left", PseudoLeftPage)
312         TABLE_ENTRY("link", PseudoLink)
313         TABLE_ENTRY("no-button", PseudoNoButton)
314         TABLE_ENTRY("not(", PseudoNot)
315         TABLE_ENTRY("nth-child(", PseudoNthChild)
316         TABLE_ENTRY("nth-last-child(", PseudoNthLastChild)
317         TABLE_ENTRY("nth-last-of-type(", PseudoNthLastOfType)
318         TABLE_ENTRY("nth-of-type(", PseudoNthOfType)
319         TABLE_ENTRY("only-child", PseudoOnlyChild)
320         TABLE_ENTRY("only-of-type", PseudoOnlyOfType)
321         TABLE_ENTRY("optional", PseudoOptional)
322         TABLE_ENTRY("out-of-range", PseudoOutOfRange)
323         TABLE_ENTRY("past", PseudoPastCue)
324         TABLE_ENTRY("read-only", PseudoReadOnly)
325         TABLE_ENTRY("read-write", PseudoReadWrite)
326         TABLE_ENTRY("required", PseudoRequired)
327         TABLE_ENTRY("right", PseudoRightPage)
328         TABLE_ENTRY("root", PseudoRoot)
329         TABLE_ENTRY("scope", PseudoScope)
330         TABLE_ENTRY("selection", PseudoSelection)
331         TABLE_ENTRY("single-button", PseudoSingleButton)
332         TABLE_ENTRY("start", PseudoStart)
333         TABLE_ENTRY("target", PseudoTarget)
334         TABLE_ENTRY("valid", PseudoValid)
335         TABLE_ENTRY("vertical", PseudoVertical)
336         TABLE_ENTRY("visited", PseudoVisited)
337         TABLE_ENTRY("window-inactive", PseudoWindowInactive)
338
339 #if ENABLE(FULLSCREEN_API)
340         TABLE_ENTRY("-webkit-animating-full-screen-transition", PseudoAnimatingFullScreenTransition)
341         TABLE_ENTRY("-webkit-full-screen", PseudoFullScreen)
342         TABLE_ENTRY("-webkit-full-screen-ancestor", PseudoFullScreenAncestor)
343         TABLE_ENTRY("-webkit-full-screen-document", PseudoFullScreenDocument)
344 #endif
345
346 #if ENABLE(IFRAME_SEAMLESS)
347         TABLE_ENTRY("-webkit-seamless-document", PseudoSeamlessDocument)
348 #endif
349
350 #if ENABLE(VIDEO_TRACK)
351         TABLE_ENTRY("cue(", PseudoCue)
352         TABLE_ENTRY("future", PseudoFutureCue)
353         TABLE_ENTRY("past", PseudoPastCue)
354 #endif
355     };
356
357 #undef TABLE_ENTRY
358
359     for (unsigned i = 0; i < WTF_ARRAY_LENGTH(table); ++i)
360         map.add(AtomicString(table[i].name, table[i].nameLength, AtomicString::ConstructFromLiteral), table[i].type);
361 }
362
363 CSSSelector::PseudoType CSSSelector::parsePseudoType(const AtomicString& name)
364 {
365     if (name.isNull())
366         return PseudoUnknown;
367
368     static NeverDestroyed<HashMap<AtomicString, CSSSelector::PseudoType>> types;
369     if (types.get().isEmpty())
370         populatePseudoTypeByNameMap(types.get());
371     if (PseudoType type = types.get().get(name))
372         return type;
373
374     if (name.startsWith("-webkit-"))
375         return PseudoWebKitCustomElement;
376
377     // FIXME: This is strange. Why would all strings that start with "cue" be "user agent custom"?
378     if (name.startsWith("x-") || name.startsWith("cue"))
379         return PseudoUserAgentCustomElement;
380
381     return PseudoUnknown;
382 }
383
384 void CSSSelector::extractPseudoType() const
385 {
386     if (m_match != PseudoClass && m_match != PseudoElement && m_match != PagePseudoClass)
387         return;
388
389     m_pseudoType = parsePseudoType(value());
390
391     bool element = false; // pseudo-element
392     bool compat = false; // single colon compatbility mode
393     bool isPagePseudoClass = false; // Page pseudo-class
394
395     switch (m_pseudoType) {
396     case PseudoAfter:
397     case PseudoBefore:
398 #if ENABLE(VIDEO_TRACK)
399     case PseudoCue:
400 #endif
401     case PseudoFirstLetter:
402     case PseudoFirstLine:
403         compat = true;
404 #if ENABLE(SHADOW_DOM)
405     case PseudoDistributed:
406 #endif
407     case PseudoResizer:
408     case PseudoScrollbar:
409     case PseudoScrollbarCorner:
410     case PseudoScrollbarButton:
411     case PseudoScrollbarThumb:
412     case PseudoScrollbarTrack:
413     case PseudoScrollbarTrackPiece:
414     case PseudoSelection:
415     case PseudoUserAgentCustomElement:
416     case PseudoWebKitCustomElement:
417         element = true;
418         break;
419     case PseudoUnknown:
420     case PseudoEmpty:
421     case PseudoFirstChild:
422     case PseudoFirstOfType:
423     case PseudoLastChild:
424     case PseudoLastOfType:
425     case PseudoOnlyChild:
426     case PseudoOnlyOfType:
427     case PseudoNthChild:
428     case PseudoNthOfType:
429     case PseudoNthLastChild:
430     case PseudoNthLastOfType:
431     case PseudoLink:
432     case PseudoVisited:
433     case PseudoAny:
434     case PseudoAnyLink:
435     case PseudoAutofill:
436     case PseudoHover:
437     case PseudoDrag:
438     case PseudoFocus:
439     case PseudoActive:
440     case PseudoChecked:
441     case PseudoEnabled:
442     case PseudoFullPageMedia:
443     case PseudoDefault:
444     case PseudoDisabled:
445     case PseudoOptional:
446     case PseudoRequired:
447     case PseudoReadOnly:
448     case PseudoReadWrite:
449     case PseudoScope:
450     case PseudoValid:
451     case PseudoInvalid:
452     case PseudoIndeterminate:
453     case PseudoTarget:
454     case PseudoLang:
455     case PseudoNot:
456     case PseudoRoot:
457     case PseudoScrollbarBack:
458     case PseudoScrollbarForward:
459     case PseudoWindowInactive:
460     case PseudoCornerPresent:
461     case PseudoDecrement:
462     case PseudoIncrement:
463     case PseudoHorizontal:
464     case PseudoVertical:
465     case PseudoStart:
466     case PseudoEnd:
467     case PseudoDoubleButton:
468     case PseudoSingleButton:
469     case PseudoNoButton:
470     case PseudoNotParsed:
471 #if ENABLE(FULLSCREEN_API)
472     case PseudoFullScreen:
473     case PseudoFullScreenDocument:
474     case PseudoFullScreenAncestor:
475     case PseudoAnimatingFullScreenTransition:
476 #endif
477 #if ENABLE(IFRAME_SEAMLESS)
478     case PseudoSeamlessDocument:
479 #endif
480     case PseudoInRange:
481     case PseudoOutOfRange:
482 #if ENABLE(VIDEO_TRACK)
483     case PseudoFutureCue:
484     case PseudoPastCue:
485 #endif
486         break;
487     case PseudoFirstPage:
488     case PseudoLeftPage:
489     case PseudoRightPage:
490         isPagePseudoClass = true;
491         break;
492     }
493
494     bool matchPagePseudoClass = (m_match == PagePseudoClass);
495     if (matchPagePseudoClass != isPagePseudoClass)
496         m_pseudoType = PseudoUnknown;
497     else if (m_match == PseudoClass && element) {
498         if (!compat)
499             m_pseudoType = PseudoUnknown;
500         else
501            m_match = PseudoElement;
502     } else if (m_match == PseudoElement && !element)
503         m_pseudoType = PseudoUnknown;
504 }
505
506 bool CSSSelector::operator==(const CSSSelector& other) const
507 {
508     const CSSSelector* sel1 = this;
509     const CSSSelector* sel2 = &other;
510
511     while (sel1 && sel2) {
512         if (sel1->attribute() != sel2->attribute()
513             || sel1->relation() != sel2->relation()
514             || sel1->m_match != sel2->m_match
515             || sel1->value() != sel2->value()
516             || sel1->pseudoType() != sel2->pseudoType()
517             || sel1->argument() != sel2->argument()) {
518             return false;
519         }
520         if (sel1->m_match == Tag) {
521             if (sel1->tagQName() != sel2->tagQName())
522                 return false;
523         }
524         sel1 = sel1->tagHistory();
525         sel2 = sel2->tagHistory();
526     }
527
528     if (sel1 || sel2)
529         return false;
530
531     return true;
532 }
533
534 String CSSSelector::selectorText(const String& rightSide) const
535 {
536     StringBuilder str;
537
538     if (m_match == CSSSelector::Tag && !m_tagIsForNamespaceRule) {
539         if (tagQName().prefix().isNull())
540             str.append(tagQName().localName());
541         else {
542             str.append(tagQName().prefix().string());
543             str.append('|');
544             str.append(tagQName().localName());
545         }
546     }
547
548     const CSSSelector* cs = this;
549     while (true) {
550         if (cs->m_match == CSSSelector::Id) {
551             str.append('#');
552             serializeIdentifier(cs->value(), str);
553         } else if (cs->m_match == CSSSelector::Class) {
554             str.append('.');
555             serializeIdentifier(cs->value(), str);
556         } else if (cs->m_match == CSSSelector::PseudoClass || cs->m_match == CSSSelector::PagePseudoClass) {
557             str.append(':');
558             str.append(cs->value());
559
560             switch (cs->pseudoType()) {
561             case PseudoNot:
562                 if (const CSSSelectorList* selectorList = cs->selectorList())
563                     str.append(selectorList->first()->selectorText());
564                 str.append(')');
565                 break;
566             case PseudoLang:
567             case PseudoNthChild:
568             case PseudoNthLastChild:
569             case PseudoNthOfType:
570             case PseudoNthLastOfType:
571                 str.append(cs->argument());
572                 str.append(')');
573                 break;
574             case PseudoAny: {
575                 const CSSSelector* firstSubSelector = cs->selectorList()->first();
576                 for (const CSSSelector* subSelector = firstSubSelector; subSelector; subSelector = CSSSelectorList::next(subSelector)) {
577                     if (subSelector != firstSubSelector)
578                         str.append(',');
579                     str.append(subSelector->selectorText());
580                 }
581                 str.append(')');
582                 break;
583             }
584             default:
585                 break;
586             }
587         } else if (cs->m_match == CSSSelector::PseudoElement) {
588             str.appendLiteral("::");
589             str.append(cs->value());
590         } else if (cs->isAttributeSelector()) {
591             str.append('[');
592             const AtomicString& prefix = cs->attribute().prefix();
593             if (!prefix.isNull()) {
594                 str.append(prefix);
595                 str.append('|');
596             }
597             str.append(cs->attribute().localName());
598             switch (cs->m_match) {
599                 case CSSSelector::Exact:
600                     str.append('=');
601                     break;
602                 case CSSSelector::Set:
603                     // set has no operator or value, just the attrName
604                     str.append(']');
605                     break;
606                 case CSSSelector::List:
607                     str.appendLiteral("~=");
608                     break;
609                 case CSSSelector::Hyphen:
610                     str.appendLiteral("|=");
611                     break;
612                 case CSSSelector::Begin:
613                     str.appendLiteral("^=");
614                     break;
615                 case CSSSelector::End:
616                     str.appendLiteral("$=");
617                     break;
618                 case CSSSelector::Contain:
619                     str.appendLiteral("*=");
620                     break;
621                 default:
622                     break;
623             }
624             if (cs->m_match != CSSSelector::Set) {
625                 serializeString(cs->value(), str);
626                 str.append(']');
627             }
628         }
629         if (cs->relation() != CSSSelector::SubSelector || !cs->tagHistory())
630             break;
631         cs = cs->tagHistory();
632     }
633
634     if (const CSSSelector* tagHistory = cs->tagHistory()) {
635         switch (cs->relation()) {
636         case CSSSelector::Descendant:
637             return tagHistory->selectorText(" " + str.toString() + rightSide);
638         case CSSSelector::Child:
639             return tagHistory->selectorText(" > " + str.toString() + rightSide);
640         case CSSSelector::DirectAdjacent:
641             return tagHistory->selectorText(" + " + str.toString() + rightSide);
642         case CSSSelector::IndirectAdjacent:
643             return tagHistory->selectorText(" ~ " + str.toString() + rightSide);
644         case CSSSelector::SubSelector:
645             ASSERT_NOT_REACHED();
646         case CSSSelector::ShadowDescendant:
647             return tagHistory->selectorText(str.toString() + rightSide);
648         }
649     }
650     return str.toString() + rightSide;
651 }
652
653 void CSSSelector::setAttribute(const QualifiedName& value, bool isCaseInsensitive)
654 {
655     createRareData();
656     m_data.m_rareData->m_attribute = value;
657     m_data.m_rareData->m_attributeCanonicalLocalName = isCaseInsensitive ? value.localName().lower() : value.localName();
658 }
659
660 void CSSSelector::setArgument(const AtomicString& value)
661 {
662     createRareData();
663     m_data.m_rareData->m_argument = value;
664 }
665
666 void CSSSelector::setSelectorList(PassOwnPtr<CSSSelectorList> selectorList)
667 {
668     createRareData();
669     m_data.m_rareData->m_selectorList = selectorList;
670 }
671
672 bool CSSSelector::parseNth() const
673 {
674     if (!m_hasRareData)
675         return false;
676     if (m_parsedNth)
677         return true;
678     m_parsedNth = m_data.m_rareData->parseNth();
679     return m_parsedNth;
680 }
681
682 bool CSSSelector::matchNth(int count) const
683 {
684     ASSERT(m_hasRareData);
685     return m_data.m_rareData->matchNth(count);
686 }
687
688 CSSSelector::RareData::RareData(PassRefPtr<AtomicStringImpl> value)
689     : m_value(value.leakRef())
690     , m_a(0)
691     , m_b(0)
692     , m_attribute(anyQName())
693     , m_argument(nullAtom)
694 {
695 }
696
697 CSSSelector::RareData::~RareData()
698 {
699     if (m_value)
700         m_value->deref();
701 }
702
703 // a helper function for parsing nth-arguments
704 bool CSSSelector::RareData::parseNth()
705 {
706     String argument = m_argument.lower();
707
708     if (argument.isEmpty())
709         return false;
710
711     m_a = 0;
712     m_b = 0;
713     if (argument == "odd") {
714         m_a = 2;
715         m_b = 1;
716     } else if (argument == "even") {
717         m_a = 2;
718         m_b = 0;
719     } else {
720         size_t n = argument.find('n');
721         if (n != notFound) {
722             if (argument[0] == '-') {
723                 if (n == 1)
724                     m_a = -1; // -n == -1n
725                 else
726                     m_a = argument.substring(0, n).toInt();
727             } else if (!n)
728                 m_a = 1; // n == 1n
729             else
730                 m_a = argument.substring(0, n).toInt();
731
732             size_t p = argument.find('+', n);
733             if (p != notFound)
734                 m_b = argument.substring(p + 1, argument.length() - p - 1).toInt();
735             else {
736                 p = argument.find('-', n);
737                 if (p != notFound)
738                     m_b = -argument.substring(p + 1, argument.length() - p - 1).toInt();
739             }
740         } else
741             m_b = argument.toInt();
742     }
743     return true;
744 }
745
746 // a helper function for checking nth-arguments
747 bool CSSSelector::RareData::matchNth(int count)
748 {
749     if (!m_a)
750         return count == m_b;
751     else if (m_a > 0) {
752         if (count < m_b)
753             return false;
754         return (count - m_b) % m_a == 0;
755     } else {
756         if (count > m_b)
757             return false;
758         return (m_b - count) % (-m_a) == 0;
759     }
760 }
761
762 } // namespace WebCore