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 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 "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
createRareData()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
specificity() const65 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
specificityForOneSelector() const94 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
specificityForPage() const130 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
pseudoId(PseudoType type)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:
NameToPseudoCompare(const AtomicString & key)372 NameToPseudoCompare(const AtomicString& key) : m_key(key) { ASSERT(m_key.is8Bit()); }
373
operator ()(const NameToPseudoStruct & entry,const NameToPseudoStruct &)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
nameToPseudoType(const AtomicString & name,bool hasArguments)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
show(int indent) const409 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
show() const432 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
parsePseudoType(const AtomicString & name,bool hasArguments)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
extractPseudoType() const454 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
operator ==(const CSSSelector & other) const571 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
selectorText(const String & rightSide) const599 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
setAttribute(const QualifiedName & value,AttributeMatchType matchType)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
setArgument(const AtomicString & value)751 void CSSSelector::setArgument(const AtomicString& value)
752 {
753 createRareData();
754 m_data.m_rareData->m_argument = value;
755 }
756
setSelectorList(PassOwnPtr<CSSSelectorList> selectorList)757 void CSSSelector::setSelectorList(PassOwnPtr<CSSSelectorList> selectorList)
758 {
759 createRareData();
760 m_data.m_rareData->m_selectorList = selectorList;
761 }
762
validateSubSelector(const CSSSelector * selector)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
isCompound() const814 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
parseNth() const835 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
matchNth(int count) const845 bool CSSSelector::matchNth(int count) const
846 {
847 ASSERT(m_hasRareData);
848 return m_data.m_rareData->matchNth(count);
849 }
850
RareData(const AtomicString & value)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
~RareData()859 CSSSelector::RareData::~RareData()
860 {
861 }
862
863 // a helper function for parsing nth-arguments
parseNth()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
matchNth(int count)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