Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(188)

Side by Side Diff: Source/core/css/parser/CSSSelectorParser.cpp

Issue 790593004: CSS Parser: Implement selector parsing [1/3] (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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
OLDNEW
« Source/core/css/parser/CSSParserValues.h ('K') | « Source/core/css/parser/CSSSelectorParser.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698