1 /* 2 * Copyright (C) 1999-2003 Lars Knoll (knoll (at) kde.org) 3 * 1999 Waldo Bastian (bastian (at) kde.org) 4 * 2001 Andreas Schlapbach (schlpbch (at) iam.unibe.ch) 5 * 2001-2003 Dirk Mueller (mueller (at) kde.org) 6 * Copyright (C) 2002, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. 7 * Copyright (C) 2008 David Smith (catfish.man (at) 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 "core/css/CSSSelector.h" 28 29 #include "core/HTMLNames.h" 30 #include "core/css/CSSOMUtils.h" 31 #include "core/css/CSSSelectorList.h" 32 #include "platform/RuntimeEnabledFeatures.h" 33 #include "wtf/Assertions.h" 34 #include "wtf/HashMap.h" 35 #include "wtf/StdLibExtras.h" 36 #include "wtf/text/StringBuilder.h" 37 38 #ifndef NDEBUG 39 #include <stdio.h> 40 #endif 41 42 namespace blink { 43 44 using namespace HTMLNames; 45 46 struct SameSizeAsCSSSelector { 47 unsigned bitfields; 48 void *pointers[1]; 49 }; 50 51 COMPILE_ASSERT(sizeof(CSSSelector) == sizeof(SameSizeAsCSSSelector), CSSSelectorShouldStaySmall); 52 53 void CSSSelector::createRareData() 54 { 55 ASSERT(m_match != Tag); 56 if (m_hasRareData) 57 return; 58 AtomicString value(m_data.m_value); 59 if (m_data.m_value) 60 m_data.m_value->deref(); 61 m_data.m_rareData = RareData::create(value).leakRef(); 62 m_hasRareData = true; 63 } 64 65 unsigned CSSSelector::specificity() const 66 { 67 // make sure the result doesn't overflow 68 static const unsigned maxValueMask = 0xffffff; 69 static const unsigned idMask = 0xff0000; 70 static const unsigned classMask = 0xff00; 71 static const unsigned elementMask = 0xff; 72 73 if (isForPage()) 74 return specificityForPage() & maxValueMask; 75 76 unsigned total = 0; 77 unsigned temp = 0; 78 79 for (const CSSSelector* selector = this; selector; selector = selector->tagHistory()) { 80 temp = total + selector->specificityForOneSelector(); 81 // Clamp each component to its max in the case of overflow. 82 if ((temp & idMask) < (total & idMask)) 83 total |= idMask; 84 else if ((temp & classMask) < (total & classMask)) 85 total |= classMask; 86 else if ((temp & elementMask) < (total & elementMask)) 87 total |= elementMask; 88 else 89 total = temp; 90 } 91 return total; 92 } 93 94 inline unsigned CSSSelector::specificityForOneSelector() const 95 { 96 // FIXME: Pseudo-elements and pseudo-classes do not have the same specificity. This function 97 // isn't quite correct. 98 switch (m_match) { 99 case Id: 100 return 0x10000; 101 case PseudoClass: 102 if (pseudoType() == PseudoHost || pseudoType() == PseudoHostContext) 103 return 0; 104 // fall through. 105 case Exact: 106 case Class: 107 case Set: 108 case List: 109 case Hyphen: 110 case PseudoElement: 111 case Contain: 112 case Begin: 113 case End: 114 // FIXME: PseudoAny should base the specificity on the sub-selectors. 115 // See http://lists.w3.org/Archives/Public/www-style/2010Sep/0530.html 116 if (pseudoType() == PseudoNot) { 117 ASSERT(selectorList()); 118 return selectorList()->first()->specificityForOneSelector(); 119 } 120 return 0x100; 121 case Tag: 122 return (tagQName().localName() != starAtom) ? 1 : 0; 123 case Unknown: 124 return 0; 125 } 126 ASSERT_NOT_REACHED(); 127 return 0; 128 } 129 130 unsigned CSSSelector::specificityForPage() const 131 { 132 // See http://dev.w3.org/csswg/css3-page/#cascading-and-page-context 133 unsigned s = 0; 134 135 for (const CSSSelector* component = this; component; component = component->tagHistory()) { 136 switch (component->m_match) { 137 case Tag: 138 s += tagQName().localName() == starAtom ? 0 : 4; 139 break; 140 case PagePseudoClass: 141 switch (component->pseudoType()) { 142 case PseudoFirstPage: 143 s += 2; 144 break; 145 case PseudoLeftPage: 146 case PseudoRightPage: 147 s += 1; 148 break; 149 case PseudoNotParsed: 150 break; 151 default: 152 ASSERT_NOT_REACHED(); 153 } 154 break; 155 default: 156 break; 157 } 158 } 159 return s; 160 } 161 162 PseudoId CSSSelector::pseudoId(PseudoType type) 163 { 164 switch (type) { 165 case PseudoFirstLine: 166 return FIRST_LINE; 167 case PseudoFirstLetter: 168 return FIRST_LETTER; 169 case PseudoSelection: 170 return SELECTION; 171 case PseudoBefore: 172 return BEFORE; 173 case PseudoAfter: 174 return AFTER; 175 case PseudoBackdrop: 176 return BACKDROP; 177 case PseudoScrollbar: 178 return SCROLLBAR; 179 case PseudoScrollbarButton: 180 return SCROLLBAR_BUTTON; 181 case PseudoScrollbarCorner: 182 return SCROLLBAR_CORNER; 183 case PseudoScrollbarThumb: 184 return SCROLLBAR_THUMB; 185 case PseudoScrollbarTrack: 186 return SCROLLBAR_TRACK; 187 case PseudoScrollbarTrackPiece: 188 return SCROLLBAR_TRACK_PIECE; 189 case PseudoResizer: 190 return RESIZER; 191 case PseudoUnknown: 192 case PseudoEmpty: 193 case PseudoFirstChild: 194 case PseudoFirstOfType: 195 case PseudoLastChild: 196 case PseudoLastOfType: 197 case PseudoOnlyChild: 198 case PseudoOnlyOfType: 199 case PseudoNthChild: 200 case PseudoNthOfType: 201 case PseudoNthLastChild: 202 case PseudoNthLastOfType: 203 case PseudoLink: 204 case PseudoVisited: 205 case PseudoAny: 206 case PseudoAnyLink: 207 case PseudoAutofill: 208 case PseudoHover: 209 case PseudoDrag: 210 case PseudoFocus: 211 case PseudoActive: 212 case PseudoChecked: 213 case PseudoEnabled: 214 case PseudoFullPageMedia: 215 case PseudoDefault: 216 case PseudoDisabled: 217 case PseudoOptional: 218 case PseudoRequired: 219 case PseudoReadOnly: 220 case PseudoReadWrite: 221 case PseudoValid: 222 case PseudoInvalid: 223 case PseudoIndeterminate: 224 case PseudoTarget: 225 case PseudoLang: 226 case PseudoNot: 227 case PseudoRoot: 228 case PseudoScope: 229 case PseudoScrollbarBack: 230 case PseudoScrollbarForward: 231 case PseudoWindowInactive: 232 case PseudoCornerPresent: 233 case PseudoDecrement: 234 case PseudoIncrement: 235 case PseudoHorizontal: 236 case PseudoVertical: 237 case PseudoStart: 238 case PseudoEnd: 239 case PseudoDoubleButton: 240 case PseudoSingleButton: 241 case PseudoNoButton: 242 case PseudoFirstPage: 243 case PseudoLeftPage: 244 case PseudoRightPage: 245 case PseudoInRange: 246 case PseudoOutOfRange: 247 case PseudoUserAgentCustomElement: 248 case PseudoWebKitCustomElement: 249 case PseudoCue: 250 case PseudoFutureCue: 251 case PseudoPastCue: 252 case PseudoUnresolved: 253 case PseudoContent: 254 case PseudoHost: 255 case PseudoHostContext: 256 case PseudoShadow: 257 case PseudoFullScreen: 258 case PseudoFullScreenDocument: 259 case PseudoFullScreenAncestor: 260 case PseudoSpatialNavigationFocus: 261 case PseudoListBox: 262 return NOPSEUDO; 263 case PseudoNotParsed: 264 ASSERT_NOT_REACHED(); 265 return NOPSEUDO; 266 } 267 268 ASSERT_NOT_REACHED(); 269 return NOPSEUDO; 270 } 271 272 // Could be made smaller and faster by replacing pointer with an 273 // offset into a string buffer and making the bit fields smaller but 274 // that could not be maintained by hand. 275 struct NameToPseudoStruct { 276 const char* string; 277 unsigned type:8; 278 }; 279 280 // These tables should be kept sorted. 281 const static NameToPseudoStruct pseudoTypeWithoutArgumentsMap[] = { 282 {"-internal-list-box", CSSSelector::PseudoListBox}, 283 {"-internal-media-controls-cast-button", CSSSelector::PseudoWebKitCustomElement}, 284 {"-internal-media-controls-overlay-cast-button", CSSSelector::PseudoWebKitCustomElement}, 285 {"-internal-spatial-navigation-focus", CSSSelector::PseudoSpatialNavigationFocus}, 286 {"-webkit-any-link", CSSSelector::PseudoAnyLink}, 287 {"-webkit-autofill", CSSSelector::PseudoAutofill}, 288 {"-webkit-drag", CSSSelector::PseudoDrag}, 289 {"-webkit-full-page-media", CSSSelector::PseudoFullPageMedia}, 290 {"-webkit-full-screen", CSSSelector::PseudoFullScreen}, 291 {"-webkit-full-screen-ancestor", CSSSelector::PseudoFullScreenAncestor}, 292 {"-webkit-full-screen-document", CSSSelector::PseudoFullScreenDocument}, 293 {"-webkit-resizer", CSSSelector::PseudoResizer}, 294 {"-webkit-scrollbar", CSSSelector::PseudoScrollbar}, 295 {"-webkit-scrollbar-button", CSSSelector::PseudoScrollbarButton}, 296 {"-webkit-scrollbar-corner", CSSSelector::PseudoScrollbarCorner}, 297 {"-webkit-scrollbar-thumb", CSSSelector::PseudoScrollbarThumb}, 298 {"-webkit-scrollbar-track", CSSSelector::PseudoScrollbarTrack}, 299 {"-webkit-scrollbar-track-piece", CSSSelector::PseudoScrollbarTrackPiece}, 300 {"active", CSSSelector::PseudoActive}, 301 {"after", CSSSelector::PseudoAfter}, 302 {"backdrop", CSSSelector::PseudoBackdrop}, 303 {"before", CSSSelector::PseudoBefore}, 304 {"checked", CSSSelector::PseudoChecked}, 305 {"content", CSSSelector::PseudoContent}, 306 {"corner-present", CSSSelector::PseudoCornerPresent}, 307 {"cue", CSSSelector::PseudoWebKitCustomElement}, 308 {"decrement", CSSSelector::PseudoDecrement}, 309 {"default", CSSSelector::PseudoDefault}, 310 {"disabled", CSSSelector::PseudoDisabled}, 311 {"double-button", CSSSelector::PseudoDoubleButton}, 312 {"empty", CSSSelector::PseudoEmpty}, 313 {"enabled", CSSSelector::PseudoEnabled}, 314 {"end", CSSSelector::PseudoEnd}, 315 {"first", CSSSelector::PseudoFirstPage}, 316 {"first-child", CSSSelector::PseudoFirstChild}, 317 {"first-letter", CSSSelector::PseudoFirstLetter}, 318 {"first-line", CSSSelector::PseudoFirstLine}, 319 {"first-of-type", CSSSelector::PseudoFirstOfType}, 320 {"focus", CSSSelector::PseudoFocus}, 321 {"future", CSSSelector::PseudoFutureCue}, 322 {"horizontal", CSSSelector::PseudoHorizontal}, 323 {"host", CSSSelector::PseudoHost}, 324 {"hover", CSSSelector::PseudoHover}, 325 {"in-range", CSSSelector::PseudoInRange}, 326 {"increment", CSSSelector::PseudoIncrement}, 327 {"indeterminate", CSSSelector::PseudoIndeterminate}, 328 {"invalid", CSSSelector::PseudoInvalid}, 329 {"last-child", CSSSelector::PseudoLastChild}, 330 {"last-of-type", CSSSelector::PseudoLastOfType}, 331 {"left", CSSSelector::PseudoLeftPage}, 332 {"link", CSSSelector::PseudoLink}, 333 {"no-button", CSSSelector::PseudoNoButton}, 334 {"only-child", CSSSelector::PseudoOnlyChild}, 335 {"only-of-type", CSSSelector::PseudoOnlyOfType}, 336 {"optional", CSSSelector::PseudoOptional}, 337 {"out-of-range", CSSSelector::PseudoOutOfRange}, 338 {"past", CSSSelector::PseudoPastCue}, 339 {"read-only", CSSSelector::PseudoReadOnly}, 340 {"read-write", CSSSelector::PseudoReadWrite}, 341 {"required", CSSSelector::PseudoRequired}, 342 {"right", CSSSelector::PseudoRightPage}, 343 {"root", CSSSelector::PseudoRoot}, 344 {"scope", CSSSelector::PseudoScope}, 345 {"selection", CSSSelector::PseudoSelection}, 346 {"shadow", CSSSelector::PseudoShadow}, 347 {"single-button", CSSSelector::PseudoSingleButton}, 348 {"start", CSSSelector::PseudoStart}, 349 {"target", CSSSelector::PseudoTarget}, 350 {"unresolved", CSSSelector::PseudoUnresolved}, 351 {"valid", CSSSelector::PseudoValid}, 352 {"vertical", CSSSelector::PseudoVertical}, 353 {"visited", CSSSelector::PseudoVisited}, 354 {"window-inactive", CSSSelector::PseudoWindowInactive}, 355 }; 356 357 const static NameToPseudoStruct pseudoTypeWithArgumentsMap[] = { 358 {"-webkit-any", CSSSelector::PseudoAny}, 359 {"cue", CSSSelector::PseudoCue}, 360 {"host", CSSSelector::PseudoHost}, 361 {"host-context", CSSSelector::PseudoHostContext}, 362 {"lang", CSSSelector::PseudoLang}, 363 {"not", CSSSelector::PseudoNot}, 364 {"nth-child", CSSSelector::PseudoNthChild}, 365 {"nth-last-child", CSSSelector::PseudoNthLastChild}, 366 {"nth-last-of-type", CSSSelector::PseudoNthLastOfType}, 367 {"nth-of-type", CSSSelector::PseudoNthOfType}, 368 }; 369 370 class NameToPseudoCompare { 371 public: 372 NameToPseudoCompare(const AtomicString& key) : m_key(key) { ASSERT(m_key.is8Bit()); } 373 374 bool operator()(const NameToPseudoStruct& entry, const NameToPseudoStruct&) 375 { 376 ASSERT(entry.string); 377 const char* key = reinterpret_cast<const char*>(m_key.characters8()); 378 // If strncmp returns 0, then either the keys are equal, or |m_key| sorts before |entry|. 379 return strncmp(entry.string, key, m_key.length()) < 0; 380 } 381 382 private: 383 const AtomicString& m_key; 384 }; 385 386 static CSSSelector::PseudoType nameToPseudoType(const AtomicString& name, bool hasArguments) 387 { 388 if (name.isNull() || !name.is8Bit()) 389 return CSSSelector::PseudoUnknown; 390 391 const NameToPseudoStruct* pseudoTypeMap; 392 const NameToPseudoStruct* pseudoTypeMapEnd; 393 if (hasArguments) { 394 pseudoTypeMap = pseudoTypeWithArgumentsMap; 395 pseudoTypeMapEnd = pseudoTypeWithArgumentsMap + WTF_ARRAY_LENGTH(pseudoTypeWithArgumentsMap); 396 } else { 397 pseudoTypeMap = pseudoTypeWithoutArgumentsMap; 398 pseudoTypeMapEnd = pseudoTypeWithoutArgumentsMap + WTF_ARRAY_LENGTH(pseudoTypeWithoutArgumentsMap); 399 } 400 NameToPseudoStruct dummyKey = { 0, CSSSelector::PseudoUnknown }; 401 const NameToPseudoStruct* match = std::lower_bound(pseudoTypeMap, pseudoTypeMapEnd, dummyKey, NameToPseudoCompare(name)); 402 if (match == pseudoTypeMapEnd || match->string != name.string()) 403 return CSSSelector::PseudoUnknown; 404 405 return static_cast<CSSSelector::PseudoType>(match->type); 406 } 407 408 #ifndef NDEBUG 409 void CSSSelector::show(int indent) const 410 { 411 printf("%*sselectorText(): %s\n", indent, "", selectorText().ascii().data()); 412 printf("%*sm_match: %d\n", indent, "", m_match); 413 printf("%*sisCustomPseudoElement(): %d\n", indent, "", isCustomPseudoElement()); 414 if (m_match != Tag) 415 printf("%*svalue(): %s\n", indent, "", value().ascii().data()); 416 printf("%*spseudoType(): %d\n", indent, "", pseudoType()); 417 if (m_match == Tag) 418 printf("%*stagQName().localName: %s\n", indent, "", tagQName().localName().ascii().data()); 419 printf("%*sisAttributeSelector(): %d\n", indent, "", isAttributeSelector()); 420 if (isAttributeSelector()) 421 printf("%*sattribute(): %s\n", indent, "", attribute().localName().ascii().data()); 422 printf("%*sargument(): %s\n", indent, "", argument().ascii().data()); 423 printf("%*sspecificity(): %u\n", indent, "", specificity()); 424 if (tagHistory()) { 425 printf("\n%*s--> (relation == %d)\n", indent, "", relation()); 426 tagHistory()->show(indent + 2); 427 } else { 428 printf("\n%*s--> (relation == %d)\n", indent, "", relation()); 429 } 430 } 431 432 void CSSSelector::show() const 433 { 434 printf("\n******* CSSSelector::show(\"%s\") *******\n", selectorText().ascii().data()); 435 show(2); 436 printf("******* end *******\n"); 437 } 438 #endif 439 440 CSSSelector::PseudoType CSSSelector::parsePseudoType(const AtomicString& name, bool hasArguments) 441 { 442 CSSSelector::PseudoType pseudoType = nameToPseudoType(name, hasArguments); 443 if (pseudoType != PseudoUnknown) 444 return pseudoType; 445 446 if (name.startsWith("-webkit-")) 447 return PseudoWebKitCustomElement; 448 if (name.startsWith("cue")) 449 return PseudoUserAgentCustomElement; 450 451 return PseudoUnknown; 452 } 453 454 void CSSSelector::extractPseudoType() const 455 { 456 if (m_match != PseudoClass && m_match != PseudoElement && m_match != PagePseudoClass) 457 return; 458 459 m_pseudoType = parsePseudoType(value(), !argument().isNull() || selectorList()); 460 461 bool element = false; // pseudo-element 462 bool compat = false; // single colon compatbility mode 463 bool isPagePseudoClass = false; // Page pseudo-class 464 465 switch (m_pseudoType) { 466 case PseudoAfter: 467 case PseudoBefore: 468 case PseudoCue: 469 case PseudoFirstLetter: 470 case PseudoFirstLine: 471 compat = true; 472 case PseudoBackdrop: 473 case PseudoResizer: 474 case PseudoScrollbar: 475 case PseudoScrollbarCorner: 476 case PseudoScrollbarButton: 477 case PseudoScrollbarThumb: 478 case PseudoScrollbarTrack: 479 case PseudoScrollbarTrackPiece: 480 case PseudoSelection: 481 case PseudoUserAgentCustomElement: 482 case PseudoWebKitCustomElement: 483 case PseudoContent: 484 case PseudoShadow: 485 element = true; 486 break; 487 case PseudoUnknown: 488 case PseudoEmpty: 489 case PseudoFirstChild: 490 case PseudoFirstOfType: 491 case PseudoLastChild: 492 case PseudoLastOfType: 493 case PseudoOnlyChild: 494 case PseudoOnlyOfType: 495 case PseudoNthChild: 496 case PseudoNthOfType: 497 case PseudoNthLastChild: 498 case PseudoNthLastOfType: 499 case PseudoLink: 500 case PseudoVisited: 501 case PseudoAny: 502 case PseudoAnyLink: 503 case PseudoAutofill: 504 case PseudoHover: 505 case PseudoDrag: 506 case PseudoFocus: 507 case PseudoActive: 508 case PseudoChecked: 509 case PseudoEnabled: 510 case PseudoFullPageMedia: 511 case PseudoDefault: 512 case PseudoDisabled: 513 case PseudoOptional: 514 case PseudoRequired: 515 case PseudoReadOnly: 516 case PseudoReadWrite: 517 case PseudoScope: 518 case PseudoValid: 519 case PseudoInvalid: 520 case PseudoIndeterminate: 521 case PseudoTarget: 522 case PseudoLang: 523 case PseudoNot: 524 case PseudoRoot: 525 case PseudoScrollbarBack: 526 case PseudoScrollbarForward: 527 case PseudoWindowInactive: 528 case PseudoCornerPresent: 529 case PseudoDecrement: 530 case PseudoIncrement: 531 case PseudoHorizontal: 532 case PseudoVertical: 533 case PseudoStart: 534 case PseudoEnd: 535 case PseudoDoubleButton: 536 case PseudoSingleButton: 537 case PseudoNoButton: 538 case PseudoNotParsed: 539 case PseudoFullScreen: 540 case PseudoFullScreenDocument: 541 case PseudoFullScreenAncestor: 542 case PseudoInRange: 543 case PseudoOutOfRange: 544 case PseudoFutureCue: 545 case PseudoPastCue: 546 case PseudoHost: 547 case PseudoHostContext: 548 case PseudoUnresolved: 549 case PseudoSpatialNavigationFocus: 550 case PseudoListBox: 551 break; 552 case PseudoFirstPage: 553 case PseudoLeftPage: 554 case PseudoRightPage: 555 isPagePseudoClass = true; 556 break; 557 } 558 559 bool matchPagePseudoClass = (m_match == PagePseudoClass); 560 if (matchPagePseudoClass != isPagePseudoClass) 561 m_pseudoType = PseudoUnknown; 562 else if (m_match == PseudoClass && element) { 563 if (!compat) 564 m_pseudoType = PseudoUnknown; 565 else 566 m_match = PseudoElement; 567 } else if (m_match == PseudoElement && !element) 568 m_pseudoType = PseudoUnknown; 569 } 570 571 bool CSSSelector::operator==(const CSSSelector& other) const 572 { 573 const CSSSelector* sel1 = this; 574 const CSSSelector* sel2 = &other; 575 576 while (sel1 && sel2) { 577 if (sel1->attribute() != sel2->attribute() 578 || sel1->relation() != sel2->relation() 579 || sel1->m_match != sel2->m_match 580 || sel1->value() != sel2->value() 581 || sel1->pseudoType() != sel2->pseudoType() 582 || sel1->argument() != sel2->argument()) { 583 return false; 584 } 585 if (sel1->m_match == Tag) { 586 if (sel1->tagQName() != sel2->tagQName()) 587 return false; 588 } 589 sel1 = sel1->tagHistory(); 590 sel2 = sel2->tagHistory(); 591 } 592 593 if (sel1 || sel2) 594 return false; 595 596 return true; 597 } 598 599 String CSSSelector::selectorText(const String& rightSide) const 600 { 601 StringBuilder str; 602 603 if (m_match == CSSSelector::Tag && !m_tagIsForNamespaceRule) { 604 if (tagQName().prefix().isNull()) 605 str.append(tagQName().localName()); 606 else { 607 str.append(tagQName().prefix().string()); 608 str.append('|'); 609 str.append(tagQName().localName()); 610 } 611 } 612 613 const CSSSelector* cs = this; 614 while (true) { 615 if (cs->m_match == CSSSelector::Id) { 616 str.append('#'); 617 serializeIdentifier(cs->value(), str); 618 } else if (cs->m_match == CSSSelector::Class) { 619 str.append('.'); 620 serializeIdentifier(cs->value(), str); 621 } else if (cs->m_match == CSSSelector::PseudoClass || cs->m_match == CSSSelector::PagePseudoClass) { 622 str.append(':'); 623 str.append(cs->value()); 624 625 switch (cs->pseudoType()) { 626 case PseudoNot: 627 ASSERT(cs->selectorList()); 628 str.append('('); 629 str.append(cs->selectorList()->first()->selectorText()); 630 str.append(')'); 631 break; 632 case PseudoLang: 633 case PseudoNthChild: 634 case PseudoNthLastChild: 635 case PseudoNthOfType: 636 case PseudoNthLastOfType: 637 str.append('('); 638 str.append(cs->argument()); 639 str.append(')'); 640 break; 641 case PseudoAny: { 642 str.append('('); 643 const CSSSelector* firstSubSelector = cs->selectorList()->first(); 644 for (const CSSSelector* subSelector = firstSubSelector; subSelector; subSelector = CSSSelectorList::next(*subSelector)) { 645 if (subSelector != firstSubSelector) 646 str.append(','); 647 str.append(subSelector->selectorText()); 648 } 649 str.append(')'); 650 break; 651 } 652 case PseudoHost: 653 case PseudoHostContext: { 654 if (cs->selectorList()) { 655 str.append('('); 656 const CSSSelector* firstSubSelector = cs->selectorList()->first(); 657 for (const CSSSelector* subSelector = firstSubSelector; subSelector; subSelector = CSSSelectorList::next(*subSelector)) { 658 if (subSelector != firstSubSelector) 659 str.append(','); 660 str.append(subSelector->selectorText()); 661 } 662 str.append(')'); 663 } 664 break; 665 } 666 default: 667 break; 668 } 669 } else if (cs->m_match == CSSSelector::PseudoElement) { 670 str.appendLiteral("::"); 671 str.append(cs->value()); 672 673 if (cs->pseudoType() == PseudoContent) { 674 if (cs->relation() == CSSSelector::SubSelector && cs->tagHistory()) 675 return cs->tagHistory()->selectorText() + str.toString() + rightSide; 676 } 677 } else if (cs->isAttributeSelector()) { 678 str.append('['); 679 const AtomicString& prefix = cs->attribute().prefix(); 680 if (!prefix.isNull()) { 681 str.append(prefix); 682 str.append('|'); 683 } 684 str.append(cs->attribute().localName()); 685 switch (cs->m_match) { 686 case CSSSelector::Exact: 687 str.append('='); 688 break; 689 case CSSSelector::Set: 690 // set has no operator or value, just the attrName 691 str.append(']'); 692 break; 693 case CSSSelector::List: 694 str.appendLiteral("~="); 695 break; 696 case CSSSelector::Hyphen: 697 str.appendLiteral("|="); 698 break; 699 case CSSSelector::Begin: 700 str.appendLiteral("^="); 701 break; 702 case CSSSelector::End: 703 str.appendLiteral("$="); 704 break; 705 case CSSSelector::Contain: 706 str.appendLiteral("*="); 707 break; 708 default: 709 break; 710 } 711 if (cs->m_match != CSSSelector::Set) { 712 serializeString(cs->value(), str); 713 if (cs->attributeMatchType() == CaseInsensitive) 714 str.appendLiteral(" i"); 715 str.append(']'); 716 } 717 } 718 if (cs->relation() != CSSSelector::SubSelector || !cs->tagHistory()) 719 break; 720 cs = cs->tagHistory(); 721 } 722 723 if (const CSSSelector* tagHistory = cs->tagHistory()) { 724 switch (cs->relation()) { 725 case CSSSelector::Descendant: 726 return tagHistory->selectorText(" " + str.toString() + rightSide); 727 case CSSSelector::Child: 728 return tagHistory->selectorText(" > " + str.toString() + rightSide); 729 case CSSSelector::ShadowDeep: 730 return tagHistory->selectorText(" /deep/ " + str.toString() + rightSide); 731 case CSSSelector::DirectAdjacent: 732 return tagHistory->selectorText(" + " + str.toString() + rightSide); 733 case CSSSelector::IndirectAdjacent: 734 return tagHistory->selectorText(" ~ " + str.toString() + rightSide); 735 case CSSSelector::SubSelector: 736 ASSERT_NOT_REACHED(); 737 case CSSSelector::ShadowPseudo: 738 return tagHistory->selectorText(str.toString() + rightSide); 739 } 740 } 741 return str.toString() + rightSide; 742 } 743 744 void CSSSelector::setAttribute(const QualifiedName& value, AttributeMatchType matchType) 745 { 746 createRareData(); 747 m_data.m_rareData->m_attribute = value; 748 m_data.m_rareData->m_bits.m_attributeMatchType = matchType; 749 } 750 751 void CSSSelector::setArgument(const AtomicString& value) 752 { 753 createRareData(); 754 m_data.m_rareData->m_argument = value; 755 } 756 757 void CSSSelector::setSelectorList(PassOwnPtr<CSSSelectorList> selectorList) 758 { 759 createRareData(); 760 m_data.m_rareData->m_selectorList = selectorList; 761 } 762 763 static bool validateSubSelector(const CSSSelector* selector) 764 { 765 switch (selector->match()) { 766 case CSSSelector::Tag: 767 case CSSSelector::Id: 768 case CSSSelector::Class: 769 case CSSSelector::Exact: 770 case CSSSelector::Set: 771 case CSSSelector::List: 772 case CSSSelector::Hyphen: 773 case CSSSelector::Contain: 774 case CSSSelector::Begin: 775 case CSSSelector::End: 776 return true; 777 case CSSSelector::PseudoElement: 778 case CSSSelector::Unknown: 779 return false; 780 case CSSSelector::PagePseudoClass: 781 case CSSSelector::PseudoClass: 782 break; 783 } 784 785 switch (selector->pseudoType()) { 786 case CSSSelector::PseudoEmpty: 787 case CSSSelector::PseudoLink: 788 case CSSSelector::PseudoVisited: 789 case CSSSelector::PseudoTarget: 790 case CSSSelector::PseudoEnabled: 791 case CSSSelector::PseudoDisabled: 792 case CSSSelector::PseudoChecked: 793 case CSSSelector::PseudoIndeterminate: 794 case CSSSelector::PseudoNthChild: 795 case CSSSelector::PseudoNthLastChild: 796 case CSSSelector::PseudoNthOfType: 797 case CSSSelector::PseudoNthLastOfType: 798 case CSSSelector::PseudoFirstChild: 799 case CSSSelector::PseudoLastChild: 800 case CSSSelector::PseudoFirstOfType: 801 case CSSSelector::PseudoLastOfType: 802 case CSSSelector::PseudoOnlyOfType: 803 case CSSSelector::PseudoHost: 804 case CSSSelector::PseudoHostContext: 805 case CSSSelector::PseudoNot: 806 case CSSSelector::PseudoSpatialNavigationFocus: 807 case CSSSelector::PseudoListBox: 808 return true; 809 default: 810 return false; 811 } 812 } 813 814 bool CSSSelector::isCompound() const 815 { 816 if (!validateSubSelector(this)) 817 return false; 818 819 const CSSSelector* prevSubSelector = this; 820 const CSSSelector* subSelector = tagHistory(); 821 822 while (subSelector) { 823 if (prevSubSelector->relation() != CSSSelector::SubSelector) 824 return false; 825 if (!validateSubSelector(subSelector)) 826 return false; 827 828 prevSubSelector = subSelector; 829 subSelector = subSelector->tagHistory(); 830 } 831 832 return true; 833 } 834 835 bool CSSSelector::parseNth() const 836 { 837 if (!m_hasRareData) 838 return false; 839 if (m_parsedNth) 840 return true; 841 m_parsedNth = m_data.m_rareData->parseNth(); 842 return m_parsedNth; 843 } 844 845 bool CSSSelector::matchNth(int count) const 846 { 847 ASSERT(m_hasRareData); 848 return m_data.m_rareData->matchNth(count); 849 } 850 851 CSSSelector::RareData::RareData(const AtomicString& value) 852 : m_value(value) 853 , m_bits() 854 , m_attribute(anyQName()) 855 , m_argument(nullAtom) 856 { 857 } 858 859 CSSSelector::RareData::~RareData() 860 { 861 } 862 863 // a helper function for parsing nth-arguments 864 bool CSSSelector::RareData::parseNth() 865 { 866 String argument = m_argument.lower(); 867 868 if (argument.isEmpty()) 869 return false; 870 871 int nthA = 0; 872 int nthB = 0; 873 if (argument == "odd") { 874 nthA = 2; 875 nthB = 1; 876 } else if (argument == "even") { 877 nthA = 2; 878 nthB = 0; 879 } else { 880 size_t n = argument.find('n'); 881 if (n != kNotFound) { 882 if (argument[0] == '-') { 883 if (n == 1) 884 nthA = -1; // -n == -1n 885 else 886 nthA = argument.substring(0, n).toInt(); 887 } else if (!n) { 888 nthA = 1; // n == 1n 889 } else { 890 nthA = argument.substring(0, n).toInt(); 891 } 892 893 size_t p = argument.find('+', n); 894 if (p != kNotFound) { 895 nthB = argument.substring(p + 1, argument.length() - p - 1).toInt(); 896 } else { 897 p = argument.find('-', n); 898 if (p != kNotFound) 899 nthB = -argument.substring(p + 1, argument.length() - p - 1).toInt(); 900 } 901 } else { 902 nthB = argument.toInt(); 903 } 904 } 905 setNthAValue(nthA); 906 setNthBValue(nthB); 907 return true; 908 } 909 910 // a helper function for checking nth-arguments 911 bool CSSSelector::RareData::matchNth(int count) 912 { 913 if (!nthAValue()) 914 return count == nthBValue(); 915 if (nthAValue() > 0) { 916 if (count < nthBValue()) 917 return false; 918 return (count - nthBValue()) % nthAValue() == 0; 919 } 920 if (count > nthBValue()) 921 return false; 922 return (nthBValue() - count) % (-nthAValue()) == 0; 923 } 924 925 } // namespace blink 926