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