• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer.
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
18  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 
26 #include "config.h"
27 #include "core/dom/SelectorQuery.h"
28 
29 #include "bindings/v8/ExceptionState.h"
30 #include "core/css/CSSParser.h"
31 #include "core/css/SelectorChecker.h"
32 #include "core/css/SelectorCheckerFastPath.h"
33 #include "core/css/SiblingTraversalStrategies.h"
34 #include "core/dom/Document.h"
35 #include "core/dom/ElementTraversal.h"
36 #include "core/dom/Node.h"
37 #include "core/dom/StaticNodeList.h"
38 
39 namespace WebCore {
40 
41 class SimpleNodeList {
42 public:
~SimpleNodeList()43     virtual ~SimpleNodeList() { }
44     virtual bool isEmpty() const = 0;
45     virtual Node* next() = 0;
46 };
47 
48 class SingleNodeList : public SimpleNodeList {
49 public:
SingleNodeList(Node * rootNode)50     explicit SingleNodeList(Node* rootNode) : m_currentNode(rootNode) { }
51 
isEmpty() const52     bool isEmpty() const { return !m_currentNode; }
53 
next()54     Node* next()
55     {
56         Node* current = m_currentNode;
57         m_currentNode = 0;
58         return current;
59     }
60 
61 private:
62     Node* m_currentNode;
63 };
64 
65 class ClassRootNodeList : public SimpleNodeList {
66 public:
ClassRootNodeList(Node & rootNode,const AtomicString & className)67     ClassRootNodeList(Node& rootNode, const AtomicString& className)
68         : m_className(className)
69         , m_rootNode(rootNode)
70         , m_currentElement(nextInternal(ElementTraversal::firstWithin(m_rootNode))) { }
71 
isEmpty() const72     bool isEmpty() const { return !m_currentElement; }
73 
next()74     Node* next()
75     {
76         Node* current = m_currentElement;
77         ASSERT(current);
78         m_currentElement = nextInternal(ElementTraversal::nextSkippingChildren(*m_currentElement, &m_rootNode));
79         return current;
80     }
81 
82 private:
nextInternal(Element * element)83     Element* nextInternal(Element* element)
84     {
85         for (; element; element = ElementTraversal::next(*element, &m_rootNode)) {
86             if (element->hasClass() && element->classNames().contains(m_className))
87                 return element;
88         }
89         return 0;
90     }
91 
92     const AtomicString& m_className;
93     Node& m_rootNode;
94     Element* m_currentElement;
95 };
96 
97 class ClassElementList : public SimpleNodeList {
98 public:
ClassElementList(Node & rootNode,const AtomicString & className)99     ClassElementList(Node& rootNode, const AtomicString& className)
100         : m_className(className)
101         , m_rootNode(rootNode)
102         , m_currentElement(nextInternal(ElementTraversal::firstWithin(rootNode))) { }
103 
isEmpty() const104     bool isEmpty() const { return !m_currentElement; }
105 
next()106     Node* next()
107     {
108         Node* current = m_currentElement;
109         ASSERT(current);
110         m_currentElement = nextInternal(ElementTraversal::next(*m_currentElement, &m_rootNode));
111         return current;
112     }
113 
114 private:
nextInternal(Element * element)115     Element* nextInternal(Element* element)
116     {
117         for (; element; element = ElementTraversal::next(*element, &m_rootNode)) {
118             if (element->hasClass() && element->classNames().contains(m_className))
119                 return element;
120         }
121         return 0;
122     }
123 
124     const AtomicString& m_className;
125     Node& m_rootNode;
126     Element* m_currentElement;
127 };
128 
initialize(const CSSSelectorList & selectorList)129 void SelectorDataList::initialize(const CSSSelectorList& selectorList)
130 {
131     ASSERT(m_selectors.isEmpty());
132 
133     unsigned selectorCount = 0;
134     for (const CSSSelector* selector = selectorList.first(); selector; selector = CSSSelectorList::next(selector))
135         selectorCount++;
136 
137     m_selectors.reserveInitialCapacity(selectorCount);
138     for (const CSSSelector* selector = selectorList.first(); selector; selector = CSSSelectorList::next(selector))
139         m_selectors.uncheckedAppend(SelectorData(selector, SelectorCheckerFastPath::canUse(selector)));
140 }
141 
selectorMatches(const SelectorData & selectorData,Element & element,const Node & rootNode) const142 inline bool SelectorDataList::selectorMatches(const SelectorData& selectorData, Element& element, const Node& rootNode) const
143 {
144     if (selectorData.isFastCheckable && !element.isSVGElement()) {
145         SelectorCheckerFastPath selectorCheckerFastPath(selectorData.selector, element);
146         if (!selectorCheckerFastPath.matchesRightmostSelector(SelectorChecker::VisitedMatchDisabled))
147             return false;
148         return selectorCheckerFastPath.matches();
149     }
150 
151     SelectorChecker selectorChecker(element.document(), SelectorChecker::QueryingRules);
152     SelectorChecker::SelectorCheckingContext selectorCheckingContext(selectorData.selector, &element, SelectorChecker::VisitedMatchDisabled);
153     selectorCheckingContext.behaviorAtBoundary = SelectorChecker::StaysWithinTreeScope;
154     selectorCheckingContext.scope = !rootNode.isDocumentNode() && rootNode.isContainerNode() ? &toContainerNode(rootNode) : 0;
155     return selectorChecker.match(selectorCheckingContext, DOMSiblingTraversalStrategy()) == SelectorChecker::SelectorMatches;
156 }
157 
matches(Element & targetElement) const158 bool SelectorDataList::matches(Element& targetElement) const
159 {
160     unsigned selectorCount = m_selectors.size();
161     for (unsigned i = 0; i < selectorCount; ++i) {
162         if (selectorMatches(m_selectors[i], targetElement, targetElement))
163             return true;
164     }
165 
166     return false;
167 }
168 
queryAll(Node & rootNode) const169 PassRefPtr<NodeList> SelectorDataList::queryAll(Node& rootNode) const
170 {
171     Vector<RefPtr<Node> > result;
172     executeQueryAll(rootNode, result);
173     return StaticNodeList::adopt(result);
174 }
175 
queryFirst(Node & rootNode) const176 PassRefPtr<Element> SelectorDataList::queryFirst(Node& rootNode) const
177 {
178     return executeQueryFirst(rootNode);
179 }
180 
collectElementsByClassName(Node & rootNode,const AtomicString & className,Vector<RefPtr<Node>> & traversalRoots) const181 void SelectorDataList::collectElementsByClassName(Node& rootNode, const AtomicString& className, Vector<RefPtr<Node> >& traversalRoots) const
182 {
183     for (Element* element = ElementTraversal::firstWithin(rootNode); element; element = ElementTraversal::next(*element, &rootNode)) {
184         if (element->hasClass() && element->classNames().contains(className))
185             traversalRoots.append(element);
186     }
187 }
188 
collectElementsByTagName(Node & rootNode,const QualifiedName & tagName,Vector<RefPtr<Node>> & traversalRoots) const189 void SelectorDataList::collectElementsByTagName(Node& rootNode, const QualifiedName& tagName, Vector<RefPtr<Node> >& traversalRoots) const
190 {
191     for (Element* element = ElementTraversal::firstWithin(rootNode); element; element = ElementTraversal::next(*element, &rootNode)) {
192         if (SelectorChecker::tagMatches(*element, tagName))
193             traversalRoots.append(element);
194     }
195 }
196 
findElementByClassName(Node & rootNode,const AtomicString & className) const197 Element* SelectorDataList::findElementByClassName(Node& rootNode, const AtomicString& className) const
198 {
199     for (Element* element = ElementTraversal::firstWithin(rootNode); element; element = ElementTraversal::next(*element, &rootNode)) {
200         if (element->hasClass() && element->classNames().contains(className))
201             return element;
202     }
203     return 0;
204 }
205 
findElementByTagName(Node & rootNode,const QualifiedName & tagName) const206 Element* SelectorDataList::findElementByTagName(Node& rootNode, const QualifiedName& tagName) const
207 {
208     for (Element* element = ElementTraversal::firstWithin(rootNode); element; element = ElementTraversal::next(*element, &rootNode)) {
209         if (SelectorChecker::tagMatches(*element, tagName))
210             return element;
211     }
212     return 0;
213 }
214 
canUseFastQuery(const Node & rootNode) const215 inline bool SelectorDataList::canUseFastQuery(const Node& rootNode) const
216 {
217     return m_selectors.size() == 1 && rootNode.inDocument() && !rootNode.document().inQuirksMode();
218 }
219 
ancestorHasClassName(Node & rootNode,const AtomicString & className)220 inline bool ancestorHasClassName(Node& rootNode, const AtomicString& className)
221 {
222     if (!rootNode.isElementNode())
223         return false;
224 
225     for (Element* element = &toElement(rootNode); element; element = element->parentElement()) {
226         if (element->hasClass() && element->classNames().contains(className))
227             return true;
228     }
229     return false;
230 }
231 
232 
233 // If returns true, traversalRoots has the elements that may match the selector query.
234 //
235 // If returns false, traversalRoots has the rootNode parameter or descendants of rootNode representing
236 // the subtree for which we can limit the querySelector traversal.
237 //
238 // The travseralRoots may be empty, regardless of the returned bool value, if this method finds that the selectors won't
239 // match any element.
findTraverseRoots(Node & rootNode,bool & matchTraverseRoots) const240 PassOwnPtr<SimpleNodeList> SelectorDataList::findTraverseRoots(Node& rootNode, bool& matchTraverseRoots) const
241 {
242     // We need to return the matches in document order. To use id lookup while there is possiblity of multiple matches
243     // we would need to sort the results. For now, just traverse the document in that case.
244     ASSERT(m_selectors.size() == 1);
245     ASSERT(m_selectors[0].selector);
246 
247     bool isRightmostSelector = true;
248     bool startFromParent = false;
249 
250     for (const CSSSelector* selector = m_selectors[0].selector; selector; selector = selector->tagHistory()) {
251         if (selector->m_match == CSSSelector::Id && !rootNode.document().containsMultipleElementsWithId(selector->value())) {
252             Element* element = rootNode.treeScope().getElementById(selector->value());
253             Node* adjustedNode = &rootNode;
254             if (element && (isTreeScopeRoot(rootNode) || element->isDescendantOf(&rootNode)))
255                 adjustedNode = element;
256             else if (!element || isRightmostSelector)
257                 adjustedNode = 0;
258             if (isRightmostSelector) {
259                 matchTraverseRoots = true;
260                 return adoptPtr(new SingleNodeList(adjustedNode));
261             }
262             if (startFromParent && adjustedNode)
263                 adjustedNode = adjustedNode->parentNode();
264 
265             matchTraverseRoots = false;
266             return adoptPtr(new SingleNodeList(adjustedNode));
267         }
268 
269         // If we have both CSSSelector::Id and CSSSelector::Class at the same time, we should use Id
270         // to find traverse root.
271         if (!startFromParent && selector->m_match == CSSSelector::Class) {
272             if (isRightmostSelector) {
273                 matchTraverseRoots = true;
274                 return adoptPtr(new ClassElementList(rootNode, selector->value()));
275             }
276             matchTraverseRoots = false;
277             // Since there exists some ancestor element which has the class name, we need to see all children of rootNode.
278             if (ancestorHasClassName(rootNode, selector->value()))
279                 return adoptPtr(new SingleNodeList(&rootNode));
280 
281             return adoptPtr(new ClassRootNodeList(rootNode, selector->value()));
282         }
283 
284         if (selector->relation() == CSSSelector::SubSelector)
285             continue;
286         isRightmostSelector = false;
287         if (selector->relation() == CSSSelector::DirectAdjacent || selector->relation() == CSSSelector::IndirectAdjacent)
288             startFromParent = true;
289         else
290             startFromParent = false;
291     }
292 
293     matchTraverseRoots = false;
294     return adoptPtr(new SingleNodeList(&rootNode));
295 }
296 
executeSlowQueryAll(Node & rootNode,Vector<RefPtr<Node>> & matchedElements) const297 void SelectorDataList::executeSlowQueryAll(Node& rootNode, Vector<RefPtr<Node> >& matchedElements) const
298 {
299     for (Element* element = ElementTraversal::firstWithin(rootNode); element; element = ElementTraversal::next(*element, &rootNode)) {
300         for (unsigned i = 0; i < m_selectors.size(); ++i) {
301             if (selectorMatches(m_selectors[i], *element, rootNode)) {
302                 matchedElements.append(element);
303                 break;
304             }
305         }
306     }
307 }
308 
executeQueryAll(Node & rootNode,Vector<RefPtr<Node>> & matchedElements) const309 void SelectorDataList::executeQueryAll(Node& rootNode, Vector<RefPtr<Node> >& matchedElements) const
310 {
311     if (!canUseFastQuery(rootNode))
312         return executeSlowQueryAll(rootNode, matchedElements);
313 
314     ASSERT(m_selectors.size() == 1);
315     ASSERT(m_selectors[0].selector);
316 
317     const CSSSelector* firstSelector = m_selectors[0].selector;
318 
319     if (!firstSelector->tagHistory()) {
320         // Fast path for querySelectorAll('#id'), querySelectorAl('.foo'), and querySelectorAll('div').
321         switch (firstSelector->m_match) {
322         case CSSSelector::Id:
323             {
324                 if (rootNode.document().containsMultipleElementsWithId(firstSelector->value()))
325                     break;
326 
327                 // Just the same as getElementById.
328                 Element* element = rootNode.treeScope().getElementById(firstSelector->value());
329                 if (element && (isTreeScopeRoot(rootNode) || element->isDescendantOf(&rootNode)))
330                     matchedElements.append(element);
331                 return;
332             }
333         case CSSSelector::Class:
334             return collectElementsByClassName(rootNode, firstSelector->value(), matchedElements);
335         case CSSSelector::Tag:
336             return collectElementsByTagName(rootNode, firstSelector->tagQName(), matchedElements);
337         default:
338             break; // If we need another fast path, add here.
339         }
340     }
341 
342     bool matchTraverseRoots;
343     OwnPtr<SimpleNodeList> traverseRoots = findTraverseRoots(rootNode, matchTraverseRoots);
344     if (traverseRoots->isEmpty())
345         return;
346 
347     const SelectorData& selector = m_selectors[0];
348     if (matchTraverseRoots) {
349         while (!traverseRoots->isEmpty()) {
350             Node& node = *traverseRoots->next();
351             Element& element = toElement(node);
352             if (selectorMatches(selector, element, rootNode))
353                 matchedElements.append(&element);
354         }
355         return;
356     }
357 
358     while (!traverseRoots->isEmpty()) {
359         Node* traverseRoot = traverseRoots->next();
360         ASSERT(traverseRoot);
361         for (Element* element = ElementTraversal::firstWithin(*traverseRoot); element; element = ElementTraversal::next(*element, traverseRoot)) {
362             if (selectorMatches(selector, *element, rootNode))
363                 matchedElements.append(element);
364         }
365     }
366 }
367 
368 // If matchTraverseRoot is true, the returned Node is the single Element that may match the selector query.
369 //
370 // If matchTraverseRoot is false, the returned Node is the rootNode parameter or a descendant of rootNode representing
371 // the subtree for which we can limit the querySelector traversal.
372 //
373 // The returned Node may be 0, regardless of matchTraverseRoot, if this method finds that the selectors won't
374 // match any element.
findTraverseRoot(Node & rootNode,bool & matchTraverseRoot) const375 Node* SelectorDataList::findTraverseRoot(Node& rootNode, bool& matchTraverseRoot) const
376 {
377     // We need to return the matches in document order. To use id lookup while there is possiblity of multiple matches
378     // we would need to sort the results. For now, just traverse the document in that case.
379     ASSERT(m_selectors.size() == 1);
380     ASSERT(m_selectors[0].selector);
381 
382     bool matchSingleNode = true;
383     bool startFromParent = false;
384     for (const CSSSelector* selector = m_selectors[0].selector; selector; selector = selector->tagHistory()) {
385         if (selector->m_match == CSSSelector::Id && !rootNode.document().containsMultipleElementsWithId(selector->value())) {
386             Element* element = rootNode.treeScope().getElementById(selector->value());
387             Node* adjustedRootNode = &rootNode;
388             if (element && (isTreeScopeRoot(rootNode) || element->isDescendantOf(&rootNode)))
389                 adjustedRootNode = element;
390             else if (!element || matchSingleNode)
391                 adjustedRootNode = 0;
392             if (matchSingleNode) {
393                 matchTraverseRoot = true;
394                 return adjustedRootNode;
395             }
396             if (startFromParent && adjustedRootNode)
397                 adjustedRootNode = adjustedRootNode->parentNode();
398             matchTraverseRoot = false;
399             return adjustedRootNode;
400         }
401         if (selector->relation() == CSSSelector::SubSelector)
402             continue;
403         matchSingleNode = false;
404         if (selector->relation() == CSSSelector::DirectAdjacent || selector->relation() == CSSSelector::IndirectAdjacent)
405             startFromParent = true;
406         else
407             startFromParent = false;
408     }
409     matchTraverseRoot = false;
410     return &rootNode;
411 }
412 
executeSlowQueryFirst(Node & rootNode) const413 Element* SelectorDataList::executeSlowQueryFirst(Node& rootNode) const
414 {
415     for (Element* element = ElementTraversal::firstWithin(rootNode); element; element = ElementTraversal::next(*element, &rootNode)) {
416         for (unsigned i = 0; i < m_selectors.size(); ++i) {
417             if (selectorMatches(m_selectors[i], *element, rootNode))
418                 return element;
419         }
420     }
421     return 0;
422 }
423 
executeQueryFirst(Node & rootNode) const424 Element* SelectorDataList::executeQueryFirst(Node& rootNode) const
425 {
426     if (!canUseFastQuery(rootNode))
427         return executeSlowQueryFirst(rootNode);
428 
429 
430     const CSSSelector* selector = m_selectors[0].selector;
431     ASSERT(selector);
432 
433     if (!selector->tagHistory()) {
434         // Fast path for querySelector('#id'), querySelector('.foo'), and querySelector('div').
435         // Many web developers uses querySelector with these simple selectors.
436         switch (selector->m_match) {
437         case CSSSelector::Id:
438             {
439                 if (rootNode.document().containsMultipleElementsWithId(selector->value()))
440                     break;
441                 Element* element = rootNode.treeScope().getElementById(selector->value());
442                 return element && (isTreeScopeRoot(rootNode) || element->isDescendantOf(&rootNode)) ? element : 0;
443             }
444         case CSSSelector::Class:
445             return findElementByClassName(rootNode, selector->value());
446         case CSSSelector::Tag:
447             return findElementByTagName(rootNode, selector->tagQName());
448         default:
449             break; // If we need another fast path, add here.
450         }
451     }
452 
453     bool matchTraverseRoot;
454     Node* traverseRootNode = findTraverseRoot(rootNode, matchTraverseRoot);
455     if (!traverseRootNode)
456         return 0;
457     if (matchTraverseRoot) {
458         ASSERT(m_selectors.size() == 1);
459         ASSERT(traverseRootNode->isElementNode());
460         Element& element = toElement(*traverseRootNode);
461         return selectorMatches(m_selectors[0], element, rootNode) ? &element : 0;
462     }
463 
464     for (Element* element = ElementTraversal::firstWithin(*traverseRootNode); element; element = ElementTraversal::next(*element, traverseRootNode)) {
465         if (selectorMatches(m_selectors[0], *element, rootNode))
466             return element;
467     }
468     return 0;
469 }
470 
SelectorQuery(const CSSSelectorList & selectorList)471 SelectorQuery::SelectorQuery(const CSSSelectorList& selectorList)
472     : m_selectorList(selectorList)
473 {
474     m_selectors.initialize(m_selectorList);
475 }
476 
matches(Element & element) const477 bool SelectorQuery::matches(Element& element) const
478 {
479     return m_selectors.matches(element);
480 }
481 
queryAll(Node & rootNode) const482 PassRefPtr<NodeList> SelectorQuery::queryAll(Node& rootNode) const
483 {
484     return m_selectors.queryAll(rootNode);
485 }
486 
queryFirst(Node & rootNode) const487 PassRefPtr<Element> SelectorQuery::queryFirst(Node& rootNode) const
488 {
489     return m_selectors.queryFirst(rootNode);
490 }
491 
add(const AtomicString & selectors,const Document & document,ExceptionState & exceptionState)492 SelectorQuery* SelectorQueryCache::add(const AtomicString& selectors, const Document& document, ExceptionState& exceptionState)
493 {
494     HashMap<AtomicString, OwnPtr<SelectorQuery> >::iterator it = m_entries.find(selectors);
495     if (it != m_entries.end())
496         return it->value.get();
497 
498     CSSParser parser(document);
499     CSSSelectorList selectorList;
500     parser.parseSelector(selectors, selectorList);
501 
502     if (!selectorList.first()) {
503         exceptionState.throwDOMException(SyntaxError, "'" + selectors + "' is not a valid selector.");
504         return 0;
505     }
506 
507     // throw a NamespaceError if the selector includes any namespace prefixes.
508     if (selectorList.selectorsNeedNamespaceResolution()) {
509         exceptionState.throwDOMException(NamespaceError, "'" + selectors + "' contains namespaces, which are not supported.");
510         return 0;
511     }
512 
513     const unsigned maximumSelectorQueryCacheSize = 256;
514     if (m_entries.size() == maximumSelectorQueryCacheSize)
515         m_entries.remove(m_entries.begin());
516 
517     OwnPtr<SelectorQuery> selectorQuery = adoptPtr(new SelectorQuery(selectorList));
518     SelectorQuery* rawSelectorQuery = selectorQuery.get();
519     m_entries.add(selectors, selectorQuery.release());
520     return rawSelectorQuery;
521 }
522 
invalidate()523 void SelectorQueryCache::invalidate()
524 {
525     m_entries.clear();
526 }
527 
528 }
529