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
39namespace WebCore {
40
41using namespace HTMLNames;
42
43struct SameSizeAsCSSSelector {
44 unsigned flags;
45 void* unionPointer;
46};
47
48static_assert(CSSSelector::RelationType::Subselector == 0, "Subselector must be 0 for consumeCombinator.");
49static_assert(sizeof(CSSSelector) == sizeof(SameSizeAsCSSSelector), "CSSSelector should remain small.");
50
51CSSSelector::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
78void 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
90static unsigned simpleSelectorSpecificityInternal(const CSSSelector& simpleSelector, bool isComputingMaximumSpecificity);
91
92static 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
101static 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
109static 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
152unsigned CSSSelector::simpleSelectorSpecificity() const
153{
154 return simpleSelectorSpecificityInternal(*this, false);
155}
156
157static unsigned staticSpecificityInternal(const CSSSelector& firstSimpleSelector, bool& ok);
158
159static 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
190static 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
201static unsigned staticSpecificityInternal(const CSSSelector& firstSimpleSelector, bool& ok)
202{
203 unsigned staticSpecificity = selectorSpecificity(firstSimpleSelector, false);
204 return CSSSelector::addSpecificities(staticSpecificity, functionalPseudoClassStaticSpecificity(firstSimpleSelector, ok));
205}
206
207unsigned CSSSelector::staticSpecificity(bool &ok) const
208{
209 ok = true;
210 return staticSpecificityInternal(*this, ok);
211}
212
213unsigned 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
238unsigned 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
268PseudoId 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
311CSSSelector::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
325bool 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
353static 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
376static 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
388String 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
754void 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
762void CSSSelector::setArgument(const AtomicString& value)
763{
764 createRareData();
765 m_data.m_rareData->m_argument = value;
766}
767
768void CSSSelector::setLangArgumentList(std::unique_ptr<Vector<AtomicString>> argumentList)
769{
770 createRareData();
771 m_data.m_rareData->m_langArgumentList = WTFMove(argumentList);
772}
773
774void CSSSelector::setSelectorList(std::unique_ptr<CSSSelectorList> selectorList)
775{
776 createRareData();
777 m_data.m_rareData->m_selectorList = WTFMove(selectorList);
778}
779
780void 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
787bool CSSSelector::matchNth(int count) const
788{
789 ASSERT(m_hasRareData);
790 return m_data.m_rareData->matchNth(count);
791}
792
793int CSSSelector::nthA() const
794{
795 ASSERT(m_hasRareData);
796 return m_data.m_rareData->m_a;
797}
798
799int CSSSelector::nthB() const
800{
801 ASSERT(m_hasRareData);
802 return m_data.m_rareData->m_b;
803}
804
805CSSSelector::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
815CSSSelector::RareData::~RareData() = default;
816
817// a helper function for checking nth-arguments
818bool 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