CSSSelector.cpp   [plain text]


/*
 * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org)
 *               1999 Waldo Bastian (bastian@kde.org)
 *               2001 Andreas Schlapbach (schlpbch@iam.unibe.ch)
 *               2001-2003 Dirk Mueller (mueller@kde.org)
 * Copyright (C) 2002, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved.
 * Copyright (C) 2008 David Smith (catfish.man@gmail.com)
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "config.h"
#include "CSSSelector.h"

#include "CSSOMUtils.h"
#include "CSSSelectorList.h"
#include "HTMLNames.h"
#include <wtf/Assertions.h>
#include <wtf/HashMap.h>
#include <wtf/StdLibExtras.h>
#include <wtf/Vector.h>

namespace WebCore {

using namespace HTMLNames;

void CSSSelector::createRareData()
{
    if (m_hasRareData)
        return;
    // Move the value to the rare data stucture.
    m_data.m_rareData = RareData::create(adoptRef(m_data.m_value)).leakRef();
    m_hasRareData = true;
}

unsigned CSSSelector::specificity() const
{
    // make sure the result doesn't overflow
    static const unsigned maxValueMask = 0xffffff;
    unsigned total = 0;
    for (const CSSSelector* selector = this; selector; selector = selector->tagHistory()) {
        if (selector->m_isForPage)
            return (total + selector->specificityForPage()) & maxValueMask;
        total = (total + selector->specificityForOneSelector()) & maxValueMask;
    }
    return total;
}

inline unsigned CSSSelector::specificityForOneSelector() const
{
    // FIXME: Pseudo-elements and pseudo-classes do not have the same specificity. This function
    // isn't quite correct.
    unsigned s = (m_tag.localName() == starAtom ? 0 : 1);
    switch (m_match) {
    case Id:
        s += 0x10000;
        break;
    case Exact:
    case Class:
    case Set:
    case List:
    case Hyphen:
    case PseudoClass:
    case PseudoElement:
    case Contain:
    case Begin:
    case End:
        // FIXME: PsuedoAny should base the specificity on the sub-selectors.
        // See http://lists.w3.org/Archives/Public/www-style/2010Sep/0530.html
        if (pseudoType() == PseudoNot && selectorList())
            s += selectorList()->first()->specificityForOneSelector();
        else
            s += 0x100;
    case None:
        break;
    }
    return s;
}

unsigned CSSSelector::specificityForPage() const
{
    // See http://dev.w3.org/csswg/css3-page/#cascading-and-page-context
    unsigned s = (m_tag.localName() == starAtom ? 0 : 4);

    switch (pseudoType()) {
    case PseudoFirstPage:
        s += 2;
        break;
    case PseudoLeftPage:
    case PseudoRightPage:
        s += 1;
        break;
    case PseudoNotParsed:
        break;
    default:
        ASSERT_NOT_REACHED();
    }
    return s;
}

PseudoId CSSSelector::pseudoId(PseudoType type)
{
    switch (type) {
    case PseudoFirstLine:
        return FIRST_LINE;
    case PseudoFirstLetter:
        return FIRST_LETTER;
    case PseudoSelection:
        return SELECTION;
    case PseudoBefore:
        return BEFORE;
    case PseudoAfter:
        return AFTER;
    case PseudoScrollbar:
        return SCROLLBAR;
    case PseudoScrollbarButton:
        return SCROLLBAR_BUTTON;
    case PseudoScrollbarCorner:
        return SCROLLBAR_CORNER;
    case PseudoScrollbarThumb:
        return SCROLLBAR_THUMB;
    case PseudoScrollbarTrack:
        return SCROLLBAR_TRACK;
    case PseudoScrollbarTrackPiece:
        return SCROLLBAR_TRACK_PIECE;
    case PseudoResizer:
        return RESIZER;
#if ENABLE(FULLSCREEN_API)
    case PseudoFullScreen:
        return FULL_SCREEN;
    case PseudoFullScreenDocument:
        return FULL_SCREEN_DOCUMENT;
    case PseudoFullScreenAncestor:
        return FULL_SCREEN_ANCESTOR;
    case PseudoAnimatingFullScreenTransition:
        return ANIMATING_FULL_SCREEN_TRANSITION;
#endif
    case PseudoUnknown:
    case PseudoEmpty:
    case PseudoFirstChild:
    case PseudoFirstOfType:
    case PseudoLastChild:
    case PseudoLastOfType:
    case PseudoOnlyChild:
    case PseudoOnlyOfType:
    case PseudoNthChild:
    case PseudoNthOfType:
    case PseudoNthLastChild:
    case PseudoNthLastOfType:
    case PseudoLink:
    case PseudoVisited:
    case PseudoAny:
    case PseudoAnyLink:
    case PseudoAutofill:
    case PseudoHover:
    case PseudoDrag:
    case PseudoFocus:
    case PseudoActive:
    case PseudoChecked:
    case PseudoEnabled:
    case PseudoFullPageMedia:
    case PseudoDefault:
    case PseudoDisabled:
    case PseudoOptional:
    case PseudoRequired:
    case PseudoReadOnly:
    case PseudoReadWrite:
    case PseudoValid:
    case PseudoInvalid:
    case PseudoIndeterminate:
    case PseudoScope:
    case PseudoTarget:
    case PseudoLang:
    case PseudoNot:
    case PseudoRoot:
    case PseudoScrollbarBack:
    case PseudoScrollbarForward:
    case PseudoWindowInactive:
    case PseudoCornerPresent:
    case PseudoDecrement:
    case PseudoIncrement:
    case PseudoHorizontal:
    case PseudoVertical:
    case PseudoStart:
    case PseudoEnd:
    case PseudoDoubleButton:
    case PseudoSingleButton:
    case PseudoNoButton:
    case PseudoFirstPage:
    case PseudoLeftPage:
    case PseudoRightPage:
    case PseudoInRange:
    case PseudoOutOfRange:
        return NOPSEUDO;
    case PseudoNotParsed:
        ASSERT_NOT_REACHED();
        return NOPSEUDO;
    }

    ASSERT_NOT_REACHED();
    return NOPSEUDO;
}

static HashMap<AtomicStringImpl*, CSSSelector::PseudoType>* nameToPseudoTypeMap()
{
    DEFINE_STATIC_LOCAL(AtomicString, active, ("active"));
    DEFINE_STATIC_LOCAL(AtomicString, after, ("after"));
    DEFINE_STATIC_LOCAL(AtomicString, any, ("-webkit-any("));
    DEFINE_STATIC_LOCAL(AtomicString, anyLink, ("-webkit-any-link"));
    DEFINE_STATIC_LOCAL(AtomicString, autofill, ("-webkit-autofill"));
    DEFINE_STATIC_LOCAL(AtomicString, before, ("before"));
    DEFINE_STATIC_LOCAL(AtomicString, checked, ("checked"));
    DEFINE_STATIC_LOCAL(AtomicString, defaultString, ("default"));
    DEFINE_STATIC_LOCAL(AtomicString, disabled, ("disabled"));
    DEFINE_STATIC_LOCAL(AtomicString, readOnly, ("read-only"));
    DEFINE_STATIC_LOCAL(AtomicString, readWrite, ("read-write"));
    DEFINE_STATIC_LOCAL(AtomicString, valid, ("valid"));
    DEFINE_STATIC_LOCAL(AtomicString, invalid, ("invalid"));
    DEFINE_STATIC_LOCAL(AtomicString, drag, ("-webkit-drag"));
    DEFINE_STATIC_LOCAL(AtomicString, dragAlias, ("-khtml-drag")); // was documented with this name in Apple documentation, so keep an alia
    DEFINE_STATIC_LOCAL(AtomicString, empty, ("empty"));
    DEFINE_STATIC_LOCAL(AtomicString, enabled, ("enabled"));
    DEFINE_STATIC_LOCAL(AtomicString, firstChild, ("first-child"));
    DEFINE_STATIC_LOCAL(AtomicString, firstLetter, ("first-letter"));
    DEFINE_STATIC_LOCAL(AtomicString, firstLine, ("first-line"));
    DEFINE_STATIC_LOCAL(AtomicString, firstOfType, ("first-of-type"));
    DEFINE_STATIC_LOCAL(AtomicString, fullPageMedia, ("-webkit-full-page-media"));
    DEFINE_STATIC_LOCAL(AtomicString, nthChild, ("nth-child("));
    DEFINE_STATIC_LOCAL(AtomicString, nthOfType, ("nth-of-type("));
    DEFINE_STATIC_LOCAL(AtomicString, nthLastChild, ("nth-last-child("));
    DEFINE_STATIC_LOCAL(AtomicString, nthLastOfType, ("nth-last-of-type("));
    DEFINE_STATIC_LOCAL(AtomicString, focus, ("focus"));
    DEFINE_STATIC_LOCAL(AtomicString, hover, ("hover"));
    DEFINE_STATIC_LOCAL(AtomicString, indeterminate, ("indeterminate"));
    DEFINE_STATIC_LOCAL(AtomicString, lastChild, ("last-child"));
    DEFINE_STATIC_LOCAL(AtomicString, lastOfType, ("last-of-type"));
    DEFINE_STATIC_LOCAL(AtomicString, link, ("link"));
    DEFINE_STATIC_LOCAL(AtomicString, lang, ("lang("));
    DEFINE_STATIC_LOCAL(AtomicString, notStr, ("not("));
    DEFINE_STATIC_LOCAL(AtomicString, onlyChild, ("only-child"));
    DEFINE_STATIC_LOCAL(AtomicString, onlyOfType, ("only-of-type"));
    DEFINE_STATIC_LOCAL(AtomicString, optional, ("optional"));
    DEFINE_STATIC_LOCAL(AtomicString, required, ("required"));
    DEFINE_STATIC_LOCAL(AtomicString, resizer, ("-webkit-resizer"));
    DEFINE_STATIC_LOCAL(AtomicString, root, ("root"));
    DEFINE_STATIC_LOCAL(AtomicString, scrollbar, ("-webkit-scrollbar"));
    DEFINE_STATIC_LOCAL(AtomicString, scrollbarButton, ("-webkit-scrollbar-button"));
    DEFINE_STATIC_LOCAL(AtomicString, scrollbarCorner, ("-webkit-scrollbar-corner"));
    DEFINE_STATIC_LOCAL(AtomicString, scrollbarThumb, ("-webkit-scrollbar-thumb"));
    DEFINE_STATIC_LOCAL(AtomicString, scrollbarTrack, ("-webkit-scrollbar-track"));
    DEFINE_STATIC_LOCAL(AtomicString, scrollbarTrackPiece, ("-webkit-scrollbar-track-piece"));
    DEFINE_STATIC_LOCAL(AtomicString, selection, ("selection"));
    DEFINE_STATIC_LOCAL(AtomicString, scope, ("scope"));
    DEFINE_STATIC_LOCAL(AtomicString, target, ("target"));
    DEFINE_STATIC_LOCAL(AtomicString, visited, ("visited"));
    DEFINE_STATIC_LOCAL(AtomicString, windowInactive, ("window-inactive"));
    DEFINE_STATIC_LOCAL(AtomicString, decrement, ("decrement"));
    DEFINE_STATIC_LOCAL(AtomicString, increment, ("increment"));
    DEFINE_STATIC_LOCAL(AtomicString, start, ("start"));
    DEFINE_STATIC_LOCAL(AtomicString, end, ("end"));
    DEFINE_STATIC_LOCAL(AtomicString, horizontal, ("horizontal"));
    DEFINE_STATIC_LOCAL(AtomicString, vertical, ("vertical"));
    DEFINE_STATIC_LOCAL(AtomicString, doubleButton, ("double-button"));
    DEFINE_STATIC_LOCAL(AtomicString, singleButton, ("single-button"));
    DEFINE_STATIC_LOCAL(AtomicString, noButton, ("no-button"));
    DEFINE_STATIC_LOCAL(AtomicString, cornerPresent, ("corner-present"));
    // Paged Media pseudo-classes
    DEFINE_STATIC_LOCAL(AtomicString, firstPage, ("first"));
    DEFINE_STATIC_LOCAL(AtomicString, leftPage, ("left"));
    DEFINE_STATIC_LOCAL(AtomicString, rightPage, ("right"));
#if ENABLE(FULLSCREEN_API)
    DEFINE_STATIC_LOCAL(AtomicString, fullScreen, ("-webkit-full-screen"));
    DEFINE_STATIC_LOCAL(AtomicString, fullScreenDocument, ("-webkit-full-screen-document"));
    DEFINE_STATIC_LOCAL(AtomicString, fullScreenAncestor, ("-webkit-full-screen-ancestor"));
    DEFINE_STATIC_LOCAL(AtomicString, animatingFullScreenTransition, ("-webkit-animating-full-screen-transition"));
#endif
    DEFINE_STATIC_LOCAL(AtomicString, inRange, ("in-range"));
    DEFINE_STATIC_LOCAL(AtomicString, outOfRange, ("out-of-range"));

    static HashMap<AtomicStringImpl*, CSSSelector::PseudoType>* nameToPseudoType = 0;
    if (!nameToPseudoType) {
        nameToPseudoType = new HashMap<AtomicStringImpl*, CSSSelector::PseudoType>;
        nameToPseudoType->set(active.impl(), CSSSelector::PseudoActive);
        nameToPseudoType->set(after.impl(), CSSSelector::PseudoAfter);
        nameToPseudoType->set(anyLink.impl(), CSSSelector::PseudoAnyLink);
        nameToPseudoType->set(any.impl(), CSSSelector::PseudoAny);
        nameToPseudoType->set(autofill.impl(), CSSSelector::PseudoAutofill);
        nameToPseudoType->set(before.impl(), CSSSelector::PseudoBefore);
        nameToPseudoType->set(checked.impl(), CSSSelector::PseudoChecked);
        nameToPseudoType->set(defaultString.impl(), CSSSelector::PseudoDefault);
        nameToPseudoType->set(disabled.impl(), CSSSelector::PseudoDisabled);
        nameToPseudoType->set(readOnly.impl(), CSSSelector::PseudoReadOnly);
        nameToPseudoType->set(readWrite.impl(), CSSSelector::PseudoReadWrite);
        nameToPseudoType->set(valid.impl(), CSSSelector::PseudoValid);
        nameToPseudoType->set(invalid.impl(), CSSSelector::PseudoInvalid);
        nameToPseudoType->set(drag.impl(), CSSSelector::PseudoDrag);
        nameToPseudoType->set(dragAlias.impl(), CSSSelector::PseudoDrag);
        nameToPseudoType->set(enabled.impl(), CSSSelector::PseudoEnabled);
        nameToPseudoType->set(empty.impl(), CSSSelector::PseudoEmpty);
        nameToPseudoType->set(firstChild.impl(), CSSSelector::PseudoFirstChild);
        nameToPseudoType->set(fullPageMedia.impl(), CSSSelector::PseudoFullPageMedia);
        nameToPseudoType->set(lastChild.impl(), CSSSelector::PseudoLastChild);
        nameToPseudoType->set(lastOfType.impl(), CSSSelector::PseudoLastOfType);
        nameToPseudoType->set(onlyChild.impl(), CSSSelector::PseudoOnlyChild);
        nameToPseudoType->set(onlyOfType.impl(), CSSSelector::PseudoOnlyOfType);
        nameToPseudoType->set(firstLetter.impl(), CSSSelector::PseudoFirstLetter);
        nameToPseudoType->set(firstLine.impl(), CSSSelector::PseudoFirstLine);
        nameToPseudoType->set(firstOfType.impl(), CSSSelector::PseudoFirstOfType);
        nameToPseudoType->set(focus.impl(), CSSSelector::PseudoFocus);
        nameToPseudoType->set(hover.impl(), CSSSelector::PseudoHover);
        nameToPseudoType->set(indeterminate.impl(), CSSSelector::PseudoIndeterminate);
        nameToPseudoType->set(link.impl(), CSSSelector::PseudoLink);
        nameToPseudoType->set(lang.impl(), CSSSelector::PseudoLang);
        nameToPseudoType->set(notStr.impl(), CSSSelector::PseudoNot);
        nameToPseudoType->set(nthChild.impl(), CSSSelector::PseudoNthChild);
        nameToPseudoType->set(nthOfType.impl(), CSSSelector::PseudoNthOfType);
        nameToPseudoType->set(nthLastChild.impl(), CSSSelector::PseudoNthLastChild);
        nameToPseudoType->set(nthLastOfType.impl(), CSSSelector::PseudoNthLastOfType);
        nameToPseudoType->set(root.impl(), CSSSelector::PseudoRoot);
        nameToPseudoType->set(windowInactive.impl(), CSSSelector::PseudoWindowInactive);
        nameToPseudoType->set(decrement.impl(), CSSSelector::PseudoDecrement);
        nameToPseudoType->set(increment.impl(), CSSSelector::PseudoIncrement);
        nameToPseudoType->set(start.impl(), CSSSelector::PseudoStart);
        nameToPseudoType->set(end.impl(), CSSSelector::PseudoEnd);
        nameToPseudoType->set(horizontal.impl(), CSSSelector::PseudoHorizontal);
        nameToPseudoType->set(vertical.impl(), CSSSelector::PseudoVertical);
        nameToPseudoType->set(doubleButton.impl(), CSSSelector::PseudoDoubleButton);
        nameToPseudoType->set(singleButton.impl(), CSSSelector::PseudoSingleButton);
        nameToPseudoType->set(noButton.impl(), CSSSelector::PseudoNoButton);
        nameToPseudoType->set(optional.impl(), CSSSelector::PseudoOptional);
        nameToPseudoType->set(required.impl(), CSSSelector::PseudoRequired);
        nameToPseudoType->set(resizer.impl(), CSSSelector::PseudoResizer);
        nameToPseudoType->set(scrollbar.impl(), CSSSelector::PseudoScrollbar);
        nameToPseudoType->set(scrollbarButton.impl(), CSSSelector::PseudoScrollbarButton);
        nameToPseudoType->set(scrollbarCorner.impl(), CSSSelector::PseudoScrollbarCorner);
        nameToPseudoType->set(scrollbarThumb.impl(), CSSSelector::PseudoScrollbarThumb);
        nameToPseudoType->set(scrollbarTrack.impl(), CSSSelector::PseudoScrollbarTrack);
        nameToPseudoType->set(scrollbarTrackPiece.impl(), CSSSelector::PseudoScrollbarTrackPiece);
        nameToPseudoType->set(cornerPresent.impl(), CSSSelector::PseudoCornerPresent);
        nameToPseudoType->set(selection.impl(), CSSSelector::PseudoSelection);
        nameToPseudoType->set(scope.impl(), CSSSelector::PseudoScope);
        nameToPseudoType->set(target.impl(), CSSSelector::PseudoTarget);
        nameToPseudoType->set(visited.impl(), CSSSelector::PseudoVisited);
        nameToPseudoType->set(firstPage.impl(), CSSSelector::PseudoFirstPage);
        nameToPseudoType->set(leftPage.impl(), CSSSelector::PseudoLeftPage);
        nameToPseudoType->set(rightPage.impl(), CSSSelector::PseudoRightPage);
#if ENABLE(FULLSCREEN_API)
        nameToPseudoType->set(fullScreen.impl(), CSSSelector::PseudoFullScreen);
        nameToPseudoType->set(fullScreenDocument.impl(), CSSSelector::PseudoFullScreenDocument);
        nameToPseudoType->set(fullScreenAncestor.impl(), CSSSelector::PseudoFullScreenAncestor);
        nameToPseudoType->set(animatingFullScreenTransition.impl(), CSSSelector::PseudoAnimatingFullScreenTransition);
#endif
        nameToPseudoType->set(inRange.impl(), CSSSelector::PseudoInRange);
        nameToPseudoType->set(outOfRange.impl(), CSSSelector::PseudoOutOfRange);
    }
    return nameToPseudoType;
}

CSSSelector::PseudoType CSSSelector::parsePseudoType(const AtomicString& name)
{
    if (name.isNull())
        return PseudoUnknown;
    HashMap<AtomicStringImpl*, CSSSelector::PseudoType>* nameToPseudoType = nameToPseudoTypeMap();
    HashMap<AtomicStringImpl*, CSSSelector::PseudoType>::iterator slot = nameToPseudoType->find(name.impl());
    return slot == nameToPseudoType->end() ? PseudoUnknown : slot->second;
}

bool CSSSelector::isUnknownPseudoType(const AtomicString& name)
{
    return parsePseudoType(name) == PseudoUnknown;
}

void CSSSelector::extractPseudoType() const
{
    if (m_match != PseudoClass && m_match != PseudoElement && m_match != PagePseudoClass)
        return;

    m_pseudoType = parsePseudoType(value());

    bool element = false; // pseudo-element
    bool compat = false; // single colon compatbility mode
    bool isPagePseudoClass = false; // Page pseudo-class

    switch (m_pseudoType) {
    case PseudoAfter:
    case PseudoBefore:
    case PseudoFirstLetter:
    case PseudoFirstLine:
        compat = true;
    case PseudoResizer:
    case PseudoScrollbar:
    case PseudoScrollbarCorner:
    case PseudoScrollbarButton:
    case PseudoScrollbarThumb:
    case PseudoScrollbarTrack:
    case PseudoScrollbarTrackPiece:
    case PseudoSelection:
        element = true;
        break;
    case PseudoUnknown:
    case PseudoEmpty:
    case PseudoFirstChild:
    case PseudoFirstOfType:
    case PseudoLastChild:
    case PseudoLastOfType:
    case PseudoOnlyChild:
    case PseudoOnlyOfType:
    case PseudoNthChild:
    case PseudoNthOfType:
    case PseudoNthLastChild:
    case PseudoNthLastOfType:
    case PseudoLink:
    case PseudoVisited:
    case PseudoAny:
    case PseudoAnyLink:
    case PseudoAutofill:
    case PseudoHover:
    case PseudoDrag:
    case PseudoFocus:
    case PseudoActive:
    case PseudoChecked:
    case PseudoEnabled:
    case PseudoFullPageMedia:
    case PseudoDefault:
    case PseudoDisabled:
    case PseudoOptional:
    case PseudoRequired:
    case PseudoReadOnly:
    case PseudoReadWrite:
    case PseudoValid:
    case PseudoInvalid:
    case PseudoIndeterminate:
    case PseudoScope:
    case PseudoTarget:
    case PseudoLang:
    case PseudoNot:
    case PseudoRoot:
    case PseudoScrollbarBack:
    case PseudoScrollbarForward:
    case PseudoWindowInactive:
    case PseudoCornerPresent:
    case PseudoDecrement:
    case PseudoIncrement:
    case PseudoHorizontal:
    case PseudoVertical:
    case PseudoStart:
    case PseudoEnd:
    case PseudoDoubleButton:
    case PseudoSingleButton:
    case PseudoNoButton:
    case PseudoNotParsed:
#if ENABLE(FULLSCREEN_API)
    case PseudoFullScreen:
    case PseudoFullScreenDocument:
    case PseudoFullScreenAncestor:
    case PseudoAnimatingFullScreenTransition:
#endif
    case PseudoInRange:
    case PseudoOutOfRange:
        break;
    case PseudoFirstPage:
    case PseudoLeftPage:
    case PseudoRightPage:
        isPagePseudoClass = true;
        break;
    }

    bool matchPagePseudoClass = (m_match == PagePseudoClass);
    if (matchPagePseudoClass != isPagePseudoClass)
        m_pseudoType = PseudoUnknown;
    else if (m_match == PseudoClass && element) {
        if (!compat)
            m_pseudoType = PseudoUnknown;
        else
           m_match = PseudoElement;
    } else if (m_match == PseudoElement && !element)
        m_pseudoType = PseudoUnknown;
}

bool CSSSelector::operator==(const CSSSelector& other)
{
    const CSSSelector* sel1 = this;
    const CSSSelector* sel2 = &other;

    while (sel1 && sel2) {
        if (sel1->m_tag != sel2->m_tag || sel1->attribute() != sel2->attribute() ||
             sel1->relation() != sel2->relation() || sel1->m_match != sel2->m_match ||
             sel1->value() != sel2->value() ||
             sel1->pseudoType() != sel2->pseudoType() ||
             sel1->argument() != sel2->argument())
            return false;
        sel1 = sel1->tagHistory();
        sel2 = sel2->tagHistory();
    }

    if (sel1 || sel2)
        return false;

    return true;
}

String CSSSelector::selectorText() const
{
    String str = "";

    const AtomicString& prefix = m_tag.prefix();
    const AtomicString& localName = m_tag.localName();
    if (m_match == CSSSelector::None || !prefix.isNull() || localName != starAtom) {
        if (prefix.isNull())
            str = localName;
        else {
            str = prefix.string();
            str.append("|");
            str.append(localName);
        }
    }

    const CSSSelector* cs = this;
    while (true) {
        if (cs->m_match == CSSSelector::Id) {
            str += "#";
            serializeIdentifier(cs->value(), str);
        } else if (cs->m_match == CSSSelector::Class) {
            str += ".";
            serializeIdentifier(cs->value(), str);
        } else if (cs->m_match == CSSSelector::PseudoClass || cs->m_match == CSSSelector::PagePseudoClass) {
            str += ":";
            str += cs->value();

            switch (cs->pseudoType()) {
            case PseudoNot:
                if (CSSSelectorList* selectorList = cs->selectorList())
                    str += selectorList->first()->selectorText();
                str += ")";
                break;
            case PseudoLang:
            case PseudoNthChild:
            case PseudoNthLastChild:
            case PseudoNthOfType:
            case PseudoNthLastOfType:
                str += cs->argument();
                str += ")";
                break;
            case PseudoAny: {
                CSSSelector* firstSubSelector = cs->selectorList()->first();
                for (CSSSelector* subSelector = firstSubSelector; subSelector; subSelector = CSSSelectorList::next(subSelector)) {
                    if (subSelector != firstSubSelector)
                        str += ",";
                    str += subSelector->selectorText();
                }
                str += ")";
                break;
            }
            default:
                break;
            }
        } else if (cs->m_match == CSSSelector::PseudoElement) {
            str += "::";
            str += cs->value();
        } else if (cs->isAttributeSelector()) {
            str += "[";
            const AtomicString& prefix = cs->attribute().prefix();
            if (!prefix.isNull()) {
                str.append(prefix);
                str.append("|");
            }
            str += cs->attribute().localName();
            switch (cs->m_match) {
                case CSSSelector::Exact:
                    str += "=";
                    break;
                case CSSSelector::Set:
                    // set has no operator or value, just the attrName
                    str += "]";
                    break;
                case CSSSelector::List:
                    str += "~=";
                    break;
                case CSSSelector::Hyphen:
                    str += "|=";
                    break;
                case CSSSelector::Begin:
                    str += "^=";
                    break;
                case CSSSelector::End:
                    str += "$=";
                    break;
                case CSSSelector::Contain:
                    str += "*=";
                    break;
                default:
                    break;
            }
            if (cs->m_match != CSSSelector::Set) {
                serializeString(cs->value(), str);
                str += "]";
            }
        }
        if (cs->relation() != CSSSelector::SubSelector || !cs->tagHistory())
            break;
        cs = cs->tagHistory();
    }

    if (CSSSelector* tagHistory = cs->tagHistory()) {
        String tagHistoryText = tagHistory->selectorText();
        if (cs->relation() == CSSSelector::DirectAdjacent)
            str = tagHistoryText + " + " + str;
        else if (cs->relation() == CSSSelector::IndirectAdjacent)
            str = tagHistoryText + " ~ " + str;
        else if (cs->relation() == CSSSelector::Child)
            str = tagHistoryText + " > " + str;
        else if (cs->relation() == CSSSelector::ShadowDescendant)
            str = tagHistoryText + str;
        else
            // Descendant
            str = tagHistoryText + " " + str;
    }

    return str;
}

void CSSSelector::setAttribute(const QualifiedName& value)
{
    createRareData();
    m_data.m_rareData->m_attribute = value;
}

void CSSSelector::setArgument(const AtomicString& value)
{
    createRareData();
    m_data.m_rareData->m_argument = value;
}

void CSSSelector::setSelectorList(PassOwnPtr<CSSSelectorList> selectorList)
{
    createRareData();
    m_data.m_rareData->m_selectorList = selectorList;
}

bool CSSSelector::parseNth()
{
    if (!m_hasRareData)
        return false;
    if (m_parsedNth)
        return true;
    m_parsedNth = m_data.m_rareData->parseNth();
    return m_parsedNth;
}

bool CSSSelector::matchNth(int count)
{
    ASSERT(m_hasRareData);
    return m_data.m_rareData->matchNth(count);
}

bool CSSSelector::isSimple() const
{
    if (selectorList() || tagHistory() || matchesPseudoElement())
        return false;

    int numConditions = 0;

    // hasTag() cannot be be used here because namespace may not be nullAtom.
    // Example:
    //     @namespace "http://www.w3.org/2000/svg";
    //     svg:not(:root) { ...
    if (m_tag != starAtom)
        numConditions++;

    if (m_match == Id || m_match == Class || m_match == PseudoClass)
        numConditions++;

    if (m_hasRareData && m_data.m_rareData->m_attribute != anyQName())
        numConditions++;

    // numConditions is 0 for a universal selector.
    // numConditions is 1 for other simple selectors.
    return numConditions <= 1;
}

CSSSelector::RareData::RareData(PassRefPtr<AtomicStringImpl> value)
    : m_value(value.leakRef())
    , m_a(0)
    , m_b(0)
    , m_attribute(anyQName())
    , m_argument(nullAtom)
{
}

CSSSelector::RareData::~RareData()
{
    if (m_value)
        m_value->deref();
}

// a helper function for parsing nth-arguments
bool CSSSelector::RareData::parseNth()
{
    String argument = m_argument.lower();

    if (argument.isEmpty())
        return false;

    m_a = 0;
    m_b = 0;
    if (argument == "odd") {
        m_a = 2;
        m_b = 1;
    } else if (argument == "even") {
        m_a = 2;
        m_b = 0;
    } else {
        size_t n = argument.find('n');
        if (n != notFound) {
            if (argument[0] == '-') {
                if (n == 1)
                    m_a = -1; // -n == -1n
                else
                    m_a = argument.substring(0, n).toInt();
            } else if (!n)
                m_a = 1; // n == 1n
            else
                m_a = argument.substring(0, n).toInt();

            size_t p = argument.find('+', n);
            if (p != notFound)
                m_b = argument.substring(p + 1, argument.length() - p - 1).toInt();
            else {
                p = argument.find('-', n);
                if (p != notFound)
                    m_b = -argument.substring(p + 1, argument.length() - p - 1).toInt();
            }
        } else
            m_b = argument.toInt();
    }
    return true;
}

// a helper function for checking nth-arguments
bool CSSSelector::RareData::matchNth(int count)
{
    if (!m_a)
        return count == m_b;
    else if (m_a > 0) {
        if (count < m_b)
            return false;
        return (count - m_b) % m_a == 0;
    } else {
        if (count > m_b)
            return false;
        return (m_b - count) % (-m_a) == 0;
    }
}

} // namespace WebCore