| /* |
| * Copyright (C) 2013 Adobe Systems Inc. All rights reserved. |
| * Copyright (C) 2009 Apple Inc. All rights reserved. |
| * Copyright (C) 2011 Google Inc. All rights reserved. |
| * Copyright (C) 2009 Joseph Pecoraro |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of Apple Inc. ("Apple") nor the names of |
| * its contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "InspectorNodeFinder.h" |
| |
| #include "Attr.h" |
| #include "Document.h" |
| #include "ElementInlines.h" |
| #include "Frame.h" |
| #include "HTMLFrameOwnerElement.h" |
| #include "NodeList.h" |
| #include "NodeTraversal.h" |
| #include "XPathNSResolver.h" |
| #include "XPathResult.h" |
| |
| namespace WebCore { |
| |
| static String stripCharacters(const String& string, const char startCharacter, const char endCharacter, bool& startCharFound, bool& endCharFound) |
| { |
| startCharFound = string.startsWith(startCharacter); |
| endCharFound = string.endsWith(endCharacter); |
| |
| unsigned start = startCharFound ? 1 : 0; |
| unsigned end = string.length() - (endCharFound ? 1 : 0); |
| return string.substring(start, end - start); |
| } |
| |
| InspectorNodeFinder::InspectorNodeFinder(const String& query, bool caseSensitive) |
| : m_query(query) |
| , m_caseSensitive(caseSensitive) |
| { |
| m_tagNameQuery = stripCharacters(query, '<', '>', m_startTagFound, m_endTagFound); |
| |
| bool startQuoteFound, endQuoteFound; |
| m_attributeQuery = stripCharacters(query, '"', '"', startQuoteFound, endQuoteFound); |
| m_exactAttributeMatch = startQuoteFound && endQuoteFound; |
| } |
| |
| void InspectorNodeFinder::performSearch(Node* parentNode) |
| { |
| if (!parentNode) |
| return; |
| |
| searchUsingXPath(*parentNode); |
| searchUsingCSSSelectors(*parentNode); |
| |
| // Keep the DOM tree traversal last. This way iframe content will come after their parents. |
| searchUsingDOMTreeTraversal(*parentNode); |
| } |
| |
| void InspectorNodeFinder::searchUsingDOMTreeTraversal(Node& parentNode) |
| { |
| // Manual plain text search. |
| for (auto* node = &parentNode; node; node = NodeTraversal::next(*node, &parentNode)) { |
| switch (node->nodeType()) { |
| case Node::TEXT_NODE: |
| case Node::COMMENT_NODE: |
| case Node::CDATA_SECTION_NODE: |
| if (checkContains(node->nodeValue(), m_query)) |
| m_results.add(node); |
| break; |
| case Node::ELEMENT_NODE: |
| if (matchesElement(downcast<Element>(*node))) |
| m_results.add(node); |
| if (is<HTMLFrameOwnerElement>(downcast<Element>(*node))) |
| performSearch(downcast<HTMLFrameOwnerElement>(*node).contentDocument()); |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| |
| bool InspectorNodeFinder::checkEquals(const String& a, const String& b) |
| { |
| if (m_caseSensitive) |
| return a == b; |
| return equalIgnoringASCIICase(a, b); |
| } |
| |
| bool InspectorNodeFinder::checkContains(const String& a, const String& b) |
| { |
| if (m_caseSensitive) |
| return a.contains(b); |
| return a.containsIgnoringASCIICase(b); |
| } |
| |
| bool InspectorNodeFinder::checkStartsWith(const String& a, const String& b) |
| { |
| if (m_caseSensitive) |
| return a.startsWith(b); |
| return a.startsWithIgnoringASCIICase(b); |
| } |
| |
| bool InspectorNodeFinder::checkEndsWith(const String& a, const String& b) |
| { |
| if (m_caseSensitive) |
| return a.endsWith(b); |
| return a.endsWithIgnoringASCIICase(b); |
| } |
| |
| bool InspectorNodeFinder::matchesAttribute(const Attribute& attribute) |
| { |
| if (checkContains(attribute.localName().string(), m_query)) |
| return true; |
| |
| auto value = attribute.value().string(); |
| return m_exactAttributeMatch ? checkEquals(value, m_attributeQuery) : checkContains(value, m_attributeQuery); |
| } |
| |
| bool InspectorNodeFinder::matchesElement(const Element& element) |
| { |
| String nodeName = element.nodeName(); |
| if ((!m_startTagFound && !m_endTagFound && checkContains(nodeName, m_tagNameQuery)) |
| || (m_startTagFound && m_endTagFound && checkEquals(nodeName, m_tagNameQuery)) |
| || (m_startTagFound && !m_endTagFound && checkStartsWith(nodeName, m_tagNameQuery)) |
| || (!m_startTagFound && m_endTagFound && checkEndsWith(nodeName, m_tagNameQuery))) |
| return true; |
| |
| if (!element.hasAttributes()) |
| return false; |
| |
| for (const Attribute& attribute : element.attributesIterator()) { |
| if (matchesAttribute(attribute)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void InspectorNodeFinder::searchUsingXPath(Node& parentNode) |
| { |
| auto evaluateResult = parentNode.document().evaluate(m_query, parentNode, nullptr, XPathResult::ORDERED_NODE_SNAPSHOT_TYPE, nullptr); |
| if (evaluateResult.hasException()) |
| return; |
| auto result = evaluateResult.releaseReturnValue(); |
| |
| auto snapshotLengthResult = result->snapshotLength(); |
| if (snapshotLengthResult.hasException()) |
| return; |
| unsigned size = snapshotLengthResult.releaseReturnValue(); |
| |
| for (unsigned i = 0; i < size; ++i) { |
| auto snapshotItemResult = result->snapshotItem(i); |
| if (snapshotItemResult.hasException()) |
| return; |
| Node* node = snapshotItemResult.releaseReturnValue(); |
| |
| if (is<Attr>(*node)) |
| node = downcast<Attr>(*node).ownerElement(); |
| |
| // 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. |
| if (parentNode.contains(node)) |
| m_results.add(node); |
| } |
| } |
| |
| void InspectorNodeFinder::searchUsingCSSSelectors(Node& parentNode) |
| { |
| if (!is<ContainerNode>(parentNode)) |
| return; |
| |
| auto queryResult = downcast<ContainerNode>(parentNode).querySelectorAll(m_query); |
| if (queryResult.hasException()) |
| return; |
| |
| auto nodeList = queryResult.releaseReturnValue(); |
| unsigned size = nodeList->length(); |
| for (unsigned i = 0; i < size; ++i) |
| m_results.add(nodeList->item(i)); |
| } |
| |
| } // namespace WebCore |