diff options
Diffstat (limited to 'webkit/glue/devtools')
-rw-r--r-- | webkit/glue/devtools/devtools_mock_rpc.h | 52 | ||||
-rw-r--r-- | webkit/glue/devtools/devtools_rpc.cc | 86 | ||||
-rw-r--r-- | webkit/glue/devtools/devtools_rpc.h | 324 | ||||
-rw-r--r-- | webkit/glue/devtools/dom_agent.h | 60 | ||||
-rw-r--r-- | webkit/glue/devtools/dom_agent_impl.cc | 406 | ||||
-rw-r--r-- | webkit/glue/devtools/dom_agent_impl.h | 119 | ||||
-rw-r--r-- | webkit/glue/devtools/dom_agent_unittest.cc | 421 | ||||
-rw-r--r-- | webkit/glue/devtools/net_agent.h | 40 | ||||
-rw-r--r-- | webkit/glue/devtools/net_agent_impl.cc | 204 | ||||
-rw-r--r-- | webkit/glue/devtools/net_agent_impl.h | 80 | ||||
-rw-r--r-- | webkit/glue/devtools/tools_agent.h | 32 |
11 files changed, 1824 insertions, 0 deletions
diff --git a/webkit/glue/devtools/devtools_mock_rpc.h b/webkit/glue/devtools/devtools_mock_rpc.h new file mode 100644 index 0000000..1a56b2f --- /dev/null +++ b/webkit/glue/devtools/devtools_mock_rpc.h @@ -0,0 +1,52 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_DEVTOOLS_DEVTOOLS_MOCK_RPC_H_ +#define WEBKIT_GLUE_DEVTOOLS_DEVTOOLS_MOCK_RPC_H_ + +#include <string> + +#include "base/string_util.h" +#include "PlatformString.h" + +#include "webkit/glue/devtools/devtools_rpc.h" +#include "webkit/glue/glue_util.h" + +using WebCore::String; + +// Universal mock delegate for DevToolsRpc. Typical usage of the mock is: +// mock->Method1(); // Set expectation. +// mock->Replay(); +// // Do Something here; +// mock->Verify(); // Verify. +class DevToolsMockRpc : public DevToolsRpc::Delegate { + public: + DevToolsMockRpc() {} + ~DevToolsMockRpc() {} + + virtual void SendRpcMessage(const std::string& msg) { + log_ = StringPrintf("%s\n%s", log_.c_str(), msg.c_str()); + } + + void Replay() { + ref_log_ = log_; + log_ = ""; + } + + void Verify() { + EXPECT_EQ(ref_log_, log_); + } + + void Reset() { + ref_log_ = ""; + log_ = ""; + } + + private: + std::string log_; + std::string ref_log_; + DISALLOW_COPY_AND_ASSIGN(DevToolsMockRpc); +}; + +#endif // WEBKIT_GLUE_DEVTOOLS_DEVTOOLS_MOCK_RPC_H_ diff --git a/webkit/glue/devtools/devtools_rpc.cc b/webkit/glue/devtools/devtools_rpc.cc new file mode 100644 index 0000000..29cbd78 --- /dev/null +++ b/webkit/glue/devtools/devtools_rpc.cc @@ -0,0 +1,86 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/glue/devtools/devtools_rpc.h" + +#include "PlatformString.h" + +// TODO(pfeldman): Remove these once JSON is available in +// WebCore namespace. +#include "base/json_reader.h" +#include "base/json_writer.h" +#include "base/values.h" +#include "webkit/glue/glue_util.h" + +DevToolsRpc::DevToolsRpc(Delegate* delegate) : delegate_(delegate) { +} + +DevToolsRpc::~DevToolsRpc() { +} + +void DevToolsRpc::SendValueMessage(const Value* value) { + std::string json; + JSONWriter::Write(value, false, &json); + delegate_->SendRpcMessage(json); +} + +// static +Value* DevToolsRpc::ParseMessage(const std::string& raw_msg) { + return JSONReader::Read(raw_msg, false); +} + +// static +void DevToolsRpc::GetListValue( + const ListValue& message, + int index, + bool* value) { + message.GetBoolean(index, value); +} + +// static +void DevToolsRpc::GetListValue( + const ListValue& message, + int index, + int* value) { + message.GetInteger(index, value); +} + +// static +void DevToolsRpc::GetListValue( + const ListValue& message, + int index, + String* value) { + std::string tmp; + message.GetString(index, &tmp); + *value = webkit_glue::StdStringToString(tmp); +} + +// static +void DevToolsRpc::GetListValue( + const ListValue& message, + int index, + Value** value) { + message.Get(index, value); +} + +// static +Value* DevToolsRpc::CreateValue(const String* value) { + return Value::CreateStringValue( + webkit_glue::StringToStdString(*value)); +} + +// static +Value* DevToolsRpc::CreateValue(int* value) { + return Value::CreateIntegerValue(*value); +} + +// static +Value* DevToolsRpc::CreateValue(bool* value) { + return Value::CreateBooleanValue(*value); +} + +// static +Value* DevToolsRpc::CreateValue(const Value* value) { + return value->DeepCopy(); +} diff --git a/webkit/glue/devtools/devtools_rpc.h b/webkit/glue/devtools/devtools_rpc.h new file mode 100644 index 0000000..3fbcc7d --- /dev/null +++ b/webkit/glue/devtools/devtools_rpc.h @@ -0,0 +1,324 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// DevTools RPC subsystem is a simple string serialization-based rpc +// implementation. The client is responsible for defining the Rpc-enabled +// interface in terms of its macros: +// +// #define MYAPI_STRUCT(METHOD0, METHOD1, METHOD2, METHOD3) +// METHOD0(Method1) +// METHOD3(Method2, int, String, Value) +// METHOD1(Method3, int) +// (snippet above should be multiline macro, add trailing backslashes) +// +// DEFINE_RPC_CLASS(MyApi, MYAPI_STRUCT) +// +// The snippet above will generate three classes: MyApi, MyApiStub and +// MyApiDispatch. +// +// 1. For each method defined in the marco MyApi will have a +// pure virtual function generated, so that MyApi would look like: +// +// class MyApi { +// private: +// MyApi() {} +// ~MyApi() {} +// virtual void Method1() = 0; +// virtual void Method2( +// int param1, +// const String& param2, +// const Value& param3) = 0; +// virtual void Method3(int param1) = 0; +// }; +// +// 2. MyApiStub will implement MyApi interface and would serialize all calls +// into the string-based calls of the underlying transport: +// +// DevToolsRpc::Delegate* transport; +// my_api = new MyApiStub(transport); +// my_api->Method1(); +// my_api->Method3(2); +// +// 3. MyApiDelegate is capable of dispatching the calls and convert them to the +// calls to the underlying MyApi methods: +// +// MyApi* real_object; +// MyApiDispatch dispatch; +// dispatch.Dispatch(real_object, raw_string_call_generated_by_stub); +// +// will make corresponding calls to the real object. + +#ifndef WEBKIT_GLUE_DEVTOOLS_DEVTOOLS_RPC_H_ +#define WEBKIT_GLUE_DEVTOOLS_DEVTOOLS_RPC_H_ + +#include <string> + +#include "PlatformString.h" +#include <wtf/OwnPtr.h> + +#include "base/basictypes.h" +#include "base/values.h" + +using WebCore::String; + +/////////////////////////////////////////////////////// +// RPC dispatch macro + +template<typename T> +struct RpcTypeTrait { + typedef T ApiType; + typedef T DispatchType; + static const DispatchType& Pass(const DispatchType& t) { + return t; + } +}; + +template<> +struct RpcTypeTrait<Value> { + typedef const Value& ApiType; + typedef Value* DispatchType; + static const Value& Pass(Value* t) { + return *t; + } +}; + +template<> +struct RpcTypeTrait<String> { + typedef const String& ApiType; + typedef String DispatchType; + static const DispatchType& Pass(const DispatchType& t) { + return t; + } +}; + +/////////////////////////////////////////////////////// +// RPC Api method declarations + +#define TOOLS_RPC_API_METHOD0(Method) \ + virtual void Method() = 0; + +#define TOOLS_RPC_API_METHOD1(Method, T1) \ + virtual void Method(RpcTypeTrait<T1>::ApiType t1) = 0; + +#define TOOLS_RPC_API_METHOD2(Method, T1, T2) \ + virtual void Method(RpcTypeTrait<T1>::ApiType t1, \ + RpcTypeTrait<T2>::ApiType t2) = 0; + +#define TOOLS_RPC_API_METHOD3(Method, T1, T2, T3) \ + virtual void Method(RpcTypeTrait<T1>::ApiType t1, \ + RpcTypeTrait<T2>::ApiType t2, \ + RpcTypeTrait<T3>::ApiType t3) = 0; + +#define TOOLS_RPC_ENUM_LITERAL0(Method) METHOD_##Method, +#define TOOLS_RPC_ENUM_LITERAL1(Method, T1) METHOD_##Method, +#define TOOLS_RPC_ENUM_LITERAL2(Method, T1, T2) METHOD_##Method, +#define TOOLS_RPC_ENUM_LITERAL3(Method, T1, T2, T3) METHOD_##Method, + +/////////////////////////////////////////////////////// +// RPC stub method implementations + +#define TOOLS_RPC_STUB_METHOD0(Method) \ + virtual void Method() { \ + InvokeAsync(METHOD_##Method); \ + } + +#define TOOLS_RPC_STUB_METHOD1(Method, T1) \ + virtual void Method(RpcTypeTrait<T1>::ApiType t1) { \ + InvokeAsync(METHOD_##Method, &t1); \ + } + +#define TOOLS_RPC_STUB_METHOD2(Method, T1, T2) \ + virtual void Method(RpcTypeTrait<T1>::ApiType t1, \ + RpcTypeTrait<T2>::ApiType t2) { \ + InvokeAsync(METHOD_##Method, &t1, &t2); \ + } + +#define TOOLS_RPC_STUB_METHOD3(Method, T1, T2, T3) \ + virtual void Method(RpcTypeTrait<T1>::ApiType t1, \ + RpcTypeTrait<T2>::ApiType t2, \ + RpcTypeTrait<T3>::ApiType t3) { \ + InvokeAsync(METHOD_##Method, &t1, &t2, &t3); \ + } + +/////////////////////////////////////////////////////// +// RPC dispatch method implementations + +#define TOOLS_RPC_DISPATCH0(Method) \ +case CLASS::METHOD_##Method: { \ + delegate->Method(); \ + return true; \ +} + +#define TOOLS_RPC_DISPATCH1(Method, T1) \ +case CLASS::METHOD_##Method: { \ + RpcTypeTrait<T1>::DispatchType t1; \ + DevToolsRpc::GetListValue(*message.get(), 1, &t1); \ + delegate->Method( \ + RpcTypeTrait<T1>::Pass(t1)); \ +} + +#define TOOLS_RPC_DISPATCH2(Method, T1, T2) \ +case CLASS::METHOD_##Method: { \ + RpcTypeTrait<T1>::DispatchType t1; \ + RpcTypeTrait<T2>::DispatchType t2; \ + DevToolsRpc::GetListValue(*message.get(), 1, &t1); \ + DevToolsRpc::GetListValue(*message.get(), 2, &t2); \ + delegate->Method( \ + RpcTypeTrait<T1>::Pass(t1), \ + RpcTypeTrait<T2>::Pass(t2) \ + ); \ + return true; \ +} + +#define TOOLS_RPC_DISPATCH3(Method, T1, T2, T3) \ +case CLASS::METHOD_##Method: { \ + RpcTypeTrait<T1>::DispatchType t1; \ + RpcTypeTrait<T2>::DispatchType t2; \ + RpcTypeTrait<T3>::DispatchType t3; \ + DevToolsRpc::GetListValue(*message.get(), 1, &t1); \ + DevToolsRpc::GetListValue(*message.get(), 2, &t2); \ + DevToolsRpc::GetListValue(*message.get(), 3, &t3); \ + delegate->Method( \ + RpcTypeTrait<T1>::Pass(t1), \ + RpcTypeTrait<T2>::Pass(t2), \ + RpcTypeTrait<T3>::Pass(t3) \ + ); \ + return true; \ +} + +#define TOOLS_END_RPC_DISPATCH() \ +} + +// This macro defines three classes: Class with the Api, ClassStub that is +// serializing method calls and ClassDispatch that is capable of dispatching +// the serialized message into its delegate. +#define DEFINE_RPC_CLASS(Class, STRUCT) \ +class Class {\ + public: \ + Class() {} \ + ~Class() {} \ + \ + enum MethodNames { \ + STRUCT(TOOLS_RPC_ENUM_LITERAL0, TOOLS_RPC_ENUM_LITERAL1, \ + TOOLS_RPC_ENUM_LITERAL2, TOOLS_RPC_ENUM_LITERAL3) \ + }; \ + \ + STRUCT( \ + TOOLS_RPC_API_METHOD0, \ + TOOLS_RPC_API_METHOD1, \ + TOOLS_RPC_API_METHOD2, \ + TOOLS_RPC_API_METHOD3) \ + private: \ + DISALLOW_COPY_AND_ASSIGN(Class); \ +}; \ +\ +class Class##Stub : public Class, public DevToolsRpc { \ + public: \ + explicit Class##Stub(Delegate* delegate) : DevToolsRpc(delegate) {} \ + virtual ~Class##Stub() {} \ + STRUCT( \ + TOOLS_RPC_STUB_METHOD0, \ + TOOLS_RPC_STUB_METHOD1, \ + TOOLS_RPC_STUB_METHOD2, \ + TOOLS_RPC_STUB_METHOD3) \ + private: \ + DISALLOW_COPY_AND_ASSIGN(Class##Stub); \ +}; \ +\ +class Class##Dispatch { \ + public: \ + Class##Dispatch() {} \ + virtual ~Class##Dispatch() {} \ + bool Dispatch(Class* delegate, const std::string& raw_msg) { \ + OwnPtr<ListValue> message( \ + static_cast<ListValue*>(DevToolsRpc::ParseMessage(raw_msg))); \ + int method; \ + message->GetInteger(0, &method); \ + typedef Class CLASS; \ + switch (method) { \ + STRUCT( \ + TOOLS_RPC_DISPATCH0, \ + TOOLS_RPC_DISPATCH1, \ + TOOLS_RPC_DISPATCH2, \ + TOOLS_RPC_DISPATCH3) \ + default: return false; \ + } \ + } \ + private: \ + DISALLOW_COPY_AND_ASSIGN(Class##Dispatch); \ +}; + +/////////////////////////////////////////////////////// +// RPC base class +class DevToolsRpc { + public: + class Delegate { + public: + Delegate() {} + virtual ~Delegate() {} + virtual void SendRpcMessage(const std::string& msg) = 0; + private: + DISALLOW_COPY_AND_ASSIGN(Delegate); + }; + + explicit DevToolsRpc(Delegate* delegate); + virtual ~DevToolsRpc(); + + void InvokeAsync(int method) { + ListValue message; + message.Append(CreateValue(&method)); + SendValueMessage(&message); + } + template<class T1> + void InvokeAsync(int method, T1 t1) { + ListValue message; + message.Append(CreateValue(&method)); + message.Append(CreateValue(t1)); + SendValueMessage(&message); + } + template<class T1, class T2> + void InvokeAsync(int method, T1 t1, T2 t2) { + ListValue message; + message.Append(CreateValue(&method)); + message.Append(CreateValue(t1)); + message.Append(CreateValue(t2)); + SendValueMessage(&message); + } + template<class T1, class T2, class T3> + void InvokeAsync(int method, T1 t1, T2 t2, T3 t3) { + ListValue message; + message.Append(CreateValue(&method)); + message.Append(CreateValue(t1)); + message.Append(CreateValue(t2)); + message.Append(CreateValue(t3)); + SendValueMessage(&message); + } + + static Value* ParseMessage(const std::string& raw_msg); + static void GetListValue(const ListValue& message, int index, bool* value); + static void GetListValue(const ListValue& message, int index, int* value); + static void GetListValue( + const ListValue& message, + int index, + String* value); + static void GetListValue(const ListValue& message, int index, Value** value); + + protected: + // Primarily for unit testing. + void set_delegate(Delegate* delegate) { this->delegate_ = delegate; } + + private: + // Value adapters for supported Rpc types. + static Value* CreateValue(const String* value); + static Value* CreateValue(int* value); + static Value* CreateValue(bool* value); + static Value* CreateValue(const Value* value); + + void SendValueMessage(const Value* value); + + Delegate* delegate_; + DISALLOW_COPY_AND_ASSIGN(DevToolsRpc); +}; + +#endif // WEBKIT_GLUE_DEVTOOLS_DEVTOOLS_RPC_H_ diff --git a/webkit/glue/devtools/dom_agent.h b/webkit/glue/devtools/dom_agent.h new file mode 100644 index 0000000..a2d8818 --- /dev/null +++ b/webkit/glue/devtools/dom_agent.h @@ -0,0 +1,60 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_DEVTOOLS_DOM_AGENT_H_ +#define WEBKIT_GLUE_DEVTOOLS_DOM_AGENT_H_ + +#include "webkit/glue/devtools/devtools_rpc.h" + +// DomAgent is a utility object that covers DOM-related functionality of the +// WebDevToolsAgent. It is capable of sending DOM tree to the client as well +// as providing DOM notifications for the nodes known to the client. +// DomAgent's environment is represented with the DomAgentDelegate interface. +#define DOM_AGENT_STRUCT(METHOD0, METHOD1, METHOD2, METHOD3) \ + /* Requests that the document root element is sent to the delegate. */ \ + METHOD0(GetDocumentElement) \ + \ + /* Requests that the element's children are sent to the client. */ \ + METHOD1(GetChildNodes, int /* id */) \ + \ + /* Sets attribute value in the element with given id. */ \ + METHOD3(SetAttribute, int /* id */, String /* name */, String /* value */) \ + \ + /* Removes attribute from the element with given id. */ \ + METHOD2(RemoveAttribute, int /* id */, String /* name */) \ + \ + /* Sets text node value in the node with given id. */ \ + METHOD2(SetTextNodeValue, int /* id */, String /* text */) \ + \ + /* 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. */ \ + METHOD0(DiscardBindings) + +DEFINE_RPC_CLASS(DomAgent, DOM_AGENT_STRUCT) + +#define DOM_AGENT_DELEGATE_STRUCT(METHOD0, METHOD1, METHOD2, METHOD3) \ + /* Notifies the delegate that document element is available. */ \ + METHOD1(DocumentElementUpdated, Value /* node */) \ + \ + /* Notifies the delegate that element's attributes are updated. */ \ + METHOD2(AttributesUpdated, int /* id */, Value /* attributes */) \ + \ + /* Notifies the delegate that element's child nodes have been updated. */ \ + METHOD2(ChildNodesUpdated, int /* id */, Value /* node */) \ + \ + /* Notifies the delegate that element's 'has children' state has been + updated */ \ + METHOD2(HasChildrenUpdated, int /* id */, bool /* new_value */) \ + \ + /* Notifies the delegate that child node has been inserted. */ \ + METHOD3(ChildNodeInserted, int /* parent_id */ , int /* prev_id */, \ + Value /* node */) \ + \ + /* Notifies the delegate that child node has been deleted. */ \ + METHOD2(ChildNodeRemoved, int /* parent_id */, int /* id */) + +DEFINE_RPC_CLASS(DomAgentDelegate, DOM_AGENT_DELEGATE_STRUCT) + +#endif // WEBKIT_GLUE_DEVTOOLS_DOM_AGENT_H_ diff --git a/webkit/glue/devtools/dom_agent_impl.cc b/webkit/glue/devtools/dom_agent_impl.cc new file mode 100644 index 0000000..5f7a66a --- /dev/null +++ b/webkit/glue/devtools/dom_agent_impl.cc @@ -0,0 +1,406 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "config.h" + +#include "webkit/glue/devtools/dom_agent_impl.h" + +#include "AtomicString.h" +#include "Document.h" +#include "Event.h" +#include "EventListener.h" +#include "EventNames.h" +#include "EventTarget.h" +#include "HTMLFrameOwnerElement.h" +#include "markup.h" +#include "MutationEvent.h" +#include "Node.h" +#include "Text.h" +#include <wtf/OwnPtr.h> +#include <wtf/Vector.h> +#undef LOG + +#include "base/values.h" +#include "webkit/glue/glue_util.h" + +using namespace WebCore; + +// static +PassRefPtr<DomAgentImpl::EventListenerWrapper> + DomAgentImpl::EventListenerWrapper::Create( + DomAgentImpl* dom_agent_impl) { + return adoptRef(new EventListenerWrapper(dom_agent_impl)); +} + +DomAgentImpl::EventListenerWrapper::EventListenerWrapper( + DomAgentImpl* dom_agent_impl) : dom_agent_impl_(dom_agent_impl) { +} + +void DomAgentImpl::EventListenerWrapper::handleEvent( + Event* event, + bool isWindowEvent) { + dom_agent_impl_->handleEvent(event, isWindowEvent); +} + +DomAgentImpl::DomAgentImpl(DomAgentDelegate* delegate) + : delegate_(delegate), + last_node_id_(1), + document_element_requested_(false) { + event_listener_ = EventListenerWrapper::Create(this); +} + +DomAgentImpl::~DomAgentImpl() { + SetDocument(NULL); +} + +void DomAgentImpl::SetDocument(Document* doc) { + DiscardBindings(); + + ListHashSet<RefPtr<Document> > copy = documents_; + for (ListHashSet<RefPtr<Document> >::iterator it = copy.begin(); + it != copy.end(); ++it) { + StopListening((*it).get()); + } + ASSERT(documents_.size() == 0); + + if (doc) { + StartListening(doc); + if (document_element_requested_) { + GetDocumentElement(); + document_element_requested_ = false; + } + } +} + +void DomAgentImpl::StartListening(Document* doc) { + if (documents_.find(doc) != documents_.end()) + return; + doc->addEventListener(eventNames().DOMContentLoadedEvent, event_listener_, + false); + doc->addEventListener(eventNames().DOMNodeInsertedEvent, event_listener_, + false); + doc->addEventListener(eventNames().DOMNodeRemovedEvent, event_listener_, + false); + doc->addEventListener(eventNames().DOMNodeRemovedFromDocumentEvent, + event_listener_, false); + doc->addEventListener(eventNames().DOMAttrModifiedEvent, event_listener_, + false); + documents_.add(doc); +} + +void DomAgentImpl::StopListening(Document* doc) { + doc->removeEventListener(eventNames().DOMContentLoadedEvent, + event_listener_.get(), false); + doc->removeEventListener(eventNames().DOMNodeInsertedEvent, + event_listener_.get(), false); + doc->removeEventListener(eventNames().DOMNodeRemovedEvent, + event_listener_.get(), false); + doc->removeEventListener(eventNames().DOMNodeRemovedFromDocumentEvent, + event_listener_.get(), false); + doc->removeEventListener(eventNames().DOMAttrModifiedEvent, + event_listener_.get(), false); + documents_.remove(doc); +} + +int DomAgentImpl::Bind(Node* node) { + HashMap<Node*, int>::iterator it = node_to_id_.find(node); + if (it != node_to_id_.end()) + return it->second; + int id = last_node_id_++; + node_to_id_.set(node, id); + id_to_node_.set(id, node); + return id; +} + +void DomAgentImpl::Unbind(Node* node) { + if (node->isFrameOwnerElement()) { + const HTMLFrameOwnerElement* frame_owner = + static_cast<const HTMLFrameOwnerElement*>(node); + StopListening(frame_owner->contentDocument()); + } + + HashMap<Node*, int>::iterator it = node_to_id_.find(node); + if (it != node_to_id_.end()) { + id_to_node_.remove(id_to_node_.find(it->second)); + children_requested_.remove(children_requested_.find(it->second)); + node_to_id_.remove(it); + } +} + +void DomAgentImpl::DiscardBindings() { + node_to_id_.clear(); + id_to_node_.clear(); + children_requested_.clear(); +} + +Node* DomAgentImpl::GetNodeForId(int id) { + HashMap<int, Node*>::iterator it = id_to_node_.find(id); + if (it != id_to_node_.end()) { + return it->second; + } + return NULL; +} + +int DomAgentImpl::GetIdForNode(Node* node) { + if (node == NULL) { + return 0; + } + HashMap<Node*, int>::iterator it = node_to_id_.find(node); + if (it != node_to_id_.end()) { + return it->second; + } + return 0; +} + +void DomAgentImpl::handleEvent(Event* event, bool isWindowEvent) { + AtomicString type = event->type(); + Node* node = event->target()->toNode(); + + // Remove mapping entry if necessary. + if (type == eventNames().DOMNodeRemovedFromDocumentEvent) { + Unbind(node); + return; + } + + if (type == eventNames().DOMAttrModifiedEvent) { + int id = GetIdForNode(node); + if (!id) { + // Node is not mapped yet -> ignore the event. + return; + } + Element* element = static_cast<Element*>(node); + OwnPtr<Value> attributesValue(BuildValueForElementAttributes(element)); + delegate_->AttributesUpdated(id, *attributesValue.get()); + } else if (type == eventNames().DOMNodeInsertedEvent) { + Node* parent = static_cast<MutationEvent*>(event)->relatedNode(); + int parent_id = GetIdForNode(parent); + if (!parent_id) { + // Parent is not mapped yet -> ignore the event. + return; + } + HashSet<int>::iterator cit = children_requested_.find(parent_id); + if (cit == children_requested_.end()) { + // No children are mapped yet -> only notify on changes of hasChildren. + delegate_->HasChildrenUpdated(parent_id, true); + } else { + // Children have been requested -> return value of a new child. + int prev_id = GetIdForNode(node->previousSibling()); + OwnPtr<Value> value(BuildValueForNode(node, 0)); + delegate_->ChildNodeInserted(parent_id, prev_id, *value.get()); + } + } else if (type == eventNames().DOMNodeRemovedEvent) { + Node* parent = static_cast<MutationEvent*>(event)->relatedNode(); + int parent_id = GetIdForNode(parent); + if (!parent_id) { + // Parent is not mapped yet -> ignore the event. + return; + } + HashSet<int>::iterator cit = children_requested_.find(parent_id); + if (cit == children_requested_.end()) { + // No children are mapped yet -> only notify on changes of hasChildren. + if (parent->childNodeCount() == 1) + delegate_->HasChildrenUpdated(parent_id, false); + } else { + int id = GetIdForNode(node); + delegate_->ChildNodeRemoved(parent_id, id); + } + } else if (type == eventNames().DOMContentLoadedEvent) { + //TODO(pfeldman): handle content load event. + } +} + +void DomAgentImpl::GetDocumentElement() { + if (documents_.size() > 0) { + OwnPtr<Value> value( + BuildValueForNode((*documents_.begin())->documentElement(), 0)); + delegate_->DocumentElementUpdated(*value.get()); + } else { + document_element_requested_ = true; + } +} + +void DomAgentImpl::GetChildNodes(int element_id) { + Node* node = GetNodeForId(element_id); + if (!node || (node->nodeType() != Node::ELEMENT_NODE)) + return; + + Element* element = static_cast<Element*>(node); + OwnPtr<Value> children(BuildValueForElementChildren(element, 1)); + children_requested_.add(element_id); + delegate_->ChildNodesUpdated(element_id, *children.get()); +} + +int DomAgentImpl::GetPathToNode(Node* node_to_select) { + ASSERT(node_to_select); // Invalid input + + // Return id in case the node is known. + int result = GetIdForNode(node_to_select); + if (result) + return result; + + Element* element = InnerParentElement(node_to_select); + ASSERT(element); // Node is detached or is a document itself + + Vector<Element*> path; + while (element && !GetIdForNode(element)) { + path.append(element); + element = InnerParentElement(element); + } + // element is known to the client + ASSERT(element); + path.append(element); + + for (int i = path.size() - 1; i >= 0; --i) { + element = path.at(i); + OwnPtr<Value> children(BuildValueForElementChildren(element, 1)); + delegate_->ChildNodesUpdated(GetIdForNode(element), *children.get()); + } + return GetIdForNode(node_to_select); +} + +void DomAgentImpl::SetAttribute( + int element_id, + const String& name, + const String& value) { + Node* node = GetNodeForId(element_id); + if (node && (node->nodeType() == Node::ELEMENT_NODE)) { + Element* element = static_cast<Element*>(node); + ExceptionCode ec = 0; + element->setAttribute(name, value, ec); + } +} + +void DomAgentImpl::RemoveAttribute(int element_id, const String& name) { + Node* node = GetNodeForId(element_id); + if (node && (node->nodeType() == Node::ELEMENT_NODE)) { + Element* element = static_cast<Element*>(node); + ExceptionCode ec = 0; + element->removeAttribute(name, ec); + } +} + +void DomAgentImpl::SetTextNodeValue(int element_id, const String& value) { + Node* node = GetNodeForId(element_id); + if (node && (node->nodeType() == Node::TEXT_NODE)) { + Text* text_node = static_cast<Text*>(node); + ExceptionCode ec = 0; + // TODO(pfeldman): Add error handling + text_node->replaceWholeText(value, ec); + } +} + +ListValue* DomAgentImpl::BuildValueForNode(Node* node, int depth) { + OwnPtr<ListValue> value(new ListValue()); + int id = Bind(node); + String nodeName; + String nodeValue; + + switch (node->nodeType()) { + case Node::TEXT_NODE: + case Node::COMMENT_NODE: + nodeValue = node->nodeValue(); + break; + case Node::ATTRIBUTE_NODE: + case Node::DOCUMENT_NODE: + case Node::DOCUMENT_FRAGMENT_NODE: + break; + case Node::ELEMENT_NODE: + default: { + nodeName = node->nodeName(); + break; + } + } + + value->Append(Value::CreateIntegerValue(id)); + value->Append(Value::CreateIntegerValue(node->nodeType())); + value->Append(Value::CreateStringValue( + webkit_glue::StringToStdWString(nodeName))); + value->Append(Value::CreateStringValue( + webkit_glue::StringToStdWString(nodeValue))); + + if (node->nodeType() == Node::ELEMENT_NODE) { + Element* element = static_cast<Element*>(node); + value->Append(BuildValueForElementAttributes(element)); + int nodeCount = InnerChildNodeCount(element); + value->Append(Value::CreateIntegerValue(nodeCount)); + OwnPtr<ListValue> children(BuildValueForElementChildren(element, depth)); + if (children->GetSize() > 0) { + value->Append(children.release()); + } + } + return value.release(); +} + +ListValue* DomAgentImpl::BuildValueForElementAttributes(Element* element) { + OwnPtr<ListValue> attributesValue(new ListValue()); + // Go through all attributes and serialize them. + const NamedAttrMap *attrMap = element->attributes(true); + if (!attrMap) { + return attributesValue.release(); + } + unsigned numAttrs = attrMap->length(); + for (unsigned i = 0; i < numAttrs; i++) { + // Add attribute pair + const Attribute *attribute = attrMap->attributeItem(i); + OwnPtr<Value> name(Value::CreateStringValue( + webkit_glue::StringToStdWString(attribute->name().toString()))); + OwnPtr<Value> value(Value::CreateStringValue( + webkit_glue::StringToStdWString(attribute->value()))); + attributesValue->Append(name.release()); + attributesValue->Append(value.release()); + } + return attributesValue.release(); +} + +ListValue* DomAgentImpl::BuildValueForElementChildren( + Element* element, + int depth) { + OwnPtr<ListValue> children(new ListValue()); + if (depth == 0) { + // Special case the_only text child. + if (element->childNodeCount() == 1) { + Node *child = element->firstChild(); + if (child->nodeType() == Node::TEXT_NODE) { + children->Append(BuildValueForNode(child, 0)); + } + } + return children.release(); + } else if (depth > 0) { + depth--; + } + + for (Node *child = InnerFirstChild(element); child != NULL; + child = child->nextSibling()) { + children->Append(BuildValueForNode(child, depth)); + } + return children.release(); +} + +Node* DomAgentImpl::InnerFirstChild(Node* node) { + if (node->isFrameOwnerElement()) { + HTMLFrameOwnerElement* frame_owner = + static_cast<HTMLFrameOwnerElement*>(node); + Document* doc = frame_owner->contentDocument(); + StartListening(doc); + return doc->firstChild(); + } else { + return node->firstChild(); + } +} + +int DomAgentImpl::InnerChildNodeCount(Node* node) { + if (node->isFrameOwnerElement()) { + return 1; + } else { + return node->childNodeCount(); + } +} + +Element* DomAgentImpl::InnerParentElement(Node* node) { + Element* element = node->parentElement(); + if (!element) { + return node->ownerDocument()->ownerElement(); + } + return element; +} diff --git a/webkit/glue/devtools/dom_agent_impl.h b/webkit/glue/devtools/dom_agent_impl.h new file mode 100644 index 0000000..12d7540 --- /dev/null +++ b/webkit/glue/devtools/dom_agent_impl.h @@ -0,0 +1,119 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_DEVTOOLS_DOM_AGENT_IMPL_H_ +#define WEBKIT_GLUE_DEVTOOLS_DOM_AGENT_IMPL_H_ + +#include "config.h" + +#include "EventListener.h" +#include <wtf/ListHashSet.h> +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> +#include <wtf/PassRefPtr.h> +#include <wtf/RefPtr.h> + +#include "webkit/glue/devtools/dom_agent.h" + +namespace WebCore { +class Document; +class Element; +class Event; +class Node; +} + +class ListValue; +class Value; + +// DomAgent implementation. +class DomAgentImpl : public DomAgent { + public: + explicit DomAgentImpl(DomAgentDelegate* delegate); + virtual ~DomAgentImpl(); + + // DomAgent implementation. + void GetDocumentElement(); + void GetChildNodes(int element_id); + void SetAttribute( + int element_id, + const WebCore::String& name, + const WebCore::String& value); + void RemoveAttribute(int element_id, const WebCore::String& name); + void SetTextNodeValue(int element_id, const WebCore::String& value); + void DiscardBindings(); + + // Initializes dom agent with the given document. + void SetDocument(WebCore::Document* document); + + // Returns node for given id according to the present binding. + WebCore::Node* GetNodeForId(int id); + + // Returns id for given node according to the present binding. + int GetIdForNode(WebCore::Node* node); + + // Sends path to a given node to the client. Returns node's id according to + // the resulting binding. + int GetPathToNode(WebCore::Node* node); + + private: + // Convenience EventListner wrapper for cleaner Ref management. + class EventListenerWrapper : public WebCore::EventListener { + public: + static PassRefPtr<EventListenerWrapper> Create( + DomAgentImpl* dom_agent_impl); + virtual ~EventListenerWrapper() {} + virtual void handleEvent(WebCore::Event* event, bool isWindowEvent); + private: + explicit EventListenerWrapper(DomAgentImpl* dom_agent_impl); + DomAgentImpl* dom_agent_impl_; + DISALLOW_COPY_AND_ASSIGN(EventListenerWrapper); + }; + + void StartListening(WebCore::Document* document); + + void StopListening(WebCore::Document* document); + + // EventListener implementation + friend class EventListenerWrapper; + virtual void handleEvent(WebCore::Event* event, bool isWindowEvent); + + // Binds given node and returns its generated id. + int Bind(WebCore::Node* node); + + // Releases Node to int binding. + void Unbind(WebCore::Node* node); + + // Serializes given node into the list value. + ListValue* BuildValueForNode( + WebCore::Node* node, + int depth); + + // Serializes given element's attributes into the list value. + ListValue* BuildValueForElementAttributes(WebCore::Element* elemen); + + // Serializes given elements's children into the list value. + ListValue* BuildValueForElementChildren( + WebCore::Element* element, + int depth); + + // We represent embedded doms as a part of the same hierarchy. Hence we + // treat children of frame owners differently. Following two methods + // encapsulate frame owner specifics. + WebCore::Node* InnerFirstChild(WebCore::Node* node); + int InnerChildNodeCount(WebCore::Node* node); + WebCore::Element* InnerParentElement(WebCore::Node* node); + + DomAgentDelegate* delegate_; + HashMap<WebCore::Node*, int> node_to_id_; + HashMap<int, WebCore::Node*> id_to_node_; + HashSet<int> children_requested_; + int last_node_id_; + ListHashSet<RefPtr<WebCore::Document> > documents_; + RefPtr<WebCore::EventListener> event_listener_; + bool document_element_requested_; + + DISALLOW_COPY_AND_ASSIGN(DomAgentImpl); +}; + +#endif // WEBKIT_GLUE_DEVTOOLS_DOM_AGENT_IMPL_H_ diff --git a/webkit/glue/devtools/dom_agent_unittest.cc b/webkit/glue/devtools/dom_agent_unittest.cc new file mode 100644 index 0000000..8c01e04 --- /dev/null +++ b/webkit/glue/devtools/dom_agent_unittest.cc @@ -0,0 +1,421 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "config.h" + +#include <wtf/OwnPtr.h> + +#include "CString.h" +#include "Document.h" +#include "HTMLFrameOwnerElement.h" +#include "PlatformString.h" +#undef LOG + +#include "base/file_path.h" +#include "base/string_util.h" +#include "base/values.h" +#include "net/base/net_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/glue/devtools/devtools_mock_rpc.h" +#include "webkit/glue/devtools/devtools_rpc.h" +#include "webkit/glue/devtools/dom_agent.h" +#include "webkit/glue/devtools/dom_agent_impl.h" +#include "webkit/glue/dom_operations.h" +#include "webkit/glue/glue_util.h" +#include "webkit/glue/webframe.h" +#include "webkit/glue/webframe_impl.h" +#include "webkit/glue/webview.h" +#include "webkit/tools/test_shell/test_shell_test.h" + +using WebCore::Document; +using WebCore::Element; +using WebCore::ExceptionCode; +using WebCore::HTMLFrameOwnerElement; +using WebCore::Node; +using WebCore::String; + +namespace { + +class MockDomAgentDelegate : public DomAgentDelegateStub, + public DevToolsMockRpc { + public: + MockDomAgentDelegate() : DomAgentDelegateStub(NULL) { + set_delegate(this); + } + ~MockDomAgentDelegate() {} +}; + +class DomAgentTests : public TestShellTest { + public: + DomAgentTests() : ec_(0) {} + + protected: + // testing::Test + virtual void SetUp() { + TestShellTest::SetUp(); + test_shell_->ResetTestController(); + GURL file_url = net::FilePathToFileURL(data_dir_); + WebFrame* main_frame = test_shell_->webView()->GetMainFrame(); + main_frame->LoadHTMLString("<html><body/></html>", + file_url); + WebFrameImpl* main_frame_impl = static_cast<WebFrameImpl*>(main_frame); + + document_ = main_frame_impl->frame()->document(); + Node* html = document_->documentElement(); + body_ = static_cast<Element*>(html->firstChild()); + + mock_delegate_.set(new MockDomAgentDelegate()); + dom_agent_.set(new DomAgentImpl(mock_delegate_.get())); + dom_agent_->SetDocument(document_.get()); + } + + virtual void TearDown() { + TestShellTest::TearDown(); + dom_agent_.set(NULL); + body_ = NULL; + document_ = NULL; + } + + static const int kHtmlElemId = 1; + static const int kBodyElemId = 2; + + RefPtr<Document> document_; + RefPtr<Element> body_; + OwnPtr<DomAgentImpl> dom_agent_; + ExceptionCode ec_; + OwnPtr<MockDomAgentDelegate> mock_delegate_; +}; + +// Requests document node and tests that the callback with the serialized +// version is called. +TEST_F(DomAgentTests, DocumentElementUpdated) { + OwnPtr<Value> v(DevToolsRpc::ParseMessage("[1,1,\"HTML\",\"\",[],1]")); + mock_delegate_->DocumentElementUpdated(*v.get()); + mock_delegate_->Replay(); + + dom_agent_->GetDocumentElement(); + mock_delegate_->Verify(); +} + +// Requests element's children and tests that the callback with the serialized +// version is called. +TEST_F(DomAgentTests, ChildNodesUpdated) { + dom_agent_->GetDocumentElement(); + mock_delegate_->Reset(); + + OwnPtr<Value> v(DevToolsRpc::ParseMessage("[[2,1,\"BODY\",\"\",[],0]]")); + mock_delegate_->ChildNodesUpdated(1, *v.get()); + mock_delegate_->Replay(); + + dom_agent_->GetChildNodes(kHtmlElemId); + mock_delegate_->Verify(); +} + +// Tests that "child node inserted" event is being fired. +TEST_F(DomAgentTests, ChildNodeInsertedUnknownParent) { + dom_agent_->GetDocumentElement(); + mock_delegate_->Reset(); + + // There should be no events fired until parent node is known to client. + RefPtr<Element> div = document_->createElement("DIV", ec_); + body_->appendChild(div, ec_); + mock_delegate_->Replay(); + mock_delegate_->Verify(); +} + +// Tests that "child node inserted" event is being fired. +TEST_F(DomAgentTests, ChildNodeInsertedKnownParent) { + dom_agent_->GetDocumentElement(); + dom_agent_->GetChildNodes(kHtmlElemId); + mock_delegate_->Reset(); + + // There should be an event fired in case parent node is known to client, + // but the event should not be specific. + mock_delegate_->HasChildrenUpdated(kBodyElemId, true); + mock_delegate_->Replay(); + + RefPtr<Element> div = document_->createElement("DIV", ec_); + body_->appendChild(div, ec_); + mock_delegate_->Verify(); +} + +// Tests that "child node inserted" event is being fired. +TEST_F(DomAgentTests, ChildNodeInsertedKnownChildren) { + dom_agent_->GetDocumentElement(); + dom_agent_->GetChildNodes(kHtmlElemId); + dom_agent_->GetChildNodes(kBodyElemId); + mock_delegate_->Reset(); + + // There should be an event fired in case parent node is known to client, + // Since children were already requested, event should have all the + // new child data. + OwnPtr<Value> v(DevToolsRpc::ParseMessage("[3,1,\"DIV\",\"\",[],0]")); + mock_delegate_->ChildNodeInserted(kBodyElemId, 0, *v.get()); + mock_delegate_->Replay(); + + RefPtr<Element> div = document_->createElement("DIV", ec_); + body_->appendChild(div, ec_); + mock_delegate_->Verify(); +} + +// Tests that "child node inserted" event is being fired. +TEST_F(DomAgentTests, ChildNodePrepend) { + RefPtr<Element> div = document_->createElement("DIV", ec_); + body_->appendChild(div, ec_); + + dom_agent_->GetDocumentElement(); + dom_agent_->GetChildNodes(kHtmlElemId); + dom_agent_->GetChildNodes(kBodyElemId); + mock_delegate_->Reset(); + + // There should be an event fired in case parent node is known to client, + // Since children were already requested, event should have all the + // new child data. + OwnPtr<Value> v(DevToolsRpc::ParseMessage("[4,1,\"DIV\",\"\",[],0]")); + mock_delegate_->ChildNodeInserted(kBodyElemId, 0, *v.get()); + mock_delegate_->Replay(); + + RefPtr<Element> new_div = document_->createElement("DIV", ec_); + body_->insertBefore(new_div, div.get(), ec_, false); + mock_delegate_->Verify(); +} + +// Tests that "child node inserted" event is being fired. +TEST_F(DomAgentTests, ChildNodeAppend) { + RefPtr<Element> div = document_->createElement("DIV", ec_); + body_->appendChild(div, ec_); + + dom_agent_->GetDocumentElement(); + dom_agent_->GetChildNodes(kHtmlElemId); + dom_agent_->GetChildNodes(kBodyElemId); + mock_delegate_->Reset(); + + // There should be an event fired in case parent node is known to client, + // Since children were already requested, event should have all the + // new child data. + OwnPtr<Value> v(DevToolsRpc::ParseMessage("[4,1,\"DIV\",\"\",[],0]")); + mock_delegate_->ChildNodeInserted(kBodyElemId, 3, *v.get()); + mock_delegate_->Replay(); + + RefPtr<Element> new_div = document_->createElement("DIV", ec_); + body_->appendChild(new_div, ec_, false); + mock_delegate_->Verify(); +} + +// Tests that "child node inserted" event is being fired. +TEST_F(DomAgentTests, ChildNodeInsert) { + RefPtr<Element> div1 = document_->createElement("DIV", ec_); + body_->appendChild(div1, ec_); + RefPtr<Element> div2 = document_->createElement("DIV", ec_); + body_->appendChild(div2, ec_); + + dom_agent_->GetDocumentElement(); + dom_agent_->GetChildNodes(kHtmlElemId); + dom_agent_->GetChildNodes(kBodyElemId); + mock_delegate_->Reset(); + + // There should be an event fired in case parent node is known to client, + // Since children were already requested, event should have all the + // new child data. + OwnPtr<Value> v(DevToolsRpc::ParseMessage("[5,1,\"DIV\",\"\",[],0]")); + mock_delegate_->ChildNodeInserted(kBodyElemId, 3, *v.get()); + mock_delegate_->Replay(); + + RefPtr<Element> new_div = document_->createElement("DIV", ec_); + body_->insertBefore(new_div, div2.get(), ec_, false); + mock_delegate_->Verify(); +} + +// Tests that "child node inserted" event is being fired. +TEST_F(DomAgentTests, ChildNodeRemovedUnknownParent) { + RefPtr<Element> div = document_->createElement("DIV", ec_); + body_->appendChild(div, ec_); + + dom_agent_->GetDocumentElement(); + mock_delegate_->Reset(); + + // There should be no events fired until parent node is known to client. + mock_delegate_->Replay(); + body_->removeChild(div.get(), ec_); + mock_delegate_->Verify(); +} + +// Tests that "child node inserted" event is being fired. +TEST_F(DomAgentTests, ChildNodeRemovedKnownParent) { + RefPtr<Element> div = document_->createElement("DIV", ec_); + body_->appendChild(div, ec_); + + dom_agent_->GetDocumentElement(); + dom_agent_->GetChildNodes(kHtmlElemId); + mock_delegate_->Reset(); + + // There should be an event fired in case parent node is known to client, + // but the event should not be specific. + mock_delegate_->HasChildrenUpdated(kBodyElemId, false); + mock_delegate_->Replay(); + + body_->removeChild(div.get(), ec_); + mock_delegate_->Verify(); +} + +// Tests that "child node inserted" event is being fired. +TEST_F(DomAgentTests, ChildNodeRemovedKnownChildren) { + RefPtr<Element> div = document_->createElement("DIV", ec_); + body_->appendChild(div, ec_); + + dom_agent_->GetDocumentElement(); + dom_agent_->GetChildNodes(kHtmlElemId); + dom_agent_->GetChildNodes(kBodyElemId); + mock_delegate_->Reset(); + + // There should be an event fired in case parent node is known to client, + // Since children were already requested, event should have removed child id. + mock_delegate_->ChildNodeRemoved(kBodyElemId, 3); + mock_delegate_->Replay(); + + body_->removeChild(div.get(), ec_); + mock_delegate_->Verify(); +} + +// Tests that "GetPathToNode" sends all missing events in path. +TEST_F(DomAgentTests, GetPathToKnownNode) { + RefPtr<Element> div1 = document_->createElement("DIV", ec_); + body_->appendChild(div1, ec_); + + dom_agent_->GetDocumentElement(); + dom_agent_->GetChildNodes(kHtmlElemId); + dom_agent_->GetChildNodes(kBodyElemId); + mock_delegate_->Reset(); + + // We expect no messages - node is already known. + mock_delegate_->Replay(); + + int id = dom_agent_->GetPathToNode(div1.get()); + mock_delegate_->Verify(); + EXPECT_EQ(3, id); +} + +// Tests that "GetPathToNode" sends all missing events in path. +TEST_F(DomAgentTests, GetPathToKnownParent) { + RefPtr<Element> div1 = document_->createElement("DIV", ec_); + body_->appendChild(div1, ec_); + + dom_agent_->GetDocumentElement(); + dom_agent_->GetChildNodes(kHtmlElemId); + mock_delegate_->Reset(); + + OwnPtr<Value> v1(DevToolsRpc::ParseMessage("[[3,1,\"DIV\",\"\",[],0]]")); + mock_delegate_->ChildNodesUpdated(kBodyElemId, *v1.get()); + mock_delegate_->Replay(); + + int id = dom_agent_->GetPathToNode(div1.get()); + mock_delegate_->Verify(); + EXPECT_EQ(3, id); +} + +// Tests that "GetPathToNode" sends all missing events in path. +TEST_F(DomAgentTests, GetPathToUnknownNode) { + RefPtr<Element> div1 = document_->createElement("DIV", ec_); + RefPtr<Element> div2 = document_->createElement("DIV", ec_); + RefPtr<Element> div3 = document_->createElement("DIV", ec_); + RefPtr<Element> div4 = document_->createElement("DIV", ec_); + body_->appendChild(div1, ec_); + div1->appendChild(div2, ec_); + div2->appendChild(div3, ec_); + div3->appendChild(div4, ec_); + + dom_agent_->GetDocumentElement(); + dom_agent_->GetChildNodes(kHtmlElemId); + mock_delegate_->Reset(); + + OwnPtr<Value> v1(DevToolsRpc::ParseMessage("[[3,1,\"DIV\",\"\",[],1]]")); + OwnPtr<Value> v2(DevToolsRpc::ParseMessage("[[4,1,\"DIV\",\"\",[],1]]")); + OwnPtr<Value> v3(DevToolsRpc::ParseMessage("[[5,1,\"DIV\",\"\",[],1]]")); + OwnPtr<Value> v4(DevToolsRpc::ParseMessage("[[6,1,\"DIV\",\"\",[],0]]")); + mock_delegate_->ChildNodesUpdated(kBodyElemId, *v1.get()); + mock_delegate_->ChildNodesUpdated(3, *v2.get()); + mock_delegate_->ChildNodesUpdated(4, *v3.get()); + mock_delegate_->ChildNodesUpdated(5, *v4.get()); + mock_delegate_->Replay(); + + int id = dom_agent_->GetPathToNode(div4.get()); + mock_delegate_->Verify(); + EXPECT_EQ(6, id); +} + +// Tests that "GetChildNodes" crosses frame owner boundaries. +TEST_F(DomAgentTests, GetChildNodesOfFrameOwner) { + RefPtr<Element> iframe = document_->createElement("IFRAME", ec_); + body_->appendChild(iframe, ec_); + + dom_agent_->GetDocumentElement(); + dom_agent_->GetChildNodes(kHtmlElemId); + dom_agent_->GetChildNodes(kBodyElemId); + mock_delegate_->Reset(); + + // Expecting HTML child with single (body) child. + OwnPtr<Value> v(DevToolsRpc::ParseMessage("[[4,1,\"HTML\",\"\",[],1]]")); + mock_delegate_->ChildNodesUpdated(3, *v.get()); + mock_delegate_->Replay(); + + dom_agent_->GetChildNodes(3); + mock_delegate_->Verify(); +} + +// Tests that "GetPathToNode" crosses frame owner boundaries. +TEST_F(DomAgentTests, GetPathToNodeOverFrameOwner) { + RefPtr<Element> iframe = document_->createElement("IFRAME", ec_); + body_->appendChild(iframe, ec_); + HTMLFrameOwnerElement* frame_owner = + static_cast<HTMLFrameOwnerElement*>(iframe.get()); + Node* inner_body = frame_owner->contentDocument()->firstChild()-> + firstChild(); + + dom_agent_->GetDocumentElement(); + dom_agent_->GetChildNodes(kHtmlElemId); + mock_delegate_->Reset(); + + OwnPtr<Value> v1(DevToolsRpc::ParseMessage("[[3,1,\"IFRAME\",\"\",[],1]]")); + OwnPtr<Value> v2(DevToolsRpc::ParseMessage("[[4,1,\"HTML\",\"\",[],1]]")); + OwnPtr<Value> v3(DevToolsRpc::ParseMessage("[[5,1,\"BODY\",\"\",[],0]]")); + mock_delegate_->ChildNodesUpdated(2, *v1.get()); + mock_delegate_->ChildNodesUpdated(3, *v2.get()); + mock_delegate_->ChildNodesUpdated(4, *v3.get()); + + mock_delegate_->Replay(); + + dom_agent_->GetPathToNode(inner_body); + mock_delegate_->Verify(); +} + +// Tests that "child node inserted" event is being fired. +TEST_F(DomAgentTests, ChildNodeInsertUnderFrameOwner) { + RefPtr<Element> iframe = document_->createElement("IFRAME", ec_); + body_->appendChild(iframe, ec_); + HTMLFrameOwnerElement* frame_owner = + static_cast<HTMLFrameOwnerElement*>(iframe.get()); + Node* inner_body = frame_owner->contentDocument()->firstChild()-> + firstChild(); + + dom_agent_->GetDocumentElement(); + dom_agent_->GetChildNodes(kHtmlElemId); + dom_agent_->GetChildNodes(kBodyElemId); + dom_agent_->GetChildNodes(3); // IFrame children + dom_agent_->GetChildNodes(4); // IFrame html's children + dom_agent_->GetChildNodes(5); // IFrame body's children + mock_delegate_->Reset(); + + // There should be an event fired in case parent node is known to client, + // Since children were already requested, event should have all the + // new child data. + OwnPtr<Value> v(DevToolsRpc::ParseMessage("[6,1,\"DIV\",\"\",[],0]")); + mock_delegate_->ChildNodeInserted(5, 0, *v.get()); + mock_delegate_->Replay(); + + RefPtr<Element> new_div = document_->createElement("DIV", ec_); + inner_body->appendChild(new_div.get(), ec_, false); + mock_delegate_->Verify(); +} + +} // namespace diff --git a/webkit/glue/devtools/net_agent.h b/webkit/glue/devtools/net_agent.h new file mode 100644 index 0000000..00db4af0 --- /dev/null +++ b/webkit/glue/devtools/net_agent.h @@ -0,0 +1,40 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_DEVTOOLS_NET_AGENT_H_ +#define WEBKIT_GLUE_DEVTOOLS_NET_AGENT_H_ + +#include "webkit/glue/devtools/devtools_rpc.h" + +// NetAgent is a utility object that covers network-related functionality of the +// WebDevToolsAgent. It is capable of sniffing network calls and passing the +// HTTPRequest-related data to the client. +// NetAgent's environment is represented with the NetAgentDelegate interface. +#define NET_AGENT_STRUCT(METHOD0, METHOD1, METHOD2, METHOD3) \ + /* Requests that the agent sends content of the resource with given id to the + delegate. */ \ + METHOD2(GetResourceContent, int /* identifier */, String /* url */) + +DEFINE_RPC_CLASS(NetAgent, NET_AGENT_STRUCT) + +#define NET_AGENT_DELEGATE_STRUCT(METHOD0, METHOD1, METHOD2, METHOD3) \ + /* Notifies the delegate that a request is about to be sent out. */ \ + METHOD2(WillSendRequest, int, Value) \ + \ + /* Notifies the delegate that response has been received. */ \ + METHOD2(DidReceiveResponse, int /* identifier */, Value /* request */) \ + \ + /* Notifies the delegate that resource loading has been finished with no + errors */ \ + METHOD2(DidFinishLoading, int /* identifier */, Value /* response */) \ + \ + /* Notifies the delegate that resource loading has failed. */ \ + METHOD2(DidFailLoading, int /* identifier */, Value /* response */) \ + \ + /* Calls delegate back with requested resource content. */ \ + METHOD2(SetResourceContent, int /* identifier */, String /* content */) + +DEFINE_RPC_CLASS(NetAgentDelegate, NET_AGENT_DELEGATE_STRUCT) + +#endif // WEBKIT_GLUE_DEVTOOLS_NET_AGENT_H_ diff --git a/webkit/glue/devtools/net_agent_impl.cc b/webkit/glue/devtools/net_agent_impl.cc new file mode 100644 index 0000000..67fe1b8 --- /dev/null +++ b/webkit/glue/devtools/net_agent_impl.cc @@ -0,0 +1,204 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "config.h" + +#include "webkit/glue/devtools/net_agent_impl.h" + +#include "CachedCSSStyleSheet.h" +#include "CachedResource.h" +#include "CachedScript.h" +#include "CachedXSLStyleSheet.h" +#include "DocLoader.h" +#include "Document.h" +#include "DocumentLoader.h" +#include "FrameLoader.h" +#include "HTTPHeaderMap.h" +#include "KURL.h" +#include "PlatformString.h" +#include "ResourceError.h" +#include "ResourceRequest.h" +#include "ResourceResponse.h" +#include "TextEncoding.h" +#include <wtf/CurrentTime.h> +#undef LOG + +#include "base/basictypes.h" +#include "base/values.h" +#include "googleurl/src/gurl.h" +#include "webkit/glue/glue_util.h" + +using namespace WebCore; + +NetAgentImpl::NetAgentImpl(NetAgentDelegate* delegate) + : delegate_(delegate), + document_(NULL), + last_cached_identifier_(-2) { +} + +NetAgentImpl::~NetAgentImpl() { + SetDocument(NULL); +} + +void NetAgentImpl::SetDocument(Document* doc) { +// loaders_.clear(); + document_ = doc; +} + +void NetAgentImpl::AssignIdentifierToRequest( + DocumentLoader* loader, + int identifier, + const ResourceRequest& request) { + loaders_.set(identifier, loader); +} + +void NetAgentImpl::WillSendRequest( + DocumentLoader* loader, + int identifier, + const ResourceRequest& request) { + KURL url = request.url(); + DictionaryValue value; + value.SetReal(L"startTime", WTF::currentTime()); + value.SetString(L"url", webkit_glue::StringToStdString(url.string())); + value.SetString(L"domain", webkit_glue::StringToStdString(url.host())); + value.SetString(L"path", webkit_glue::StringToStdString(url.path())); + value.SetString(L"lastPathComponent", + webkit_glue::StringToStdString(url.lastPathComponent())); + value.Set(L"requestHeaders", + BuildValueForHeaders(request.httpHeaderFields())); + delegate_->WillSendRequest(identifier, value); +} + +void NetAgentImpl::DidReceiveResponse( + DocumentLoader* loader, + int identifier, + const ResourceResponse &response) { + if (!document_) { + return; + } + KURL url = response.url(); + + DictionaryValue value; + value.SetReal(L"responseReceivedTime", WTF::currentTime()); + value.SetString(L"url", + webkit_glue::StringToStdWString(url.string())); + value.SetInteger(L"expectedContentLength", + static_cast<int>(response.expectedContentLength())); + value.SetInteger(L"responseStatusCode", response.httpStatusCode()); + value.SetString(L"mimeType", + webkit_glue::StringToStdWString(response.mimeType())); + value.SetString(L"suggestedFilename", + webkit_glue::StringToStdWString(response.suggestedFilename())); + value.Set(L"responseHeaders", + BuildValueForHeaders(response.httpHeaderFields())); + + delegate_->DidReceiveResponse(identifier, value); +} + +void NetAgentImpl::DidReceiveContentLength( + DocumentLoader* loader, + int identifier, + int length) { +} + +void NetAgentImpl::DidFinishLoading( + DocumentLoader* loader, + int identifier) { + DictionaryValue value; + value.SetReal(L"endTime", WTF::currentTime()); + delegate_->DidFinishLoading(identifier, value); +} + +void NetAgentImpl::DidFailLoading( + DocumentLoader* loader, + int identifier, + const ResourceError& error) { + DictionaryValue value; + value.SetReal(L"endTime", WTF::currentTime()); + value.SetInteger(L"errorCode", error.errorCode()); + value.SetString(L"localizedDescription", + webkit_glue::StringToStdString(error.localizedDescription())); + delegate_->DidFailLoading(identifier, value); +} + +void NetAgentImpl::DidLoadResourceFromMemoryCache( + DocumentLoader* loader, + const ResourceRequest& request, + const ResourceResponse& response, + int length) { + int identifier = last_cached_identifier_--; + loaders_.set(identifier, loader); +} + +void NetAgentImpl::GetResourceContent( + int identifier, + const String& url) { + if (!document_) { + return; + } + HashMap<int, RefPtr<DocumentLoader> >::iterator it = + loaders_.find(identifier); + if (it == loaders_.end() || !it->second) { + return; + } + + RefPtr<DocumentLoader> loader = it->second; + String source; + + if (url == loader->requestURL()) { + RefPtr<SharedBuffer> buffer = loader->mainResourceData(); + String text_encoding_name = document_->inputEncoding(); + if (buffer) { + WebCore::TextEncoding encoding(text_encoding_name); + if (!encoding.isValid()) + encoding = WindowsLatin1Encoding(); + source = encoding.decode(buffer->data(), buffer->size()); + } + } else { + CachedResource* cachedResource = document_-> + docLoader()->cachedResource(url); + if (!cachedResource->isPurgeable()) { + // TODO(pfeldman): Try making unpurgeable. + return; + } + + // Try to get the decoded source. Only applies to some CachedResource + // types. + switch (cachedResource->type()) { + case CachedResource::CSSStyleSheet: { + CachedCSSStyleSheet *sheet = + reinterpret_cast<CachedCSSStyleSheet*>(cachedResource); + source = sheet->sheetText(); + break; + } + case CachedResource::Script: { + CachedScript *script = + reinterpret_cast<CachedScript*>(cachedResource); + source = script->script(); + break; + } +#if ENABLE(XSLT) + case CachedResource::XSLStyleSheet: { + CachedXSLStyleSheet *sheet = + reinterpret_cast<CachedXSLStyleSheet*>(cachedResource); + source = sheet->sheet(); + break; + } +#endif + default: + break; + } + } + delegate_->SetResourceContent(identifier, source); +} + +Value* NetAgentImpl::BuildValueForHeaders(const HTTPHeaderMap& headers) { + OwnPtr<DictionaryValue> value(new DictionaryValue()); + HTTPHeaderMap::const_iterator end = headers.end(); + for (HTTPHeaderMap::const_iterator it = headers.begin(); it != end; ++it) { + value->SetString(webkit_glue::StringToStdWString(it->first), + webkit_glue::StringToStdString(it->second)); + } + return value.release(); +} diff --git a/webkit/glue/devtools/net_agent_impl.h b/webkit/glue/devtools/net_agent_impl.h new file mode 100644 index 0000000..b9d56fc --- /dev/null +++ b/webkit/glue/devtools/net_agent_impl.h @@ -0,0 +1,80 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_DEVTOOLS_NET_AGENT_IMPL_H_ +#define WEBKIT_GLUE_DEVTOOLS_NET_AGENT_IMPL_H_ + +#include <wtf/HashMap.h> +#include <wtf/RefCounted.h> +#include <wtf/RefPtr.h> + +#include "webkit/glue/devtools/net_agent.h" + +namespace WebCore { +class Document; +class DocumentLoader; +class HTTPHeaderMap; +class ResourceError; +class ResourceResponse; +class String; +struct ResourceRequest; +} + +class Value; + +// NetAgent is a utility object that covers network-related functionality of the +// WebDevToolsAgent. It is capable of sniffing network calls and passing the +// HttpRequest-related data to the client. +// NetAgent's environment is represented with the NetAgentDelegate interface. +class NetAgentImpl : public NetAgent { + public: + explicit NetAgentImpl(NetAgentDelegate* delegate); + virtual ~NetAgentImpl(); + + // Initializes net agent with the given document. + void SetDocument(WebCore::Document* document); + + // NetAgent implementation. + void GetResourceContent(int identifier, const WebCore::String& request_url); + void AssignIdentifierToRequest( + WebCore::DocumentLoader* loader, + int identifier, + const WebCore::ResourceRequest& request); + void WillSendRequest( + WebCore::DocumentLoader* loader, + int identifier, + const WebCore::ResourceRequest& request); + void DidReceiveResponse( + WebCore::DocumentLoader* loader, + int identifier, + const WebCore::ResourceResponse &response); + void DidReceiveContentLength( + WebCore::DocumentLoader* loader, + int identifier, + int length); + void DidFinishLoading( + WebCore::DocumentLoader* loader, + int identifier); + void DidFailLoading( + WebCore::DocumentLoader* loader, + int identifier, + const WebCore::ResourceError& error); + void DidLoadResourceFromMemoryCache( + WebCore::DocumentLoader* loader, + const WebCore::ResourceRequest& request, + const WebCore::ResourceResponse& response, + int length); + + private: + // Serializes headers map into a value. + Value* BuildValueForHeaders(const WebCore::HTTPHeaderMap& headers); + + NetAgentDelegate* delegate_; + WebCore::Document* document_; + HashMap<int, RefPtr<WebCore::DocumentLoader> > loaders_; + int last_cached_identifier_; + DISALLOW_COPY_AND_ASSIGN(NetAgentImpl); +}; + +#endif // WEBKIT_GLUE_DEVTOOLS_NET_AGENT_IMPL_H_ diff --git a/webkit/glue/devtools/tools_agent.h b/webkit/glue/devtools/tools_agent.h new file mode 100644 index 0000000..0d1bde9 --- /dev/null +++ b/webkit/glue/devtools/tools_agent.h @@ -0,0 +1,32 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_DEVTOOLS_TOOLS_AGENT_H_ +#define WEBKIT_GLUE_DEVTOOLS_TOOLS_AGENT_H_ + +#include "webkit/glue/devtools/devtools_rpc.h" + +// Tools agent provides API for enabling / disabling other agents as well as +// API for auxiliary UI functions such as dom elements highlighting. +#define TOOLS_AGENT_STRUCT(METHOD0, METHOD1, METHOD2, METHOD3) \ + /* Enables / disables Dom agent. */ \ + METHOD1(SetDomAgentEnabled, bool /* enabled */) \ + \ + /* Enables / disables Net agent */ \ + METHOD1(SetNetAgentEnabled, bool /* enabled */) \ + \ + /* Highlights Dom node with given ID */ \ + METHOD1(HighlightDOMNode, int /* node_id */) \ + \ + /* Clears Dom Node highlight. */ \ + METHOD0(HideDOMNodeHighlight) + +DEFINE_RPC_CLASS(ToolsAgent, TOOLS_AGENT_STRUCT) + +#define TOOLS_AGENT_DELEGATE_STRUCT(METHOD0, METHOD1, METHOD2, METHOD3) \ + METHOD1(UpdateFocusedNode, int /* node_id */) \ + +DEFINE_RPC_CLASS(ToolsAgentDelegate, TOOLS_AGENT_DELEGATE_STRUCT) + +#endif // WEBKIT_GLUE_DEVTOOLS_TOOLS_AGENT_H_ |