diff options
author | pfeldman@chromium.org <pfeldman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-03-25 10:56:58 +0000 |
---|---|---|
committer | pfeldman@chromium.org <pfeldman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-03-25 10:56:58 +0000 |
commit | 3bd1ac2ee1c845bcd8daecf9fe83928db0c672c9 (patch) | |
tree | d5c8932ca4a01d7509b63c24460653bd3d59cb15 /webkit/glue | |
parent | 146e53ce74beec8c5094f144ff8ad277d76b3a38 (diff) | |
download | chromium_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.h | 8 | ||||
-rw-r--r-- | webkit/glue/devtools/dom_agent_impl.cc | 110 | ||||
-rw-r--r-- | webkit/glue/devtools/dom_agent_impl.h | 10 | ||||
-rw-r--r-- | webkit/glue/devtools/js/devtools.js | 28 | ||||
-rw-r--r-- | webkit/glue/devtools/js/devtools_host_stub.js | 7 | ||||
-rw-r--r-- | webkit/glue/devtools/js/dom_agent.js | 63 |
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; } |