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 WebCore { 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 return NOPSEUDO; 261 case PseudoNotParsed: 262 ASSERT_NOT_REACHED(); 263 return NOPSEUDO; 264 } 265 266 ASSERT_NOT_REACHED(); 267 return NOPSEUDO; 268 } 269 270 // Could be made smaller and faster by replacing pointer with an 271 // offset into a string buffer and making the bit fields smaller but 272 // that could not be maintained by hand. 273 struct NameToPseudoStruct { 274 const char* string; 275 unsigned type:8; 276 }; 277 278 // This table should be kept sorted. 279 const static NameToPseudoStruct pseudoTypeMap[] = { 280 {"-webkit-any(", CSSSelector::PseudoAny}, 281 {"-webkit-any-link", CSSSelector::PseudoAnyLink}, 282 {"-webkit-autofill", CSSSelector::PseudoAutofill}, 283 {"-webkit-drag", CSSSelector::PseudoDrag}, 284 {"-webkit-full-page-media", CSSSelector::PseudoFullPageMedia}, 285 {"-webkit-full-screen", CSSSelector::PseudoFullScreen}, 286 {"-webkit-full-screen-ancestor", CSSSelector::PseudoFullScreenAncestor}, 287 {"-webkit-full-screen-document", CSSSelector::PseudoFullScreenDocument}, 288 {"-webkit-resizer", CSSSelector::PseudoResizer}, 289 {"-webkit-scrollbar", CSSSelector::PseudoScrollbar}, 290 {"-webkit-scrollbar-button", CSSSelector::PseudoScrollbarButton}, 291 {"-webkit-scrollbar-corner", CSSSelector::PseudoScrollbarCorner}, 292 {"-webkit-scrollbar-thumb", CSSSelector::PseudoScrollbarThumb}, 293 {"-webkit-scrollbar-track", CSSSelector::PseudoScrollbarTrack}, 294 {"-webkit-scrollbar-track-piece", CSSSelector::PseudoScrollbarTrackPiece}, 295 {"active", CSSSelector::PseudoActive}, 296 {"after", CSSSelector::PseudoAfter}, 297 {"backdrop", CSSSelector::PseudoBackdrop}, 298 {"before", CSSSelector::PseudoBefore}, 299 {"checked", CSSSelector::PseudoChecked}, 300 {"content", CSSSelector::PseudoContent}, 301 {"corner-present", CSSSelector::PseudoCornerPresent}, 302 {"cue", CSSSelector::PseudoWebKitCustomElement}, 303 {"cue(", CSSSelector::PseudoCue}, 304 {"decrement", CSSSelector::PseudoDecrement}, 305 {"default", CSSSelector::PseudoDefault}, 306 {"disabled", CSSSelector::PseudoDisabled}, 307 {"double-button", CSSSelector::PseudoDoubleButton}, 308 {"empty", CSSSelector::PseudoEmpty}, 309 {"enabled", CSSSelector::PseudoEnabled}, 310 {"end", CSSSelector::PseudoEnd}, 311 {"first", CSSSelector::PseudoFirstPage}, 312 {"first-child", CSSSelector::PseudoFirstChild}, 313 {"first-letter", CSSSelector::PseudoFirstLetter}, 314 {"first-line", CSSSelector::PseudoFirstLine}, 315 {"first-of-type", CSSSelector::PseudoFirstOfType}, 316 {"focus", CSSSelector::PseudoFocus}, 317 {"future", CSSSelector::PseudoFutureCue}, 318 {"horizontal", CSSSelector::PseudoHorizontal}, 319 {"host", CSSSelector::PseudoHost}, 320 {"host(", CSSSelector::PseudoHost}, 321 {"host-context(", CSSSelector::PseudoHostContext}, 322 {"hover", CSSSelector::PseudoHover}, 323 {"in-range", CSSSelector::PseudoInRange}, 324 {"increment", CSSSelector::PseudoIncrement}, 325 {"indeterminate", CSSSelector::PseudoIndeterminate}, 326 {"invalid", CSSSelector::PseudoInvalid}, 327 {"lang(", CSSSelector::PseudoLang}, 328 {"last-child", CSSSelector::PseudoLastChild}, 329 {"last-of-type", CSSSelector::PseudoLastOfType}, 330 {"left", CSSSelector::PseudoLeftPage}, 331 {"link", CSSSelector::PseudoLink}, 332 {"no-button", CSSSelector::PseudoNoButton}, 333 {"not(", CSSSelector::PseudoNot}, 334 {"nth-child(", CSSSelector::PseudoNthChild}, 335 {"nth-last-child(", CSSSelector::PseudoNthLastChild}, 336 {"nth-last-of-type(", CSSSelector::PseudoNthLastOfType}, 337 {"nth-of-type(", CSSSelector::PseudoNthOfType}, 338 {"only-child", CSSSelector::PseudoOnlyChild}, 339 {"only-of-type", CSSSelector::PseudoOnlyOfType}, 340 {"optional", CSSSelector::PseudoOptional}, 341 {"out-of-range", CSSSelector::PseudoOutOfRange}, 342 {"past", CSSSelector::PseudoPastCue}, 343 {"read-only", CSSSelector::PseudoReadOnly}, 344 {"read-write", CSSSelector::PseudoReadWrite}, 345 {"required", CSSSelector::PseudoRequired}, 346 {"right", CSSSelector::PseudoRightPage}, 347 {"root", CSSSelector::PseudoRoot}, 348 {"scope", CSSSelector::PseudoScope}, 349 {"selection", CSSSelector::PseudoSelection}, 350 {"shadow", CSSSelector::PseudoShadow}, 351 {"single-button", CSSSelector::PseudoSingleButton}, 352 {"start", CSSSelector::PseudoStart}, 353 {"target", CSSSelector::PseudoTarget}, 354 {"unresolved", CSSSelector::PseudoUnresolved}, 355 {"valid", CSSSelector::PseudoValid}, 356 {"vertical", CSSSelector::PseudoVertical}, 357 {"visited", CSSSelector::PseudoVisited}, 358 {"window-inactive", CSSSelector::PseudoWindowInactive}, 359 }; 360 361 class NameToPseudoCompare { 362 public: 363 NameToPseudoCompare(const AtomicString& key) : m_key(key) { ASSERT(m_key.is8Bit()); } 364 365 bool operator()(const NameToPseudoStruct& entry, const NameToPseudoStruct&) 366 { 367 ASSERT(entry.string); 368 const char* key = reinterpret_cast<const char*>(m_key.characters8()); 369 // If strncmp returns 0, then either the keys are equal, or |m_key| sorts before |entry|. 370 return strncmp(entry.string, key, m_key.length()) < 0; 371 } 372 373 private: 374 const AtomicString& m_key; 375 }; 376 377 static CSSSelector::PseudoType nameToPseudoType(const AtomicString& name) 378 { 379 if (name.isNull() || !name.is8Bit()) 380 return CSSSelector::PseudoUnknown; 381 382 const NameToPseudoStruct* pseudoTypeMapEnd = pseudoTypeMap + WTF_ARRAY_LENGTH(pseudoTypeMap); 383 NameToPseudoStruct dummyKey = { 0, CSSSelector::PseudoUnknown }; 384 const NameToPseudoStruct* match = std::lower_bound(pseudoTypeMap, pseudoTypeMapEnd, dummyKey, NameToPseudoCompare(name)); 385 if (match == pseudoTypeMapEnd || match->string != name.string()) 386 return CSSSelector::PseudoUnknown; 387 388 return static_cast<CSSSelector::PseudoType>(match->type); 389 } 390 391 #ifndef NDEBUG 392 void CSSSelector::show(int indent) const 393 { 394 printf("%*sselectorText(): %s\n", indent, "", selectorText().ascii().data()); 395 printf("%*sm_match: %d\n", indent, "", m_match); 396 printf("%*sisCustomPseudoElement(): %d\n", indent, "", isCustomPseudoElement()); 397 if (m_match != Tag) 398 printf("%*svalue(): %s\n", indent, "", value().ascii().data()); 399 printf("%*spseudoType(): %d\n", indent, "", pseudoType()); 400 if (m_match == Tag) 401 printf("%*stagQName().localName: %s\n", indent, "", tagQName().localName().ascii().data()); 402 printf("%*sisAttributeSelector(): %d\n", indent, "", isAttributeSelector()); 403 if (isAttributeSelector()) 404 printf("%*sattribute(): %s\n", indent, "", attribute().localName().ascii().data()); 405 printf("%*sargument(): %s\n", indent, "", argument().ascii().data()); 406 printf("%*sspecificity(): %u\n", indent, "", specificity()); 407 if (tagHistory()) { 408 printf("\n%*s--> (relation == %d)\n", indent, "", relation()); 409 tagHistory()->show(indent + 2); 410 } else { 411 printf("\n%*s--> (relation == %d)\n", indent, "", relation()); 412 } 413 } 414 415 void CSSSelector::show() const 416 { 417 printf("\n******* CSSSelector::show(\"%s\") *******\n", selectorText().ascii().data()); 418 show(2); 419 printf("******* end *******\n"); 420 } 421 #endif 422 423 CSSSelector::PseudoType CSSSelector::parsePseudoType(const AtomicString& name) 424 { 425 CSSSelector::PseudoType pseudoType = nameToPseudoType(name); 426 if (pseudoType != PseudoUnknown) 427 return pseudoType; 428 429 if (name.startsWith("-webkit-")) 430 return PseudoWebKitCustomElement; 431 if (name.startsWith("cue")) 432 return PseudoUserAgentCustomElement; 433 434 return PseudoUnknown; 435 } 436 437 void CSSSelector::extractPseudoType() const 438 { 439 if (m_match != PseudoClass && m_match != PseudoElement && m_match != PagePseudoClass) 440 return; 441 442 m_pseudoType = parsePseudoType(value()); 443 444 bool element = false; // pseudo-element 445 bool compat = false; // single colon compatbility mode 446 bool isPagePseudoClass = false; // Page pseudo-class 447 448 switch (m_pseudoType) { 449 case PseudoAfter: 450 case PseudoBefore: 451 case PseudoCue: 452 case PseudoFirstLetter: 453 case PseudoFirstLine: 454 compat = true; 455 case PseudoBackdrop: 456 case PseudoResizer: 457 case PseudoScrollbar: 458 case PseudoScrollbarCorner: 459 case PseudoScrollbarButton: 460 case PseudoScrollbarThumb: 461 case PseudoScrollbarTrack: 462 case PseudoScrollbarTrackPiece: 463 case PseudoSelection: 464 case PseudoUserAgentCustomElement: 465 case PseudoWebKitCustomElement: 466 case PseudoContent: 467 case PseudoShadow: 468 element = true; 469 break; 470 case PseudoUnknown: 471 case PseudoEmpty: 472 case PseudoFirstChild: 473 case PseudoFirstOfType: 474 case PseudoLastChild: 475 case PseudoLastOfType: 476 case PseudoOnlyChild: 477 case PseudoOnlyOfType: 478 case PseudoNthChild: 479 case PseudoNthOfType: 480 case PseudoNthLastChild: 481 case PseudoNthLastOfType: 482 case PseudoLink: 483 case PseudoVisited: 484 case PseudoAny: 485 case PseudoAnyLink: 486 case PseudoAutofill: 487 case PseudoHover: 488 case PseudoDrag: 489 case PseudoFocus: 490 case PseudoActive: 491 case PseudoChecked: 492 case PseudoEnabled: 493 case PseudoFullPageMedia: 494 case PseudoDefault: 495 case PseudoDisabled: 496 case PseudoOptional: 497 case PseudoRequired: 498 case PseudoReadOnly: 499 case PseudoReadWrite: 500 case PseudoScope: 501 case PseudoValid: 502 case PseudoInvalid: 503 case PseudoIndeterminate: 504 case PseudoTarget: 505 case PseudoLang: 506 case PseudoNot: 507 case PseudoRoot: 508 case PseudoScrollbarBack: 509 case PseudoScrollbarForward: 510 case PseudoWindowInactive: 511 case PseudoCornerPresent: 512 case PseudoDecrement: 513 case PseudoIncrement: 514 case PseudoHorizontal: 515 case PseudoVertical: 516 case PseudoStart: 517 case PseudoEnd: 518 case PseudoDoubleButton: 519 case PseudoSingleButton: 520 case PseudoNoButton: 521 case PseudoNotParsed: 522 case PseudoFullScreen: 523 case PseudoFullScreenDocument: 524 case PseudoFullScreenAncestor: 525 case PseudoInRange: 526 case PseudoOutOfRange: 527 case PseudoFutureCue: 528 case PseudoPastCue: 529 case PseudoHost: 530 case PseudoHostContext: 531 case PseudoUnresolved: 532 break; 533 case PseudoFirstPage: 534 case PseudoLeftPage: 535 case PseudoRightPage: 536 isPagePseudoClass = true; 537 break; 538 } 539 540 bool matchPagePseudoClass = (m_match == PagePseudoClass); 541 if (matchPagePseudoClass != isPagePseudoClass) 542 m_pseudoType = PseudoUnknown; 543 else if (m_match == PseudoClass && element) { 544 if (!compat) 545 m_pseudoType = PseudoUnknown; 546 else 547 m_match = PseudoElement; 548 } else if (m_match == PseudoElement && !element) 549 m_pseudoType = PseudoUnknown; 550 } 551 552 bool CSSSelector::operator==(const CSSSelector& other) const 553 { 554 const CSSSelector* sel1 = this; 555 const CSSSelector* sel2 = &other; 556 557 while (sel1 && sel2) { 558 if (sel1->attribute() != sel2->attribute() 559 || sel1->relation() != sel2->relation() 560 || sel1->m_match != sel2->m_match 561 || sel1->value() != sel2->value() 562 || sel1->pseudoType() != sel2->pseudoType() 563 || sel1->argument() != sel2->argument()) { 564 return false; 565 } 566 if (sel1->m_match == Tag) { 567 if (sel1->tagQName() != sel2->tagQName()) 568 return false; 569 } 570 sel1 = sel1->tagHistory(); 571 sel2 = sel2->tagHistory(); 572 } 573 574 if (sel1 || sel2) 575 return false; 576 577 return true; 578 } 579 580 String CSSSelector::selectorText(const String& rightSide) const 581 { 582 StringBuilder str; 583 584 if (m_match == CSSSelector::Tag && !m_tagIsForNamespaceRule) { 585 if (tagQName().prefix().isNull()) 586 str.append(tagQName().localName()); 587 else { 588 str.append(tagQName().prefix().string()); 589 str.append('|'); 590 str.append(tagQName().localName()); 591 } 592 } 593 594 const CSSSelector* cs = this; 595 while (true) { 596 if (cs->m_match == CSSSelector::Id) { 597 str.append('#'); 598 serializeIdentifier(cs->value(), str); 599 } else if (cs->m_match == CSSSelector::Class) { 600 str.append('.'); 601 serializeIdentifier(cs->value(), str); 602 } else if (cs->m_match == CSSSelector::PseudoClass || cs->m_match == CSSSelector::PagePseudoClass) { 603 str.append(':'); 604 str.append(cs->value()); 605 606 switch (cs->pseudoType()) { 607 case PseudoNot: 608 ASSERT(cs->selectorList()); 609 str.append(cs->selectorList()->first()->selectorText()); 610 str.append(')'); 611 break; 612 case PseudoLang: 613 case PseudoNthChild: 614 case PseudoNthLastChild: 615 case PseudoNthOfType: 616 case PseudoNthLastOfType: 617 str.append(cs->argument()); 618 str.append(')'); 619 break; 620 case PseudoAny: { 621 const CSSSelector* firstSubSelector = cs->selectorList()->first(); 622 for (const CSSSelector* subSelector = firstSubSelector; subSelector; subSelector = CSSSelectorList::next(*subSelector)) { 623 if (subSelector != firstSubSelector) 624 str.append(','); 625 str.append(subSelector->selectorText()); 626 } 627 str.append(')'); 628 break; 629 } 630 case PseudoHost: 631 case PseudoHostContext: { 632 if (cs->selectorList()) { 633 const CSSSelector* firstSubSelector = cs->selectorList()->first(); 634 for (const CSSSelector* subSelector = firstSubSelector; subSelector; subSelector = CSSSelectorList::next(*subSelector)) { 635 if (subSelector != firstSubSelector) 636 str.append(','); 637 str.append(subSelector->selectorText()); 638 } 639 str.append(')'); 640 } 641 break; 642 } 643 default: 644 break; 645 } 646 } else if (cs->m_match == CSSSelector::PseudoElement) { 647 str.appendLiteral("::"); 648 str.append(cs->value()); 649 650 if (cs->pseudoType() == PseudoContent) { 651 if (cs->relation() == CSSSelector::SubSelector && cs->tagHistory()) 652 return cs->tagHistory()->selectorText() + str.toString() + rightSide; 653 } 654 } else if (cs->isAttributeSelector()) { 655 str.append('['); 656 const AtomicString& prefix = cs->attribute().prefix(); 657 if (!prefix.isNull()) { 658 str.append(prefix); 659 str.append("|"); 660 } 661 str.append(cs->attribute().localName()); 662 switch (cs->m_match) { 663 case CSSSelector::Exact: 664 str.append('='); 665 break; 666 case CSSSelector::Set: 667 // set has no operator or value, just the attrName 668 str.append(']'); 669 break; 670 case CSSSelector::List: 671 str.appendLiteral("~="); 672 break; 673 case CSSSelector::Hyphen: 674 str.appendLiteral("|="); 675 break; 676 case CSSSelector::Begin: 677 str.appendLiteral("^="); 678 break; 679 case CSSSelector::End: 680 str.appendLiteral("$="); 681 break; 682 case CSSSelector::Contain: 683 str.appendLiteral("*="); 684 break; 685 default: 686 break; 687 } 688 if (cs->m_match != CSSSelector::Set) { 689 serializeString(cs->value(), str); 690 str.append(']'); 691 } 692 } 693 if (cs->relation() != CSSSelector::SubSelector || !cs->tagHistory()) 694 break; 695 cs = cs->tagHistory(); 696 } 697 698 if (const CSSSelector* tagHistory = cs->tagHistory()) { 699 switch (cs->relation()) { 700 case CSSSelector::Descendant: 701 return tagHistory->selectorText(" " + str.toString() + rightSide); 702 case CSSSelector::Child: 703 return tagHistory->selectorText(" > " + str.toString() + rightSide); 704 case CSSSelector::ShadowDeep: 705 return tagHistory->selectorText(" /deep/ " + str.toString() + rightSide); 706 case CSSSelector::DirectAdjacent: 707 return tagHistory->selectorText(" + " + str.toString() + rightSide); 708 case CSSSelector::IndirectAdjacent: 709 return tagHistory->selectorText(" ~ " + str.toString() + rightSide); 710 case CSSSelector::SubSelector: 711 ASSERT_NOT_REACHED(); 712 case CSSSelector::ShadowPseudo: 713 return tagHistory->selectorText(str.toString() + rightSide); 714 } 715 } 716 return str.toString() + rightSide; 717 } 718 719 void CSSSelector::setAttribute(const QualifiedName& value) 720 { 721 createRareData(); 722 m_data.m_rareData->m_attribute = value; 723 } 724 725 void CSSSelector::setArgument(const AtomicString& value) 726 { 727 createRareData(); 728 m_data.m_rareData->m_argument = value; 729 } 730 731 void CSSSelector::setSelectorList(PassOwnPtr<CSSSelectorList> selectorList) 732 { 733 createRareData(); 734 m_data.m_rareData->m_selectorList = selectorList; 735 } 736 737 static bool validateSubSelector(const CSSSelector* selector) 738 { 739 switch (selector->match()) { 740 case CSSSelector::Tag: 741 case CSSSelector::Id: 742 case CSSSelector::Class: 743 case CSSSelector::Exact: 744 case CSSSelector::Set: 745 case CSSSelector::List: 746 case CSSSelector::Hyphen: 747 case CSSSelector::Contain: 748 case CSSSelector::Begin: 749 case CSSSelector::End: 750 return true; 751 case CSSSelector::PseudoElement: 752 case CSSSelector::Unknown: 753 return false; 754 case CSSSelector::PagePseudoClass: 755 case CSSSelector::PseudoClass: 756 break; 757 } 758 759 switch (selector->pseudoType()) { 760 case CSSSelector::PseudoEmpty: 761 case CSSSelector::PseudoLink: 762 case CSSSelector::PseudoVisited: 763 case CSSSelector::PseudoTarget: 764 case CSSSelector::PseudoEnabled: 765 case CSSSelector::PseudoDisabled: 766 case CSSSelector::PseudoChecked: 767 case CSSSelector::PseudoIndeterminate: 768 case CSSSelector::PseudoNthChild: 769 case CSSSelector::PseudoNthLastChild: 770 case CSSSelector::PseudoNthOfType: 771 case CSSSelector::PseudoNthLastOfType: 772 case CSSSelector::PseudoFirstChild: 773 case CSSSelector::PseudoLastChild: 774 case CSSSelector::PseudoFirstOfType: 775 case CSSSelector::PseudoLastOfType: 776 case CSSSelector::PseudoOnlyOfType: 777 case CSSSelector::PseudoHost: 778 case CSSSelector::PseudoHostContext: 779 case CSSSelector::PseudoNot: 780 return true; 781 default: 782 return false; 783 } 784 } 785 786 bool CSSSelector::isCompound() const 787 { 788 if (!validateSubSelector(this)) 789 return false; 790 791 const CSSSelector* prevSubSelector = this; 792 const CSSSelector* subSelector = tagHistory(); 793 794 while (subSelector) { 795 if (prevSubSelector->relation() != CSSSelector::SubSelector) 796 return false; 797 if (!validateSubSelector(subSelector)) 798 return false; 799 800 prevSubSelector = subSelector; 801 subSelector = subSelector->tagHistory(); 802 } 803 804 return true; 805 } 806 807 bool CSSSelector::parseNth() const 808 { 809 if (!m_hasRareData) 810 return false; 811 if (m_parsedNth) 812 return true; 813 m_parsedNth = m_data.m_rareData->parseNth(); 814 return m_parsedNth; 815 } 816 817 bool CSSSelector::matchNth(int count) const 818 { 819 ASSERT(m_hasRareData); 820 return m_data.m_rareData->matchNth(count); 821 } 822 823 CSSSelector::RareData::RareData(const AtomicString& value) 824 : m_value(value) 825 , m_a(0) 826 , m_b(0) 827 , m_attribute(anyQName()) 828 , m_argument(nullAtom) 829 { 830 } 831 832 CSSSelector::RareData::~RareData() 833 { 834 } 835 836 // a helper function for parsing nth-arguments 837 bool CSSSelector::RareData::parseNth() 838 { 839 String argument = m_argument.lower(); 840 841 if (argument.isEmpty()) 842 return false; 843 844 m_a = 0; 845 m_b = 0; 846 if (argument == "odd") { 847 m_a = 2; 848 m_b = 1; 849 } else if (argument == "even") { 850 m_a = 2; 851 m_b = 0; 852 } else { 853 size_t n = argument.find('n'); 854 if (n != kNotFound) { 855 if (argument[0] == '-') { 856 if (n == 1) 857 m_a = -1; // -n == -1n 858 else 859 m_a = argument.substring(0, n).toInt(); 860 } else if (!n) 861 m_a = 1; // n == 1n 862 else 863 m_a = argument.substring(0, n).toInt(); 864 865 size_t p = argument.find('+', n); 866 if (p != kNotFound) 867 m_b = argument.substring(p + 1, argument.length() - p - 1).toInt(); 868 else { 869 p = argument.find('-', n); 870 if (p != kNotFound) 871 m_b = -argument.substring(p + 1, argument.length() - p - 1).toInt(); 872 } 873 } else 874 m_b = argument.toInt(); 875 } 876 return true; 877 } 878 879 // a helper function for checking nth-arguments 880 bool CSSSelector::RareData::matchNth(int count) 881 { 882 if (!m_a) 883 return count == m_b; 884 else if (m_a > 0) { 885 if (count < m_b) 886 return false; 887 return (count - m_b) % m_a == 0; 888 } else { 889 if (count > m_b) 890 return false; 891 return (m_b - count) % (-m_a) == 0; 892 } 893 } 894 895 } // namespace WebCore 896