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
44namespace WebCore {
45
46static 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
56InspectorNodeFinder::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
67void 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
79void 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
102bool InspectorNodeFinder::checkEquals(const String& a, const String& b)
103{
104 if (m_caseSensitive)
105 return a == b;
106 return equalIgnoringASCIICase(a, b);
107}
108
109bool 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
116bool 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
123bool 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
130bool 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
139bool 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
159void 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
186void 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
202