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