| 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 | 
| 202 |  |