1/*
2 * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org)
3 * 1999 Waldo Bastian (bastian@kde.org)
4 * Copyright (C) 2004, 2006, 2007, 2008, 2009, 2010, 2013, 2014 Apple Inc. All rights reserved.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public License
17 * along with this library; see the file COPYING.LIB. If not, write to
18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 */
21
22#pragma once
23
24#include "QualifiedName.h"
25#include "RenderStyleConstants.h"
26
27namespace WebCore {
28 class CSSSelectorList;
29
30 enum class SelectorSpecificityIncrement {
31 ClassA = 0x10000,
32 ClassB = 0x100,
33 ClassC = 1
34 };
35
36 // this class represents a selector for a StyleRule
37 class CSSSelector {
38 WTF_MAKE_FAST_ALLOCATED;
39 public:
40 CSSSelector();
41 CSSSelector(const CSSSelector&);
42 explicit CSSSelector(const QualifiedName&, bool tagIsForNamespaceRule = false);
43
44 ~CSSSelector();
45
46 /**
47 * Re-create selector text from selector's data
48 */
49 String selectorText(const String& = emptyString()) const;
50
51 // checks if the 2 selectors (including sub selectors) agree.
52 bool operator==(const CSSSelector&) const;
53
54 static const unsigned maxValueMask = 0xffffff;
55 static const unsigned idMask = 0xff0000;
56 static const unsigned classMask = 0xff00;
57 static const unsigned elementMask = 0xff;
58
59 unsigned staticSpecificity(bool& ok) const;
60 unsigned specificityForPage() const;
61 unsigned simpleSelectorSpecificity() const;
62 static unsigned addSpecificities(unsigned, unsigned);
63
64 /* how the attribute value has to match.... Default is Exact */
65 enum Match {
66 Unknown = 0,
67 Tag,
68 Id,
69 Class,
70 Exact,
71 Set,
72 List,
73 Hyphen,
74 PseudoClass,
75 PseudoElement,
76 Contain, // css3: E[foo*="bar"]
77 Begin, // css3: E[foo^="bar"]
78 End, // css3: E[foo$="bar"]
79 PagePseudoClass
80 };
81
82 enum RelationType {
83 Subselector,
84 DescendantSpace,
85 Child,
86 DirectAdjacent,
87 IndirectAdjacent,
88 ShadowDescendant
89 };
90
91 enum PseudoClassType {
92 PseudoClassUnknown = 0,
93 PseudoClassEmpty,
94 PseudoClassFirstChild,
95 PseudoClassFirstOfType,
96 PseudoClassLastChild,
97 PseudoClassLastOfType,
98 PseudoClassOnlyChild,
99 PseudoClassOnlyOfType,
100 PseudoClassNthChild,
101 PseudoClassNthOfType,
102 PseudoClassNthLastChild,
103 PseudoClassNthLastOfType,
104 PseudoClassLink,
105 PseudoClassVisited,
106 PseudoClassAny,
107 PseudoClassAnyLink,
108 PseudoClassAnyLinkDeprecated,
109 PseudoClassAutofill,
110 PseudoClassAutofillStrongPassword,
111 PseudoClassHover,
112 PseudoClassDrag,
113 PseudoClassFocus,
114 PseudoClassFocusWithin,
115 PseudoClassActive,
116 PseudoClassChecked,
117 PseudoClassEnabled,
118 PseudoClassFullPageMedia,
119 PseudoClassDefault,
120 PseudoClassDisabled,
121 PseudoClassMatches,
122 PseudoClassOptional,
123 PseudoClassPlaceholderShown,
124 PseudoClassRequired,
125 PseudoClassReadOnly,
126 PseudoClassReadWrite,
127 PseudoClassValid,
128 PseudoClassInvalid,
129 PseudoClassIndeterminate,
130 PseudoClassTarget,
131 PseudoClassLang,
132 PseudoClassNot,
133 PseudoClassRoot,
134 PseudoClassScope,
135 PseudoClassWindowInactive,
136 PseudoClassCornerPresent,
137 PseudoClassDecrement,
138 PseudoClassIncrement,
139 PseudoClassHorizontal,
140 PseudoClassVertical,
141 PseudoClassStart,
142 PseudoClassEnd,
143 PseudoClassDoubleButton,
144 PseudoClassSingleButton,
145 PseudoClassNoButton,
146#if ENABLE(FULLSCREEN_API)
147 PseudoClassFullScreen,
148 PseudoClassFullScreenDocument,
149 PseudoClassFullScreenAncestor,
150 PseudoClassAnimatingFullScreenTransition,
151 PseudoClassFullScreenControlsHidden,
152#endif
153 PseudoClassInRange,
154 PseudoClassOutOfRange,
155#if ENABLE(VIDEO_TRACK)
156 PseudoClassFuture,
157 PseudoClassPast,
158#endif
159#if ENABLE(CSS_SELECTORS_LEVEL4)
160 PseudoClassDir,
161 PseudoClassRole,
162#endif
163 PseudoClassHost,
164 PseudoClassDefined,
165#if ENABLE(ATTACHMENT_ELEMENT)
166 PseudoClassHasAttachment,
167#endif
168 };
169
170 enum PseudoElementType {
171 PseudoElementUnknown = 0,
172 PseudoElementAfter,
173 PseudoElementBefore,
174#if ENABLE(VIDEO_TRACK)
175 PseudoElementCue,
176#endif
177 PseudoElementFirstLetter,
178 PseudoElementFirstLine,
179 PseudoElementMarker,
180 PseudoElementResizer,
181 PseudoElementScrollbar,
182 PseudoElementScrollbarButton,
183 PseudoElementScrollbarCorner,
184 PseudoElementScrollbarThumb,
185 PseudoElementScrollbarTrack,
186 PseudoElementScrollbarTrackPiece,
187 PseudoElementSelection,
188 PseudoElementSlotted,
189 PseudoElementWebKitCustom,
190
191 // WebKitCustom that appeared in an old prefixed form
192 // and need special handling.
193 PseudoElementWebKitCustomLegacyPrefixed,
194 };
195
196 enum PagePseudoClassType {
197 PagePseudoClassFirst = 1,
198 PagePseudoClassLeft,
199 PagePseudoClassRight,
200 };
201
202 enum MarginBoxType {
203 TopLeftCornerMarginBox,
204 TopLeftMarginBox,
205 TopCenterMarginBox,
206 TopRightMarginBox,
207 TopRightCornerMarginBox,
208 BottomLeftCornerMarginBox,
209 BottomLeftMarginBox,
210 BottomCenterMarginBox,
211 BottomRightMarginBox,
212 BottomRightCornerMarginBox,
213 LeftTopMarginBox,
214 LeftMiddleMarginBox,
215 LeftBottomMarginBox,
216 RightTopMarginBox,
217 RightMiddleMarginBox,
218 RightBottomMarginBox,
219 };
220
221 enum AttributeMatchType {
222 CaseSensitive,
223 CaseInsensitive,
224 };
225
226 static PseudoElementType parsePseudoElementType(const String&);
227 static PseudoId pseudoId(PseudoElementType);
228
229 // Selectors are kept in an array by CSSSelectorList. The next component of the selector is
230 // the next item in the array.
231 const CSSSelector* tagHistory() const { return m_isLastInTagHistory ? 0 : const_cast<CSSSelector*>(this + 1); }
232
233 const QualifiedName& tagQName() const;
234 const AtomString& tagLowercaseLocalName() const;
235
236 const AtomString& value() const;
237 const AtomString& serializingValue() const;
238 const QualifiedName& attribute() const;
239 const AtomString& attributeCanonicalLocalName() const;
240 const AtomString& argument() const { return m_hasRareData ? m_data.m_rareData->m_argument : nullAtom(); }
241 bool attributeValueMatchingIsCaseInsensitive() const;
242 const Vector<AtomString>* langArgumentList() const { return m_hasRareData ? m_data.m_rareData->m_langArgumentList.get() : nullptr; }
243 const CSSSelectorList* selectorList() const { return m_hasRareData ? m_data.m_rareData->m_selectorList.get() : nullptr; }
244
245 void setValue(const AtomString&, bool matchLowerCase = false);
246
247 void setAttribute(const QualifiedName&, bool convertToLowercase, AttributeMatchType);
248 void setNth(int a, int b);
249 void setArgument(const AtomString&);
250 void setLangArgumentList(std::unique_ptr<Vector<AtomString>>);
251 void setSelectorList(std::unique_ptr<CSSSelectorList>);
252
253 bool matchNth(int count) const;
254 int nthA() const;
255 int nthB() const;
256
257 bool hasDescendantRelation() const { return relation() == DescendantSpace; }
258
259 bool hasDescendantOrChildRelation() const { return relation() == Child || hasDescendantRelation(); }
260
261 PseudoClassType pseudoClassType() const
262 {
263 ASSERT(match() == PseudoClass);
264 return static_cast<PseudoClassType>(m_pseudoType);
265 }
266 void setPseudoClassType(PseudoClassType pseudoType)
267 {
268 m_pseudoType = pseudoType;
269 ASSERT(m_pseudoType == pseudoType);
270 }
271
272 PseudoElementType pseudoElementType() const
273 {
274 ASSERT(match() == PseudoElement);
275 return static_cast<PseudoElementType>(m_pseudoType);
276 }
277 void setPseudoElementType(PseudoElementType pseudoElementType)
278 {
279 m_pseudoType = pseudoElementType;
280 ASSERT(m_pseudoType == pseudoElementType);
281 }
282
283 PagePseudoClassType pagePseudoClassType() const
284 {
285 ASSERT(match() == PagePseudoClass);
286 return static_cast<PagePseudoClassType>(m_pseudoType);
287 }
288 void setPagePseudoType(PagePseudoClassType pagePseudoType)
289 {
290 m_pseudoType = pagePseudoType;
291 ASSERT(m_pseudoType == pagePseudoType);
292 }
293
294 bool matchesPseudoElement() const;
295 bool isUnknownPseudoElement() const;
296 bool isCustomPseudoElement() const;
297 bool isWebKitCustomPseudoElement() const;
298 bool isSiblingSelector() const;
299 bool isAttributeSelector() const;
300
301 RelationType relation() const { return static_cast<RelationType>(m_relation); }
302 void setRelation(RelationType relation)
303 {
304 m_relation = relation;
305 ASSERT(m_relation == relation);
306 }
307
308 Match match() const { return static_cast<Match>(m_match); }
309 void setMatch(Match match)
310 {
311 m_match = match;
312 ASSERT(m_match == match);
313 }
314
315 bool isLastInSelectorList() const { return m_isLastInSelectorList; }
316 void setLastInSelectorList() { m_isLastInSelectorList = true; }
317 bool isLastInTagHistory() const { return m_isLastInTagHistory; }
318 void setNotLastInTagHistory() { m_isLastInTagHistory = false; }
319
320 bool isForPage() const { return m_isForPage; }
321 void setForPage() { m_isForPage = true; }
322
323 private:
324 unsigned m_relation : 4; // enum RelationType.
325 mutable unsigned m_match : 4; // enum Match.
326 mutable unsigned m_pseudoType : 8; // PseudoType.
327 unsigned m_isLastInSelectorList : 1;
328 unsigned m_isLastInTagHistory : 1;
329 unsigned m_hasRareData : 1;
330 unsigned m_hasNameWithCase : 1;
331 unsigned m_isForPage : 1;
332 unsigned m_tagIsForNamespaceRule : 1;
333 unsigned m_caseInsensitiveAttributeValueMatching : 1;
334#if !ASSERT_WITH_SECURITY_IMPLICATION_DISABLED
335 unsigned m_destructorHasBeenCalled : 1;
336#endif
337
338 unsigned simpleSelectorSpecificityForPage() const;
339
340 // Hide.
341 CSSSelector& operator=(const CSSSelector&);
342
343 struct RareData : public RefCounted<RareData> {
344 static Ref<RareData> create(AtomString&& value) { return adoptRef(*new RareData(WTFMove(value))); }
345 ~RareData();
346
347 bool matchNth(int count);
348
349 // For quirks mode, class and id are case-insensitive. In the case where uppercase
350 // letters are used in quirks mode, |m_matchingValue| holds the lowercase class/id
351 // and |m_serializingValue| holds the original string.
352 AtomString m_matchingValue;
353 AtomString m_serializingValue;
354
355 int m_a; // Used for :nth-*
356 int m_b; // Used for :nth-*
357 QualifiedName m_attribute; // used for attribute selector
358 AtomString m_attributeCanonicalLocalName;
359 AtomString m_argument; // Used for :contains and :nth-*
360 std::unique_ptr<Vector<AtomString>> m_langArgumentList; // Used for :lang arguments.
361 std::unique_ptr<CSSSelectorList> m_selectorList; // Used for :matches() and :not().
362
363 private:
364 RareData(AtomString&& value);
365 };
366 void createRareData();
367
368 struct NameWithCase : public RefCounted<NameWithCase> {
369 NameWithCase(const QualifiedName& originalName, const AtomString& lowercaseName)
370 : m_originalName(originalName)
371 , m_lowercaseLocalName(lowercaseName)
372 {
373 ASSERT(originalName.localName() != lowercaseName);
374 }
375
376 const QualifiedName m_originalName;
377 const AtomString m_lowercaseLocalName;
378 };
379
380 union DataUnion {
381 DataUnion() : m_value(0) { }
382 AtomStringImpl* m_value;
383 QualifiedName::QualifiedNameImpl* m_tagQName;
384 RareData* m_rareData;
385 NameWithCase* m_nameWithCase;
386 } m_data;
387 };
388
389inline const QualifiedName& CSSSelector::attribute() const
390{
391 ASSERT(isAttributeSelector());
392 ASSERT(m_hasRareData);
393 return m_data.m_rareData->m_attribute;
394}
395
396inline const AtomString& CSSSelector::attributeCanonicalLocalName() const
397{
398 ASSERT(isAttributeSelector());
399 ASSERT(m_hasRareData);
400 return m_data.m_rareData->m_attributeCanonicalLocalName;
401}
402
403inline bool CSSSelector::matchesPseudoElement() const
404{
405 return match() == PseudoElement;
406}
407
408inline bool CSSSelector::isUnknownPseudoElement() const
409{
410 return match() == PseudoElement && pseudoElementType() == PseudoElementUnknown;
411}
412
413inline bool CSSSelector::isCustomPseudoElement() const
414{
415 return match() == PseudoElement
416 && (pseudoElementType() == PseudoElementWebKitCustom
417 || pseudoElementType() == PseudoElementWebKitCustomLegacyPrefixed);
418}
419
420inline bool CSSSelector::isWebKitCustomPseudoElement() const
421{
422 return pseudoElementType() == PseudoElementWebKitCustom || pseudoElementType() == PseudoElementWebKitCustomLegacyPrefixed;
423}
424
425static inline bool pseudoClassIsRelativeToSiblings(CSSSelector::PseudoClassType type)
426{
427 return type == CSSSelector::PseudoClassEmpty
428 || type == CSSSelector::PseudoClassFirstChild
429 || type == CSSSelector::PseudoClassFirstOfType
430 || type == CSSSelector::PseudoClassLastChild
431 || type == CSSSelector::PseudoClassLastOfType
432 || type == CSSSelector::PseudoClassOnlyChild
433 || type == CSSSelector::PseudoClassOnlyOfType
434 || type == CSSSelector::PseudoClassNthChild
435 || type == CSSSelector::PseudoClassNthOfType
436 || type == CSSSelector::PseudoClassNthLastChild
437 || type == CSSSelector::PseudoClassNthLastOfType;
438}
439
440inline bool CSSSelector::isSiblingSelector() const
441{
442 return relation() == DirectAdjacent
443 || relation() == IndirectAdjacent
444 || (match() == CSSSelector::PseudoClass && pseudoClassIsRelativeToSiblings(pseudoClassType()));
445}
446
447inline bool CSSSelector::isAttributeSelector() const
448{
449 return match() == CSSSelector::Exact
450 || match() == CSSSelector::Set
451 || match() == CSSSelector::List
452 || match() == CSSSelector::Hyphen
453 || match() == CSSSelector::Contain
454 || match() == CSSSelector::Begin
455 || match() == CSSSelector::End;
456}
457
458inline void CSSSelector::setValue(const AtomString& value, bool matchLowerCase)
459{
460 ASSERT(match() != Tag);
461 AtomString matchingValue = matchLowerCase ? value.convertToASCIILowercase() : value;
462 if (!m_hasRareData && matchingValue != value)
463 createRareData();
464
465 // Need to do ref counting manually for the union.
466 if (!m_hasRareData) {
467 if (m_data.m_value)
468 m_data.m_value->deref();
469 m_data.m_value = value.impl();
470 m_data.m_value->ref();
471 return;
472 }
473
474 m_data.m_rareData->m_matchingValue = WTFMove(matchingValue);
475 m_data.m_rareData->m_serializingValue = value;
476}
477
478inline CSSSelector::CSSSelector()
479 : m_relation(DescendantSpace)
480 , m_match(Unknown)
481 , m_pseudoType(0)
482 , m_isLastInSelectorList(false)
483 , m_isLastInTagHistory(true)
484 , m_hasRareData(false)
485 , m_hasNameWithCase(false)
486 , m_isForPage(false)
487 , m_tagIsForNamespaceRule(false)
488 , m_caseInsensitiveAttributeValueMatching(false)
489#if !ASSERT_WITH_SECURITY_IMPLICATION_DISABLED
490 , m_destructorHasBeenCalled(false)
491#endif
492{
493}
494
495inline CSSSelector::CSSSelector(const CSSSelector& o)
496 : m_relation(o.m_relation)
497 , m_match(o.m_match)
498 , m_pseudoType(o.m_pseudoType)
499 , m_isLastInSelectorList(o.m_isLastInSelectorList)
500 , m_isLastInTagHistory(o.m_isLastInTagHistory)
501 , m_hasRareData(o.m_hasRareData)
502 , m_hasNameWithCase(o.m_hasNameWithCase)
503 , m_isForPage(o.m_isForPage)
504 , m_tagIsForNamespaceRule(o.m_tagIsForNamespaceRule)
505 , m_caseInsensitiveAttributeValueMatching(o.m_caseInsensitiveAttributeValueMatching)
506#if !ASSERT_WITH_SECURITY_IMPLICATION_DISABLED
507 , m_destructorHasBeenCalled(false)
508#endif
509{
510 if (o.m_hasRareData) {
511 m_data.m_rareData = o.m_data.m_rareData;
512 m_data.m_rareData->ref();
513 } else if (o.m_hasNameWithCase) {
514 m_data.m_nameWithCase = o.m_data.m_nameWithCase;
515 m_data.m_nameWithCase->ref();
516 } if (o.match() == Tag) {
517 m_data.m_tagQName = o.m_data.m_tagQName;
518 m_data.m_tagQName->ref();
519 } else if (o.m_data.m_value) {
520 m_data.m_value = o.m_data.m_value;
521 m_data.m_value->ref();
522 }
523}
524
525inline CSSSelector::~CSSSelector()
526{
527 ASSERT_WITH_SECURITY_IMPLICATION(!m_destructorHasBeenCalled);
528#if !ASSERT_WITH_SECURITY_IMPLICATION_DISABLED
529 m_destructorHasBeenCalled = true;
530#endif
531 if (m_hasRareData) {
532 m_data.m_rareData->deref();
533 m_data.m_rareData = nullptr;
534 m_hasRareData = false;
535 } else if (m_hasNameWithCase) {
536 m_data.m_nameWithCase->deref();
537 m_data.m_nameWithCase = nullptr;
538 m_hasNameWithCase = false;
539 } else if (match() == Tag) {
540 m_data.m_tagQName->deref();
541 m_data.m_tagQName = nullptr;
542 m_match = Unknown;
543 } else if (m_data.m_value) {
544 m_data.m_value->deref();
545 m_data.m_value = nullptr;
546 }
547}
548
549inline const QualifiedName& CSSSelector::tagQName() const
550{
551 ASSERT(match() == Tag);
552 if (m_hasNameWithCase)
553 return m_data.m_nameWithCase->m_originalName;
554 return *reinterpret_cast<const QualifiedName*>(&m_data.m_tagQName);
555}
556
557inline const AtomString& CSSSelector::tagLowercaseLocalName() const
558{
559 if (m_hasNameWithCase)
560 return m_data.m_nameWithCase->m_lowercaseLocalName;
561 return m_data.m_tagQName->m_localName;
562}
563
564inline const AtomString& CSSSelector::value() const
565{
566 ASSERT(match() != Tag);
567 if (m_hasRareData)
568 return m_data.m_rareData->m_matchingValue;
569
570 // AtomString is really just an AtomStringImpl* so the cast below is safe.
571 return *reinterpret_cast<const AtomString*>(&m_data.m_value);
572}
573
574inline const AtomString& CSSSelector::serializingValue() const
575{
576 ASSERT(match() != Tag);
577 if (m_hasRareData)
578 return m_data.m_rareData->m_serializingValue;
579
580 // AtomString is really just an AtomStringImpl* so the cast below is safe.
581 return *reinterpret_cast<const AtomString*>(&m_data.m_value);
582}
583
584inline bool CSSSelector::attributeValueMatchingIsCaseInsensitive() const
585{
586 return m_caseInsensitiveAttributeValueMatching;
587}
588
589} // namespace WebCore
590