Fix Qt build.
[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("read-only", PseudoReadOnly)
324         TABLE_ENTRY("read-write", PseudoReadWrite)
325         TABLE_ENTRY("required", PseudoRequired)
326         TABLE_ENTRY("right", PseudoRightPage)
327         TABLE_ENTRY("root", PseudoRoot)
328         TABLE_ENTRY("scope", PseudoScope)
329         TABLE_ENTRY("selection", PseudoSelection)
330         TABLE_ENTRY("single-button", PseudoSingleButton)
331         TABLE_ENTRY("start", PseudoStart)
332         TABLE_ENTRY("target", PseudoTarget)
333         TABLE_ENTRY("valid", PseudoValid)
334         TABLE_ENTRY("vertical", PseudoVertical)
335         TABLE_ENTRY("visited", PseudoVisited)
336         TABLE_ENTRY("window-inactive", PseudoWindowInactive)
337
338 #if ENABLE(FULLSCREEN_API)
339         TABLE_ENTRY("-webkit-animating-full-screen-transition", PseudoAnimatingFullScreenTransition)
340         TABLE_ENTRY("-webkit-full-screen", PseudoFullScreen)
341         TABLE_ENTRY("-webkit-full-screen-ancestor", PseudoFullScreenAncestor)
342         TABLE_ENTRY("-webkit-full-screen-document", PseudoFullScreenDocument)
343 #endif
344
345 #if ENABLE(IFRAME_SEAMLESS)
346         TABLE_ENTRY("-webkit-seamless-document", PseudoSeamlessDocument)
347 #endif
348
349 #if ENABLE(VIDEO_TRACK)
350         TABLE_ENTRY("cue(", PseudoCue)
351         TABLE_ENTRY("future", PseudoFutureCue)
352         TABLE_ENTRY("past", PseudoPastCue)
353 #endif
354     };
355
356 #undef TABLE_ENTRY
357
358     for (unsigned i = 0; i < WTF_ARRAY_LENGTH(table); ++i)
359         map.add(AtomicString(table[i].name, table[i].nameLength, AtomicString::ConstructFromLiteral), table[i].type);
360 }
361
362 CSSSelector::PseudoType CSSSelector::parsePseudoType(const AtomicString& name)
363 {
364     if (name.isNull())
365         return PseudoUnknown;
366
367     static NeverDestroyed<HashMap<AtomicString, CSSSelector::PseudoType>> types;
368     if (types.get().isEmpty())
369         populatePseudoTypeByNameMap(types.get());
370     if (PseudoType type = types.get().get(name))
371         return type;
372
373     if (name.startsWith("-webkit-"))
374         return PseudoWebKitCustomElement;
375
376     // FIXME: This is strange. Why would all strings that start with "cue" be "user agent custom"?
377     if (name.startsWith("x-") || name.startsWith("cue"))
378         return PseudoUserAgentCustomElement;
379
380     return PseudoUnknown;
381 }
382
383 void CSSSelector::extractPseudoType() const
384 {
385     if (m_match != PseudoClass && m_match != PseudoElement && m_match != PagePseudoClass)
386         return;
387
388     m_pseudoType = parsePseudoType(value());
389
390     bool element = false; // pseudo-element
391     bool compat = false; // single colon compatbility mode
392     bool isPagePseudoClass = false; // Page pseudo-class
393
394     switch (m_pseudoType) {
395     case PseudoAfter:
396     case PseudoBefore:
397 #if ENABLE(VIDEO_TRACK)
398     case PseudoCue:
399 #endif
400     case PseudoFirstLetter:
401     case PseudoFirstLine:
402         compat = true;
403 #if ENABLE(SHADOW_DOM)
404     case PseudoDistributed:
405 #endif
406     case PseudoResizer:
407     case PseudoScrollbar:
408     case PseudoScrollbarCorner:
409     case PseudoScrollbarButton:
410     case PseudoScrollbarThumb:
411     case PseudoScrollbarTrack:
412     case PseudoScrollbarTrackPiece:
413     case PseudoSelection:
414     case PseudoUserAgentCustomElement:
415     case PseudoWebKitCustomElement:
416         element = true;
417         break;
418     case PseudoUnknown:
419     case PseudoEmpty:
420     case PseudoFirstChild:
421     case PseudoFirstOfType:
422     case PseudoLastChild:
423     case PseudoLastOfType:
424     case PseudoOnlyChild:
425     case PseudoOnlyOfType:
426     case PseudoNthChild:
427     case PseudoNthOfType:
428     case PseudoNthLastChild:
429     case PseudoNthLastOfType:
430     case PseudoLink:
431     case PseudoVisited:
432     case PseudoAny:
433     case PseudoAnyLink:
434     case PseudoAutofill:
435     case PseudoHover:
436     case PseudoDrag:
437     case PseudoFocus:
438     case PseudoActive:
439     case PseudoChecked:
440     case PseudoEnabled:
441     case PseudoFullPageMedia:
442     case PseudoDefault:
443     case PseudoDisabled:
444     case PseudoOptional:
445     case PseudoRequired:
446     case PseudoReadOnly:
447     case PseudoReadWrite:
448     case PseudoScope:
449     case PseudoValid:
450     case PseudoInvalid:
451     case PseudoIndeterminate:
452     case PseudoTarget:
453     case PseudoLang:
454     case PseudoNot:
455     case PseudoRoot:
456     case PseudoScrollbarBack:
457     case PseudoScrollbarForward:
458     case PseudoWindowInactive:
459     case PseudoCornerPresent:
460     case PseudoDecrement:
461     case PseudoIncrement:
462     case PseudoHorizontal:
463     case PseudoVertical:
464     case PseudoStart:
465     case PseudoEnd:
466     case PseudoDoubleButton:
467     case PseudoSingleButton:
468     case PseudoNoButton:
469     case PseudoNotParsed:
470 #if ENABLE(FULLSCREEN_API)
471     case PseudoFullScreen:
472     case PseudoFullScreenDocument:
473     case PseudoFullScreenAncestor:
474     case PseudoAnimatingFullScreenTransition:
475 #endif
476 #if ENABLE(IFRAME_SEAMLESS)
477     case PseudoSeamlessDocument:
478 #endif
479     case PseudoInRange:
480     case PseudoOutOfRange:
481 #if ENABLE(VIDEO_TRACK)
482     case PseudoFutureCue:
483     case PseudoPastCue:
484 #endif
485         break;
486     case PseudoFirstPage:
487     case PseudoLeftPage:
488     case PseudoRightPage:
489         isPagePseudoClass = true;
490         break;
491     }
492
493     bool matchPagePseudoClass = (m_match == PagePseudoClass);
494     if (matchPagePseudoClass != isPagePseudoClass)
495         m_pseudoType = PseudoUnknown;
496     else if (m_match == PseudoClass && element) {
497         if (!compat)
498             m_pseudoType = PseudoUnknown;
499         else
500            m_match = PseudoElement;
501     } else if (m_match == PseudoElement && !element)
502         m_pseudoType = PseudoUnknown;
503 }
504
505 bool CSSSelector::operator==(const CSSSelector& other) const
506 {
507     const CSSSelector* sel1 = this;
508     const CSSSelector* sel2 = &other;
509
510     while (sel1 && sel2) {
511         if (sel1->attribute() != sel2->attribute()
512             || sel1->relation() != sel2->relation()
513             || sel1->m_match != sel2->m_match
514             || sel1->value() != sel2->value()
515             || sel1->pseudoType() != sel2->pseudoType()
516             || sel1->argument() != sel2->argument()) {
517             return false;
518         }
519         if (sel1->m_match == Tag) {
520             if (sel1->tagQName() != sel2->tagQName())
521                 return false;
522         }
523         sel1 = sel1->tagHistory();
524         sel2 = sel2->tagHistory();
525     }
526
527     if (sel1 || sel2)
528         return false;
529
530     return true;
531 }
532
533 String CSSSelector::selectorText(const String& rightSide) const
534 {
535     StringBuilder str;
536
537     if (m_match == CSSSelector::Tag && !m_tagIsForNamespaceRule) {
538         if (tagQName().prefix().isNull())
539             str.append(tagQName().localName());
540         else {
541             str.append(tagQName().prefix().string());
542             str.append('|');
543             str.append(tagQName().localName());
544         }
545     }
546
547     const CSSSelector* cs = this;
548     while (true) {
549         if (cs->m_match == CSSSelector::Id) {
550             str.append('#');
551             serializeIdentifier(cs->value(), str);
552         } else if (cs->m_match == CSSSelector::Class) {
553             str.append('.');
554             serializeIdentifier(cs->value(), str);
555         } else if (cs->m_match == CSSSelector::PseudoClass || cs->m_match == CSSSelector::PagePseudoClass) {
556             str.append(':');
557             str.append(cs->value());
558
559             switch (cs->pseudoType()) {
560             case PseudoNot:
561                 if (const CSSSelectorList* selectorList = cs->selectorList())
562                     str.append(selectorList->first()->selectorText());
563                 str.append(')');
564                 break;
565             case PseudoLang:
566             case PseudoNthChild:
567             case PseudoNthLastChild:
568             case PseudoNthOfType:
569             case PseudoNthLastOfType:
570                 str.append(cs->argument());
571                 str.append(')');
572                 break;
573             case PseudoAny: {
574                 const CSSSelector* firstSubSelector = cs->selectorList()->first();
575                 for (const CSSSelector* subSelector = firstSubSelector; subSelector; subSelector = CSSSelectorList::next(subSelector)) {
576                     if (subSelector != firstSubSelector)
577                         str.append(',');
578                     str.append(subSelector->selectorText());
579                 }
580                 str.append(')');
581                 break;
582             }
583             default:
584                 break;
585             }
586         } else if (cs->m_match == CSSSelector::PseudoElement) {
587             str.appendLiteral("::");
588             str.append(cs->value());
589         } else if (cs->isAttributeSelector()) {
590             str.append('[');
591             const AtomicString& prefix = cs->attribute().prefix();
592             if (!prefix.isNull()) {
593                 str.append(prefix);
594                 str.append('|');
595             }
596             str.append(cs->attribute().localName());
597             switch (cs->m_match) {
598                 case CSSSelector::Exact:
599                     str.append('=');
600                     break;
601                 case CSSSelector::Set:
602                     // set has no operator or value, just the attrName
603                     str.append(']');
604                     break;
605                 case CSSSelector::List:
606                     str.appendLiteral("~=");
607                     break;
608                 case CSSSelector::Hyphen:
609                     str.appendLiteral("|=");
610                     break;
611                 case CSSSelector::Begin:
612                     str.appendLiteral("^=");
613                     break;
614                 case CSSSelector::End:
615                     str.appendLiteral("$=");
616                     break;
617                 case CSSSelector::Contain:
618                     str.appendLiteral("*=");
619                     break;
620                 default:
621                     break;
622             }
623             if (cs->m_match != CSSSelector::Set) {
624                 serializeString(cs->value(), str);
625                 str.append(']');
626             }
627         }
628         if (cs->relation() != CSSSelector::SubSelector || !cs->tagHistory())
629             break;
630         cs = cs->tagHistory();
631     }
632
633     if (const CSSSelector* tagHistory = cs->tagHistory()) {
634         switch (cs->relation()) {
635         case CSSSelector::Descendant:
636             return tagHistory->selectorText(" " + str.toString() + rightSide);
637         case CSSSelector::Child:
638             return tagHistory->selectorText(" > " + str.toString() + rightSide);
639         case CSSSelector::DirectAdjacent:
640             return tagHistory->selectorText(" + " + str.toString() + rightSide);
641         case CSSSelector::IndirectAdjacent:
642             return tagHistory->selectorText(" ~ " + str.toString() + rightSide);
643         case CSSSelector::SubSelector:
644             ASSERT_NOT_REACHED();
645         case CSSSelector::ShadowDescendant:
646             return tagHistory->selectorText(str.toString() + rightSide);
647         }
648     }
649     return str.toString() + rightSide;
650 }
651
652 void CSSSelector::setAttribute(const QualifiedName& value, bool isCaseInsensitive)
653 {
654     createRareData();
655     m_data.m_rareData->m_attribute = value;
656     m_data.m_rareData->m_attributeCanonicalLocalName = isCaseInsensitive ? value.localName().lower() : value.localName();
657 }
658
659 void CSSSelector::setArgument(const AtomicString& value)
660 {
661     createRareData();
662     m_data.m_rareData->m_argument = value;
663 }
664
665 void CSSSelector::setSelectorList(PassOwnPtr<CSSSelectorList> selectorList)
666 {
667     createRareData();
668     m_data.m_rareData->m_selectorList = selectorList;
669 }
670
671 bool CSSSelector::parseNth() const
672 {
673     if (!m_hasRareData)
674         return false;
675     if (m_parsedNth)
676         return true;
677     m_parsedNth = m_data.m_rareData->parseNth();
678     return m_parsedNth;
679 }
680
681 bool CSSSelector::matchNth(int count) const
682 {
683     ASSERT(m_hasRareData);
684     return m_data.m_rareData->matchNth(count);
685 }
686
687 CSSSelector::RareData::RareData(PassRefPtr<AtomicStringImpl> value)
688     : m_value(value.leakRef())
689     , m_a(0)
690     , m_b(0)
691     , m_attribute(anyQName())
692     , m_argument(nullAtom)
693 {
694 }
695
696 CSSSelector::RareData::~RareData()
697 {
698     if (m_value)
699         m_value->deref();
700 }
701
702 // a helper function for parsing nth-arguments
703 bool CSSSelector::RareData::parseNth()
704 {
705     String argument = m_argument.lower();
706
707     if (argument.isEmpty())
708         return false;
709
710     m_a = 0;
711     m_b = 0;
712     if (argument == "odd") {
713         m_a = 2;
714         m_b = 1;
715     } else if (argument == "even") {
716         m_a = 2;
717         m_b = 0;
718     } else {
719         size_t n = argument.find('n');
720         if (n != notFound) {
721             if (argument[0] == '-') {
722                 if (n == 1)
723                     m_a = -1; // -n == -1n
724                 else
725                     m_a = argument.substring(0, n).toInt();
726             } else if (!n)
727                 m_a = 1; // n == 1n
728             else
729                 m_a = argument.substring(0, n).toInt();
730
731             size_t p = argument.find('+', n);
732             if (p != notFound)
733                 m_b = argument.substring(p + 1, argument.length() - p - 1).toInt();
734             else {
735                 p = argument.find('-', n);
736                 if (p != notFound)
737                     m_b = -argument.substring(p + 1, argument.length() - p - 1).toInt();
738             }
739         } else
740             m_b = argument.toInt();
741     }
742     return true;
743 }
744
745 // a helper function for checking nth-arguments
746 bool CSSSelector::RareData::matchNth(int count)
747 {
748     if (!m_a)
749         return count == m_b;
750     else if (m_a > 0) {
751         if (count < m_b)
752             return false;
753         return (count - m_b) % m_a == 0;
754     } else {
755         if (count > m_b)
756             return false;
757         return (m_b - count) % (-m_a) == 0;
758     }
759 }
760
761 } // namespace WebCore