blob: 5429c16f4089876ded314a4cdc0686af256e3125 [file] [log] [blame]
/*
* 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, mediaControlsRewindButton, ("-webkit-media-controls-rewind-button"));
DEFINE_STATIC_LOCAL(AtomicString, mediaControlsReturnToRealtimeButton, ("-webkit-media-controls-return-to-realtime-button"));
DEFINE_STATIC_LOCAL(AtomicString, mediaControlsStatusDisplay, ("-webkit-media-controls-status-display"));
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 == mediaControlsRewindButton) {
m_pseudoType = PseudoMediaControlsRewindButton;
element = true;
} else if (m_value == mediaControlsReturnToRealtimeButton) {
m_pseudoType = PseudoMediaControlsReturnToRealtimeButton;
element = true;
} else if (m_value == mediaControlsStatusDisplay) {
m_pseudoType = PseudoMediaControlsStatusDisplay;
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