| 1 | /* |
| 2 | * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) |
| 3 | * 1999 Waldo Bastian (bastian@kde.org) |
| 4 | * 2001 Andreas Schlapbach (schlpbch@iam.unibe.ch) |
| 5 | * 2001-2003 Dirk Mueller (mueller@kde.org) |
| 6 | * Copyright (C) 2002, 2006, 2007, 2008, 2009, 2010, 2013, 2014 Apple Inc. All rights reserved. |
| 7 | * Copyright (C) 2008 David Smith (catfish.man@gmail.com) |
| 8 | * Copyright (C) 2010 Google Inc. All rights reserved. |
| 9 | * |
| 10 | * This library is free software; you can redistribute it and/or |
| 11 | * modify it under the terms of the GNU Library General Public |
| 12 | * License as published by the Free Software Foundation; either |
| 13 | * version 2 of the License, or (at your option) any later version. |
| 14 | * |
| 15 | * This library is distributed in the hope that it will be useful, |
| 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 18 | * Library General Public License for more details. |
| 19 | * |
| 20 | * You should have received a copy of the GNU Library General Public License |
| 21 | * along with this library; see the file COPYING.LIB. If not, write to |
| 22 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| 23 | * Boston, MA 02110-1301, USA. |
| 24 | */ |
| 25 | |
| 26 | #include "config.h" |
| 27 | #include "CSSSelector.h" |
| 28 | |
| 29 | #include "CSSMarkup.h" |
| 30 | #include "CSSSelectorList.h" |
| 31 | #include "HTMLNames.h" |
| 32 | #include "SelectorPseudoTypeMap.h" |
| 33 | #include <wtf/Assertions.h> |
| 34 | #include <wtf/StdLibExtras.h> |
| 35 | #include <wtf/Vector.h> |
| 36 | #include <wtf/text/AtomicStringHash.h> |
| 37 | #include <wtf/text/StringBuilder.h> |
| 38 | |
| 39 | namespace WebCore { |
| 40 | |
| 41 | using namespace HTMLNames; |
| 42 | |
| 43 | struct SameSizeAsCSSSelector { |
| 44 | unsigned flags; |
| 45 | void* unionPointer; |
| 46 | }; |
| 47 | |
| 48 | static_assert(CSSSelector::RelationType::Subselector == 0, "Subselector must be 0 for consumeCombinator." ); |
| 49 | static_assert(sizeof(CSSSelector) == sizeof(SameSizeAsCSSSelector), "CSSSelector should remain small." ); |
| 50 | |
| 51 | CSSSelector::CSSSelector(const QualifiedName& tagQName, bool tagIsForNamespaceRule) |
| 52 | : m_relation(DescendantSpace) |
| 53 | , m_match(Tag) |
| 54 | , m_pseudoType(0) |
| 55 | , m_isLastInSelectorList(false) |
| 56 | , m_isLastInTagHistory(true) |
| 57 | , m_hasRareData(false) |
| 58 | , m_hasNameWithCase(false) |
| 59 | , m_isForPage(false) |
| 60 | , m_tagIsForNamespaceRule(tagIsForNamespaceRule) |
| 61 | , m_caseInsensitiveAttributeValueMatching(false) |
| 62 | #if !ASSERT_WITH_SECURITY_IMPLICATION_DISABLED |
| 63 | , m_destructorHasBeenCalled(false) |
| 64 | #endif |
| 65 | { |
| 66 | const AtomicString& tagLocalName = tagQName.localName(); |
| 67 | const AtomicString tagLocalNameASCIILowercase = tagLocalName.convertToASCIILowercase(); |
| 68 | |
| 69 | if (tagLocalName == tagLocalNameASCIILowercase) { |
| 70 | m_data.m_tagQName = tagQName.impl(); |
| 71 | m_data.m_tagQName->ref(); |
| 72 | } else { |
| 73 | m_data.m_nameWithCase = adoptRef(new NameWithCase(tagQName, tagLocalNameASCIILowercase)).leakRef(); |
| 74 | m_hasNameWithCase = true; |
| 75 | } |
| 76 | } |
| 77 | |
| 78 | void CSSSelector::createRareData() |
| 79 | { |
| 80 | ASSERT(match() != Tag); |
| 81 | ASSERT(!m_hasNameWithCase); |
| 82 | if (m_hasRareData) |
| 83 | return; |
| 84 | // Move the value to the rare data stucture. |
| 85 | AtomicString value { adoptRef(m_data.m_value) }; |
| 86 | m_data.m_rareData = &RareData::create(WTFMove(value)).leakRef(); |
| 87 | m_hasRareData = true; |
| 88 | } |
| 89 | |
| 90 | static unsigned simpleSelectorSpecificityInternal(const CSSSelector& simpleSelector, bool isComputingMaximumSpecificity); |
| 91 | |
| 92 | static unsigned selectorSpecificity(const CSSSelector& firstSimpleSelector, bool isComputingMaximumSpecificity) |
| 93 | { |
| 94 | unsigned total = simpleSelectorSpecificityInternal(firstSimpleSelector, isComputingMaximumSpecificity); |
| 95 | |
| 96 | for (const CSSSelector* selector = firstSimpleSelector.tagHistory(); selector; selector = selector->tagHistory()) |
| 97 | total = CSSSelector::addSpecificities(total, simpleSelectorSpecificityInternal(*selector, isComputingMaximumSpecificity)); |
| 98 | return total; |
| 99 | } |
| 100 | |
| 101 | static unsigned maxSpecificity(const CSSSelectorList& selectorList) |
| 102 | { |
| 103 | unsigned maxSpecificity = 0; |
| 104 | for (const CSSSelector* subSelector = selectorList.first(); subSelector; subSelector = CSSSelectorList::next(subSelector)) |
| 105 | maxSpecificity = std::max(maxSpecificity, selectorSpecificity(*subSelector, true)); |
| 106 | return maxSpecificity; |
| 107 | } |
| 108 | |
| 109 | static unsigned simpleSelectorSpecificityInternal(const CSSSelector& simpleSelector, bool isComputingMaximumSpecificity) |
| 110 | { |
| 111 | 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." ); |
| 112 | |
| 113 | switch (simpleSelector.match()) { |
| 114 | case CSSSelector::Id: |
| 115 | return static_cast<unsigned>(SelectorSpecificityIncrement::ClassA); |
| 116 | |
| 117 | case CSSSelector::PagePseudoClass: |
| 118 | break; |
| 119 | case CSSSelector::PseudoClass: |
| 120 | if (simpleSelector.pseudoClassType() == CSSSelector::PseudoClassMatches) { |
| 121 | ASSERT_WITH_MESSAGE(simpleSelector.selectorList() && simpleSelector.selectorList()->first(), "The parser should never generate a valid selector for an empty :matches()." ); |
| 122 | if (!isComputingMaximumSpecificity) |
| 123 | return 0; |
| 124 | return maxSpecificity(*simpleSelector.selectorList()); |
| 125 | } |
| 126 | |
| 127 | if (simpleSelector.pseudoClassType() == CSSSelector::PseudoClassNot) { |
| 128 | ASSERT_WITH_MESSAGE(simpleSelector.selectorList() && simpleSelector.selectorList()->first(), "The parser should never generate a valid selector for an empty :not()." ); |
| 129 | return maxSpecificity(*simpleSelector.selectorList()); |
| 130 | } |
| 131 | FALLTHROUGH; |
| 132 | case CSSSelector::Exact: |
| 133 | case CSSSelector::Class: |
| 134 | case CSSSelector::Set: |
| 135 | case CSSSelector::List: |
| 136 | case CSSSelector::Hyphen: |
| 137 | case CSSSelector::Contain: |
| 138 | case CSSSelector::Begin: |
| 139 | case CSSSelector::End: |
| 140 | return static_cast<unsigned>(SelectorSpecificityIncrement::ClassB); |
| 141 | case CSSSelector::Tag: |
| 142 | return (simpleSelector.tagQName().localName() != starAtom()) ? static_cast<unsigned>(SelectorSpecificityIncrement::ClassC) : 0; |
| 143 | case CSSSelector::PseudoElement: |
| 144 | return static_cast<unsigned>(SelectorSpecificityIncrement::ClassC); |
| 145 | case CSSSelector::Unknown: |
| 146 | return 0; |
| 147 | } |
| 148 | ASSERT_NOT_REACHED(); |
| 149 | return 0; |
| 150 | } |
| 151 | |
| 152 | unsigned CSSSelector::simpleSelectorSpecificity() const |
| 153 | { |
| 154 | return simpleSelectorSpecificityInternal(*this, false); |
| 155 | } |
| 156 | |
| 157 | static unsigned staticSpecificityInternal(const CSSSelector& firstSimpleSelector, bool& ok); |
| 158 | |
| 159 | static unsigned simpleSelectorFunctionalPseudoClassStaticSpecificity(const CSSSelector& simpleSelector, bool& ok) |
| 160 | { |
| 161 | if (simpleSelector.match() == CSSSelector::PseudoClass) { |
| 162 | CSSSelector::PseudoClassType pseudoClassType = simpleSelector.pseudoClassType(); |
| 163 | if (pseudoClassType == CSSSelector::PseudoClassMatches || pseudoClassType == CSSSelector::PseudoClassNthChild || pseudoClassType == CSSSelector::PseudoClassNthLastChild) { |
| 164 | const CSSSelectorList* selectorList = simpleSelector.selectorList(); |
| 165 | if (!selectorList) { |
| 166 | ASSERT_WITH_MESSAGE(pseudoClassType != CSSSelector::PseudoClassMatches, ":matches() should never be created without a valid selector list." ); |
| 167 | return 0; |
| 168 | } |
| 169 | |
| 170 | const CSSSelector& firstSubselector = *selectorList->first(); |
| 171 | |
| 172 | unsigned initialSpecificity = staticSpecificityInternal(firstSubselector, ok); |
| 173 | if (!ok) |
| 174 | return 0; |
| 175 | |
| 176 | const CSSSelector* subselector = &firstSubselector; |
| 177 | while ((subselector = CSSSelectorList::next(subselector))) { |
| 178 | unsigned subSelectorSpecificity = staticSpecificityInternal(*subselector, ok); |
| 179 | if (initialSpecificity != subSelectorSpecificity) |
| 180 | ok = false; |
| 181 | if (!ok) |
| 182 | return 0; |
| 183 | } |
| 184 | return initialSpecificity; |
| 185 | } |
| 186 | } |
| 187 | return 0; |
| 188 | } |
| 189 | |
| 190 | static unsigned functionalPseudoClassStaticSpecificity(const CSSSelector& firstSimpleSelector, bool& ok) |
| 191 | { |
| 192 | unsigned total = 0; |
| 193 | for (const CSSSelector* selector = &firstSimpleSelector; selector; selector = selector->tagHistory()) { |
| 194 | total = CSSSelector::addSpecificities(total, simpleSelectorFunctionalPseudoClassStaticSpecificity(*selector, ok)); |
| 195 | if (!ok) |
| 196 | return 0; |
| 197 | } |
| 198 | return total; |
| 199 | } |
| 200 | |
| 201 | static unsigned staticSpecificityInternal(const CSSSelector& firstSimpleSelector, bool& ok) |
| 202 | { |
| 203 | unsigned staticSpecificity = selectorSpecificity(firstSimpleSelector, false); |
| 204 | return CSSSelector::addSpecificities(staticSpecificity, functionalPseudoClassStaticSpecificity(firstSimpleSelector, ok)); |
| 205 | } |
| 206 | |
| 207 | unsigned CSSSelector::staticSpecificity(bool &ok) const |
| 208 | { |
| 209 | ok = true; |
| 210 | return staticSpecificityInternal(*this, ok); |
| 211 | } |
| 212 | |
| 213 | unsigned CSSSelector::addSpecificities(unsigned a, unsigned b) |
| 214 | { |
| 215 | unsigned total = a; |
| 216 | |
| 217 | unsigned newIdValue = (b & idMask); |
| 218 | if (((total & idMask) + newIdValue) & ~idMask) |
| 219 | total |= idMask; |
| 220 | else |
| 221 | total += newIdValue; |
| 222 | |
| 223 | unsigned newClassValue = (b & classMask); |
| 224 | if (((total & classMask) + newClassValue) & ~classMask) |
| 225 | total |= classMask; |
| 226 | else |
| 227 | total += newClassValue; |
| 228 | |
| 229 | unsigned newElementValue = (b & elementMask); |
| 230 | if (((total & elementMask) + newElementValue) & ~elementMask) |
| 231 | total |= elementMask; |
| 232 | else |
| 233 | total += newElementValue; |
| 234 | |
| 235 | return total; |
| 236 | } |
| 237 | |
| 238 | unsigned CSSSelector::specificityForPage() const |
| 239 | { |
| 240 | ASSERT(isForPage()); |
| 241 | |
| 242 | // See http://dev.w3.org/csswg/css3-page/#cascading-and-page-context |
| 243 | unsigned s = 0; |
| 244 | |
| 245 | for (const CSSSelector* component = this; component; component = component->tagHistory()) { |
| 246 | switch (component->match()) { |
| 247 | case Tag: |
| 248 | s += tagQName().localName() == starAtom() ? 0 : 4; |
| 249 | break; |
| 250 | case PagePseudoClass: |
| 251 | switch (component->pagePseudoClassType()) { |
| 252 | case PagePseudoClassFirst: |
| 253 | s += 2; |
| 254 | break; |
| 255 | case PagePseudoClassLeft: |
| 256 | case PagePseudoClassRight: |
| 257 | s += 1; |
| 258 | break; |
| 259 | } |
| 260 | break; |
| 261 | default: |
| 262 | break; |
| 263 | } |
| 264 | } |
| 265 | return s; |
| 266 | } |
| 267 | |
| 268 | PseudoId CSSSelector::pseudoId(PseudoElementType type) |
| 269 | { |
| 270 | switch (type) { |
| 271 | case PseudoElementFirstLine: |
| 272 | return PseudoId::FirstLine; |
| 273 | case PseudoElementFirstLetter: |
| 274 | return PseudoId::FirstLetter; |
| 275 | case PseudoElementSelection: |
| 276 | return PseudoId::Selection; |
| 277 | case PseudoElementMarker: |
| 278 | return PseudoId::Marker; |
| 279 | case PseudoElementBefore: |
| 280 | return PseudoId::Before; |
| 281 | case PseudoElementAfter: |
| 282 | return PseudoId::After; |
| 283 | case PseudoElementScrollbar: |
| 284 | return PseudoId::Scrollbar; |
| 285 | case PseudoElementScrollbarButton: |
| 286 | return PseudoId::ScrollbarButton; |
| 287 | case PseudoElementScrollbarCorner: |
| 288 | return PseudoId::ScrollbarCorner; |
| 289 | case PseudoElementScrollbarThumb: |
| 290 | return PseudoId::ScrollbarThumb; |
| 291 | case PseudoElementScrollbarTrack: |
| 292 | return PseudoId::ScrollbarTrack; |
| 293 | case PseudoElementScrollbarTrackPiece: |
| 294 | return PseudoId::ScrollbarTrackPiece; |
| 295 | case PseudoElementResizer: |
| 296 | return PseudoId::Resizer; |
| 297 | #if ENABLE(VIDEO_TRACK) |
| 298 | case PseudoElementCue: |
| 299 | #endif |
| 300 | case PseudoElementSlotted: |
| 301 | case PseudoElementUnknown: |
| 302 | case PseudoElementWebKitCustom: |
| 303 | case PseudoElementWebKitCustomLegacyPrefixed: |
| 304 | return PseudoId::None; |
| 305 | } |
| 306 | |
| 307 | ASSERT_NOT_REACHED(); |
| 308 | return PseudoId::None; |
| 309 | } |
| 310 | |
| 311 | CSSSelector::PseudoElementType CSSSelector::parsePseudoElementType(const String& name) |
| 312 | { |
| 313 | if (name.isNull()) |
| 314 | return PseudoElementUnknown; |
| 315 | |
| 316 | PseudoElementType type = parsePseudoElementString(*name.impl()); |
| 317 | if (type == PseudoElementUnknown) { |
| 318 | if (name.startsWith("-webkit-" )) |
| 319 | type = PseudoElementWebKitCustom; |
| 320 | } |
| 321 | return type; |
| 322 | } |
| 323 | |
| 324 | |
| 325 | bool CSSSelector::operator==(const CSSSelector& other) const |
| 326 | { |
| 327 | const CSSSelector* sel1 = this; |
| 328 | const CSSSelector* sel2 = &other; |
| 329 | |
| 330 | while (sel1 && sel2) { |
| 331 | if (sel1->attribute() != sel2->attribute() |
| 332 | || sel1->relation() != sel2->relation() |
| 333 | || sel1->match() != sel2->match() |
| 334 | || sel1->value() != sel2->value() |
| 335 | || sel1->m_pseudoType != sel2->m_pseudoType |
| 336 | || sel1->argument() != sel2->argument()) { |
| 337 | return false; |
| 338 | } |
| 339 | if (sel1->match() == Tag) { |
| 340 | if (sel1->tagQName() != sel2->tagQName()) |
| 341 | return false; |
| 342 | } |
| 343 | sel1 = sel1->tagHistory(); |
| 344 | sel2 = sel2->tagHistory(); |
| 345 | } |
| 346 | |
| 347 | if (sel1 || sel2) |
| 348 | return false; |
| 349 | |
| 350 | return true; |
| 351 | } |
| 352 | |
| 353 | static void appendPseudoClassFunctionTail(StringBuilder& str, const CSSSelector* selector) |
| 354 | { |
| 355 | switch (selector->pseudoClassType()) { |
| 356 | #if ENABLE(CSS_SELECTORS_LEVEL4) |
| 357 | case CSSSelector::PseudoClassDir: |
| 358 | #endif |
| 359 | case CSSSelector::PseudoClassLang: |
| 360 | case CSSSelector::PseudoClassNthChild: |
| 361 | case CSSSelector::PseudoClassNthLastChild: |
| 362 | case CSSSelector::PseudoClassNthOfType: |
| 363 | case CSSSelector::PseudoClassNthLastOfType: |
| 364 | #if ENABLE(CSS_SELECTORS_LEVEL4) |
| 365 | case CSSSelector::PseudoClassRole: |
| 366 | #endif |
| 367 | str.append(selector->argument()); |
| 368 | str.append(')'); |
| 369 | break; |
| 370 | default: |
| 371 | break; |
| 372 | } |
| 373 | |
| 374 | } |
| 375 | |
| 376 | static void appendLangArgumentList(StringBuilder& str, const Vector<AtomicString>& argumentList) |
| 377 | { |
| 378 | unsigned argumentListSize = argumentList.size(); |
| 379 | for (unsigned i = 0; i < argumentListSize; ++i) { |
| 380 | str.append('"'); |
| 381 | str.append(argumentList[i]); |
| 382 | str.append('"'); |
| 383 | if (i != argumentListSize - 1) |
| 384 | str.appendLiteral(", " ); |
| 385 | } |
| 386 | } |
| 387 | |
| 388 | String CSSSelector::selectorText(const String& rightSide) const |
| 389 | { |
| 390 | StringBuilder str; |
| 391 | |
| 392 | if (match() == CSSSelector::Tag && !m_tagIsForNamespaceRule) { |
| 393 | if (tagQName().prefix().isNull()) |
| 394 | str.append(tagQName().localName()); |
| 395 | else { |
| 396 | str.append(tagQName().prefix().string()); |
| 397 | str.append('|'); |
| 398 | str.append(tagQName().localName()); |
| 399 | } |
| 400 | } |
| 401 | |
| 402 | const CSSSelector* cs = this; |
| 403 | while (true) { |
| 404 | if (cs->match() == CSSSelector::Id) { |
| 405 | str.append('#'); |
| 406 | serializeIdentifier(cs->serializingValue(), str); |
| 407 | } else if (cs->match() == CSSSelector::Class) { |
| 408 | str.append('.'); |
| 409 | serializeIdentifier(cs->serializingValue(), str); |
| 410 | } else if (cs->match() == CSSSelector::PseudoClass) { |
| 411 | switch (cs->pseudoClassType()) { |
| 412 | #if ENABLE(FULLSCREEN_API) |
| 413 | case CSSSelector::PseudoClassAnimatingFullScreenTransition: |
| 414 | str.appendLiteral(":-webkit-animating-full-screen-transition" ); |
| 415 | break; |
| 416 | #endif |
| 417 | case CSSSelector::PseudoClassAny: { |
| 418 | str.appendLiteral(":-webkit-any(" ); |
| 419 | cs->selectorList()->buildSelectorsText(str); |
| 420 | str.append(')'); |
| 421 | break; |
| 422 | } |
| 423 | case CSSSelector::PseudoClassAnyLink: |
| 424 | str.appendLiteral(":any-link" ); |
| 425 | break; |
| 426 | case CSSSelector::PseudoClassAnyLinkDeprecated: |
| 427 | str.appendLiteral(":-webkit-any-link" ); |
| 428 | break; |
| 429 | case CSSSelector::PseudoClassAutofill: |
| 430 | str.appendLiteral(":-webkit-autofill" ); |
| 431 | break; |
| 432 | case CSSSelector::PseudoClassAutofillStrongPassword: |
| 433 | str.appendLiteral(":-webkit-autofill-strong-password" ); |
| 434 | break; |
| 435 | case CSSSelector::PseudoClassDrag: |
| 436 | str.appendLiteral(":-webkit-drag" ); |
| 437 | break; |
| 438 | case CSSSelector::PseudoClassFullPageMedia: |
| 439 | str.appendLiteral(":-webkit-full-page-media" ); |
| 440 | break; |
| 441 | #if ENABLE(FULLSCREEN_API) |
| 442 | case CSSSelector::PseudoClassFullScreen: |
| 443 | str.appendLiteral(":-webkit-full-screen" ); |
| 444 | break; |
| 445 | case CSSSelector::PseudoClassFullScreenAncestor: |
| 446 | str.appendLiteral(":-webkit-full-screen-ancestor" ); |
| 447 | break; |
| 448 | case CSSSelector::PseudoClassFullScreenDocument: |
| 449 | str.appendLiteral(":-webkit-full-screen-document" ); |
| 450 | break; |
| 451 | case CSSSelector::PseudoClassFullScreenControlsHidden: |
| 452 | str.appendLiteral(":-webkit-full-screen-controls-hidden" ); |
| 453 | break; |
| 454 | #endif |
| 455 | case CSSSelector::PseudoClassActive: |
| 456 | str.appendLiteral(":active" ); |
| 457 | break; |
| 458 | case CSSSelector::PseudoClassChecked: |
| 459 | str.appendLiteral(":checked" ); |
| 460 | break; |
| 461 | case CSSSelector::PseudoClassCornerPresent: |
| 462 | str.appendLiteral(":corner-present" ); |
| 463 | break; |
| 464 | case CSSSelector::PseudoClassDecrement: |
| 465 | str.appendLiteral(":decrement" ); |
| 466 | break; |
| 467 | case CSSSelector::PseudoClassDefault: |
| 468 | str.appendLiteral(":default" ); |
| 469 | break; |
| 470 | #if ENABLE(CSS_SELECTORS_LEVEL4) |
| 471 | case CSSSelector::PseudoClassDir: |
| 472 | str.appendLiteral(":dir(" ); |
| 473 | appendPseudoClassFunctionTail(str, cs); |
| 474 | break; |
| 475 | #endif |
| 476 | case CSSSelector::PseudoClassDisabled: |
| 477 | str.appendLiteral(":disabled" ); |
| 478 | break; |
| 479 | case CSSSelector::PseudoClassDoubleButton: |
| 480 | str.appendLiteral(":double-button" ); |
| 481 | break; |
| 482 | case CSSSelector::PseudoClassEmpty: |
| 483 | str.appendLiteral(":empty" ); |
| 484 | break; |
| 485 | case CSSSelector::PseudoClassEnabled: |
| 486 | str.appendLiteral(":enabled" ); |
| 487 | break; |
| 488 | case CSSSelector::PseudoClassEnd: |
| 489 | str.appendLiteral(":end" ); |
| 490 | break; |
| 491 | case CSSSelector::PseudoClassFirstChild: |
| 492 | str.appendLiteral(":first-child" ); |
| 493 | break; |
| 494 | case CSSSelector::PseudoClassFirstOfType: |
| 495 | str.appendLiteral(":first-of-type" ); |
| 496 | break; |
| 497 | case CSSSelector::PseudoClassFocus: |
| 498 | str.appendLiteral(":focus" ); |
| 499 | break; |
| 500 | case CSSSelector::PseudoClassFocusWithin: |
| 501 | str.appendLiteral(":focus-within" ); |
| 502 | break; |
| 503 | #if ENABLE(VIDEO_TRACK) |
| 504 | case CSSSelector::PseudoClassFuture: |
| 505 | str.appendLiteral(":future" ); |
| 506 | break; |
| 507 | #endif |
| 508 | #if ENABLE(ATTACHMENT_ELEMENT) |
| 509 | case CSSSelector::PseudoClassHasAttachment: |
| 510 | str.appendLiteral(":has-attachment" ); |
| 511 | break; |
| 512 | #endif |
| 513 | case CSSSelector::PseudoClassHorizontal: |
| 514 | str.appendLiteral(":horizontal" ); |
| 515 | break; |
| 516 | case CSSSelector::PseudoClassHover: |
| 517 | str.appendLiteral(":hover" ); |
| 518 | break; |
| 519 | case CSSSelector::PseudoClassInRange: |
| 520 | str.appendLiteral(":in-range" ); |
| 521 | break; |
| 522 | case CSSSelector::PseudoClassIncrement: |
| 523 | str.appendLiteral(":increment" ); |
| 524 | break; |
| 525 | case CSSSelector::PseudoClassIndeterminate: |
| 526 | str.appendLiteral(":indeterminate" ); |
| 527 | break; |
| 528 | case CSSSelector::PseudoClassInvalid: |
| 529 | str.appendLiteral(":invalid" ); |
| 530 | break; |
| 531 | case CSSSelector::PseudoClassLang: |
| 532 | str.appendLiteral(":lang(" ); |
| 533 | ASSERT_WITH_MESSAGE(cs->langArgumentList() && !cs->langArgumentList()->isEmpty(), "An empty :lang() is invalid and should never be generated by the parser." ); |
| 534 | appendLangArgumentList(str, *cs->langArgumentList()); |
| 535 | str.append(')'); |
| 536 | break; |
| 537 | case CSSSelector::PseudoClassLastChild: |
| 538 | str.appendLiteral(":last-child" ); |
| 539 | break; |
| 540 | case CSSSelector::PseudoClassLastOfType: |
| 541 | str.appendLiteral(":last-of-type" ); |
| 542 | break; |
| 543 | case CSSSelector::PseudoClassLink: |
| 544 | str.appendLiteral(":link" ); |
| 545 | break; |
| 546 | case CSSSelector::PseudoClassNoButton: |
| 547 | str.appendLiteral(":no-button" ); |
| 548 | break; |
| 549 | case CSSSelector::PseudoClassNot: |
| 550 | str.appendLiteral(":not(" ); |
| 551 | cs->selectorList()->buildSelectorsText(str); |
| 552 | str.append(')'); |
| 553 | break; |
| 554 | case CSSSelector::PseudoClassNthChild: |
| 555 | str.appendLiteral(":nth-child(" ); |
| 556 | str.append(cs->argument()); |
| 557 | if (const CSSSelectorList* selectorList = cs->selectorList()) { |
| 558 | str.appendLiteral(" of " ); |
| 559 | selectorList->buildSelectorsText(str); |
| 560 | } |
| 561 | str.append(')'); |
| 562 | break; |
| 563 | case CSSSelector::PseudoClassNthLastChild: |
| 564 | str.appendLiteral(":nth-last-child(" ); |
| 565 | str.append(cs->argument()); |
| 566 | if (const CSSSelectorList* selectorList = cs->selectorList()) { |
| 567 | str.appendLiteral(" of " ); |
| 568 | selectorList->buildSelectorsText(str); |
| 569 | } |
| 570 | str.append(')'); |
| 571 | break; |
| 572 | case CSSSelector::PseudoClassNthLastOfType: |
| 573 | str.appendLiteral(":nth-last-of-type(" ); |
| 574 | appendPseudoClassFunctionTail(str, cs); |
| 575 | break; |
| 576 | case CSSSelector::PseudoClassNthOfType: |
| 577 | str.appendLiteral(":nth-of-type(" ); |
| 578 | appendPseudoClassFunctionTail(str, cs); |
| 579 | break; |
| 580 | case CSSSelector::PseudoClassOnlyChild: |
| 581 | str.appendLiteral(":only-child" ); |
| 582 | break; |
| 583 | case CSSSelector::PseudoClassOnlyOfType: |
| 584 | str.appendLiteral(":only-of-type" ); |
| 585 | break; |
| 586 | case CSSSelector::PseudoClassOptional: |
| 587 | str.appendLiteral(":optional" ); |
| 588 | break; |
| 589 | case CSSSelector::PseudoClassMatches: { |
| 590 | str.appendLiteral(":matches(" ); |
| 591 | cs->selectorList()->buildSelectorsText(str); |
| 592 | str.append(')'); |
| 593 | break; |
| 594 | } |
| 595 | case CSSSelector::PseudoClassPlaceholderShown: |
| 596 | str.appendLiteral(":placeholder-shown" ); |
| 597 | break; |
| 598 | case CSSSelector::PseudoClassOutOfRange: |
| 599 | str.appendLiteral(":out-of-range" ); |
| 600 | break; |
| 601 | #if ENABLE(VIDEO_TRACK) |
| 602 | case CSSSelector::PseudoClassPast: |
| 603 | str.appendLiteral(":past" ); |
| 604 | break; |
| 605 | #endif |
| 606 | case CSSSelector::PseudoClassReadOnly: |
| 607 | str.appendLiteral(":read-only" ); |
| 608 | break; |
| 609 | case CSSSelector::PseudoClassReadWrite: |
| 610 | str.appendLiteral(":read-write" ); |
| 611 | break; |
| 612 | case CSSSelector::PseudoClassRequired: |
| 613 | str.appendLiteral(":required" ); |
| 614 | break; |
| 615 | #if ENABLE(CSS_SELECTORS_LEVEL4) |
| 616 | case CSSSelector::PseudoClassRole: |
| 617 | str.appendLiteral(":role(" ); |
| 618 | appendPseudoClassFunctionTail(str, cs); |
| 619 | break; |
| 620 | #endif |
| 621 | case CSSSelector::PseudoClassRoot: |
| 622 | str.appendLiteral(":root" ); |
| 623 | break; |
| 624 | case CSSSelector::PseudoClassScope: |
| 625 | str.appendLiteral(":scope" ); |
| 626 | break; |
| 627 | case CSSSelector::PseudoClassSingleButton: |
| 628 | str.appendLiteral(":single-button" ); |
| 629 | break; |
| 630 | case CSSSelector::PseudoClassStart: |
| 631 | str.appendLiteral(":start" ); |
| 632 | break; |
| 633 | case CSSSelector::PseudoClassTarget: |
| 634 | str.appendLiteral(":target" ); |
| 635 | break; |
| 636 | case CSSSelector::PseudoClassValid: |
| 637 | str.appendLiteral(":valid" ); |
| 638 | break; |
| 639 | case CSSSelector::PseudoClassVertical: |
| 640 | str.appendLiteral(":vertical" ); |
| 641 | break; |
| 642 | case CSSSelector::PseudoClassVisited: |
| 643 | str.appendLiteral(":visited" ); |
| 644 | break; |
| 645 | case CSSSelector::PseudoClassWindowInactive: |
| 646 | str.appendLiteral(":window-inactive" ); |
| 647 | break; |
| 648 | case CSSSelector::PseudoClassHost: |
| 649 | str.appendLiteral(":host" ); |
| 650 | break; |
| 651 | case CSSSelector::PseudoClassDefined: |
| 652 | str.appendLiteral(":defined" ); |
| 653 | break; |
| 654 | case CSSSelector::PseudoClassUnknown: |
| 655 | ASSERT_NOT_REACHED(); |
| 656 | } |
| 657 | } else if (cs->match() == CSSSelector::PseudoElement) { |
| 658 | switch (cs->pseudoElementType()) { |
| 659 | case CSSSelector::PseudoElementSlotted: |
| 660 | str.appendLiteral("::slotted(" ); |
| 661 | cs->selectorList()->buildSelectorsText(str); |
| 662 | str.append(')'); |
| 663 | break; |
| 664 | case CSSSelector::PseudoElementWebKitCustomLegacyPrefixed: |
| 665 | if (cs->value() == "placeholder" ) |
| 666 | str.appendLiteral("::-webkit-input-placeholder" ); |
| 667 | break; |
| 668 | default: |
| 669 | str.appendLiteral("::" ); |
| 670 | str.append(cs->serializingValue()); |
| 671 | } |
| 672 | } else if (cs->isAttributeSelector()) { |
| 673 | str.append('['); |
| 674 | const AtomicString& prefix = cs->attribute().prefix(); |
| 675 | if (!prefix.isEmpty()) { |
| 676 | str.append(prefix); |
| 677 | str.append('|'); |
| 678 | } |
| 679 | str.append(cs->attribute().localName()); |
| 680 | switch (cs->match()) { |
| 681 | case CSSSelector::Exact: |
| 682 | str.append('='); |
| 683 | break; |
| 684 | case CSSSelector::Set: |
| 685 | // set has no operator or value, just the attrName |
| 686 | str.append(']'); |
| 687 | break; |
| 688 | case CSSSelector::List: |
| 689 | str.appendLiteral("~=" ); |
| 690 | break; |
| 691 | case CSSSelector::Hyphen: |
| 692 | str.appendLiteral("|=" ); |
| 693 | break; |
| 694 | case CSSSelector::Begin: |
| 695 | str.appendLiteral("^=" ); |
| 696 | break; |
| 697 | case CSSSelector::End: |
| 698 | str.appendLiteral("$=" ); |
| 699 | break; |
| 700 | case CSSSelector::Contain: |
| 701 | str.appendLiteral("*=" ); |
| 702 | break; |
| 703 | default: |
| 704 | break; |
| 705 | } |
| 706 | if (cs->match() != CSSSelector::Set) { |
| 707 | serializeString(cs->serializingValue(), str); |
| 708 | if (cs->attributeValueMatchingIsCaseInsensitive()) |
| 709 | str.appendLiteral(" i]" ); |
| 710 | else |
| 711 | str.append(']'); |
| 712 | } |
| 713 | } else if (cs->match() == CSSSelector::PagePseudoClass) { |
| 714 | switch (cs->pagePseudoClassType()) { |
| 715 | case PagePseudoClassFirst: |
| 716 | str.appendLiteral(":first" ); |
| 717 | break; |
| 718 | case PagePseudoClassLeft: |
| 719 | str.appendLiteral(":left" ); |
| 720 | break; |
| 721 | case PagePseudoClassRight: |
| 722 | str.appendLiteral(":right" ); |
| 723 | break; |
| 724 | } |
| 725 | } |
| 726 | |
| 727 | if (cs->relation() != CSSSelector::Subselector || !cs->tagHistory()) |
| 728 | break; |
| 729 | cs = cs->tagHistory(); |
| 730 | } |
| 731 | |
| 732 | if (const CSSSelector* tagHistory = cs->tagHistory()) { |
| 733 | switch (cs->relation()) { |
| 734 | case CSSSelector::DescendantSpace: |
| 735 | return tagHistory->selectorText(" " + str.toString() + rightSide); |
| 736 | case CSSSelector::Child: |
| 737 | return tagHistory->selectorText(" > " + str.toString() + rightSide); |
| 738 | case CSSSelector::DirectAdjacent: |
| 739 | return tagHistory->selectorText(" + " + str.toString() + rightSide); |
| 740 | case CSSSelector::IndirectAdjacent: |
| 741 | return tagHistory->selectorText(" ~ " + str.toString() + rightSide); |
| 742 | case CSSSelector::Subselector: |
| 743 | ASSERT_NOT_REACHED(); |
| 744 | #if ASSERT_DISABLED |
| 745 | FALLTHROUGH; |
| 746 | #endif |
| 747 | case CSSSelector::ShadowDescendant: |
| 748 | return tagHistory->selectorText(str.toString() + rightSide); |
| 749 | } |
| 750 | } |
| 751 | return str.toString() + rightSide; |
| 752 | } |
| 753 | |
| 754 | void CSSSelector::setAttribute(const QualifiedName& value, bool convertToLowercase, AttributeMatchType matchType) |
| 755 | { |
| 756 | createRareData(); |
| 757 | m_data.m_rareData->m_attribute = value; |
| 758 | m_data.m_rareData->m_attributeCanonicalLocalName = convertToLowercase ? value.localName().convertToASCIILowercase() : value.localName(); |
| 759 | m_caseInsensitiveAttributeValueMatching = matchType == CaseInsensitive; |
| 760 | } |
| 761 | |
| 762 | void CSSSelector::setArgument(const AtomicString& value) |
| 763 | { |
| 764 | createRareData(); |
| 765 | m_data.m_rareData->m_argument = value; |
| 766 | } |
| 767 | |
| 768 | void CSSSelector::setLangArgumentList(std::unique_ptr<Vector<AtomicString>> argumentList) |
| 769 | { |
| 770 | createRareData(); |
| 771 | m_data.m_rareData->m_langArgumentList = WTFMove(argumentList); |
| 772 | } |
| 773 | |
| 774 | void CSSSelector::setSelectorList(std::unique_ptr<CSSSelectorList> selectorList) |
| 775 | { |
| 776 | createRareData(); |
| 777 | m_data.m_rareData->m_selectorList = WTFMove(selectorList); |
| 778 | } |
| 779 | |
| 780 | void CSSSelector::setNth(int a, int b) |
| 781 | { |
| 782 | createRareData(); |
| 783 | m_data.m_rareData->m_a = a; |
| 784 | m_data.m_rareData->m_b = b; |
| 785 | } |
| 786 | |
| 787 | bool CSSSelector::matchNth(int count) const |
| 788 | { |
| 789 | ASSERT(m_hasRareData); |
| 790 | return m_data.m_rareData->matchNth(count); |
| 791 | } |
| 792 | |
| 793 | int CSSSelector::nthA() const |
| 794 | { |
| 795 | ASSERT(m_hasRareData); |
| 796 | return m_data.m_rareData->m_a; |
| 797 | } |
| 798 | |
| 799 | int CSSSelector::nthB() const |
| 800 | { |
| 801 | ASSERT(m_hasRareData); |
| 802 | return m_data.m_rareData->m_b; |
| 803 | } |
| 804 | |
| 805 | CSSSelector::RareData::RareData(AtomicString&& value) |
| 806 | : m_matchingValue(value) |
| 807 | , m_serializingValue(value) |
| 808 | , m_a(0) |
| 809 | , m_b(0) |
| 810 | , m_attribute(anyQName()) |
| 811 | , m_argument(nullAtom()) |
| 812 | { |
| 813 | } |
| 814 | |
| 815 | CSSSelector::RareData::~RareData() = default; |
| 816 | |
| 817 | // a helper function for checking nth-arguments |
| 818 | bool CSSSelector::RareData::matchNth(int count) |
| 819 | { |
| 820 | if (!m_a) |
| 821 | return count == m_b; |
| 822 | else if (m_a > 0) { |
| 823 | if (count < m_b) |
| 824 | return false; |
| 825 | return (count - m_b) % m_a == 0; |
| 826 | } else { |
| 827 | if (count > m_b) |
| 828 | return false; |
| 829 | return (m_b - count) % (-m_a) == 0; |
| 830 | } |
| 831 | } |
| 832 | |
| 833 | } // namespace WebCore |
| 834 | |