summaryrefslogtreecommitdiffstats
path: root/webkit/glue
diff options
context:
space:
mode:
authorpfeldman@chromium.org <pfeldman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-03-25 10:56:58 +0000
committerpfeldman@chromium.org <pfeldman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-03-25 10:56:58 +0000
commit3bd1ac2ee1c845bcd8daecf9fe83928db0c672c9 (patch)
treed5c8932ca4a01d7509b63c24460653bd3d59cb15 /webkit/glue
parent146e53ce74beec8c5094f144ff8ad277d76b3a38 (diff)
downloadchromium_src-3bd1ac2ee1c845bcd8daecf9fe83928db0c672c9.zip
chromium_src-3bd1ac2ee1c845bcd8daecf9fe83928db0c672c9.tar.gz
chromium_src-3bd1ac2ee1c845bcd8daecf9fe83928db0c672c9.tar.bz2
DevTools: Element Panel search implementation.
Review URL: http://codereview.chromium.org/47009 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@12442 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit/glue')
-rw-r--r--webkit/glue/devtools/dom_agent.h8
-rw-r--r--webkit/glue/devtools/dom_agent_impl.cc110
-rw-r--r--webkit/glue/devtools/dom_agent_impl.h10
-rw-r--r--webkit/glue/devtools/js/devtools.js28
-rw-r--r--webkit/glue/devtools/js/devtools_host_stub.js7
-rw-r--r--webkit/glue/devtools/js/dom_agent.js63
6 files changed, 222 insertions, 4 deletions
diff --git a/webkit/glue/devtools/dom_agent.h b/webkit/glue/devtools/dom_agent.h
index 3bd20fa..33306de 100644
--- a/webkit/glue/devtools/dom_agent.h
+++ b/webkit/glue/devtools/dom_agent.h
@@ -27,6 +27,9 @@
/* Sets text node value in the node with given id. */ \
METHOD2(SetTextNodeValue, int /* id */, String /* text */) \
\
+ /* Perform search. */ \
+ METHOD2(PerformSearch, int /* call_id */, String /* query */) \
+ \
/* Tells dom agent that the client has lost all of the dom-related
information and is no longer interested in the notifications related to the
nodes issued earlier. */ \
@@ -38,6 +41,9 @@ DEFINE_RPC_CLASS(DomAgent, DOM_AGENT_STRUCT)
/* Response to GetChildNodes. */ \
METHOD1(DidGetChildNodes, int /* call_id */) \
\
+ /* Perform search. */ \
+ METHOD2(DidPerformSearch, int /* call_id */, Value /* results */) \
+ \
/* Notifies the delegate that element's attributes are updated. */ \
METHOD2(AttributesUpdated, int /* id */, Value /* attributes */) \
\
@@ -56,7 +62,7 @@ DEFINE_RPC_CLASS(DomAgent, DOM_AGENT_STRUCT)
Value /* node */) \
\
/* Notifies the delegate that child node has been deleted. */ \
- METHOD2(ChildNodeRemoved, int /* parent_id */, int /* id */) \
+ METHOD2(ChildNodeRemoved, int /* parent_id */, int /* id */)
DEFINE_RPC_CLASS(DomAgentDelegate, DOM_AGENT_DELEGATE_STRUCT)
diff --git a/webkit/glue/devtools/dom_agent_impl.cc b/webkit/glue/devtools/dom_agent_impl.cc
index 34e1be5..6d6471c 100644
--- a/webkit/glue/devtools/dom_agent_impl.cc
+++ b/webkit/glue/devtools/dom_agent_impl.cc
@@ -14,18 +14,32 @@
#include "markup.h"
#include "MutationEvent.h"
#include "Node.h"
+#include "NodeList.h"
#include "PlatformString.h"
#include "Text.h"
+#include "XPathResult.h"
+#include <wtf/HashSet.h>
#include <wtf/OwnPtr.h>
+#include <wtf/RefPtr.h>
#include <wtf/Vector.h>
#undef LOG
+#include "base/string_util.h"
#include "base/values.h"
#include "webkit/glue/devtools/dom_agent_impl.h"
#include "webkit/glue/glue_util.h"
using namespace WebCore;
+const char DomAgentImpl::kExactTagNames[] = "//*[name() == '%s')]";
+const char DomAgentImpl::kPartialTagNames[] = "//*[contains(name(), '%s')]";
+const char DomAgentImpl::kStartOfTagNames[] = "//*[starts-with(name(), '%s')]";
+const char DomAgentImpl::kPartialTagNamesAndAttributeValues[] =
+ "//*[contains(name(), '%s') or contains(@*, '%s')]";
+const char DomAgentImpl::kPartialAttributeValues[] = "//*[contains(@*, '%s')]";
+const char DomAgentImpl::kPlainText[] =
+ "//text()[contains(., '%s')] | //comment()[contains(., '%s')]";
+
// static
PassRefPtr<DomAgentImpl::EventListenerWrapper>
DomAgentImpl::EventListenerWrapper::Create(
@@ -305,6 +319,102 @@ void DomAgentImpl::SetTextNodeValue(int element_id, const String& value) {
}
}
+void DomAgentImpl::PerformSearch(int call_id, const String& query) {
+ String tag_name_query = query;
+ String attribute_name_query = query;
+
+ bool start_tag_found = tag_name_query.startsWith("<", true);
+ bool end_tag_found = tag_name_query.endsWith(">", true);
+
+ if (start_tag_found || end_tag_found) {
+ int tag_name_query_length = tag_name_query.length();
+ int start = start_tag_found ? 1 : 0;
+ int end = end_tag_found ? tag_name_query_length - 1 : tag_name_query_length;
+ tag_name_query = tag_name_query.substring(start, end - start);
+ }
+
+ Vector<String> xpath_queries;
+ xpath_queries.append(String::format(kPlainText, query.utf8().data(),
+ query.utf8().data()));
+ if (tag_name_query.length() && start_tag_found && end_tag_found) {
+ xpath_queries.append(String::format(kExactTagNames,
+ tag_name_query.utf8().data()));
+ } else if (tag_name_query.length() && start_tag_found) {
+ xpath_queries.append(String::format(kStartOfTagNames,
+ tag_name_query.utf8().data()));
+ } else if (tag_name_query.length() && end_tag_found) {
+ // FIXME(pfeldman): we should have a matchEndOfTagNames search function if
+ // endTagFound is true but not startTagFound.
+ // This requires ends-with() support in XPath, WebKit only supports
+ // starts-with() and contains().
+ xpath_queries.append(String::format(kPartialTagNames,
+ tag_name_query.utf8().data()));
+ } else if (query == "//*" || query == "*") {
+ // These queries will match every node. Matching everything isn't useful
+ // and can be slow for large pages, so limit the search functions list to
+ // plain text and attribute matching.
+ xpath_queries.append(String::format(kPartialAttributeValues,
+ query.utf8().data()));
+ } else {
+ // TODO(pfeldman): Add more patterns.
+ xpath_queries.append(String::format(kPartialTagNamesAndAttributeValues,
+ tag_name_query.utf8().data(),
+ query.utf8().data()));
+ }
+
+ ExceptionCode ec = 0;
+ Vector<Document*> search_documents;
+ Document* main_document = (*documents_.begin()).get();
+ search_documents.append(main_document);
+
+ // Find all frames, iframes and object elements to search their documents.
+ RefPtr<NodeList> node_list = main_document->querySelectorAll(
+ "iframe, frame, object", ec);
+ if (ec) {
+ ListValue list;
+ delegate_->DidPerformSearch(call_id, list);
+ return;
+ }
+ for (unsigned int i = 0; i < node_list->length(); ++i) {
+ Node* node = node_list->item(i);
+ if (node->isFrameOwnerElement()) {
+ const HTMLFrameOwnerElement* frame_owner =
+ static_cast<const HTMLFrameOwnerElement*>(node);
+ if (frame_owner->contentDocument()) {
+ search_documents.append(search_documents);
+ }
+ }
+ }
+
+ HashSet<int> node_ids;
+ for (Vector<Document*>::iterator it = search_documents.begin();
+ it != search_documents.end(); ++it) {
+ for (Vector<String>::iterator qit = xpath_queries.begin();
+ qit != xpath_queries.end(); ++qit) {
+ String query = *qit;
+ RefPtr<XPathResult> result = (*it)->evaluate(query, *it, NULL,
+ XPathResult::UNORDERED_NODE_ITERATOR_TYPE, 0, ec);
+ if (ec) {
+ ListValue list;
+ delegate_->DidPerformSearch(call_id, list);
+ return;
+ }
+ Node* node = result->iterateNext(ec);
+ while (node && !ec) {
+ node_ids.add(PushNodePathToClient(node));
+ node = result->iterateNext(ec);
+ }
+ }
+ }
+
+ ListValue list;
+ for (HashSet<int>::iterator it = node_ids.begin();
+ it != node_ids.end(); ++it) {
+ list.Append(Value::CreateIntegerValue(*it));
+ }
+ delegate_->DidPerformSearch(call_id, list);
+}
+
ListValue* DomAgentImpl::BuildValueForNode(Node* node, int depth) {
OwnPtr<ListValue> value(new ListValue());
int id = Bind(node);
diff --git a/webkit/glue/devtools/dom_agent_impl.h b/webkit/glue/devtools/dom_agent_impl.h
index 50b2458..3f02669 100644
--- a/webkit/glue/devtools/dom_agent_impl.h
+++ b/webkit/glue/devtools/dom_agent_impl.h
@@ -40,6 +40,7 @@ class DomAgentImpl : public DomAgent {
const WebCore::String& value);
void RemoveAttribute(int element_id, const WebCore::String& name);
void SetTextNodeValue(int element_id, const WebCore::String& value);
+ void PerformSearch(int call_id, const String& query);
void DiscardBindings();
// Initializes dom agent with the given document.
@@ -56,7 +57,14 @@ class DomAgentImpl : public DomAgent {
int PushNodePathToClient(WebCore::Node* node);
private:
- // Convenience EventListner wrapper for cleaner Ref management.
+ static const char kExactTagNames[];
+ static const char kPartialTagNames[];
+ static const char kStartOfTagNames[];
+ static const char kPartialTagNamesAndAttributeValues[];
+ static const char kPartialAttributeValues[];
+ static const char kPlainText[];
+
+// Convenience EventListner wrapper for cleaner Ref management.
class EventListenerWrapper : public WebCore::EventListener {
public:
static PassRefPtr<EventListenerWrapper> Create(
diff --git a/webkit/glue/devtools/js/devtools.js b/webkit/glue/devtools/js/devtools.js
index 33d23a9..3c3b41a 100644
--- a/webkit/glue/devtools/js/devtools.js
+++ b/webkit/glue/devtools/js/devtools.js
@@ -67,3 +67,31 @@ WebInspector.ElementsTreeElement.prototype.updateChildren = function() {
webkitUpdateChildren.call(self);
});
};
+
+WebInspector.ElementsPanel.prototype.performSearch = function(query) {
+ this.searchCanceled();
+ var self = this;
+ domAgent.performSearch(query, function(node) {
+ var treeElement = self.treeOutline.findTreeElement(node);
+ if (treeElement)
+ treeElement.highlighted = true;
+ });
+};
+
+
+WebInspector.ElementsPanel.prototype.searchCanceled = function() {
+ var self = this;
+ domAgent.searchCanceled(function(node) {
+ var treeElement = self.treeOutline.findTreeElement(node);
+ if (treeElement)
+ treeElement.highlighted = false;
+ });
+};
+
+
+WebInspector.ElementsPanel.prototype.jumpToNextSearchResult = function() {
+};
+
+
+WebInspector.ElementsPanel.prototype.jumpToPreviousSearchResult = function() {
+};
diff --git a/webkit/glue/devtools/js/devtools_host_stub.js b/webkit/glue/devtools/js/devtools_host_stub.js
index 2d51897..8eebe54 100644
--- a/webkit/glue/devtools/js/devtools_host_stub.js
+++ b/webkit/glue/devtools/js/devtools_host_stub.js
@@ -88,6 +88,13 @@ RemoteDomAgentStub.prototype.SetTextNodeValue = function() {
};
+RemoteDomAgentStub.prototype.PerformSearch = function(callId, query) {
+ setTimeout(function() {
+ RemoteDomAgent.DidPerformSearch(callId, [1]);
+ }, 0);
+};
+
+
/**
* @constructor
*/
diff --git a/webkit/glue/devtools/js/dom_agent.js b/webkit/glue/devtools/js/dom_agent.js
index 5e5693a..0eb642a 100644
--- a/webkit/glue/devtools/js/dom_agent.js
+++ b/webkit/glue/devtools/js/dom_agent.js
@@ -256,6 +256,8 @@ devtools.DomAgent = function() {
this.idToDomNode_ = { 0 : this.document };
RemoteDomAgent.DidGetChildNodes =
devtools.Callback.processCallback;
+ RemoteDomAgent.DidPerformSearch =
+ devtools.Callback.processCallback;
RemoteDomAgent.AttributesUpdated =
goog.bind(this.attributesUpdated, this);
RemoteDomAgent.SetDocumentElement =
@@ -268,6 +270,10 @@ devtools.DomAgent = function() {
goog.bind(this.childNodeInserted, this);
RemoteDomAgent.ChildNodeRemoved =
goog.bind(this.childNodeRemoved, this);
+ /**
+ * @type {Array.<number>} Node ids for search results.
+ */
+ this.searchResults_ = [];
};
@@ -351,9 +357,21 @@ devtools.DomAgent.prototype.setChildNodes = function(parentId, payloads) {
return;
}
parent.setChildrenPayload_(payloads);
- var children = parent.children;
+ this.bindNodes_(parent.children);
+};
+
+
+/**
+ * Binds nodes to ids recursively.
+ * @param {Array.<devtools.DomNode>} children Nodes to bind.
+ */
+devtools.DomAgent.prototype.bindNodes_ = function(children) {
for (var i = 0; i < children.length; ++i) {
- this.idToDomNode_[children[i].id] = children[i];
+ var child = children[i];
+ this.idToDomNode_[child.id] = child;
+ if (child.children) {
+ this.bindNodes_(child.children);
+ }
}
};
@@ -396,6 +414,47 @@ devtools.DomAgent.prototype.childNodeRemoved = function(
};
+/**
+ * @see DomAgentDelegate.
+ * {@inheritDoc}.
+ */
+devtools.DomAgent.prototype.performSearch = function(query, forEach) {
+ RemoteDomAgent.PerformSearch(
+ devtools.Callback.wrap(
+ goog.bind(this.performSearchCallback_, this, forEach)),
+ query);
+};
+
+
+/**
+ * Invokes callback for each node that needs to clear highlighting.
+ * @param {function(devtools.DomNode):undefined} forEach callback to call.
+ */
+devtools.DomAgent.prototype.searchCanceled = function(forEach) {
+ for (var i = 0; i < this.searchResults_.length; ++i) {
+ var nodeId = this.searchResults_[i];
+ var node = this.idToDomNode_[nodeId];
+ forEach(node);
+ }
+};
+
+
+/**
+ * Invokes callback for each node that needs to gain highlighting.
+ * @param {function(devtools.DomNode):undefined} forEach callback to call.
+ * @param {Array.<number>} nodeIds Ids to highlight.
+ */
+devtools.DomAgent.prototype.performSearchCallback_ = function(forEach,
+ nodeIds) {
+ this.searchResults_ = [];
+ for (var i = 0; i < nodeIds.length; ++i) {
+ var node = this.idToDomNode_[nodeIds[i]];
+ this.searchResults_.push(nodeIds[i]);
+ forEach(node);
+ }
+};
+
+
function firstChildSkippingWhitespace() {
return this.firstChild;
}