203 lines
7.1 KiB
C++
203 lines
7.1 KiB
C++
/*
|
|
* 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 "Element.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
|