Chromium Code Reviews| 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 |