OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "config.h" | |
6 #include "core/css/parser/CSSSelectorParser.h" | |
7 | |
8 #include "core/css/CSSSelectorList.h" | |
9 #include "core/css/StyleSheetContents.h" | |
10 #include "platform/RuntimeEnabledFeatures.h" | |
11 | |
12 namespace blink { | |
13 | |
14 void CSSSelectorParser::parseSelector(CSSParserTokenRange tokenRange, const CSSP arserContext& context, CSSSelectorList& output) | |
15 { | |
16 CSSSelectorParser parser(tokenRange, context); | |
17 parser.m_tokenRange.consumeWhitespaceAndComments(); | |
18 CSSSelectorList result; | |
19 parser.consumeComplexSelectorList(result); | |
20 if (parser.m_tokenRange.atEnd()) | |
alancutter (OOO until 2018)
2014/12/15 13:52:59
Should this also check m_failedParsing as well?
Timothy Loh
2014/12/15 23:43:04
Not really, but I'll add an assert to make it clea
| |
21 output.adopt(result); | |
22 } | |
23 | |
24 CSSSelectorParser::CSSSelectorParser(CSSParserTokenRange tokenRange, const CSSPa rserContext& context) | |
25 : m_tokenRange(tokenRange) | |
26 , m_context(context) | |
27 , m_defaultNamespace(starAtom) | |
28 , m_styleSheet(nullptr) | |
29 , m_failedParsing(false) | |
30 { | |
31 } | |
32 | |
33 void CSSSelectorParser::consumeComplexSelectorList(CSSSelectorList& output) | |
34 { | |
35 Vector<OwnPtr<CSSParserSelector> > selectorList; | |
alancutter (OOO until 2018)
2014/12/15 13:52:59
No need for space between angle brackets.
Timothy Loh
2014/12/15 23:43:04
Done.
| |
36 OwnPtr<CSSParserSelector> selector = consumeComplexSelector(); | |
37 if (!selector) | |
38 return; | |
39 selectorList.append(selector.release()); | |
40 while (!m_tokenRange.atEnd() && m_tokenRange.peek().type() == CommaToken) { | |
41 m_tokenRange.consumeIncludingWhitespaceAndComments(); | |
42 selector = consumeComplexSelector(); | |
43 if (!selector) | |
44 return; | |
45 selectorList.append(selector.release()); | |
46 } | |
47 | |
48 if (!m_failedParsing) | |
49 output.adoptSelectorVector(selectorList); | |
50 } | |
51 | |
52 PassOwnPtr<CSSParserSelector> CSSSelectorParser::consumeComplexSelector() | |
53 { | |
54 OwnPtr<CSSParserSelector> selector = consumeCompoundSelector(); | |
55 if (!selector) | |
56 return nullptr; | |
57 while (CSSSelector::Relation combinator = consumeCombinator()) { | |
58 OwnPtr<CSSParserSelector> nextSelector = consumeCompoundSelector(); | |
59 if (!nextSelector) | |
60 return combinator == CSSSelector::Descendant ? selector.release() : nullptr; | |
61 CSSParserSelector* end = nextSelector.get(); | |
62 while (end->tagHistory()) | |
63 end = end->tagHistory(); | |
64 end->setRelation(combinator); | |
65 if (selector->isContentPseudoElement()) | |
66 end->setRelationIsAffectedByPseudoContent(); | |
67 end->setTagHistory(selector.release()); | |
68 | |
69 selector = nextSelector.release(); | |
70 } | |
71 | |
72 return selector.release(); | |
73 } | |
74 | |
75 PassOwnPtr<CSSParserSelector> CSSSelectorParser::consumeCompoundSelector() | |
76 { | |
77 OwnPtr<CSSParserSelector> selector; | |
78 | |
79 AtomicString namespacePrefix; | |
80 AtomicString elementName; | |
81 bool hasNamespace; | |
82 if (!consumeName(elementName, namespacePrefix, hasNamespace)) { | |
83 selector = consumeSimpleSelector(); | |
84 if (!selector) | |
85 return nullptr; | |
86 } | |
87 if (m_context.isHTMLDocument()) | |
88 elementName = elementName.lower(); | |
89 | |
90 while (OwnPtr<CSSParserSelector> nextSelector = consumeSimpleSelector()) { | |
91 if (selector) | |
92 selector = rewriteSpecifiers(selector.release(), nextSelector.releas e()); | |
93 else | |
94 selector = nextSelector.release(); | |
95 } | |
96 | |
97 if (!selector) { | |
98 if (hasNamespace) | |
99 return CSSParserSelector::create(determineNameInNamespace(namespaceP refix, elementName)); | |
100 return CSSParserSelector::create(QualifiedName(nullAtom, elementName, m_ defaultNamespace)); | |
101 } | |
102 if (elementName.isNull()) | |
103 rewriteSpecifiersWithNamespaceIfNeeded(selector.get()); | |
104 else | |
105 rewriteSpecifiersWithElementName(namespacePrefix, elementName, selector. get()); | |
106 return selector.release(); | |
107 } | |
108 | |
109 PassOwnPtr<CSSParserSelector> CSSSelectorParser::consumeSimpleSelector() | |
110 { | |
111 const CSSParserToken& token = m_tokenRange.peek(); | |
112 OwnPtr<CSSParserSelector> selector; | |
113 if (token.type() == HashToken) | |
114 selector = consumeId(); | |
115 else if (token.type() == DelimiterToken && token.delimiter() == '.') | |
116 selector = consumeClass(); | |
117 else if (token.type() == LeftBracketToken) | |
118 selector = consumeAttribute(); | |
119 else if (token.type() == ColonToken) | |
120 selector = consumePseudo(); | |
121 else | |
122 return nullptr; | |
123 if (!selector) | |
124 m_failedParsing = true; | |
125 return selector.release(); | |
126 } | |
127 | |
128 bool CSSSelectorParser::consumeName(AtomicString& name, AtomicString& namespaceP refix, bool& hasNamespace) | |
129 { | |
130 name = nullAtom; | |
131 namespacePrefix = nullAtom; | |
132 hasNamespace = false; | |
133 | |
134 const CSSParserToken& firstToken = m_tokenRange.peek(); | |
135 if (firstToken.type() == IdentToken) { | |
136 name = AtomicString(firstToken.value()); | |
137 m_tokenRange.consumeIncludingComments(); | |
138 } else if (firstToken.type() == DelimiterToken && firstToken.delimiter() == '*') { | |
139 name = starAtom; | |
140 m_tokenRange.consumeIncludingComments(); | |
141 } else if (firstToken.type() == DelimiterToken && firstToken.delimiter() == '|') { | |
142 // No namespace | |
143 } else { | |
144 return false; | |
145 } | |
146 | |
147 if (m_tokenRange.peek().type() != DelimiterToken || m_tokenRange.peek().deli miter() != '|') | |
148 return true; | |
149 m_tokenRange.consumeIncludingComments(); | |
150 | |
151 hasNamespace = true; | |
152 namespacePrefix = name; | |
153 const CSSParserToken& nameToken = m_tokenRange.consumeIncludingComments(); | |
154 if (nameToken.type() == IdentToken) { | |
155 name = AtomicString(nameToken.value()); | |
156 } else if (nameToken.type() == DelimiterToken && nameToken.delimiter() == '* ') { | |
157 name = starAtom; | |
158 } else { | |
159 name = nullAtom; | |
160 namespacePrefix = nullAtom; | |
161 return false; | |
162 } | |
163 | |
164 return true; | |
165 } | |
166 | |
167 PassOwnPtr<CSSParserSelector> CSSSelectorParser::consumeId() | |
168 { | |
169 ASSERT(m_tokenRange.peek().type() == HashToken); | |
170 if (m_tokenRange.peek().hashTokenType() != HashTokenId) | |
171 return nullptr; | |
172 OwnPtr<CSSParserSelector> selector = CSSParserSelector::create(); | |
173 selector->setMatch(CSSSelector::Id); | |
174 const String& value = m_tokenRange.consumeIncludingComments().value(); | |
175 if (isQuirksModeBehavior(m_context.mode())) | |
176 selector->setValue(AtomicString(value.lower())); | |
177 else | |
178 selector->setValue(AtomicString(value)); | |
179 return selector.release(); | |
180 } | |
181 | |
182 PassOwnPtr<CSSParserSelector> CSSSelectorParser::consumeClass() | |
183 { | |
184 ASSERT(m_tokenRange.peek().type() == DelimiterToken); | |
185 ASSERT(m_tokenRange.peek().delimiter() == '.'); | |
186 m_tokenRange.consumeIncludingComments(); | |
187 if (m_tokenRange.peek().type() != IdentToken) | |
188 return nullptr; | |
189 OwnPtr<CSSParserSelector> selector = CSSParserSelector::create(); | |
190 selector->setMatch(CSSSelector::Class); | |
191 const String& value = m_tokenRange.consumeIncludingComments().value(); | |
192 if (isQuirksModeBehavior(m_context.mode())) | |
193 selector->setValue(AtomicString(value.lower())); | |
194 else | |
195 selector->setValue(AtomicString(value)); | |
196 return selector.release(); | |
197 } | |
198 | |
199 PassOwnPtr<CSSParserSelector> CSSSelectorParser::consumeAttribute() | |
200 { | |
201 // FIXME: Implement attribute parsing | |
202 return nullptr; | |
203 } | |
204 | |
205 PassOwnPtr<CSSParserSelector> CSSSelectorParser::consumePseudo() | |
206 { | |
207 // FIXME: Implement pseudo-element and pseudo-class parsing | |
208 return nullptr; | |
209 } | |
210 | |
211 CSSSelector::Relation CSSSelectorParser::consumeCombinator() | |
212 { | |
213 CSSSelector::Relation fallbackResult = CSSSelector::SubSelector; | |
214 while (m_tokenRange.peek().type() == WhitespaceToken || m_tokenRange.peek(). type() == CommentToken) { | |
215 if (m_tokenRange.consume().type() == WhitespaceToken) | |
216 fallbackResult = CSSSelector::Descendant; | |
217 } | |
218 | |
219 if (m_tokenRange.peek().type() != DelimiterToken) | |
220 return fallbackResult; | |
221 | |
222 UChar delimiter = m_tokenRange.peek().delimiter(); | |
223 | |
224 if (delimiter == '+' || delimiter == '~' || delimiter == '>') { | |
225 m_tokenRange.consumeIncludingWhitespaceAndComments(); | |
226 if (delimiter == '+') | |
227 return CSSSelector::DirectAdjacent; | |
228 if (delimiter == '~') | |
229 return CSSSelector::IndirectAdjacent; | |
230 return CSSSelector::Child; | |
231 } | |
232 | |
233 // Match /deep/ | |
234 if (delimiter != '/') | |
235 return fallbackResult; | |
236 m_tokenRange.consumeIncludingComments(); | |
237 const CSSParserToken& ident = m_tokenRange.consumeIncludingComments(); | |
238 if (ident.type() != IdentToken || !equalIgnoringCase(ident.value(), "deep")) | |
239 m_failedParsing = true; | |
240 const CSSParserToken& slash = m_tokenRange.consumeIncludingWhitespaceAndComm ents(); | |
241 if (slash.type() != DelimiterToken || slash.delimiter() != '/') | |
242 m_failedParsing = true; | |
243 return CSSSelector::ShadowDeep; | |
244 } | |
245 | |
246 QualifiedName CSSSelectorParser::determineNameInNamespace(const AtomicString& pr efix, const AtomicString& localName) | |
247 { | |
248 if (!m_styleSheet) | |
249 return QualifiedName(prefix, localName, m_defaultNamespace); | |
250 return QualifiedName(prefix, localName, m_styleSheet->determineNamespace(pre fix)); | |
251 } | |
252 | |
253 void CSSSelectorParser::rewriteSpecifiersWithNamespaceIfNeeded(CSSParserSelector * specifiers) | |
254 { | |
255 if (m_defaultNamespace != starAtom || specifiers->crossesTreeScopes()) | |
256 rewriteSpecifiersWithElementName(nullAtom, starAtom, specifiers, /*tagIs ForNamespaceRule*/true); | |
257 } | |
258 | |
259 void CSSSelectorParser::rewriteSpecifiersWithElementName(const AtomicString& nam espacePrefix, const AtomicString& elementName, CSSParserSelector* specifiers, bo ol tagIsForNamespaceRule) | |
260 { | |
261 AtomicString determinedNamespace = namespacePrefix != nullAtom && m_styleShe et ? m_styleSheet->determineNamespace(namespacePrefix) : m_defaultNamespace; | |
262 QualifiedName tag(namespacePrefix, elementName, determinedNamespace); | |
263 | |
264 if (specifiers->crossesTreeScopes()) | |
265 return rewriteSpecifiersWithElementNameForCustomPseudoElement(tag, eleme ntName, specifiers, tagIsForNamespaceRule); | |
266 | |
267 if (specifiers->isContentPseudoElement()) | |
268 return rewriteSpecifiersWithElementNameForContentPseudoElement(tag, elem entName, specifiers, tagIsForNamespaceRule); | |
269 | |
270 // *:host never matches, so we can't discard the * otherwise we can't tell t he | |
271 // difference between *:host and just :host. | |
272 if (tag == anyQName() && !specifiers->hasHostPseudoSelector()) | |
273 return; | |
274 if (specifiers->pseudoType() != CSSSelector::PseudoCue) | |
275 specifiers->prependTagSelector(tag, tagIsForNamespaceRule); | |
276 } | |
277 | |
278 void CSSSelectorParser::rewriteSpecifiersWithElementNameForCustomPseudoElement(c onst QualifiedName& tag, const AtomicString& elementName, CSSParserSelector* spe cifiers, bool tagIsForNamespaceRule) | |
279 { | |
280 CSSParserSelector* lastShadowPseudo = specifiers; | |
281 CSSParserSelector* history = specifiers; | |
282 while (history->tagHistory()) { | |
283 history = history->tagHistory(); | |
284 if (history->crossesTreeScopes() || history->hasShadowPseudo()) | |
285 lastShadowPseudo = history; | |
286 } | |
287 | |
288 if (lastShadowPseudo->tagHistory()) { | |
289 if (tag != anyQName()) | |
290 lastShadowPseudo->tagHistory()->prependTagSelector(tag, tagIsForName spaceRule); | |
291 return; | |
292 } | |
293 | |
294 // For shadow-ID pseudo-elements to be correctly matched, the ShadowPseudo c ombinator has to be used. | |
295 // We therefore create a new Selector with that combinator here in any case, even if matching any (host) element in any namespace (i.e. '*'). | |
296 OwnPtr<CSSParserSelector> elementNameSelector = adoptPtr(new CSSParserSelect or(tag)); | |
297 lastShadowPseudo->setTagHistory(elementNameSelector.release()); | |
298 lastShadowPseudo->setRelation(CSSSelector::ShadowPseudo); | |
299 } | |
300 | |
301 void CSSSelectorParser::rewriteSpecifiersWithElementNameForContentPseudoElement( const QualifiedName& tag, const AtomicString& elementName, CSSParserSelector* sp ecifiers, bool tagIsForNamespaceRule) | |
302 { | |
303 CSSParserSelector* last = specifiers; | |
304 CSSParserSelector* history = specifiers; | |
305 while (history->tagHistory()) { | |
306 history = history->tagHistory(); | |
307 if (history->isContentPseudoElement() || history->relationIsAffectedByPs eudoContent()) | |
308 last = history; | |
309 } | |
310 | |
311 if (last->tagHistory()) { | |
312 if (tag != anyQName()) | |
313 last->tagHistory()->prependTagSelector(tag, tagIsForNamespaceRule); | |
314 return; | |
315 } | |
316 | |
317 // For shadow-ID pseudo-elements to be correctly matched, the ShadowPseudo c ombinator has to be used. | |
318 // We therefore create a new Selector with that combinator here in any case, even if matching any (host) element in any namespace (i.e. '*'). | |
319 OwnPtr<CSSParserSelector> elementNameSelector = adoptPtr(new CSSParserSelect or(tag)); | |
320 last->setTagHistory(elementNameSelector.release()); | |
321 } | |
322 | |
323 PassOwnPtr<CSSParserSelector> CSSSelectorParser::rewriteSpecifiers(PassOwnPtr<CS SParserSelector> specifiers, PassOwnPtr<CSSParserSelector> newSpecifier) | |
324 { | |
325 if (newSpecifier->crossesTreeScopes()) { | |
326 // Unknown pseudo element always goes at the top of selector chain. | |
327 newSpecifier->appendTagHistory(CSSSelector::ShadowPseudo, specifiers); | |
328 return newSpecifier; | |
329 } | |
330 if (newSpecifier->isContentPseudoElement()) { | |
331 newSpecifier->appendTagHistory(CSSSelector::SubSelector, specifiers); | |
332 return newSpecifier; | |
333 } | |
334 if (specifiers->crossesTreeScopes()) { | |
335 // Specifiers for unknown pseudo element go right behind it in the chain . | |
336 specifiers->insertTagHistory(CSSSelector::SubSelector, newSpecifier, CSS Selector::ShadowPseudo); | |
337 return specifiers; | |
338 } | |
339 if (specifiers->isContentPseudoElement()) { | |
340 specifiers->insertTagHistory(CSSSelector::SubSelector, newSpecifier, CSS Selector::SubSelector); | |
341 return specifiers; | |
342 } | |
343 specifiers->appendTagHistory(CSSSelector::SubSelector, newSpecifier); | |
344 return specifiers; | |
345 } | |
alancutter (OOO until 2018)
2014/12/15 13:52:59
RS that these are directly copied from the Bison p
| |
346 | |
347 } // namespace blink | |
OLD | NEW |