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 Apple Inc. All rights reserved.
 * Copyright (C) 2008 David Smith (catfish.man@gmail.com)
 *
 * 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 "wtf/Assertions.h"
#include "HTMLNames.h"

#include <wtf/StdLibExtras.h>

namespace WebCore {
    
using namespace HTMLNames;

unsigned int CSSSelector::specificity()
{
    // FIXME: Pseudo-elements and pseudo-classes do not have the same specificity. This function
    // isn't quite correct.
    int 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:
            s += 0x100;
        case None:
            break;
    }

    if (CSSSelector* tagHistory = this->tagHistory())
        s += tagHistory->specificity();

    // make sure it doesn't overflow
    return s & 0xffffff;
}

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

    DEFINE_STATIC_LOCAL(AtomicString, active, ("active"));
    DEFINE_STATIC_LOCAL(AtomicString, after, ("after"));
    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, fileUploadButton, ("-webkit-file-upload-button"));
    DEFINE_STATIC_LOCAL(AtomicString, disabled, ("disabled"));
    DEFINE_STATIC_LOCAL(AtomicString, readOnly, ("read-only"));
    DEFINE_STATIC_LOCAL(AtomicString, readWrite, ("read-write"));
    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, inputPlaceholder, ("-webkit-input-placeholder"));
    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, mediaControlsPanel, ("-webkit-media-controls-panel"));
    DEFINE_STATIC_LOCAL(AtomicString, mediaControlsMuteButton, ("-webkit-media-controls-mute-button"));
    DEFINE_STATIC_LOCAL(AtomicString, mediaControlsPlayButton, ("-webkit-media-controls-play-button"));
    DEFINE_STATIC_LOCAL(AtomicString, mediaControlsTimeline, ("-webkit-media-controls-timeline"));
    DEFINE_STATIC_LOCAL(AtomicString, mediaControlsSeekBackButton, ("-webkit-media-controls-seek-back-button"));
    DEFINE_STATIC_LOCAL(AtomicString, mediaControlsSeekForwardButton, ("-webkit-media-controls-seek-forward-button"));
    DEFINE_STATIC_LOCAL(AtomicString, mediaControlsFullscreenButton, ("-webkit-media-controls-fullscreen-button"));
    DEFINE_STATIC_LOCAL(AtomicString, mediaControlsTimelineContainer, ("-webkit-media-controls-timeline-container"));
    DEFINE_STATIC_LOCAL(AtomicString, mediaControlsCurrentTimeDisplay, ("-webkit-media-controls-current-time-display"));
    DEFINE_STATIC_LOCAL(AtomicString, mediaControlsTimeRemainingDisplay, ("-webkit-media-controls-time-remaining-display"));
    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, 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, searchCancelButton, ("-webkit-search-cancel-button"));
    DEFINE_STATIC_LOCAL(AtomicString, searchDecoration, ("-webkit-search-decoration"));
    DEFINE_STATIC_LOCAL(AtomicString, searchResultsDecoration, ("-webkit-search-results-decoration"));
    DEFINE_STATIC_LOCAL(AtomicString, searchResultsButton, ("-webkit-search-results-button"));
    DEFINE_STATIC_LOCAL(AtomicString, selection, ("selection"));
    DEFINE_STATIC_LOCAL(AtomicString, sliderThumb, ("-webkit-slider-thumb"));
    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"));

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

    m_pseudoType = PseudoUnknown;
    if (m_value == active)
        m_pseudoType = PseudoActive;
    else if (m_value == after) {
        m_pseudoType = PseudoAfter;
        element = true;
        compat = true;
    } else if (m_value == anyLink)
        m_pseudoType = PseudoAnyLink;
    else if (m_value == autofill)
        m_pseudoType = PseudoAutofill;
    else if (m_value == before) {
        m_pseudoType = PseudoBefore;
        element = true;
        compat = true;
    } else if (m_value == checked)
        m_pseudoType = PseudoChecked;
    else if (m_value == fileUploadButton) {
        m_pseudoType = PseudoFileUploadButton;
        element = true;
    } else if (m_value == disabled)
        m_pseudoType = PseudoDisabled;
    else if (m_value == readOnly)
        m_pseudoType = PseudoReadOnly;
    else if (m_value == readWrite)
        m_pseudoType = PseudoReadWrite;
    else if (m_value == drag || m_value == dragAlias)
        m_pseudoType = PseudoDrag;
    else if (m_value == enabled)
        m_pseudoType = PseudoEnabled;
    else if (m_value == empty)
        m_pseudoType = PseudoEmpty;
    else if (m_value == firstChild)
        m_pseudoType = PseudoFirstChild;
    else if (m_value == fullPageMedia)
        m_pseudoType = PseudoFullPageMedia;
    else if (m_value == inputPlaceholder) {
        m_pseudoType = PseudoInputPlaceholder;
        element = true;
    } else if (m_value == lastChild)
        m_pseudoType = PseudoLastChild;
    else if (m_value == lastOfType)
        m_pseudoType = PseudoLastOfType;
    else if (m_value == onlyChild)
        m_pseudoType = PseudoOnlyChild;
    else if (m_value == onlyOfType)
        m_pseudoType = PseudoOnlyOfType;
    else if (m_value == firstLetter) {
        m_pseudoType = PseudoFirstLetter;
        element = true;
        compat = true;
    } else if (m_value == firstLine) {
        m_pseudoType = PseudoFirstLine;
        element = true;
        compat = true;
    } else if (m_value == firstOfType)
        m_pseudoType = PseudoFirstOfType;
    else if (m_value == focus)
        m_pseudoType = PseudoFocus;
    else if (m_value == hover)
        m_pseudoType = PseudoHover;
    else if (m_value == indeterminate)
        m_pseudoType = PseudoIndeterminate;
    else if (m_value == link)
        m_pseudoType = PseudoLink;
    else if (m_value == lang)
        m_pseudoType = PseudoLang;
    else if (m_value == mediaControlsPanel) {
        m_pseudoType = PseudoMediaControlsPanel;
        element = true;
    } else if (m_value == mediaControlsMuteButton) {
        m_pseudoType = PseudoMediaControlsMuteButton;
        element = true;
    } else if (m_value == mediaControlsPlayButton) {
        m_pseudoType = PseudoMediaControlsPlayButton;
        element = true;
    } else if (m_value == mediaControlsCurrentTimeDisplay) {
        m_pseudoType = PseudoMediaControlsCurrentTimeDisplay;
        element = true;
    } else if (m_value == mediaControlsTimeRemainingDisplay) {
        m_pseudoType = PseudoMediaControlsTimeRemainingDisplay;
        element = true;
    } else if (m_value == mediaControlsTimeline) {
        m_pseudoType = PseudoMediaControlsTimeline;
        element = true;
    } else if (m_value == mediaControlsSeekBackButton) {
        m_pseudoType = PseudoMediaControlsSeekBackButton;
        element = true;
    } else if (m_value == mediaControlsSeekForwardButton) {
        m_pseudoType = PseudoMediaControlsSeekForwardButton;
        element = true;
    } else if (m_value == mediaControlsFullscreenButton) {
        m_pseudoType = PseudoMediaControlsFullscreenButton;
        element = true;
    } else if (m_value == mediaControlsTimelineContainer) {
        m_pseudoType = PseudoMediaControlsTimelineContainer;
        element = true;
    } else if (m_value == notStr)
        m_pseudoType = PseudoNot;
    else if (m_value == nthChild)
        m_pseudoType = PseudoNthChild;
    else if (m_value == nthOfType)
        m_pseudoType = PseudoNthOfType;
    else if (m_value == nthLastChild)
        m_pseudoType = PseudoNthLastChild;
    else if (m_value == nthLastOfType)
        m_pseudoType = PseudoNthLastOfType;
    else if (m_value == root)
        m_pseudoType = PseudoRoot;
    else if (m_value == windowInactive)
        m_pseudoType = PseudoWindowInactive;
    else if (m_value == decrement)
        m_pseudoType = PseudoDecrement;
    else if (m_value == increment)
        m_pseudoType = PseudoIncrement;
    else if (m_value == start)
        m_pseudoType = PseudoStart;
    else if (m_value == end)
        m_pseudoType = PseudoEnd;
    else if (m_value == horizontal)
        m_pseudoType = PseudoHorizontal;
    else if (m_value == vertical)
        m_pseudoType = PseudoVertical;
    else if (m_value == doubleButton)
        m_pseudoType = PseudoDoubleButton;
    else if (m_value == singleButton)
        m_pseudoType = PseudoSingleButton;
    else if (m_value == noButton)
        m_pseudoType = PseudoNoButton;
    else if (m_value == scrollbarCorner) {
        element = true;
        m_pseudoType = PseudoScrollbarCorner;
    } else if (m_value == resizer) {
        element = true;
        m_pseudoType = PseudoResizer;
    } else if (m_value == scrollbar) {
        element = true;
        m_pseudoType = PseudoScrollbar;
    } else if (m_value == scrollbarButton) {
        element = true;
        m_pseudoType = PseudoScrollbarButton;
    } else if (m_value == scrollbarCorner) {
        element = true;
        m_pseudoType = PseudoScrollbarCorner;
    } else if (m_value == scrollbarThumb) {
        element = true;
        m_pseudoType = PseudoScrollbarThumb;
    } else if (m_value == scrollbarTrack) {
        element = true;
        m_pseudoType = PseudoScrollbarTrack;
    } else if (m_value == scrollbarTrackPiece) {
        element = true;
        m_pseudoType = PseudoScrollbarTrackPiece;
    } else if (m_value == cornerPresent)
         m_pseudoType = PseudoCornerPresent;
    else if (m_value == searchCancelButton) {
        m_pseudoType = PseudoSearchCancelButton;
        element = true;
    } else if (m_value == searchDecoration) {
        m_pseudoType = PseudoSearchDecoration;
        element = true;
    } else if (m_value == searchResultsDecoration) {
        m_pseudoType = PseudoSearchResultsDecoration;
        element = true;
    } else if (m_value == searchResultsButton) {
        m_pseudoType = PseudoSearchResultsButton;
        element = true;
    }  else if (m_value == selection) {
        m_pseudoType = PseudoSelection;
        element = true;
    } else if (m_value == sliderThumb) {
        m_pseudoType = PseudoSliderThumb;
        element = true;
    } else if (m_value == target)
        m_pseudoType = PseudoTarget;
    else if (m_value == visited)
        m_pseudoType = PseudoVisited;

    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->m_value != sel2->m_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 + "|" + localName;
    }

    const CSSSelector* cs = this;
    while (true) {
        if (cs->m_match == CSSSelector::Id) {
            str += "#";
            str += cs->m_value;
        } else if (cs->m_match == CSSSelector::Class) {
            str += ".";
            str += cs->m_value;
        } else if (cs->m_match == CSSSelector::PseudoClass) {
            str += ":";
            str += cs->m_value;
            if (cs->pseudoType() == PseudoNot) {
                if (CSSSelector* subSel = cs->simpleSelector())
                    str += subSel->selectorText();
                str += ")";
            } else if (cs->pseudoType() == PseudoLang
                    || cs->pseudoType() == PseudoNthChild
                    || cs->pseudoType() == PseudoNthLastChild
                    || cs->pseudoType() == PseudoNthOfType
                    || cs->pseudoType() == PseudoNthLastOfType) {
                str += cs->argument();
                str += ")";
            }
        } else if (cs->m_match == CSSSelector::PseudoElement) {
            str += "::";
            str += cs->m_value;
        } else if (cs->hasAttribute()) {
            str += "[";
            const AtomicString& prefix = cs->attribute().prefix();
            if (!prefix.isNull())
                str += prefix + "|";
            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) {
                str += "\"";
                str += cs->m_value;
                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
            // Descendant
            str = tagHistoryText + " " + str;
    }

    return str;
}
    
void CSSSelector::setTagHistory(CSSSelector* tagHistory) 
{ 
    if (m_hasRareData) 
        m_data.m_rareData->m_tagHistory.set(tagHistory); 
    else 
        m_data.m_tagHistory = tagHistory; 
}

const QualifiedName& CSSSelector::attribute() const
{ 
    switch (m_match) {
    case Id:
        return idAttr;
    case Class:
        return classAttr;
    default:
        return m_hasRareData ? m_data.m_rareData->m_attribute : anyQName();
    }
}

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::setSimpleSelector(CSSSelector* value)
{
    createRareData(); 
    m_data.m_rareData->m_simpleSelector.set(value); 
}

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);
}

// a helper function for parsing nth-arguments
bool CSSSelector::RareData::parseNth()
{    
    const String& argument = m_argument;
    
    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 {
        int n = argument.find('n');
        if (n != -1) {
            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();
            
            int p = argument.find('+', n);
            if (p != -1)
                m_b = argument.substring(p + 1, argument.length() - p - 1).toInt();
            else {
                p = argument.find('-', n);
                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