/* * 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 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