Home | History | Annotate | Download | only in css
      1 /*
      2  * Copyright (C) 1999-2003 Lars Knoll (knoll (at) kde.org)
      3  *               1999 Waldo Bastian (bastian (at) kde.org)
      4  *               2001 Andreas Schlapbach (schlpbch (at) iam.unibe.ch)
      5  *               2001-2003 Dirk Mueller (mueller (at) kde.org)
      6  * Copyright (C) 2002, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved.
      7  * Copyright (C) 2008 David Smith (catfish.man (at) 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 
     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 
     65 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 
     94 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 
    130 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 
    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:
    363     NameToPseudoCompare(const AtomicString& key) : m_key(key) { ASSERT(m_key.is8Bit()); }
    364 
    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 
    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
    392 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 
    415 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 
    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 
    437 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 
    552 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 
    580 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 
    719 void CSSSelector::setAttribute(const QualifiedName& value)
    720 {
    721     createRareData();
    722     m_data.m_rareData->m_attribute = value;
    723 }
    724 
    725 void CSSSelector::setArgument(const AtomicString& value)
    726 {
    727     createRareData();
    728     m_data.m_rareData->m_argument = value;
    729 }
    730 
    731 void CSSSelector::setSelectorList(PassOwnPtr<CSSSelectorList> selectorList)
    732 {
    733     createRareData();
    734     m_data.m_rareData->m_selectorList = selectorList;
    735 }
    736 
    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 
    786 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 
    807 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 
    817 bool CSSSelector::matchNth(int count) const
    818 {
    819     ASSERT(m_hasRareData);
    820     return m_data.m_rareData->matchNth(count);
    821 }
    822 
    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 
    832 CSSSelector::RareData::~RareData()
    833 {
    834 }
    835 
    836 // a helper function for parsing nth-arguments
    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
    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