summaryrefslogtreecommitdiffstats
path: root/webkit/glue/devtools
diff options
context:
space:
mode:
Diffstat (limited to 'webkit/glue/devtools')
-rw-r--r--webkit/glue/devtools/devtools_mock_rpc.h52
-rw-r--r--webkit/glue/devtools/devtools_rpc.cc86
-rw-r--r--webkit/glue/devtools/devtools_rpc.h324
-rw-r--r--webkit/glue/devtools/dom_agent.h60
-rw-r--r--webkit/glue/devtools/dom_agent_impl.cc406
-rw-r--r--webkit/glue/devtools/dom_agent_impl.h119
-rw-r--r--webkit/glue/devtools/dom_agent_unittest.cc421
-rw-r--r--webkit/glue/devtools/net_agent.h40
-rw-r--r--webkit/glue/devtools/net_agent_impl.cc204
-rw-r--r--webkit/glue/devtools/net_agent_impl.h80
-rw-r--r--webkit/glue/devtools/tools_agent.h32
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_