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