Web Inspector: Search: allow DOM searches to be case sensitive
[WebKit-https.git] / Source / WebCore / inspector / InspectorNodeFinder.cpp
1 /*
2  * Copyright (C) 2013 Adobe Systems Inc. All rights reserved.
3  * Copyright (C) 2009 Apple Inc. All rights reserved.
4  * Copyright (C) 2011 Google Inc. All rights reserved.
5  * Copyright (C) 2009 Joseph Pecoraro
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1.  Redistributions of source code must retain the above copyright
12  *     notice, this list of conditions and the following disclaimer.
13  * 2.  Redistributions in binary form must reproduce the above copyright
14  *     notice, this list of conditions and the following disclaimer in the
15  *     documentation and/or other materials provided with the distribution.
16  * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
17  *     its contributors may be used to endorse or promote products derived
18  *     from this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
21  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
24  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31
32 #include "config.h"
33 #include "InspectorNodeFinder.h"
34
35 #include "Attr.h"
36 #include "Document.h"
37 #include "Element.h"
38 #include "HTMLFrameOwnerElement.h"
39 #include "NodeList.h"
40 #include "NodeTraversal.h"
41 #include "XPathNSResolver.h"
42 #include "XPathResult.h"
43
44 namespace WebCore {
45
46 static String stripCharacters(const String& string, const char startCharacter, const char endCharacter, bool& startCharFound, bool& endCharFound)
47 {
48     startCharFound = string.startsWith(startCharacter);
49     endCharFound = string.endsWith(endCharacter);
50
51     unsigned start = startCharFound ? 1 : 0;
52     unsigned end = string.length() - (endCharFound ? 1 : 0);
53     return string.substring(start, end - start);
54 }
55
56 InspectorNodeFinder::InspectorNodeFinder(const String& query, bool caseSensitive)
57     : m_query(query)
58     , m_caseSensitive(caseSensitive)
59 {
60     m_tagNameQuery = stripCharacters(query, '<', '>', m_startTagFound, m_endTagFound);
61
62     bool startQuoteFound, endQuoteFound;
63     m_attributeQuery = stripCharacters(query, '"', '"', startQuoteFound, endQuoteFound);
64     m_exactAttributeMatch = startQuoteFound && endQuoteFound;
65 }
66
67 void InspectorNodeFinder::performSearch(Node* parentNode)
68 {
69     if (!parentNode)
70         return;
71
72     searchUsingXPath(*parentNode);
73     searchUsingCSSSelectors(*parentNode);
74
75     // Keep the DOM tree traversal last. This way iframe content will come after their parents.
76     searchUsingDOMTreeTraversal(*parentNode);
77 }
78
79 void InspectorNodeFinder::searchUsingDOMTreeTraversal(Node& parentNode)
80 {
81     // Manual plain text search.
82     for (auto* node = &parentNode; node; node = NodeTraversal::next(*node, &parentNode)) {
83         switch (node->nodeType()) {
84         case Node::TEXT_NODE:
85         case Node::COMMENT_NODE:
86         case Node::CDATA_SECTION_NODE:
87             if (checkContains(node->nodeValue(), m_query))
88                 m_results.add(node);
89             break;
90         case Node::ELEMENT_NODE:
91             if (matchesElement(downcast<Element>(*node)))
92                 m_results.add(node);
93             if (is<HTMLFrameOwnerElement>(downcast<Element>(*node)))
94                 performSearch(downcast<HTMLFrameOwnerElement>(*node).contentDocument());
95             break;
96         default:
97             break;
98         }
99     }
100 }
101
102 bool InspectorNodeFinder::checkEquals(const String& a, const String& b)
103 {
104     if (m_caseSensitive)
105         return a == b;
106     return equalIgnoringASCIICase(a, b);
107 }
108
109 bool InspectorNodeFinder::checkContains(const String& a, const String& b)
110 {
111     if (m_caseSensitive)
112         return a.contains(b);
113     return a.containsIgnoringASCIICase(b);
114 }
115
116 bool InspectorNodeFinder::checkStartsWith(const String& a, const String& b)
117 {
118     if (m_caseSensitive)
119         return a.startsWith(b);
120     return a.startsWithIgnoringASCIICase(b);
121 }
122
123 bool InspectorNodeFinder::checkEndsWith(const String& a, const String& b)
124 {
125     if (m_caseSensitive)
126         return a.endsWith(b);
127     return a.endsWithIgnoringASCIICase(b);
128 }
129
130 bool InspectorNodeFinder::matchesAttribute(const Attribute& attribute)
131 {
132     if (checkContains(attribute.localName().string(), m_query))
133         return true;
134
135     auto value = attribute.value().string();
136     return m_exactAttributeMatch ? checkEquals(value, m_attributeQuery) : checkContains(value, m_attributeQuery);
137 }
138
139 bool InspectorNodeFinder::matchesElement(const Element& element)
140 {
141     String nodeName = element.nodeName();
142     if ((!m_startTagFound && !m_endTagFound && checkContains(nodeName, m_tagNameQuery))
143         || (m_startTagFound && m_endTagFound && checkEquals(nodeName, m_tagNameQuery))
144         || (m_startTagFound && !m_endTagFound && checkStartsWith(nodeName, m_tagNameQuery))
145         || (!m_startTagFound && m_endTagFound && checkEndsWith(nodeName, m_tagNameQuery)))
146         return true;
147
148     if (!element.hasAttributes())
149         return false;
150
151     for (const Attribute& attribute : element.attributesIterator()) {
152         if (matchesAttribute(attribute))
153             return true;
154     }
155
156     return false;
157 }
158
159 void InspectorNodeFinder::searchUsingXPath(Node& parentNode)
160 {
161     auto evaluateResult = parentNode.document().evaluate(m_query, &parentNode, nullptr, XPathResult::ORDERED_NODE_SNAPSHOT_TYPE, nullptr);
162     if (evaluateResult.hasException())
163         return;
164     auto result = evaluateResult.releaseReturnValue();
165
166     auto snapshotLengthResult = result->snapshotLength();
167     if (snapshotLengthResult.hasException())
168         return;
169     unsigned size = snapshotLengthResult.releaseReturnValue();
170
171     for (unsigned i = 0; i < size; ++i) {
172         auto snapshotItemResult = result->snapshotItem(i);
173         if (snapshotItemResult.hasException())
174             return;
175         Node* node = snapshotItemResult.releaseReturnValue();
176
177         if (is<Attr>(*node))
178             node = downcast<Attr>(*node).ownerElement();
179
180         // XPath can get out of the context node that we pass as the starting point to evaluate, so we need to filter for just the nodes we care about.
181         if (parentNode.contains(node))
182             m_results.add(node);
183     }
184 }
185
186 void InspectorNodeFinder::searchUsingCSSSelectors(Node& parentNode)
187 {
188     if (!is<ContainerNode>(parentNode))
189         return;
190
191     auto queryResult = downcast<ContainerNode>(parentNode).querySelectorAll(m_query);
192     if (queryResult.hasException())
193         return;
194
195     auto nodeList = queryResult.releaseReturnValue();
196     unsigned size = nodeList->length();
197     for (unsigned i = 0; i < size; ++i)
198         m_results.add(nodeList->item(i));
199 }
200
201 } // namespace WebCore