| /* |
| * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) |
| * 1999 Waldo Bastian (bastian@kde.org) |
| * 2001 Andreas Schlapbach (schlpbch@iam.unibe.ch) |
| * 2001-2003 Dirk Mueller (mueller@kde.org) |
| * Copyright (C) 2002, 2006, 2007, 2008, 2009, 2010, 2013, 2014 Apple Inc. All rights reserved. |
| * Copyright (C) 2008 David Smith (catfish.man@gmail.com) |
| * Copyright (C) 2010 Google Inc. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "config.h" |
| #include "CSSSelector.h" |
| |
| #include "CSSMarkup.h" |
| #include "CSSSelectorList.h" |
| #include "HTMLNames.h" |
| #include "RuntimeEnabledFeatures.h" |
| #include "SelectorPseudoTypeMap.h" |
| #include <wtf/Assertions.h> |
| #include <wtf/StdLibExtras.h> |
| #include <wtf/Vector.h> |
| #include <wtf/text/AtomStringHash.h> |
| #include <wtf/text/StringBuilder.h> |
| |
| 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."); |
| |
| DEFINE_ALLOCATOR_WITH_HEAP_IDENTIFIER(CSSSelectorRareData); |
| |
| CSSSelector::CSSSelector(const QualifiedName& tagQName, bool tagIsForNamespaceRule) |
| : m_relation(DescendantSpace) |
| , m_match(Tag) |
| , m_pseudoType(0) |
| , 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 AtomString& tagLocalName = tagQName.localName(); |
| const AtomString 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(match() != Tag); |
| ASSERT(!m_hasNameWithCase); |
| if (m_hasRareData) |
| return; |
| // Move the value to the rare data stucture. |
| AtomString value { adoptRef(m_data.m_value) }; |
| m_data.m_rareData = &RareData::create(WTFMove(value)).leakRef(); |
| m_hasRareData = true; |
| } |
| |
| static unsigned simpleSelectorSpecificityInternal(const CSSSelector& simpleSelector, bool isComputingMaximumSpecificity); |
| |
| static unsigned selectorSpecificity(const CSSSelector& firstSimpleSelector, bool isComputingMaximumSpecificity) |
| { |
| unsigned total = simpleSelectorSpecificityInternal(firstSimpleSelector, isComputingMaximumSpecificity); |
| |
| for (const CSSSelector* selector = firstSimpleSelector.tagHistory(); selector; selector = selector->tagHistory()) |
| total = CSSSelector::addSpecificities(total, simpleSelectorSpecificityInternal(*selector, isComputingMaximumSpecificity)); |
| return total; |
| } |
| |
| 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->match()) { |
| case Tag: |
| s += tagQName().localName() == starAtom() ? 0 : 4; |
| break; |
| case PagePseudoClass: |
| switch (component->pagePseudoClassType()) { |
| case PagePseudoClassFirst: |
| s += 2; |
| break; |
| case PagePseudoClassLeft: |
| case PagePseudoClassRight: |
| s += 1; |
| break; |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| return s; |
| } |
| |
| PseudoId CSSSelector::pseudoId(PseudoElementType type) |
| { |
| switch (type) { |
| case PseudoElementFirstLine: |
| return PseudoId::FirstLine; |
| case PseudoElementFirstLetter: |
| return PseudoId::FirstLetter; |
| case PseudoElementSelection: |
| return PseudoId::Selection; |
| case PseudoElementHighlight: |
| return PseudoId::Highlight; |
| case PseudoElementMarker: |
| return PseudoId::Marker; |
| case PseudoElementBefore: |
| return PseudoId::Before; |
| case PseudoElementAfter: |
| return PseudoId::After; |
| case PseudoElementScrollbar: |
| return PseudoId::Scrollbar; |
| case PseudoElementScrollbarButton: |
| return PseudoId::ScrollbarButton; |
| case PseudoElementScrollbarCorner: |
| return PseudoId::ScrollbarCorner; |
| case PseudoElementScrollbarThumb: |
| return PseudoId::ScrollbarThumb; |
| case PseudoElementScrollbarTrack: |
| return PseudoId::ScrollbarTrack; |
| case PseudoElementScrollbarTrackPiece: |
| return PseudoId::ScrollbarTrackPiece; |
| case PseudoElementResizer: |
| return PseudoId::Resizer; |
| #if ENABLE(VIDEO_TRACK) |
| case PseudoElementCue: |
| #endif |
| case PseudoElementSlotted: |
| case PseudoElementPart: |
| case PseudoElementUnknown: |
| case PseudoElementWebKitCustom: |
| case PseudoElementWebKitCustomLegacyPrefixed: |
| return PseudoId::None; |
| } |
| |
| ASSERT_NOT_REACHED(); |
| return PseudoId::None; |
| } |
| |
| CSSSelector::PseudoElementType CSSSelector::parsePseudoElementType(StringView name) |
| { |
| if (name.isNull()) |
| return PseudoElementUnknown; |
| |
| auto type = parsePseudoElementString(name); |
| if (type == PseudoElementUnknown) { |
| if (name.startsWith("-webkit-")) |
| type = PseudoElementWebKitCustom; |
| } |
| |
| if (type == PseudoElementHighlight && !RuntimeEnabledFeatures::sharedFeatures().highlightAPIEnabled()) |
| return PseudoElementUnknown; |
| |
| if (type == PseudoElementPart && !RuntimeEnabledFeatures::sharedFeatures().cssShadowPartsEnabled()) |
| return PseudoElementUnknown; |
| |
| return type; |
| } |
| |
| bool CSSSelector::operator==(const CSSSelector& other) const |
| { |
| const CSSSelector* sel1 = this; |
| const CSSSelector* sel2 = &other; |
| |
| while (sel1 && sel2) { |
| if (sel1->attribute() != sel2->attribute() |
| || sel1->relation() != sel2->relation() |
| || sel1->match() != sel2->match() |
| || sel1->value() != sel2->value() |
| || sel1->m_pseudoType != sel2->m_pseudoType |
| || sel1->argument() != sel2->argument()) { |
| return false; |
| } |
| if (sel1->match() == Tag) { |
| if (sel1->tagQName() != sel2->tagQName()) |
| return false; |
| } |
| sel1 = sel1->tagHistory(); |
| sel2 = sel2->tagHistory(); |
| } |
| |
| if (sel1 || sel2) |
| return false; |
| |
| return true; |
| } |
| |
| static void appendPseudoClassFunctionTail(StringBuilder& builder, 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 |
| builder.append(selector->argument()); |
| builder.append(')'); |
| break; |
| default: |
| break; |
| } |
| |
| } |
| |
| static void appendLangArgumentList(StringBuilder& builder, const Vector<AtomString>& argumentList) |
| { |
| unsigned argumentListSize = argumentList.size(); |
| for (unsigned i = 0; i < argumentListSize; ++i) { |
| builder.append('"'); |
| builder.append(argumentList[i]); |
| builder.append('"'); |
| if (i != argumentListSize - 1) |
| builder.appendLiteral(", "); |
| } |
| } |
| |
| // http://dev.w3.org/csswg/css-syntax/#serializing-anb |
| static void outputNthChildAnPlusB(const CSSSelector& selector, StringBuilder& builder) |
| { |
| auto outputFirstTerm = [&builder] (int a) { |
| switch (a) { |
| case 1: |
| break; |
| case -1: |
| builder.append('-'); |
| break; |
| default: |
| builder.append(a); |
| } |
| }; |
| |
| if (selector.argument() == nullAtom()) |
| return; |
| |
| int a = selector.nthA(); |
| int b = selector.nthB(); |
| if (a == 0 && b == 0) |
| builder.append('0'); |
| else if (a == 0) |
| builder.append(b); |
| else if (b == 0) { |
| outputFirstTerm(a); |
| builder.append('n'); |
| } else if (b < 0) { |
| outputFirstTerm(a); |
| builder.append('n', b); |
| } else { |
| outputFirstTerm(a); |
| builder.append("n+", b); |
| } |
| } |
| |
| String CSSSelector::selectorText(const String& rightSide) const |
| { |
| StringBuilder builder; |
| |
| if (match() == CSSSelector::Tag && !m_tagIsForNamespaceRule) { |
| if (tagQName().prefix().isNull()) |
| builder.append(tagQName().localName()); |
| else { |
| builder.append(tagQName().prefix().string()); |
| builder.append('|'); |
| builder.append(tagQName().localName()); |
| } |
| } |
| |
| const CSSSelector* cs = this; |
| while (true) { |
| if (cs->match() == CSSSelector::Id) { |
| builder.append('#'); |
| serializeIdentifier(cs->serializingValue(), builder); |
| } else if (cs->match() == CSSSelector::Class) { |
| builder.append('.'); |
| serializeIdentifier(cs->serializingValue(), builder); |
| } else if (cs->match() == CSSSelector::PseudoClass) { |
| switch (cs->pseudoClassType()) { |
| #if ENABLE(FULLSCREEN_API) |
| case CSSSelector::PseudoClassAnimatingFullScreenTransition: |
| builder.appendLiteral(":-webkit-animating-full-screen-transition"); |
| break; |
| #endif |
| case CSSSelector::PseudoClassAny: { |
| builder.appendLiteral(":-webkit-any("); |
| cs->selectorList()->buildSelectorsText(builder); |
| builder.append(')'); |
| break; |
| } |
| case CSSSelector::PseudoClassAnyLink: |
| builder.appendLiteral(":any-link"); |
| break; |
| case CSSSelector::PseudoClassAnyLinkDeprecated: |
| builder.appendLiteral(":-webkit-any-link"); |
| break; |
| case CSSSelector::PseudoClassAutofill: |
| builder.appendLiteral(":-webkit-autofill"); |
| break; |
| case CSSSelector::PseudoClassAutofillStrongPassword: |
| builder.appendLiteral(":-webkit-autofill-strong-password"); |
| break; |
| case CSSSelector::PseudoClassAutofillStrongPasswordViewable: |
| builder.appendLiteral(":-webkit-autofill-strong-password-viewable"); |
| break; |
| case CSSSelector::PseudoClassDirectFocus: |
| builder.appendLiteral(":-webkit-direct-focus"); |
| break; |
| case CSSSelector::PseudoClassDrag: |
| builder.appendLiteral(":-webkit-drag"); |
| break; |
| case CSSSelector::PseudoClassFullPageMedia: |
| builder.appendLiteral(":-webkit-full-page-media"); |
| break; |
| #if ENABLE(FULLSCREEN_API) |
| case CSSSelector::PseudoClassFullScreen: |
| builder.appendLiteral(":-webkit-full-screen"); |
| break; |
| case CSSSelector::PseudoClassFullScreenAncestor: |
| builder.appendLiteral(":-webkit-full-screen-ancestor"); |
| break; |
| case CSSSelector::PseudoClassFullScreenDocument: |
| builder.appendLiteral(":-webkit-full-screen-document"); |
| break; |
| case CSSSelector::PseudoClassFullScreenControlsHidden: |
| builder.appendLiteral(":-webkit-full-screen-controls-hidden"); |
| break; |
| #endif |
| #if ENABLE(PICTURE_IN_PICTURE_API) |
| case CSSSelector::PseudoClassPictureInPicture: |
| builder.appendLiteral(":picture-in-picture"); |
| break; |
| #endif |
| case CSSSelector::PseudoClassActive: |
| builder.appendLiteral(":active"); |
| break; |
| case CSSSelector::PseudoClassChecked: |
| builder.appendLiteral(":checked"); |
| break; |
| case CSSSelector::PseudoClassCornerPresent: |
| builder.appendLiteral(":corner-present"); |
| break; |
| case CSSSelector::PseudoClassDecrement: |
| builder.appendLiteral(":decrement"); |
| break; |
| case CSSSelector::PseudoClassDefault: |
| builder.appendLiteral(":default"); |
| break; |
| #if ENABLE(CSS_SELECTORS_LEVEL4) |
| case CSSSelector::PseudoClassDir: |
| builder.appendLiteral(":dir("); |
| appendPseudoClassFunctionTail(builder, cs); |
| break; |
| #endif |
| case CSSSelector::PseudoClassDisabled: |
| builder.appendLiteral(":disabled"); |
| break; |
| case CSSSelector::PseudoClassDoubleButton: |
| builder.appendLiteral(":double-button"); |
| break; |
| case CSSSelector::PseudoClassEmpty: |
| builder.appendLiteral(":empty"); |
| break; |
| case CSSSelector::PseudoClassEnabled: |
| builder.appendLiteral(":enabled"); |
| break; |
| case CSSSelector::PseudoClassEnd: |
| builder.appendLiteral(":end"); |
| break; |
| case CSSSelector::PseudoClassFirstChild: |
| builder.appendLiteral(":first-child"); |
| break; |
| case CSSSelector::PseudoClassFirstOfType: |
| builder.appendLiteral(":first-of-type"); |
| break; |
| case CSSSelector::PseudoClassFocus: |
| builder.appendLiteral(":focus"); |
| break; |
| case CSSSelector::PseudoClassFocusWithin: |
| builder.appendLiteral(":focus-within"); |
| break; |
| #if ENABLE(VIDEO_TRACK) |
| case CSSSelector::PseudoClassFuture: |
| builder.appendLiteral(":future"); |
| break; |
| #endif |
| #if ENABLE(ATTACHMENT_ELEMENT) |
| case CSSSelector::PseudoClassHasAttachment: |
| builder.appendLiteral(":has-attachment"); |
| break; |
| #endif |
| case CSSSelector::PseudoClassHorizontal: |
| builder.appendLiteral(":horizontal"); |
| break; |
| case CSSSelector::PseudoClassHover: |
| builder.appendLiteral(":hover"); |
| break; |
| case CSSSelector::PseudoClassInRange: |
| builder.appendLiteral(":in-range"); |
| break; |
| case CSSSelector::PseudoClassIncrement: |
| builder.appendLiteral(":increment"); |
| break; |
| case CSSSelector::PseudoClassIndeterminate: |
| builder.appendLiteral(":indeterminate"); |
| break; |
| case CSSSelector::PseudoClassInvalid: |
| builder.appendLiteral(":invalid"); |
| break; |
| case CSSSelector::PseudoClassLang: |
| builder.appendLiteral(":lang("); |
| ASSERT_WITH_MESSAGE(cs->argumentList() && !cs->argumentList()->isEmpty(), "An empty :lang() is invalid and should never be generated by the parser."); |
| appendLangArgumentList(builder, *cs->argumentList()); |
| builder.append(')'); |
| break; |
| case CSSSelector::PseudoClassLastChild: |
| builder.appendLiteral(":last-child"); |
| break; |
| case CSSSelector::PseudoClassLastOfType: |
| builder.appendLiteral(":last-of-type"); |
| break; |
| case CSSSelector::PseudoClassLink: |
| builder.appendLiteral(":link"); |
| break; |
| case CSSSelector::PseudoClassNoButton: |
| builder.appendLiteral(":no-button"); |
| break; |
| case CSSSelector::PseudoClassNot: |
| builder.appendLiteral(":not("); |
| cs->selectorList()->buildSelectorsText(builder); |
| builder.append(')'); |
| break; |
| case CSSSelector::PseudoClassNthChild: |
| builder.appendLiteral(":nth-child("); |
| outputNthChildAnPlusB(*cs, builder); |
| if (const CSSSelectorList* selectorList = cs->selectorList()) { |
| builder.appendLiteral(" of "); |
| selectorList->buildSelectorsText(builder); |
| } |
| builder.append(')'); |
| break; |
| case CSSSelector::PseudoClassNthLastChild: |
| builder.appendLiteral(":nth-last-child("); |
| outputNthChildAnPlusB(*cs, builder); |
| if (const CSSSelectorList* selectorList = cs->selectorList()) { |
| builder.appendLiteral(" of "); |
| selectorList->buildSelectorsText(builder); |
| } |
| builder.append(')'); |
| break; |
| case CSSSelector::PseudoClassNthLastOfType: |
| builder.appendLiteral(":nth-last-of-type("); |
| appendPseudoClassFunctionTail(builder, cs); |
| break; |
| case CSSSelector::PseudoClassNthOfType: |
| builder.appendLiteral(":nth-of-type("); |
| appendPseudoClassFunctionTail(builder, cs); |
| break; |
| case CSSSelector::PseudoClassOnlyChild: |
| builder.appendLiteral(":only-child"); |
| break; |
| case CSSSelector::PseudoClassOnlyOfType: |
| builder.appendLiteral(":only-of-type"); |
| break; |
| case CSSSelector::PseudoClassOptional: |
| builder.appendLiteral(":optional"); |
| break; |
| case CSSSelector::PseudoClassMatches: { |
| builder.appendLiteral(":matches("); |
| cs->selectorList()->buildSelectorsText(builder); |
| builder.append(')'); |
| break; |
| } |
| case CSSSelector::PseudoClassPlaceholderShown: |
| builder.appendLiteral(":placeholder-shown"); |
| break; |
| case CSSSelector::PseudoClassOutOfRange: |
| builder.appendLiteral(":out-of-range"); |
| break; |
| #if ENABLE(VIDEO_TRACK) |
| case CSSSelector::PseudoClassPast: |
| builder.appendLiteral(":past"); |
| break; |
| #endif |
| case CSSSelector::PseudoClassReadOnly: |
| builder.appendLiteral(":read-only"); |
| break; |
| case CSSSelector::PseudoClassReadWrite: |
| builder.appendLiteral(":read-write"); |
| break; |
| case CSSSelector::PseudoClassRequired: |
| builder.appendLiteral(":required"); |
| break; |
| #if ENABLE(CSS_SELECTORS_LEVEL4) |
| case CSSSelector::PseudoClassRole: |
| builder.appendLiteral(":role("); |
| appendPseudoClassFunctionTail(builder, cs); |
| break; |
| #endif |
| case CSSSelector::PseudoClassRoot: |
| builder.appendLiteral(":root"); |
| break; |
| case CSSSelector::PseudoClassScope: |
| builder.appendLiteral(":scope"); |
| break; |
| case CSSSelector::PseudoClassSingleButton: |
| builder.appendLiteral(":single-button"); |
| break; |
| case CSSSelector::PseudoClassStart: |
| builder.appendLiteral(":start"); |
| break; |
| case CSSSelector::PseudoClassTarget: |
| builder.appendLiteral(":target"); |
| break; |
| case CSSSelector::PseudoClassValid: |
| builder.appendLiteral(":valid"); |
| break; |
| case CSSSelector::PseudoClassVertical: |
| builder.appendLiteral(":vertical"); |
| break; |
| case CSSSelector::PseudoClassVisited: |
| builder.appendLiteral(":visited"); |
| break; |
| case CSSSelector::PseudoClassWindowInactive: |
| builder.appendLiteral(":window-inactive"); |
| break; |
| case CSSSelector::PseudoClassHost: |
| builder.appendLiteral(":host"); |
| break; |
| case CSSSelector::PseudoClassDefined: |
| builder.appendLiteral(":defined"); |
| break; |
| case CSSSelector::PseudoClassUnknown: |
| ASSERT_NOT_REACHED(); |
| } |
| } else if (cs->match() == CSSSelector::PseudoElement) { |
| switch (cs->pseudoElementType()) { |
| case CSSSelector::PseudoElementSlotted: |
| builder.appendLiteral("::slotted("); |
| cs->selectorList()->buildSelectorsText(builder); |
| builder.append(')'); |
| break; |
| case CSSSelector::PseudoElementPart: { |
| builder.appendLiteral("::part("); |
| bool isFirst = true; |
| for (auto& partName : *cs->argumentList()) { |
| if (!isFirst) |
| builder.append(' '); |
| isFirst = false; |
| builder.append(partName); |
| } |
| builder.append(')'); |
| break; |
| } |
| case CSSSelector::PseudoElementWebKitCustomLegacyPrefixed: |
| if (cs->value() == "placeholder") |
| builder.appendLiteral("::-webkit-input-placeholder"); |
| break; |
| default: |
| builder.appendLiteral("::"); |
| builder.append(cs->serializingValue()); |
| } |
| } else if (cs->isAttributeSelector()) { |
| builder.append('['); |
| const AtomString& prefix = cs->attribute().prefix(); |
| if (!prefix.isEmpty()) { |
| builder.append(prefix); |
| builder.append('|'); |
| } |
| builder.append(cs->attribute().localName()); |
| switch (cs->match()) { |
| case CSSSelector::Exact: |
| builder.append('='); |
| break; |
| case CSSSelector::Set: |
| // set has no operator or value, just the attrName |
| builder.append(']'); |
| break; |
| case CSSSelector::List: |
| builder.appendLiteral("~="); |
| break; |
| case CSSSelector::Hyphen: |
| builder.appendLiteral("|="); |
| break; |
| case CSSSelector::Begin: |
| builder.appendLiteral("^="); |
| break; |
| case CSSSelector::End: |
| builder.appendLiteral("$="); |
| break; |
| case CSSSelector::Contain: |
| builder.appendLiteral("*="); |
| break; |
| default: |
| break; |
| } |
| if (cs->match() != CSSSelector::Set) { |
| serializeString(cs->serializingValue(), builder); |
| if (cs->attributeValueMatchingIsCaseInsensitive()) |
| builder.appendLiteral(" i]"); |
| else |
| builder.append(']'); |
| } |
| } else if (cs->match() == CSSSelector::PagePseudoClass) { |
| switch (cs->pagePseudoClassType()) { |
| case PagePseudoClassFirst: |
| builder.appendLiteral(":first"); |
| break; |
| case PagePseudoClassLeft: |
| builder.appendLiteral(":left"); |
| break; |
| case PagePseudoClassRight: |
| builder.appendLiteral(":right"); |
| break; |
| } |
| } |
| |
| if (cs->relation() != CSSSelector::Subselector || !cs->tagHistory()) |
| break; |
| cs = cs->tagHistory(); |
| } |
| |
| if (const CSSSelector* tagHistory = cs->tagHistory()) { |
| switch (cs->relation()) { |
| case CSSSelector::DescendantSpace: |
| return tagHistory->selectorText(" " + builder.toString() + rightSide); |
| case CSSSelector::Child: |
| return tagHistory->selectorText(" > " + builder.toString() + rightSide); |
| case CSSSelector::DirectAdjacent: |
| return tagHistory->selectorText(" + " + builder.toString() + rightSide); |
| case CSSSelector::IndirectAdjacent: |
| return tagHistory->selectorText(" ~ " + builder.toString() + rightSide); |
| case CSSSelector::Subselector: |
| ASSERT_NOT_REACHED(); |
| #if !ASSERT_ENABLED |
| FALLTHROUGH; |
| #endif |
| case CSSSelector::ShadowDescendant: |
| return tagHistory->selectorText(builder.toString() + rightSide); |
| } |
| } |
| return builder.toString() + rightSide; |
| } |
| |
| 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 AtomString& value) |
| { |
| createRareData(); |
| m_data.m_rareData->m_argument = value; |
| } |
| |
| void CSSSelector::setArgumentList(std::unique_ptr<Vector<AtomString>> argumentList) |
| { |
| createRareData(); |
| m_data.m_rareData->m_argumentList = 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_data.m_rareData->m_a = a; |
| m_data.m_rareData->m_b = b; |
| } |
| |
| bool CSSSelector::matchNth(int count) const |
| { |
| ASSERT(m_hasRareData); |
| return m_data.m_rareData->matchNth(count); |
| } |
| |
| int CSSSelector::nthA() const |
| { |
| ASSERT(m_hasRareData); |
| return m_data.m_rareData->m_a; |
| } |
| |
| int CSSSelector::nthB() const |
| { |
| ASSERT(m_hasRareData); |
| return m_data.m_rareData->m_b; |
| } |
| |
| CSSSelector::RareData::RareData(AtomString&& value) |
| : m_matchingValue(value) |
| , m_serializingValue(value) |
| , m_a(0) |
| , m_b(0) |
| , m_attribute(anyQName()) |
| , m_argument(nullAtom()) |
| { |
| } |
| |
| CSSSelector::RareData::~RareData() = default; |
| |
| // 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 |