diff options
Diffstat (limited to 'Source/WebCore/css/CSSSelector.cpp')
-rw-r--r-- | Source/WebCore/css/CSSSelector.cpp | 1074 |
1 files changed, 612 insertions, 462 deletions
diff --git a/Source/WebCore/css/CSSSelector.cpp b/Source/WebCore/css/CSSSelector.cpp index 3e645ca16..2388a79a3 100644 --- a/Source/WebCore/css/CSSSelector.cpp +++ b/Source/WebCore/css/CSSSelector.cpp @@ -3,7 +3,7 @@ * 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, 2013 Apple Inc. All rights reserved. + * Copyright (C) 2002, 2006, 2007, 2008, 2009, 2010, 2013, 2014 Apple Inc. All rights reserved. * Copyright (C) 2008 David Smith (catfish.man@gmail.com) * Copyright (C) 2010 Google Inc. All rights reserved. * @@ -26,12 +26,12 @@ #include "config.h" #include "CSSSelector.h" -#include "CSSOMUtils.h" +#include "CSSMarkup.h" #include "CSSSelectorList.h" #include "HTMLNames.h" +#include "SelectorPseudoTypeMap.h" #include <wtf/Assertions.h> #include <wtf/HashMap.h> -#include <wtf/NeverDestroyed.h> #include <wtf/StdLibExtras.h> #include <wtf/Vector.h> #include <wtf/text/AtomicStringHash.h> @@ -41,99 +41,223 @@ namespace WebCore { using namespace HTMLNames; +struct SameSizeAsCSSSelector { + unsigned flags; + void* unionPointer; +}; + +static_assert(CSSSelector::RelationType::Subselector == 0, "Subselector must be 0 for consumeCombinator."); +static_assert(sizeof(CSSSelector) == sizeof(SameSizeAsCSSSelector), "CSSSelector should remain small."); + +CSSSelector::CSSSelector(const QualifiedName& tagQName, bool tagIsForNamespaceRule) + : m_relation(DescendantSpace) + , m_match(Tag) + , m_pseudoType(0) + , m_parsedNth(false) + , m_isLastInSelectorList(false) + , m_isLastInTagHistory(true) + , m_hasRareData(false) + , m_hasNameWithCase(false) + , m_isForPage(false) + , m_tagIsForNamespaceRule(tagIsForNamespaceRule) + , m_caseInsensitiveAttributeValueMatching(false) +#if !ASSERT_WITH_SECURITY_IMPLICATION_DISABLED + , m_destructorHasBeenCalled(false) +#endif +{ + const AtomicString& tagLocalName = tagQName.localName(); + const AtomicString tagLocalNameASCIILowercase = tagLocalName.convertToASCIILowercase(); + + if (tagLocalName == tagLocalNameASCIILowercase) { + m_data.m_tagQName = tagQName.impl(); + m_data.m_tagQName->ref(); + } else { + m_data.m_nameWithCase = adoptRef(new NameWithCase(tagQName, tagLocalNameASCIILowercase)).leakRef(); + m_hasNameWithCase = true; + } +} + void CSSSelector::createRareData() { - ASSERT(m_match != Tag); + ASSERT(match() != Tag); + ASSERT(!m_hasNameWithCase); if (m_hasRareData) return; // Move the value to the rare data stucture. - m_data.m_rareData = RareData::create(adoptRef(m_data.m_value)).leakRef(); + AtomicString value { adoptRef(m_data.m_value) }; + m_data.m_rareData = &RareData::create(WTFMove(value)).leakRef(); m_hasRareData = true; } -unsigned CSSSelector::specificity() const -{ - // make sure the result doesn't overflow - static const unsigned maxValueMask = 0xffffff; - static const unsigned idMask = 0xff0000; - static const unsigned classMask = 0xff00; - static const unsigned elementMask = 0xff; +static unsigned simpleSelectorSpecificityInternal(const CSSSelector& simpleSelector, bool isComputingMaximumSpecificity); - if (isForPage()) - return specificityForPage() & maxValueMask; +static unsigned selectorSpecificity(const CSSSelector& firstSimpleSelector, bool isComputingMaximumSpecificity) +{ + unsigned total = simpleSelectorSpecificityInternal(firstSimpleSelector, isComputingMaximumSpecificity); - unsigned total = 0; - unsigned temp = 0; - - for (const CSSSelector* selector = this; selector; selector = selector->tagHistory()) { - temp = total + selector->specificityForOneSelector(); - // Clamp each component to its max in the case of overflow. - if ((temp & idMask) < (total & idMask)) - total |= idMask; - else if ((temp & classMask) < (total & classMask)) - total |= classMask; - else if ((temp & elementMask) < (total & elementMask)) - total |= elementMask; - else - total = temp; - } + for (const CSSSelector* selector = firstSimpleSelector.tagHistory(); selector; selector = selector->tagHistory()) + total = CSSSelector::addSpecificities(total, simpleSelectorSpecificityInternal(*selector, isComputingMaximumSpecificity)); 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. - switch (m_match) { - case Id: - return 0x10000; - 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()) - return selectorList()->first()->specificityForOneSelector(); - return 0x100; - case Tag: - return (tagQName().localName() != starAtom) ? 1 : 0; - case Unknown: +static unsigned maxSpecificity(const CSSSelectorList& selectorList) +{ + unsigned maxSpecificity = 0; + for (const CSSSelector* subSelector = selectorList.first(); subSelector; subSelector = CSSSelectorList::next(subSelector)) + maxSpecificity = std::max(maxSpecificity, selectorSpecificity(*subSelector, true)); + return maxSpecificity; +} + +static unsigned simpleSelectorSpecificityInternal(const CSSSelector& simpleSelector, bool isComputingMaximumSpecificity) +{ + ASSERT_WITH_MESSAGE(!simpleSelector.isForPage(), "At the time of this writing, page selectors are not treated as real selectors that are matched. The value computed here only account for real selectors."); + + switch (simpleSelector.match()) { + case CSSSelector::Id: + return static_cast<unsigned>(SelectorSpecificityIncrement::ClassA); + + case CSSSelector::PagePseudoClass: + break; + case CSSSelector::PseudoClass: + if (simpleSelector.pseudoClassType() == CSSSelector::PseudoClassMatches) { + ASSERT_WITH_MESSAGE(simpleSelector.selectorList() && simpleSelector.selectorList()->first(), "The parser should never generate a valid selector for an empty :matches()."); + if (!isComputingMaximumSpecificity) + return 0; + return maxSpecificity(*simpleSelector.selectorList()); + } + + if (simpleSelector.pseudoClassType() == CSSSelector::PseudoClassNot) { + ASSERT_WITH_MESSAGE(simpleSelector.selectorList() && simpleSelector.selectorList()->first(), "The parser should never generate a valid selector for an empty :not()."); + return maxSpecificity(*simpleSelector.selectorList()); + } + FALLTHROUGH; + case CSSSelector::Exact: + case CSSSelector::Class: + case CSSSelector::Set: + case CSSSelector::List: + case CSSSelector::Hyphen: + case CSSSelector::Contain: + case CSSSelector::Begin: + case CSSSelector::End: + return static_cast<unsigned>(SelectorSpecificityIncrement::ClassB); + case CSSSelector::Tag: + return (simpleSelector.tagQName().localName() != starAtom) ? static_cast<unsigned>(SelectorSpecificityIncrement::ClassC) : 0; + case CSSSelector::PseudoElement: + return static_cast<unsigned>(SelectorSpecificityIncrement::ClassC); + case CSSSelector::Unknown: return 0; } ASSERT_NOT_REACHED(); return 0; } +unsigned CSSSelector::simpleSelectorSpecificity() const +{ + return simpleSelectorSpecificityInternal(*this, false); +} + +static unsigned staticSpecificityInternal(const CSSSelector& firstSimpleSelector, bool& ok); + +static unsigned simpleSelectorFunctionalPseudoClassStaticSpecificity(const CSSSelector& simpleSelector, bool& ok) +{ + if (simpleSelector.match() == CSSSelector::PseudoClass) { + CSSSelector::PseudoClassType pseudoClassType = simpleSelector.pseudoClassType(); + if (pseudoClassType == CSSSelector::PseudoClassMatches || pseudoClassType == CSSSelector::PseudoClassNthChild || pseudoClassType == CSSSelector::PseudoClassNthLastChild) { + const CSSSelectorList* selectorList = simpleSelector.selectorList(); + if (!selectorList) { + ASSERT_WITH_MESSAGE(pseudoClassType != CSSSelector::PseudoClassMatches, ":matches() should never be created without a valid selector list."); + return 0; + } + + const CSSSelector& firstSubselector = *selectorList->first(); + + unsigned initialSpecificity = staticSpecificityInternal(firstSubselector, ok); + if (!ok) + return 0; + + const CSSSelector* subselector = &firstSubselector; + while ((subselector = CSSSelectorList::next(subselector))) { + unsigned subSelectorSpecificity = staticSpecificityInternal(*subselector, ok); + if (initialSpecificity != subSelectorSpecificity) + ok = false; + if (!ok) + return 0; + } + return initialSpecificity; + } + } + return 0; +} + +static unsigned functionalPseudoClassStaticSpecificity(const CSSSelector& firstSimpleSelector, bool& ok) +{ + unsigned total = 0; + for (const CSSSelector* selector = &firstSimpleSelector; selector; selector = selector->tagHistory()) { + total = CSSSelector::addSpecificities(total, simpleSelectorFunctionalPseudoClassStaticSpecificity(*selector, ok)); + if (!ok) + return 0; + } + return total; +} + +static unsigned staticSpecificityInternal(const CSSSelector& firstSimpleSelector, bool& ok) +{ + unsigned staticSpecificity = selectorSpecificity(firstSimpleSelector, false); + return CSSSelector::addSpecificities(staticSpecificity, functionalPseudoClassStaticSpecificity(firstSimpleSelector, ok)); +} + +unsigned CSSSelector::staticSpecificity(bool &ok) const +{ + ok = true; + return staticSpecificityInternal(*this, ok); +} + +unsigned CSSSelector::addSpecificities(unsigned a, unsigned b) +{ + unsigned total = a; + + unsigned newIdValue = (b & idMask); + if (((total & idMask) + newIdValue) & ~idMask) + total |= idMask; + else + total += newIdValue; + + unsigned newClassValue = (b & classMask); + if (((total & classMask) + newClassValue) & ~classMask) + total |= classMask; + else + total += newClassValue; + + unsigned newElementValue = (b & elementMask); + if (((total & elementMask) + newElementValue) & ~elementMask) + total |= elementMask; + else + total += newElementValue; + + return total; +} + unsigned CSSSelector::specificityForPage() const { + ASSERT(isForPage()); + // See http://dev.w3.org/csswg/css3-page/#cascading-and-page-context unsigned s = 0; for (const CSSSelector* component = this; component; component = component->tagHistory()) { - switch (component->m_match) { + switch (component->match()) { case Tag: s += tagQName().localName() == starAtom ? 0 : 4; break; - case PseudoClass: - switch (component->pseudoType()) { - case PseudoFirstPage: + case PagePseudoClass: + switch (component->pagePseudoClassType()) { + case PagePseudoClassFirst: s += 2; break; - case PseudoLeftPage: - case PseudoRightPage: + case PagePseudoClassLeft: + case PagePseudoClassRight: s += 1; break; - case PseudoNotParsed: - break; - default: - ASSERT_NOT_REACHED(); } break; default: @@ -143,112 +267,41 @@ unsigned CSSSelector::specificityForPage() const return s; } -PseudoId CSSSelector::pseudoId(PseudoType type) +PseudoId CSSSelector::pseudoId(PseudoElementType type) { switch (type) { - case PseudoFirstLine: + case PseudoElementFirstLine: return FIRST_LINE; - case PseudoFirstLetter: + case PseudoElementFirstLetter: return FIRST_LETTER; - case PseudoSelection: + case PseudoElementSelection: return SELECTION; - case PseudoBefore: + case PseudoElementBefore: return BEFORE; - case PseudoAfter: + case PseudoElementAfter: return AFTER; - case PseudoScrollbar: + case PseudoElementScrollbar: return SCROLLBAR; - case PseudoScrollbarButton: + case PseudoElementScrollbarButton: return SCROLLBAR_BUTTON; - case PseudoScrollbarCorner: + case PseudoElementScrollbarCorner: return SCROLLBAR_CORNER; - case PseudoScrollbarThumb: + case PseudoElementScrollbarThumb: return SCROLLBAR_THUMB; - case PseudoScrollbarTrack: + case PseudoElementScrollbarTrack: return SCROLLBAR_TRACK; - case PseudoScrollbarTrackPiece: + case PseudoElementScrollbarTrackPiece: return SCROLLBAR_TRACK_PIECE; - case PseudoResizer: + case PseudoElementResizer: 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 PseudoTarget: - case PseudoLang: - case PseudoNot: - case PseudoRoot: - case PseudoScope: - 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: - case PseudoUserAgentCustomElement: - case PseudoWebKitCustomElement: #if ENABLE(VIDEO_TRACK) - case PseudoCue: - case PseudoFutureCue: - case PseudoPastCue: + case PseudoElementCue: #endif -#if ENABLE(IFRAME_SEAMLESS) - case PseudoSeamlessDocument: -#endif - return NOPSEUDO; - case PseudoNotParsed: - ASSERT_NOT_REACHED(); + case PseudoElementSlotted: + case PseudoElementUnknown: + case PseudoElementUserAgentCustom: + case PseudoElementWebKitCustom: + case PseudoElementWebKitCustomLegacyPrefixed: return NOPSEUDO; } @@ -256,253 +309,23 @@ PseudoId CSSSelector::pseudoId(PseudoType type) return NOPSEUDO; } -static NEVER_INLINE void populatePseudoTypeByNameMap(HashMap<AtomicString, CSSSelector::PseudoType>& map) -{ - struct TableEntry { - const char* name; - unsigned nameLength; - CSSSelector::PseudoType type; - }; - - // Could use strlen in this macro but not all compilers can constant-fold it. -#define TABLE_ENTRY(name, type) { name, sizeof(name) - 1, CSSSelector::type }, - - static const TableEntry table[] = { - TABLE_ENTRY("-khtml-drag", PseudoDrag) - TABLE_ENTRY("-webkit-any(", PseudoAny) - TABLE_ENTRY("-webkit-any-link", PseudoAnyLink) - TABLE_ENTRY("-webkit-autofill", PseudoAutofill) - TABLE_ENTRY("-webkit-drag", PseudoDrag) - TABLE_ENTRY("-webkit-full-page-media", PseudoFullPageMedia) - TABLE_ENTRY("-webkit-resizer", PseudoResizer) - TABLE_ENTRY("-webkit-scrollbar", PseudoScrollbar) - TABLE_ENTRY("-webkit-scrollbar-button", PseudoScrollbarButton) - TABLE_ENTRY("-webkit-scrollbar-corner", PseudoScrollbarCorner) - TABLE_ENTRY("-webkit-scrollbar-thumb", PseudoScrollbarThumb) - TABLE_ENTRY("-webkit-scrollbar-track", PseudoScrollbarTrack) - TABLE_ENTRY("-webkit-scrollbar-track-piece", PseudoScrollbarTrackPiece) - TABLE_ENTRY("active", PseudoActive) - TABLE_ENTRY("after", PseudoAfter) - TABLE_ENTRY("before", PseudoBefore) - TABLE_ENTRY("checked", PseudoChecked) - TABLE_ENTRY("corner-present", PseudoCornerPresent) - TABLE_ENTRY("decrement", PseudoDecrement) - TABLE_ENTRY("default", PseudoDefault) - TABLE_ENTRY("disabled", PseudoDisabled) - TABLE_ENTRY("double-button", PseudoDoubleButton) - TABLE_ENTRY("empty", PseudoEmpty) - TABLE_ENTRY("enabled", PseudoEnabled) - TABLE_ENTRY("end", PseudoEnd) - TABLE_ENTRY("first", PseudoFirstPage) - TABLE_ENTRY("first-child", PseudoFirstChild) - TABLE_ENTRY("first-letter", PseudoFirstLetter) - TABLE_ENTRY("first-line", PseudoFirstLine) - TABLE_ENTRY("first-of-type", PseudoFirstOfType) - TABLE_ENTRY("focus", PseudoFocus) - TABLE_ENTRY("horizontal", PseudoHorizontal) - TABLE_ENTRY("hover", PseudoHover) - TABLE_ENTRY("in-range", PseudoInRange) - TABLE_ENTRY("increment", PseudoIncrement) - TABLE_ENTRY("indeterminate", PseudoIndeterminate) - TABLE_ENTRY("invalid", PseudoInvalid) - TABLE_ENTRY("lang(", PseudoLang) - TABLE_ENTRY("last-child", PseudoLastChild) - TABLE_ENTRY("last-of-type", PseudoLastOfType) - TABLE_ENTRY("left", PseudoLeftPage) - TABLE_ENTRY("link", PseudoLink) - TABLE_ENTRY("no-button", PseudoNoButton) - TABLE_ENTRY("not(", PseudoNot) - TABLE_ENTRY("nth-child(", PseudoNthChild) - TABLE_ENTRY("nth-last-child(", PseudoNthLastChild) - TABLE_ENTRY("nth-last-of-type(", PseudoNthLastOfType) - TABLE_ENTRY("nth-of-type(", PseudoNthOfType) - TABLE_ENTRY("only-child", PseudoOnlyChild) - TABLE_ENTRY("only-of-type", PseudoOnlyOfType) - TABLE_ENTRY("optional", PseudoOptional) - TABLE_ENTRY("out-of-range", PseudoOutOfRange) - TABLE_ENTRY("read-only", PseudoReadOnly) - TABLE_ENTRY("read-write", PseudoReadWrite) - TABLE_ENTRY("required", PseudoRequired) - TABLE_ENTRY("right", PseudoRightPage) - TABLE_ENTRY("root", PseudoRoot) - TABLE_ENTRY("scope", PseudoScope) - TABLE_ENTRY("selection", PseudoSelection) - TABLE_ENTRY("single-button", PseudoSingleButton) - TABLE_ENTRY("start", PseudoStart) - TABLE_ENTRY("target", PseudoTarget) - TABLE_ENTRY("valid", PseudoValid) - TABLE_ENTRY("vertical", PseudoVertical) - TABLE_ENTRY("visited", PseudoVisited) - TABLE_ENTRY("window-inactive", PseudoWindowInactive) - -#if ENABLE(FULLSCREEN_API) - TABLE_ENTRY("-webkit-animating-full-screen-transition", PseudoAnimatingFullScreenTransition) - TABLE_ENTRY("-webkit-full-screen", PseudoFullScreen) - TABLE_ENTRY("-webkit-full-screen-ancestor", PseudoFullScreenAncestor) - TABLE_ENTRY("-webkit-full-screen-document", PseudoFullScreenDocument) -#endif - -#if ENABLE(IFRAME_SEAMLESS) - TABLE_ENTRY("-webkit-seamless-document", PseudoSeamlessDocument) -#endif - -#if ENABLE(VIDEO_TRACK) - TABLE_ENTRY("cue(", PseudoCue) - TABLE_ENTRY("future", PseudoFutureCue) - TABLE_ENTRY("past", PseudoPastCue) -#endif - }; - -#undef TABLE_ENTRY - - for (unsigned i = 0; i < WTF_ARRAY_LENGTH(table); ++i) - map.add(AtomicString(table[i].name, table[i].nameLength, AtomicString::ConstructFromLiteral), table[i].type); -} - -CSSSelector::PseudoType CSSSelector::parsePseudoType(const AtomicString& name) +CSSSelector::PseudoElementType CSSSelector::parsePseudoElementType(const String& name) { if (name.isNull()) - return PseudoUnknown; - - static NeverDestroyed<HashMap<AtomicString, CSSSelector::PseudoType>> types; - if (types.get().isEmpty()) - populatePseudoTypeByNameMap(types); - if (PseudoType type = types.get().get(name)) - return type; - - if (name.startsWith("-webkit-")) - return PseudoWebKitCustomElement; - - // FIXME: This is strange. Why would all strings that start with "cue" be "user agent custom"? - if (name.startsWith("x-") || name.startsWith("cue")) - return PseudoUserAgentCustomElement; - - return PseudoUnknown; -} - -void CSSSelector::extractPseudoType() const -{ - if (m_match != PseudoClass && m_match != PseudoElement && m_match != PagePseudoClass) - return; + return PseudoElementUnknown; - m_pseudoType = parsePseudoType(value()); + PseudoElementType type = parsePseudoElementString(*name.impl()); + if (type == PseudoElementUnknown) { + if (name.startsWith("-webkit-")) + type = PseudoElementWebKitCustom; - 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: -#if ENABLE(VIDEO_TRACK) - case PseudoCue: -#endif - case PseudoFirstLetter: - case PseudoFirstLine: - compat = true; - FALLTHROUGH; -#if ENABLE(SHADOW_DOM) - case PseudoDistributed: -#endif - case PseudoResizer: - case PseudoScrollbar: - case PseudoScrollbarCorner: - case PseudoScrollbarButton: - case PseudoScrollbarThumb: - case PseudoScrollbarTrack: - case PseudoScrollbarTrackPiece: - case PseudoSelection: - case PseudoUserAgentCustomElement: - case PseudoWebKitCustomElement: - 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 PseudoScope: - case PseudoValid: - case PseudoInvalid: - case PseudoIndeterminate: - 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 -#if ENABLE(IFRAME_SEAMLESS) - case PseudoSeamlessDocument: -#endif - case PseudoInRange: - case PseudoOutOfRange: -#if ENABLE(VIDEO_TRACK) - case PseudoFutureCue: - case PseudoPastCue: -#endif - break; - case PseudoFirstPage: - case PseudoLeftPage: - case PseudoRightPage: - isPagePseudoClass = true; - break; + if (name.startsWith("x-")) + type = PseudoElementUserAgentCustom; } - - 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; + return type; } + bool CSSSelector::operator==(const CSSSelector& other) const { const CSSSelector* sel1 = this; @@ -511,13 +334,13 @@ bool CSSSelector::operator==(const CSSSelector& other) const while (sel1 && sel2) { if (sel1->attribute() != sel2->attribute() || sel1->relation() != sel2->relation() - || sel1->m_match != sel2->m_match + || sel1->match() != sel2->match() || sel1->value() != sel2->value() - || sel1->pseudoType() != sel2->pseudoType() + || sel1->m_pseudoType != sel2->m_pseudoType || sel1->argument() != sel2->argument()) { return false; } - if (sel1->m_match == Tag) { + if (sel1->match() == Tag) { if (sel1->tagQName() != sel2->tagQName()) return false; } @@ -531,11 +354,46 @@ bool CSSSelector::operator==(const CSSSelector& other) const return true; } +static void appendPseudoClassFunctionTail(StringBuilder& str, const CSSSelector* selector) +{ + switch (selector->pseudoClassType()) { +#if ENABLE(CSS_SELECTORS_LEVEL4) + case CSSSelector::PseudoClassDir: +#endif + case CSSSelector::PseudoClassLang: + case CSSSelector::PseudoClassNthChild: + case CSSSelector::PseudoClassNthLastChild: + case CSSSelector::PseudoClassNthOfType: + case CSSSelector::PseudoClassNthLastOfType: +#if ENABLE(CSS_SELECTORS_LEVEL4) + case CSSSelector::PseudoClassRole: +#endif + str.append(selector->argument()); + str.append(')'); + break; + default: + break; + } + +} + +static void appendLangArgumentList(StringBuilder& str, const Vector<AtomicString>& argumentList) +{ + unsigned argumentListSize = argumentList.size(); + for (unsigned i = 0; i < argumentListSize; ++i) { + str.append('"'); + str.append(argumentList[i]); + str.append('"'); + if (i != argumentListSize - 1) + str.appendLiteral(", "); + } +} + String CSSSelector::selectorText(const String& rightSide) const { StringBuilder str; - if (m_match == CSSSelector::Tag && !m_tagIsForNamespaceRule) { + if (match() == CSSSelector::Tag && !m_tagIsForNamespaceRule) { if (tagQName().prefix().isNull()) str.append(tagQName().localName()); else { @@ -547,55 +405,272 @@ String CSSSelector::selectorText(const String& rightSide) const const CSSSelector* cs = this; while (true) { - if (cs->m_match == CSSSelector::Id) { + if (cs->match() == CSSSelector::Id) { str.append('#'); - serializeIdentifier(cs->value(), str); - } else if (cs->m_match == CSSSelector::Class) { + serializeIdentifier(cs->serializingValue(), str); + } else if (cs->match() == CSSSelector::Class) { str.append('.'); - serializeIdentifier(cs->value(), str); - } else if (cs->m_match == CSSSelector::PseudoClass || cs->m_match == CSSSelector::PagePseudoClass) { - str.append(':'); - str.append(cs->value()); - - switch (cs->pseudoType()) { - case PseudoNot: - if (const CSSSelectorList* selectorList = cs->selectorList()) - str.append(selectorList->first()->selectorText()); + serializeIdentifier(cs->serializingValue(), str); + } else if (cs->match() == CSSSelector::PseudoClass) { + switch (cs->pseudoClassType()) { +#if ENABLE(FULLSCREEN_API) + case CSSSelector::PseudoClassAnimatingFullScreenTransition: + str.appendLiteral(":-webkit-animating-full-screen-transition"); + break; +#endif + case CSSSelector::PseudoClassAny: { + str.appendLiteral(":-webkit-any("); + cs->selectorList()->buildSelectorsText(str); str.append(')'); break; - case PseudoLang: - case PseudoNthChild: - case PseudoNthLastChild: - case PseudoNthOfType: - case PseudoNthLastOfType: + } + case CSSSelector::PseudoClassAnyLink: + str.appendLiteral(":any-link"); + break; + case CSSSelector::PseudoClassAnyLinkDeprecated: + str.appendLiteral(":-webkit-any-link"); + break; + case CSSSelector::PseudoClassAutofill: + str.appendLiteral(":-webkit-autofill"); + break; + case CSSSelector::PseudoClassDrag: + str.appendLiteral(":-webkit-drag"); + break; + case CSSSelector::PseudoClassFullPageMedia: + str.appendLiteral(":-webkit-full-page-media"); + break; +#if ENABLE(FULLSCREEN_API) + case CSSSelector::PseudoClassFullScreen: + str.appendLiteral(":-webkit-full-screen"); + break; + case CSSSelector::PseudoClassFullScreenAncestor: + str.appendLiteral(":-webkit-full-screen-ancestor"); + break; + case CSSSelector::PseudoClassFullScreenDocument: + str.appendLiteral(":-webkit-full-screen-document"); + break; +#endif + case CSSSelector::PseudoClassActive: + str.appendLiteral(":active"); + break; + case CSSSelector::PseudoClassChecked: + str.appendLiteral(":checked"); + break; + case CSSSelector::PseudoClassCornerPresent: + str.appendLiteral(":corner-present"); + break; + case CSSSelector::PseudoClassDecrement: + str.appendLiteral(":decrement"); + break; + case CSSSelector::PseudoClassDefault: + str.appendLiteral(":default"); + break; +#if ENABLE(CSS_SELECTORS_LEVEL4) + case CSSSelector::PseudoClassDir: + str.appendLiteral(":dir("); + appendPseudoClassFunctionTail(str, cs); + break; +#endif + case CSSSelector::PseudoClassDisabled: + str.appendLiteral(":disabled"); + break; + case CSSSelector::PseudoClassDoubleButton: + str.appendLiteral(":double-button"); + break; + case CSSSelector::PseudoClassEmpty: + str.appendLiteral(":empty"); + break; + case CSSSelector::PseudoClassEnabled: + str.appendLiteral(":enabled"); + break; + case CSSSelector::PseudoClassEnd: + str.appendLiteral(":end"); + break; + case CSSSelector::PseudoClassFirstChild: + str.appendLiteral(":first-child"); + break; + case CSSSelector::PseudoClassFirstOfType: + str.appendLiteral(":first-of-type"); + break; + case CSSSelector::PseudoClassFocus: + str.appendLiteral(":focus"); + break; + case CSSSelector::PseudoClassFocusWithin: + str.appendLiteral(":focus-within"); + break; +#if ENABLE(VIDEO_TRACK) + case CSSSelector::PseudoClassFuture: + str.appendLiteral(":future"); + break; +#endif + case CSSSelector::PseudoClassHorizontal: + str.appendLiteral(":horizontal"); + break; + case CSSSelector::PseudoClassHover: + str.appendLiteral(":hover"); + break; + case CSSSelector::PseudoClassInRange: + str.appendLiteral(":in-range"); + break; + case CSSSelector::PseudoClassIncrement: + str.appendLiteral(":increment"); + break; + case CSSSelector::PseudoClassIndeterminate: + str.appendLiteral(":indeterminate"); + break; + case CSSSelector::PseudoClassInvalid: + str.appendLiteral(":invalid"); + break; + case CSSSelector::PseudoClassLang: + str.appendLiteral(":lang("); + ASSERT_WITH_MESSAGE(cs->langArgumentList() && !cs->langArgumentList()->isEmpty(), "An empty :lang() is invalid and should never be generated by the parser."); + appendLangArgumentList(str, *cs->langArgumentList()); + str.append(')'); + break; + case CSSSelector::PseudoClassLastChild: + str.appendLiteral(":last-child"); + break; + case CSSSelector::PseudoClassLastOfType: + str.appendLiteral(":last-of-type"); + break; + case CSSSelector::PseudoClassLink: + str.appendLiteral(":link"); + break; + case CSSSelector::PseudoClassNoButton: + str.appendLiteral(":no-button"); + break; + case CSSSelector::PseudoClassNot: + str.appendLiteral(":not("); + cs->selectorList()->buildSelectorsText(str); + str.append(')'); + break; + case CSSSelector::PseudoClassNthChild: + str.appendLiteral(":nth-child("); str.append(cs->argument()); + if (const CSSSelectorList* selectorList = cs->selectorList()) { + str.appendLiteral(" of "); + selectorList->buildSelectorsText(str); + } str.append(')'); break; - case PseudoAny: { - const CSSSelector* firstSubSelector = cs->selectorList()->first(); - for (const CSSSelector* subSelector = firstSubSelector; subSelector; subSelector = CSSSelectorList::next(subSelector)) { - if (subSelector != firstSubSelector) - str.append(','); - str.append(subSelector->selectorText()); + case CSSSelector::PseudoClassNthLastChild: + str.appendLiteral(":nth-last-child("); + str.append(cs->argument()); + if (const CSSSelectorList* selectorList = cs->selectorList()) { + str.appendLiteral(" of "); + selectorList->buildSelectorsText(str); } str.append(')'); break; + case CSSSelector::PseudoClassNthLastOfType: + str.appendLiteral(":nth-last-of-type("); + appendPseudoClassFunctionTail(str, cs); + break; + case CSSSelector::PseudoClassNthOfType: + str.appendLiteral(":nth-of-type("); + appendPseudoClassFunctionTail(str, cs); + break; + case CSSSelector::PseudoClassOnlyChild: + str.appendLiteral(":only-child"); + break; + case CSSSelector::PseudoClassOnlyOfType: + str.appendLiteral(":only-of-type"); + break; + case CSSSelector::PseudoClassOptional: + str.appendLiteral(":optional"); + break; + case CSSSelector::PseudoClassMatches: { + str.appendLiteral(":matches("); + cs->selectorList()->buildSelectorsText(str); + str.append(')'); + break; } - default: + case CSSSelector::PseudoClassPlaceholderShown: + str.appendLiteral(":placeholder-shown"); + break; + case CSSSelector::PseudoClassOutOfRange: + str.appendLiteral(":out-of-range"); + break; +#if ENABLE(VIDEO_TRACK) + case CSSSelector::PseudoClassPast: + str.appendLiteral(":past"); + break; +#endif + case CSSSelector::PseudoClassReadOnly: + str.appendLiteral(":read-only"); + break; + case CSSSelector::PseudoClassReadWrite: + str.appendLiteral(":read-write"); + break; + case CSSSelector::PseudoClassRequired: + str.appendLiteral(":required"); break; +#if ENABLE(CSS_SELECTORS_LEVEL4) + case CSSSelector::PseudoClassRole: + str.appendLiteral(":role("); + appendPseudoClassFunctionTail(str, cs); + break; +#endif + case CSSSelector::PseudoClassRoot: + str.appendLiteral(":root"); + break; + case CSSSelector::PseudoClassScope: + str.appendLiteral(":scope"); + break; + case CSSSelector::PseudoClassSingleButton: + str.appendLiteral(":single-button"); + break; + case CSSSelector::PseudoClassStart: + str.appendLiteral(":start"); + break; + case CSSSelector::PseudoClassTarget: + str.appendLiteral(":target"); + break; + case CSSSelector::PseudoClassValid: + str.appendLiteral(":valid"); + break; + case CSSSelector::PseudoClassVertical: + str.appendLiteral(":vertical"); + break; + case CSSSelector::PseudoClassVisited: + str.appendLiteral(":visited"); + break; + case CSSSelector::PseudoClassWindowInactive: + str.appendLiteral(":window-inactive"); + break; + case CSSSelector::PseudoClassHost: + str.appendLiteral(":host"); + break; + case CSSSelector::PseudoClassDefined: + str.appendLiteral(":defined"); + break; + case CSSSelector::PseudoClassUnknown: + ASSERT_NOT_REACHED(); + } + } else if (cs->match() == CSSSelector::PseudoElement) { + switch (cs->pseudoElementType()) { + case CSSSelector::PseudoElementSlotted: + str.appendLiteral("::slotted("); + cs->selectorList()->buildSelectorsText(str); + str.append(')'); + break; + case CSSSelector::PseudoElementWebKitCustomLegacyPrefixed: + if (cs->value() == "placeholder") + str.appendLiteral("::-webkit-input-placeholder"); + break; + default: + str.appendLiteral("::"); + str.append(cs->serializingValue()); } - } else if (cs->m_match == CSSSelector::PseudoElement) { - str.appendLiteral("::"); - str.append(cs->value()); } else if (cs->isAttributeSelector()) { str.append('['); const AtomicString& prefix = cs->attribute().prefix(); - if (!prefix.isNull()) { + if (!prefix.isEmpty()) { str.append(prefix); str.append('|'); } str.append(cs->attribute().localName()); - switch (cs->m_match) { + switch (cs->match()) { case CSSSelector::Exact: str.append('='); break; @@ -621,19 +696,35 @@ String CSSSelector::selectorText(const String& rightSide) const default: break; } - if (cs->m_match != CSSSelector::Set) { - serializeString(cs->value(), str); - str.append(']'); + if (cs->match() != CSSSelector::Set) { + serializeString(cs->serializingValue(), str); + if (cs->attributeValueMatchingIsCaseInsensitive()) + str.appendLiteral(" i]"); + else + str.append(']'); + } + } else if (cs->match() == CSSSelector::PagePseudoClass) { + switch (cs->pagePseudoClassType()) { + case PagePseudoClassFirst: + str.appendLiteral(":first"); + break; + case PagePseudoClassLeft: + str.appendLiteral(":left"); + break; + case PagePseudoClassRight: + str.appendLiteral(":right"); + break; } } - if (cs->relation() != CSSSelector::SubSelector || !cs->tagHistory()) + + if (cs->relation() != CSSSelector::Subselector || !cs->tagHistory()) break; cs = cs->tagHistory(); } if (const CSSSelector* tagHistory = cs->tagHistory()) { switch (cs->relation()) { - case CSSSelector::Descendant: + case CSSSelector::DescendantSpace: return tagHistory->selectorText(" " + str.toString() + rightSide); case CSSSelector::Child: return tagHistory->selectorText(" > " + str.toString() + rightSide); @@ -641,7 +732,11 @@ String CSSSelector::selectorText(const String& rightSide) const return tagHistory->selectorText(" + " + str.toString() + rightSide); case CSSSelector::IndirectAdjacent: return tagHistory->selectorText(" ~ " + str.toString() + rightSide); - case CSSSelector::SubSelector: +#if ENABLE(CSS_SELECTORS_LEVEL4) + case CSSSelector::DescendantDoubleChild: + return tagHistory->selectorText(" >> " + str.toString() + rightSide); +#endif + case CSSSelector::Subselector: ASSERT_NOT_REACHED(); #if ASSERT_DISABLED FALLTHROUGH; @@ -657,21 +752,45 @@ void CSSSelector::setAttribute(const QualifiedName& value, bool isCaseInsensitiv { createRareData(); m_data.m_rareData->m_attribute = value; - m_data.m_rareData->m_attributeCanonicalLocalName = isCaseInsensitive ? value.localName().lower() : value.localName(); + m_data.m_rareData->m_attributeCanonicalLocalName = isCaseInsensitive ? value.localName().convertToASCIILowercase() : value.localName(); } +void CSSSelector::setAttribute(const QualifiedName& value, bool convertToLowercase, AttributeMatchType matchType) +{ + createRareData(); + m_data.m_rareData->m_attribute = value; + m_data.m_rareData->m_attributeCanonicalLocalName = convertToLowercase ? value.localName().convertToASCIILowercase() : value.localName(); + m_caseInsensitiveAttributeValueMatching = matchType == CaseInsensitive; +} + void CSSSelector::setArgument(const AtomicString& value) { createRareData(); m_data.m_rareData->m_argument = value; } -void CSSSelector::setSelectorList(PassOwnPtr<CSSSelectorList> selectorList) +void CSSSelector::setLangArgumentList(std::unique_ptr<Vector<AtomicString>> argumentList) { createRareData(); - m_data.m_rareData->m_selectorList = selectorList; + m_data.m_rareData->m_langArgumentList = WTFMove(argumentList); } +void CSSSelector::setSelectorList(std::unique_ptr<CSSSelectorList> selectorList) +{ + createRareData(); + m_data.m_rareData->m_selectorList = WTFMove(selectorList); +} + +void CSSSelector::setNth(int a, int b) +{ + createRareData(); + m_parsedNth = true; // FIXME-NEWPARSER: Can remove this parsed boolean once old parser is gone. + m_data.m_rareData->m_a = a; + m_data.m_rareData->m_b = b; +} + +// FIXME-NEWPARSER: All the code to parse nth-child stuff can be removed when +// the new parser is enabled. bool CSSSelector::parseNth() const { if (!m_hasRareData) @@ -688,8 +807,23 @@ bool CSSSelector::matchNth(int count) const return m_data.m_rareData->matchNth(count); } -CSSSelector::RareData::RareData(PassRefPtr<AtomicStringImpl> value) - : m_value(value.leakRef()) +int CSSSelector::nthA() const +{ + ASSERT(m_hasRareData); + ASSERT(m_parsedNth); + return m_data.m_rareData->m_a; +} + +int CSSSelector::nthB() const +{ + ASSERT(m_hasRareData); + ASSERT(m_parsedNth); + return m_data.m_rareData->m_b; +} + +CSSSelector::RareData::RareData(AtomicString&& value) + : m_matchingValue(value) + , m_serializingValue(value) , m_a(0) , m_b(0) , m_attribute(anyQName()) @@ -699,49 +833,65 @@ CSSSelector::RareData::RareData(PassRefPtr<AtomicStringImpl> value) 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()) + if (m_argument.isEmpty()) return false; - m_a = 0; - m_b = 0; - if (argument == "odd") { + if (equalLettersIgnoringASCIICase(m_argument, "odd")) { m_a = 2; m_b = 1; - } else if (argument == "even") { + } else if (equalLettersIgnoringASCIICase(m_argument, "even")) { m_a = 2; m_b = 0; } else { - size_t n = argument.find('n'); + m_a = 0; + m_b = 0; + + size_t n = std::min(m_argument.find('n'), m_argument.find('N')); if (n != notFound) { - if (argument[0] == '-') { + if (m_argument[0] == '-') { if (n == 1) m_a = -1; // -n == -1n - else - m_a = argument.substring(0, n).toInt(); + else { + bool ok; + m_a = StringView(m_argument).substring(0, n).toIntStrict(ok); + if (!ok) + return false; + } } 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(); + bool ok; + m_a = StringView(m_argument).substring(0, n).toIntStrict(ok); + if (!ok) + return false; } - } else - m_b = argument.toInt(); + + size_t p = m_argument.find('+', n); + if (p != notFound) { + bool ok; + m_b = StringView(m_argument).substring(p + 1).toIntStrict(ok); + if (!ok) + return false; + } else { + p = m_argument.find('-', n); + if (p != notFound) { + bool ok; + m_b = -StringView(m_argument).substring(p + 1).toIntStrict(ok); + if (!ok) + return false; + } + } + } else { + bool ok; + m_b = m_argument.string().toIntStrict(&ok); + if (!ok) + return false; + } } return true; } |