diff options
author | pfeldman@chromium.org <pfeldman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-12-19 11:44:19 +0000 |
---|---|---|
committer | pfeldman@chromium.org <pfeldman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-12-19 11:44:19 +0000 |
commit | 70019150e9f7f53074cacb335de0d238bbc37380 (patch) | |
tree | 444eddb81717f95b38585bfedde240d9643ba6a2 /content/browser/devtools | |
parent | 08b035b9918342155f54549486d6108807c3bfad (diff) | |
download | chromium_src-70019150e9f7f53074cacb335de0d238bbc37380.zip chromium_src-70019150e9f7f53074cacb335de0d238bbc37380.tar.gz chromium_src-70019150e9f7f53074cacb335de0d238bbc37380.tar.bz2 |
DevTools: rename debugger/ to devtools/, move DevTools files into content/renderer/devtools.
Review URL: https://codereview.chromium.org/11630004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@173903 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content/browser/devtools')
25 files changed, 3768 insertions, 0 deletions
diff --git a/content/browser/devtools/DEPS b/content/browser/devtools/DEPS new file mode 100644 index 0000000..3e98aee --- /dev/null +++ b/content/browser/devtools/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + # Generated by the local devtools_resources.gyp:devtools_resources + "+grit/devtools_resources_map.h", +] diff --git a/content/browser/devtools/OWNERS b/content/browser/devtools/OWNERS new file mode 100644 index 0000000..bb6028e --- /dev/null +++ b/content/browser/devtools/OWNERS @@ -0,0 +1,2 @@ +pfeldman@chromium.org +yurys@chromium.org diff --git a/content/browser/devtools/devtools_agent_host.cc b/content/browser/devtools/devtools_agent_host.cc new file mode 100644 index 0000000..fa5bc0e --- /dev/null +++ b/content/browser/devtools/devtools_agent_host.cc @@ -0,0 +1,57 @@ +// Copyright (c) 2012 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 "content/browser/devtools/devtools_agent_host.h" + +#include "base/basictypes.h" +#include "content/common/devtools_messages.h" + +namespace content { + +DevToolsAgentHost::DevToolsAgentHost() : close_listener_(NULL) { +} + +void DevToolsAgentHost::Attach() { + SendMessageToAgent(new DevToolsAgentMsg_Attach(MSG_ROUTING_NONE)); + NotifyClientAttaching(); +} + +void DevToolsAgentHost::Reattach(const std::string& saved_agent_state) { + SendMessageToAgent(new DevToolsAgentMsg_Reattach( + MSG_ROUTING_NONE, + saved_agent_state)); + NotifyClientAttaching(); +} + +void DevToolsAgentHost::Detach() { + SendMessageToAgent(new DevToolsAgentMsg_Detach(MSG_ROUTING_NONE)); + NotifyClientDetaching(); +} + +void DevToolsAgentHost::DipatchOnInspectorBackend(const std::string& message) { + SendMessageToAgent(new DevToolsAgentMsg_DispatchOnInspectorBackend( + MSG_ROUTING_NONE, message)); +} + +void DevToolsAgentHost::InspectElement(int x, int y) { + SendMessageToAgent(new DevToolsAgentMsg_InspectElement(MSG_ROUTING_NONE, + x, y)); +} + +void DevToolsAgentHost::AddMessageToConsole(ConsoleMessageLevel level, + const std::string& message) { + SendMessageToAgent(new DevToolsAgentMsg_AddMessageToConsole( + MSG_ROUTING_NONE, + level, + message)); +} + +void DevToolsAgentHost::NotifyCloseListener() { + if (close_listener_) { + close_listener_->AgentHostClosing(this); + close_listener_ = NULL; + } +} + +} // namespace content diff --git a/content/browser/devtools/devtools_agent_host.h b/content/browser/devtools/devtools_agent_host.h new file mode 100644 index 0000000..ae44862 --- /dev/null +++ b/content/browser/devtools/devtools_agent_host.h @@ -0,0 +1,59 @@ +// Copyright (c) 2012 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 CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_AGENT_HOST_H_ +#define CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_AGENT_HOST_H_ + +#include <string> + +#include "content/common/content_export.h" +#include "content/public/common/console_message_level.h" + +namespace IPC { +class Message; +} + +namespace content { + +// Describes interface for managing devtools agents from the browser process. +class CONTENT_EXPORT DevToolsAgentHost { + public: + class CONTENT_EXPORT CloseListener { + public: + virtual void AgentHostClosing(DevToolsAgentHost*) = 0; + protected: + virtual ~CloseListener() {} + }; + + // Sends the message to the devtools agent hosted by this object. + void Attach(); + void Reattach(const std::string& saved_agent_state); + void Detach(); + void DipatchOnInspectorBackend(const std::string& message); + void InspectElement(int x, int y); + void AddMessageToConsole(ConsoleMessageLevel level, + const std::string& message); + + virtual int GetRenderProcessId() = 0; + + void set_close_listener(CloseListener* listener) { + close_listener_ = listener; + } + + protected: + DevToolsAgentHost(); + virtual ~DevToolsAgentHost() {} + + virtual void SendMessageToAgent(IPC::Message* msg) = 0; + virtual void NotifyClientAttaching() = 0; + virtual void NotifyClientDetaching() = 0; + + void NotifyCloseListener(); + + CloseListener* close_listener_; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_AGENT_HOST_H_ diff --git a/content/browser/devtools/devtools_browser_target.cc b/content/browser/devtools/devtools_browser_target.cc new file mode 100644 index 0000000..44291b4 --- /dev/null +++ b/content/browser/devtools/devtools_browser_target.cc @@ -0,0 +1,118 @@ +// Copyright (c) 2012 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 "content/browser/devtools/devtools_browser_target.h" + +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/values.h" + + +namespace content { + +DevToolsBrowserTarget::DevToolsBrowserTarget(int connection_id) + : connection_id_(connection_id) { +} + +DevToolsBrowserTarget::~DevToolsBrowserTarget() { + for (HandlersMap::iterator i = handlers_.begin(); i != handlers_.end(); ++i) + delete i->second; +} + +void DevToolsBrowserTarget::RegisterHandler(Handler* handler) { + std::string domain = handler->Domain(); + DCHECK(handlers_.find(domain) == handlers_.end()); + handlers_[domain] = handler; +} + +std::string DevToolsBrowserTarget::HandleMessage(const std::string& data) { + int error_code; + std::string error_message; + scoped_ptr<base::Value> command( + base::JSONReader::ReadAndReturnError( + data, 0, &error_code, &error_message)); + + if (!command || !command->IsType(base::Value::TYPE_DICTIONARY)) + return SerializeErrorResponse( + -1, CreateErrorObject(error_code, error_message)); + + int request_id; + std::string domain; + std::string method; + base::DictionaryValue* command_dict = NULL; + bool ok = true; + ok &= command->GetAsDictionary(&command_dict); + ok &= command_dict->GetInteger("id", &request_id); + ok &= command_dict->GetString("method", &method); + if (!ok) + return SerializeErrorResponse( + request_id, CreateErrorObject(-1, "Malformed request")); + + base::DictionaryValue* params = NULL; + command_dict->GetDictionary("params", ¶ms); + + size_t pos = method.find("."); + if (pos == std::string::npos) + return SerializeErrorResponse( + request_id, CreateErrorObject(-1, "Method unsupported")); + + domain = method.substr(0, pos); + if (domain.empty() || handlers_.find(domain) == handlers_.end()) + return SerializeErrorResponse( + request_id, CreateErrorObject(-1, "Domain unsupported")); + + base::Value* error_object = NULL; + base::Value* domain_result = handlers_[domain]->OnProtocolCommand( + method, params, &error_object); + + if (error_object) + return SerializeErrorResponse(request_id, error_object); + + if (!domain_result) + return SerializeErrorResponse( + request_id, CreateErrorObject(-1, "Invalid call")); + + DictionaryValue* response = new DictionaryValue(); + response->Set("result", domain_result); + return SerializeResponse(request_id, response); +} + +std::string DevToolsBrowserTarget::SerializeErrorResponse( + int request_id, base::Value* error_object) { + scoped_ptr<base::DictionaryValue> error_response(new base::DictionaryValue()); + error_response->SetInteger("id", request_id); + error_response->Set("error", error_object); + // Serialize response. + std::string json_response; + base::JSONWriter::WriteWithOptions(error_response.get(), + base::JSONWriter::OPTIONS_PRETTY_PRINT, + &json_response); + return json_response; +} + +base::Value* DevToolsBrowserTarget::CreateErrorObject( + int error_code, const std::string& message) { + base::DictionaryValue* error_object = new base::DictionaryValue(); + error_object->SetInteger("code", error_code); + error_object->SetString("message", message); + return error_object; +} + +std::string DevToolsBrowserTarget::SerializeResponse( + int request_id, base::Value* response) { + scoped_ptr<base::DictionaryValue> ret(new base::DictionaryValue()); + ret->SetInteger("id", request_id); + ret->Set("response", response); + + // Serialize response. + std::string json_response; + base::JSONWriter::WriteWithOptions(ret.get(), + base::JSONWriter::OPTIONS_PRETTY_PRINT, + &json_response); + return json_response; +} + +} // namespace content diff --git a/content/browser/devtools/devtools_browser_target.h b/content/browser/devtools/devtools_browser_target.h new file mode 100644 index 0000000..a0a34e3 --- /dev/null +++ b/content/browser/devtools/devtools_browser_target.h @@ -0,0 +1,68 @@ +// Copyright (c) 2012 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 CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_BROWSER_TARGET_H_ +#define CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_BROWSER_TARGET_H_ + +#include <map> +#include <string> + +#include "base/basictypes.h" + +namespace base { + +class DictionaryValue; +class Value; + +} // namespace base + +namespace content { + +// This class handles the "Browser" target for remote debugging. +class DevToolsBrowserTarget { + public: + class Handler { + public: + virtual ~Handler() {} + + // Returns the domain name for this handler. + virtual std::string Domain() = 0; + + // |return_value| and |error_message_out| ownership is transferred to the + // caller. + virtual base::Value* OnProtocolCommand( + const std::string& method, + const base::DictionaryValue* params, + base::Value** error_message_out) = 0; + }; + + explicit DevToolsBrowserTarget(int connection_id); + ~DevToolsBrowserTarget(); + + int connection_id() const { return connection_id_; } + + // Takes ownership of |handler|. + void RegisterHandler(Handler* handler); + + std::string HandleMessage(const std::string& data); + + private: + const int connection_id_; + + typedef std::map<std::string, Handler*> HandlersMap; + HandlersMap handlers_; + + // Takes ownership of |error_object|. + std::string SerializeErrorResponse(int request_id, base::Value* error_object); + + base::Value* CreateErrorObject(int error_code, const std::string& message); + + std::string SerializeResponse(int request_id, base::Value* response); + + DISALLOW_COPY_AND_ASSIGN(DevToolsBrowserTarget); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_BROWSER_TARGET_H_ diff --git a/content/browser/devtools/devtools_frontend_host.cc b/content/browser/devtools/devtools_frontend_host.cc new file mode 100644 index 0000000..fe7e82d --- /dev/null +++ b/content/browser/devtools/devtools_frontend_host.cc @@ -0,0 +1,125 @@ +// Copyright (c) 2012 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 "content/browser/devtools/devtools_frontend_host.h" + +#include "content/browser/devtools/devtools_manager_impl.h" +#include "content/browser/renderer_host/render_view_host_impl.h" +#include "content/browser/web_contents/web_contents_impl.h" +#include "content/common/devtools_messages.h" +#include "content/public/browser/devtools_client_host.h" +#include "content/public/browser/devtools_frontend_host_delegate.h" + +namespace content { + +// static +DevToolsClientHost* DevToolsClientHost::CreateDevToolsFrontendHost( + WebContents* client_web_contents, + DevToolsFrontendHostDelegate* delegate) { + return new DevToolsFrontendHost( + static_cast<WebContentsImpl*>(client_web_contents), delegate); +} + +// static +void DevToolsClientHost::SetupDevToolsFrontendClient( + RenderViewHost* frontend_rvh) { + frontend_rvh->Send(new DevToolsMsg_SetupDevToolsClient( + frontend_rvh->GetRoutingID())); +} + +DevToolsFrontendHost::DevToolsFrontendHost( + WebContentsImpl* web_contents, + DevToolsFrontendHostDelegate* delegate) + : RenderViewHostObserver(web_contents->GetRenderViewHost()), + web_contents_(web_contents), + delegate_(delegate) { +} + +DevToolsFrontendHost::~DevToolsFrontendHost() { + DevToolsManager::GetInstance()->ClientHostClosing(this); +} + +void DevToolsFrontendHost::DispatchOnInspectorFrontend( + const std::string& message) { + RenderViewHostImpl* target_host = + static_cast<RenderViewHostImpl*>(web_contents_->GetRenderViewHost()); + target_host->Send(new DevToolsClientMsg_DispatchOnInspectorFrontend( + target_host->GetRoutingID(), + message)); +} + +void DevToolsFrontendHost::InspectedContentsClosing() { + delegate_->InspectedContentsClosing(); +} + +void DevToolsFrontendHost::FrameNavigating(const std::string& url) { + delegate_->FrameNavigating(url); +} + +void DevToolsFrontendHost::ContentsReplaced(WebContents* new_contents) { + delegate_->ContentsReplaced(new_contents); +} + +void DevToolsFrontendHost::ReplacedWithAnotherClient() { +} + +bool DevToolsFrontendHost::OnMessageReceived( + const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(DevToolsFrontendHost, message) + IPC_MESSAGE_HANDLER(DevToolsAgentMsg_DispatchOnInspectorBackend, + OnDispatchOnInspectorBackend) + IPC_MESSAGE_HANDLER(DevToolsHostMsg_ActivateWindow, OnActivateWindow) + IPC_MESSAGE_HANDLER(DevToolsHostMsg_CloseWindow, OnCloseWindow) + IPC_MESSAGE_HANDLER(DevToolsHostMsg_MoveWindow, OnMoveWindow) + IPC_MESSAGE_HANDLER(DevToolsHostMsg_RequestSetDockSide, + OnRequestSetDockSide) + IPC_MESSAGE_HANDLER(DevToolsHostMsg_OpenInNewTab, OnOpenInNewTab) + IPC_MESSAGE_HANDLER(DevToolsHostMsg_Save, OnSave) + IPC_MESSAGE_HANDLER(DevToolsHostMsg_Append, OnAppend) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void DevToolsFrontendHost::OnDispatchOnInspectorBackend( + const std::string& message) { + DevToolsManagerImpl::GetInstance()->DispatchOnInspectorBackend(this, message); +// delegate_->DispatchOnInspectorBackend(message); +} + +void DevToolsFrontendHost::OnActivateWindow() { + delegate_->ActivateWindow(); +} + +void DevToolsFrontendHost::OnCloseWindow() { + delegate_->CloseWindow(); +} + +void DevToolsFrontendHost::OnMoveWindow(int x, int y) { + delegate_->MoveWindow(x, y); +} + +void DevToolsFrontendHost::OnOpenInNewTab(const std::string& url) { + delegate_->OpenInNewTab(url); +} + +void DevToolsFrontendHost::OnSave( + const std::string& url, + const std::string& content, + bool save_as) { + delegate_->SaveToFile(url, content, save_as); +} + +void DevToolsFrontendHost::OnAppend( + const std::string& url, + const std::string& content) { + delegate_->AppendToFile(url, content); +} + +void DevToolsFrontendHost::OnRequestSetDockSide(const std::string& side) { + delegate_->SetDockSide(side); +} + +} // namespace content diff --git a/content/browser/devtools/devtools_frontend_host.h b/content/browser/devtools/devtools_frontend_host.h new file mode 100644 index 0000000..6168692 --- /dev/null +++ b/content/browser/devtools/devtools_frontend_host.h @@ -0,0 +1,59 @@ +// Copyright (c) 2012 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 CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_FRONTEND_HOST_H_ +#define CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_FRONTEND_HOST_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "content/public/browser/devtools_client_host.h" +#include "content/public/browser/render_view_host_observer.h" + +namespace content { + +class DevToolsFrontendHostDelegate; +class WebContentsImpl; + +// This class handles messages from DevToolsClient and calls corresponding +// methods on DevToolsFrontendHostDelegate which is implemented by the +// embedder. This allows us to avoid exposing DevTools client messages through +// the content public API. +class DevToolsFrontendHost : public DevToolsClientHost, + public RenderViewHostObserver { + public: + DevToolsFrontendHost(WebContentsImpl* web_contents, + DevToolsFrontendHostDelegate* delegate); + + private: + virtual ~DevToolsFrontendHost(); + + // DevToolsClientHost implementation. + virtual void DispatchOnInspectorFrontend(const std::string& message) OVERRIDE; + virtual void InspectedContentsClosing() OVERRIDE; + virtual void FrameNavigating(const std::string& url) OVERRIDE; + virtual void ContentsReplaced(WebContents* new_contents) OVERRIDE; + virtual void ReplacedWithAnotherClient() OVERRIDE; + + // RenderViewHostObserver overrides. + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + + void OnDispatchOnInspectorBackend(const std::string& message); + void OnActivateWindow(); + void OnCloseWindow(); + void OnMoveWindow(int x, int y); + void OnRequestSetDockSide(const std::string& side); + void OnOpenInNewTab(const std::string& url); + void OnSave(const std::string& url, const std::string& content, bool save_as); + void OnAppend(const std::string& url, const std::string& content); + + WebContentsImpl* web_contents_; + DevToolsFrontendHostDelegate* delegate_; + DISALLOW_COPY_AND_ASSIGN(DevToolsFrontendHost); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_FRONTEND_HOST_H_ diff --git a/content/browser/devtools/devtools_http_handler_impl.cc b/content/browser/devtools/devtools_http_handler_impl.cc new file mode 100644 index 0000000..6d214ab --- /dev/null +++ b/content/browser/devtools/devtools_http_handler_impl.cc @@ -0,0 +1,873 @@ +// Copyright (c) 2012 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 "content/browser/devtools/devtools_http_handler_impl.h" + +#include <algorithm> +#include <utility> + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/file_util.h" +#include "base/json/json_writer.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/message_loop_proxy.h" +#include "base/string_number_conversions.h" +#include "base/stringprintf.h" +#include "base/threading/thread.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "content/browser/devtools/devtools_browser_target.h" +#include "content/browser/devtools/devtools_tracing_handler.h" +#include "content/browser/web_contents/web_contents_impl.h" +#include "content/common/devtools_messages.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/devtools_agent_host_registry.h" +#include "content/public/browser/devtools_client_host.h" +#include "content/public/browser/devtools_http_handler_delegate.h" +#include "content/public/browser/devtools_manager.h" +#include "content/public/browser/favicon_status.h" +#include "content/public/browser/navigation_entry.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_types.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/render_widget_host.h" +#include "content/public/common/content_client.h" +#include "content/public/common/url_constants.h" +#include "googleurl/src/gurl.h" +#include "grit/devtools_resources_map.h" +#include "net/base/escape.h" +#include "net/base/io_buffer.h" +#include "net/base/ip_endpoint.h" +#include "net/server/http_server_request_info.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebDevToolsAgent.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebString.h" +#include "ui/base/layout.h" +#include "webkit/user_agent/user_agent.h" +#include "webkit/user_agent/user_agent_util.h" + +namespace content { + +const int kBufferSize = 16 * 1024; + +namespace { + +static const char* kDevToolsHandlerThreadName = "Chrome_DevToolsHandlerThread"; + +class DevToolsDefaultBindingHandler + : public DevToolsHttpHandler::RenderViewHostBinding { + public: + DevToolsDefaultBindingHandler() { + } + + virtual std::string GetIdentifier(RenderViewHost* rvh) OVERRIDE { + int process_id = rvh->GetProcess()->GetID(); + int routing_id = rvh->GetRoutingID(); + return base::StringPrintf("%d_%d", process_id, routing_id); + } + + virtual RenderViewHost* ForIdentifier( + const std::string& identifier) OVERRIDE { + size_t pos = identifier.find("_"); + if (pos == std::string::npos) + return NULL; + + int process_id; + if (!base::StringToInt(identifier.substr(0, pos), &process_id)) + return NULL; + + int routing_id; + if (!base::StringToInt(identifier.substr(pos+1), &routing_id)) + return NULL; + + return RenderViewHost::FromID(process_id, routing_id); + } +}; + + +// An internal implementation of DevToolsClientHost that delegates +// messages sent for DevToolsClient to a DebuggerShell instance. +class DevToolsClientHostImpl : public DevToolsClientHost { + public: + DevToolsClientHostImpl( + MessageLoop* message_loop, + net::HttpServer* server, + int connection_id) + : message_loop_(message_loop), + server_(server), + connection_id_(connection_id), + is_closed_(false), + detach_reason_("target_closed") { + } + + ~DevToolsClientHostImpl() {} + + // DevToolsClientHost interface + virtual void InspectedContentsClosing() { + if (is_closed_) + return; + is_closed_ = true; + + std::string response = + WebKit::WebDevToolsAgent::inspectorDetachedEvent( + WebKit::WebString::fromUTF8(detach_reason_)).utf8(); + message_loop_->PostTask( + FROM_HERE, + base::Bind(&net::HttpServer::SendOverWebSocket, + server_, + connection_id_, + response)); + + message_loop_->PostTask( + FROM_HERE, + base::Bind(&net::HttpServer::Close, server_, connection_id_)); + } + + virtual void DispatchOnInspectorFrontend(const std::string& data) { + message_loop_->PostTask( + FROM_HERE, + base::Bind(&net::HttpServer::SendOverWebSocket, + server_, + connection_id_, + data)); + } + + virtual void ContentsReplaced(WebContents* new_contents) { + } + + virtual void ReplacedWithAnotherClient() { + detach_reason_ = "replaced_with_devtools"; + } + + private: + virtual void FrameNavigating(const std::string& url) {} + MessageLoop* message_loop_; + net::HttpServer* server_; + int connection_id_; + bool is_closed_; + std::string detach_reason_; +}; + +} // namespace + +// static +int DevToolsHttpHandler::GetFrontendResourceId(const std::string& name) { + for (size_t i = 0; i < kDevtoolsResourcesSize; ++i) { + if (name == kDevtoolsResources[i].name) + return kDevtoolsResources[i].value; + } + return -1; +} + +// static +DevToolsHttpHandler* DevToolsHttpHandler::Start( + const net::StreamListenSocketFactory* socket_factory, + const std::string& frontend_url, + DevToolsHttpHandlerDelegate* delegate) { + DevToolsHttpHandlerImpl* http_handler = + new DevToolsHttpHandlerImpl(socket_factory, + frontend_url, + delegate); + http_handler->Start(); + return http_handler; +} + +DevToolsHttpHandlerImpl::~DevToolsHttpHandlerImpl() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + // Stop() must be called prior to destruction. + DCHECK(server_.get() == NULL); + DCHECK(thread_.get() == NULL); +} + +void DevToolsHttpHandlerImpl::Start() { + if (thread_.get()) + return; + thread_.reset(new base::Thread(kDevToolsHandlerThreadName)); + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&DevToolsHttpHandlerImpl::StartHandlerThread, this)); +} + +// Runs on FILE thread. +void DevToolsHttpHandlerImpl::StartHandlerThread() { + base::Thread::Options options; + options.message_loop_type = MessageLoop::TYPE_IO; + if (!thread_->StartWithOptions(options)) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&DevToolsHttpHandlerImpl::ResetHandlerThread, this)); + return; + } + + thread_->message_loop()->PostTask( + FROM_HERE, + base::Bind(&DevToolsHttpHandlerImpl::Init, this)); +} + +void DevToolsHttpHandlerImpl::ResetHandlerThread() { + thread_.reset(); +} + +void DevToolsHttpHandlerImpl::ResetHandlerThreadAndRelease() { + ResetHandlerThread(); + Release(); +} + +void DevToolsHttpHandlerImpl::Stop() { + if (!thread_.get()) + return; + BrowserThread::PostTaskAndReply( + BrowserThread::FILE, FROM_HERE, + base::Bind(&DevToolsHttpHandlerImpl::StopHandlerThread, this), + base::Bind(&DevToolsHttpHandlerImpl::ResetHandlerThreadAndRelease, this)); +} + +void DevToolsHttpHandlerImpl::SetRenderViewHostBinding( + RenderViewHostBinding* binding) { + if (binding) + binding_ = binding; + else + binding_ = default_binding_.get(); +} + +GURL DevToolsHttpHandlerImpl::GetFrontendURL(RenderViewHost* render_view_host) { + net::IPEndPoint ip_address; + if (server_->GetLocalAddress(&ip_address)) + return GURL(); + std::string host = ip_address.ToString(); + std::string id = binding_->GetIdentifier(render_view_host); + return GURL(std::string("http://") + + ip_address.ToString() + + GetFrontendURLInternal(id, host)); +} + +static std::string PathWithoutParams(const std::string& path) { + size_t query_position = path.find("?"); + if (query_position != std::string::npos) + return path.substr(0, query_position); + return path; +} + +static std::string GetMimeType(const std::string& filename) { + if (EndsWith(filename, ".html", false)) { + return "text/html"; + } else if (EndsWith(filename, ".css", false)) { + return "text/css"; + } else if (EndsWith(filename, ".js", false)) { + return "application/javascript"; + } else if (EndsWith(filename, ".png", false)) { + return "image/png"; + } else if (EndsWith(filename, ".gif", false)) { + return "image/gif"; + } + NOTREACHED(); + return "text/plain"; +} + +void DevToolsHttpHandlerImpl::Observe(int type, + const NotificationSource& source, + const NotificationDetails& details) { + RenderProcessHost* process = Source<RenderProcessHost>(source).ptr(); + DevToolsManager* manager = DevToolsManager::GetInstance(); + for (ConnectionToClientHostMap::iterator it = + connection_to_client_host_ui_.begin(); + it != connection_to_client_host_ui_.end(); ++it) { + DevToolsAgentHost* agent = manager->GetDevToolsAgentHostFor(it->second); + if (!agent) + continue; + RenderViewHost* rvh = DevToolsAgentHostRegistry::GetRenderViewHost(agent); + if (rvh && rvh->GetProcess() == process) + it->second->InspectedContentsClosing(); + } +} + +void DevToolsHttpHandlerImpl::OnHttpRequest( + int connection_id, + const net::HttpServerRequestInfo& info) { + if (info.path.find("/json") == 0) { + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind(&DevToolsHttpHandlerImpl::OnJsonRequestUI, + this, + connection_id, + info)); + return; + } + + if (info.path.find("/thumb/") == 0) { + // Thumbnail request. + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind(&DevToolsHttpHandlerImpl::OnThumbnailRequestUI, + this, + connection_id, + info)); + return; + } + + if (info.path == "" || info.path == "/") { + // Discovery page request. + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind(&DevToolsHttpHandlerImpl::OnDiscoveryPageRequestUI, + this, + connection_id)); + return; + } + + if (info.path.find("/devtools/") != 0) { + server_->Send404(connection_id); + return; + } + + std::string filename = PathWithoutParams(info.path.substr(10)); + std::string mime_type = GetMimeType(filename); + + FilePath frontend_dir = delegate_->GetDebugFrontendDir(); + if (!frontend_dir.empty()) { + FilePath path = frontend_dir.AppendASCII(filename); + std::string data; + file_util::ReadFileToString(path, &data); + server_->Send200(connection_id, data, mime_type); + return; + } + if (delegate_->BundlesFrontendResources()) { + int resource_id = DevToolsHttpHandler::GetFrontendResourceId(filename); + if (resource_id != -1) { + base::StringPiece data = GetContentClient()->GetDataResource( + resource_id, ui::SCALE_FACTOR_NONE); + server_->Send200(connection_id, data.as_string(), mime_type); + return; + } + } + server_->Send404(connection_id); +} + +void DevToolsHttpHandlerImpl::OnWebSocketRequest( + int connection_id, + const net::HttpServerRequestInfo& request) { + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind( + &DevToolsHttpHandlerImpl::OnWebSocketRequestUI, + this, + connection_id, + request)); +} + +void DevToolsHttpHandlerImpl::OnWebSocketMessage( + int connection_id, + const std::string& data) { + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind( + &DevToolsHttpHandlerImpl::OnWebSocketMessageUI, + this, + connection_id, + data)); +} + +void DevToolsHttpHandlerImpl::OnClose(int connection_id) { + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind( + &DevToolsHttpHandlerImpl::OnCloseUI, + this, + connection_id)); +} + +struct DevToolsHttpHandlerImpl::PageInfo { + PageInfo() + : attached(false) { + } + + std::string id; + std::string url; + bool attached; + std::string title; + std::string thumbnail_url; + std::string favicon_url; + base::TimeTicks last_selected_time; +}; + +// static +bool DevToolsHttpHandlerImpl::SortPageListByTime(const PageInfo& info1, + const PageInfo& info2) { + return info1.last_selected_time > info2.last_selected_time; +} + +DevToolsHttpHandlerImpl::PageList DevToolsHttpHandlerImpl::GeneratePageList() { + PageList page_list; + for (RenderProcessHost::iterator it(RenderProcessHost::AllHostsIterator()); + !it.IsAtEnd(); it.Advance()) { + RenderProcessHost* render_process_host = it.GetCurrentValue(); + DCHECK(render_process_host); + + // Ignore processes that don't have a connection, such as crashed contents. + if (!render_process_host->HasConnection()) + continue; + + RenderProcessHost::RenderWidgetHostsIterator rwit( + render_process_host->GetRenderWidgetHostsIterator()); + for (; !rwit.IsAtEnd(); rwit.Advance()) { + const RenderWidgetHost* widget = rwit.GetCurrentValue(); + DCHECK(widget); + if (!widget || !widget->IsRenderView()) + continue; + + RenderViewHost* host = + RenderViewHost::From(const_cast<RenderWidgetHost*>(widget)); + page_list.push_back(CreatePageInfo(host)); + } + } + std::sort(page_list.begin(), page_list.end(), SortPageListByTime); + return page_list; +} + +std::string DevToolsHttpHandlerImpl::GetFrontendURLInternal( + const std::string rvh_id, + const std::string& host) { + return base::StringPrintf( + "%s%sws=%s/devtools/page/%s", + overridden_frontend_url_.c_str(), + overridden_frontend_url_.find("?") == std::string::npos ? "?" : "&", + host.c_str(), + rvh_id.c_str()); +} + +static bool ParseJsonPath( + const std::string& path, + std::string* command, + std::string* target_id) { + + // Fall back to list in case of empty query. + if (path.empty()) { + *command = "list"; + return true; + } + + if (path.find("/") != 0) { + // Malformed command. + return false; + } + *command = path.substr(1); + + size_t separator_pos = command->find("/"); + if (separator_pos != std::string::npos) { + *target_id = command->substr(separator_pos + 1); + *command = command->substr(0, separator_pos); + } + return true; +} + +void DevToolsHttpHandlerImpl::OnJsonRequestUI( + int connection_id, + const net::HttpServerRequestInfo& info) { + // Trim /json and ?jsonp=... + std::string path = info.path.substr(5); + std::string jsonp; + size_t jsonp_pos = path.find("?jsonp="); + if (jsonp_pos != std::string::npos) { + jsonp = path.substr(jsonp_pos + 7); + path = path.substr(0, jsonp_pos); + } + + // Trim fragment and query + size_t query_pos = path.find("?"); + if (query_pos != std::string::npos) + path = path.substr(0, query_pos); + + size_t fragment_pos = path.find("#"); + if (fragment_pos != std::string::npos) + path = path.substr(0, fragment_pos); + + std::string command; + std::string target_id; + if (!ParseJsonPath(path, &command, &target_id)) { + SendJson(connection_id, + net::HTTP_NOT_FOUND, + NULL, + "Malformed query: " + info.path, + jsonp); + return; + } + + if (command == "version") { + DictionaryValue version; + version.SetString("Protocol-Version", + WebKit::WebDevToolsAgent::inspectorProtocolVersion()); + version.SetString("WebKit-Version", + webkit_glue::GetWebKitVersion()); + version.SetString("User-Agent", + webkit_glue::GetUserAgent(GURL(chrome::kAboutBlankURL))); + SendJson(connection_id, net::HTTP_OK, &version, "", jsonp); + return; + } + + if (command == "list") { + PageList page_list = GeneratePageList(); + ListValue json_pages_list; + std::string host = info.headers["Host"]; + for (PageList::iterator i = page_list.begin(); i != page_list.end(); ++i) + json_pages_list.Append(SerializePageInfo(*i, host)); + SendJson(connection_id, net::HTTP_OK, &json_pages_list, "", jsonp); + return; + } + + if (command == "new") { + RenderViewHost* rvh = delegate_->CreateNewTarget(); + if (!rvh) { + SendJson(connection_id, + net::HTTP_INTERNAL_SERVER_ERROR, + NULL, + "Could not create new page", + jsonp); + return; + } + PageInfo page_info = CreatePageInfo(rvh); + std::string host = info.headers["Host"]; + scoped_ptr<DictionaryValue> dictionary(SerializePageInfo(page_info, host)); + SendJson(connection_id, net::HTTP_OK, dictionary.get(), "", jsonp); + return; + } + + if (command == "activate" || command == "close") { + RenderViewHost* rvh = binding_->ForIdentifier(target_id); + if (!rvh) { + SendJson(connection_id, + net::HTTP_NOT_FOUND, + NULL, + "No such target id: " + target_id, + jsonp); + return; + } + + if (command == "activate") { + rvh->GetDelegate()->Activate(); + SendJson(connection_id, net::HTTP_OK, NULL, "Target activated", jsonp); + return; + } + + if (command == "close") { + rvh->ClosePage(); + SendJson(connection_id, net::HTTP_OK, NULL, "Target is closing", jsonp); + return; + } + } + SendJson(connection_id, + net::HTTP_NOT_FOUND, + NULL, + "Unknown command: " + command, + jsonp); + return; +} + +void DevToolsHttpHandlerImpl::OnThumbnailRequestUI( + int connection_id, + const net::HttpServerRequestInfo& info) { + std::string prefix = "/thumb/"; + size_t pos = info.path.find(prefix); + if (pos != 0) { + Send404(connection_id); + return; + } + + std::string page_url = info.path.substr(prefix.length()); + std::string data = delegate_->GetPageThumbnailData(GURL(page_url)); + if (!data.empty()) + Send200(connection_id, data, "image/png"); + else + Send404(connection_id); +} + +void DevToolsHttpHandlerImpl::OnDiscoveryPageRequestUI(int connection_id) { + std::string response = delegate_->GetDiscoveryPageHTML(); + Send200(connection_id, response, "text/html; charset=UTF-8"); +} + +void DevToolsHttpHandlerImpl::OnWebSocketRequestUI( + int connection_id, + const net::HttpServerRequestInfo& request) { + if (!thread_.get()) + return; + std::string browser_prefix = "/devtools/browser"; + size_t browser_pos = request.path.find(browser_prefix); + if (browser_pos == 0) { + if (browser_target_) { + Send500(connection_id, "Another client already attached"); + return; + } + browser_target_.reset(new DevToolsBrowserTarget(connection_id)); + browser_target_->RegisterHandler(new DevToolsTracingHandler()); + AcceptWebSocket(connection_id, request); + return; + } + + std::string page_prefix = "/devtools/page/"; + size_t pos = request.path.find(page_prefix); + if (pos != 0) { + Send404(connection_id); + return; + } + + std::string page_id = request.path.substr(page_prefix.length()); + RenderViewHost* rvh = binding_->ForIdentifier(page_id); + if (!rvh) { + Send500(connection_id, "No such target id: " + page_id); + return; + } + + DevToolsManager* manager = DevToolsManager::GetInstance(); + DevToolsAgentHost* agent = DevToolsAgentHostRegistry::GetDevToolsAgentHost( + rvh); + if (manager->GetDevToolsClientHostFor(agent)) { + Send500(connection_id, + "Target with given id is being inspected: " + page_id); + return; + } + + DevToolsClientHostImpl* client_host = + new DevToolsClientHostImpl(thread_->message_loop(), + server_, + connection_id); + connection_to_client_host_ui_[connection_id] = client_host; + + manager->RegisterDevToolsClientHostFor(agent, client_host); + + AcceptWebSocket(connection_id, request); +} + +void DevToolsHttpHandlerImpl::OnWebSocketMessageUI( + int connection_id, + const std::string& data) { + if (browser_target_ && connection_id == browser_target_->connection_id()) { + std::string json_response = browser_target_->HandleMessage(data); + + thread_->message_loop()->PostTask( + FROM_HERE, + base::Bind(&net::HttpServer::SendOverWebSocket, + server_.get(), + connection_id, + json_response)); + return; + } + + ConnectionToClientHostMap::iterator it = + connection_to_client_host_ui_.find(connection_id); + if (it == connection_to_client_host_ui_.end()) + return; + + DevToolsManager* manager = DevToolsManager::GetInstance(); + manager->DispatchOnInspectorBackend(it->second, data); +} + +void DevToolsHttpHandlerImpl::OnCloseUI(int connection_id) { + ConnectionToClientHostMap::iterator it = + connection_to_client_host_ui_.find(connection_id); + if (it != connection_to_client_host_ui_.end()) { + DevToolsClientHostImpl* client_host = + static_cast<DevToolsClientHostImpl*>(it->second); + DevToolsManager::GetInstance()->ClientHostClosing(client_host); + delete client_host; + connection_to_client_host_ui_.erase(connection_id); + } + if (browser_target_ && browser_target_->connection_id() == connection_id) { + browser_target_.reset(); + return; + } +} + +DevToolsHttpHandlerImpl::DevToolsHttpHandlerImpl( + const net::StreamListenSocketFactory* socket_factory, + const std::string& frontend_url, + DevToolsHttpHandlerDelegate* delegate) + : overridden_frontend_url_(frontend_url), + socket_factory_(socket_factory), + delegate_(delegate) { + if (overridden_frontend_url_.empty()) + overridden_frontend_url_ = "/devtools/devtools.html"; + + default_binding_.reset(new DevToolsDefaultBindingHandler); + binding_ = default_binding_.get(); + + registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_TERMINATED, + NotificationService::AllBrowserContextsAndSources()); + registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_CLOSED, + NotificationService::AllBrowserContextsAndSources()); + + // Balanced in ResetHandlerThreadAndRelease(). + AddRef(); +} + +// Runs on the handler thread +void DevToolsHttpHandlerImpl::Init() { + server_ = new net::HttpServer(*socket_factory_.get(), this); +} + +// Runs on the handler thread +void DevToolsHttpHandlerImpl::Teardown() { + server_ = NULL; +} + +// Runs on FILE thread to make sure that it is serialized against +// {Start|Stop}HandlerThread and to allow calling pthread_join. +void DevToolsHttpHandlerImpl::StopHandlerThread() { + if (!thread_->message_loop()) + return; + thread_->message_loop()->PostTask( + FROM_HERE, + base::Bind(&DevToolsHttpHandlerImpl::Teardown, this)); + // Thread::Stop joins the thread. + thread_->Stop(); +} + +void DevToolsHttpHandlerImpl::SendJson(int connection_id, + net::HttpStatusCode status_code, + Value* value, + const std::string& message, + const std::string& jsonp) { + if (!thread_.get()) + return; + + // Serialize value and message. + std::string json_value; + if (value) { + base::JSONWriter::WriteWithOptions(value, + base::JSONWriter::OPTIONS_PRETTY_PRINT, + &json_value); + } + std::string json_message; + scoped_ptr<Value> message_object(Value::CreateStringValue(message)); + base::JSONWriter::Write(message_object.get(), &json_message); + + std::string response; + std::string mime_type = "application/json; charset=UTF-8"; + + // Wrap jsonp if necessary. + if (!jsonp.empty()) { + mime_type = "text/javascript; charset=UTF-8"; + response = StringPrintf("%s(%s, %d, %s);", + jsonp.c_str(), + json_value.empty() ? "undefined" + : json_value.c_str(), + status_code, + json_message.c_str()); + // JSONP always returns 200. + status_code = net::HTTP_OK; + } else { + response = StringPrintf("%s%s", json_value.c_str(), message.c_str()); + } + + thread_->message_loop()->PostTask( + FROM_HERE, + base::Bind(&net::HttpServer::Send, + server_.get(), + connection_id, + status_code, + response, + mime_type)); +} + +void DevToolsHttpHandlerImpl::Send200(int connection_id, + const std::string& data, + const std::string& mime_type) { + if (!thread_.get()) + return; + thread_->message_loop()->PostTask( + FROM_HERE, + base::Bind(&net::HttpServer::Send200, + server_.get(), + connection_id, + data, + mime_type)); +} + +void DevToolsHttpHandlerImpl::Send404(int connection_id) { + if (!thread_.get()) + return; + thread_->message_loop()->PostTask( + FROM_HERE, + base::Bind(&net::HttpServer::Send404, server_.get(), connection_id)); +} + +void DevToolsHttpHandlerImpl::Send500(int connection_id, + const std::string& message) { + if (!thread_.get()) + return; + thread_->message_loop()->PostTask( + FROM_HERE, + base::Bind(&net::HttpServer::Send500, server_.get(), connection_id, + message)); +} + +void DevToolsHttpHandlerImpl::AcceptWebSocket( + int connection_id, + const net::HttpServerRequestInfo& request) { + if (!thread_.get()) + return; + thread_->message_loop()->PostTask( + FROM_HERE, + base::Bind(&net::HttpServer::AcceptWebSocket, server_.get(), + connection_id, request)); +} + +DevToolsHttpHandlerImpl::PageInfo +DevToolsHttpHandlerImpl::CreatePageInfo(RenderViewHost* rvh) +{ + RenderViewHostDelegate* host_delegate = rvh->GetDelegate(); + DevToolsAgentHost* agent = + DevToolsAgentHostRegistry::GetDevToolsAgentHost(rvh); + DevToolsClientHost* client_host = DevToolsManager::GetInstance()-> + GetDevToolsClientHostFor(agent); + PageInfo page_info; + page_info.id = binding_->GetIdentifier(rvh); + page_info.attached = client_host != NULL; + page_info.url = host_delegate->GetURL().spec(); + + WebContents* web_contents = host_delegate->GetAsWebContents(); + if (web_contents) { + page_info.title = UTF16ToUTF8( + net::EscapeForHTML(web_contents->GetTitle())); + page_info.last_selected_time = web_contents->GetLastSelectedTime(); + + NavigationController& controller = web_contents->GetController(); + NavigationEntry* entry = controller.GetActiveEntry(); + if (entry != NULL && entry->GetURL().is_valid()) { + page_info.thumbnail_url = "/thumb/" + entry->GetURL().spec(); + page_info.favicon_url = entry->GetFavicon().url.spec(); + } + } + return page_info; +} + +DictionaryValue* DevToolsHttpHandlerImpl::SerializePageInfo( + const PageInfo& page_info, + const std::string& host) { + DictionaryValue* dictionary = new DictionaryValue; + dictionary->SetString("title", page_info.title); + dictionary->SetString("url", page_info.url); + dictionary->SetString("thumbnailUrl", page_info.thumbnail_url); + dictionary->SetString("faviconUrl", page_info.favicon_url); + if (!page_info.attached) { + dictionary->SetString("webSocketDebuggerUrl", + base::StringPrintf("ws://%s/devtools/page/%s", + host.c_str(), + page_info.id.c_str())); + std::string devtools_frontend_url = GetFrontendURLInternal( + page_info.id.c_str(), + host); + dictionary->SetString("devtoolsFrontendUrl", devtools_frontend_url); + } + return dictionary; +} + +} // namespace content diff --git a/content/browser/devtools/devtools_http_handler_impl.h b/content/browser/devtools/devtools_http_handler_impl.h new file mode 100644 index 0000000..9ee6f92 --- /dev/null +++ b/content/browser/devtools/devtools_http_handler_impl.h @@ -0,0 +1,143 @@ +// Copyright (c) 2012 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 CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_HTTP_HANDLER_IMPL_H_ +#define CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_HTTP_HANDLER_IMPL_H_ + +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "content/common/content_export.h" +#include "content/public/browser/devtools_http_handler.h" +#include "content/public/browser/devtools_http_handler_delegate.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "net/server/http_server.h" + +namespace base { +class DictionaryValue; +class Thread; +class Value; +} + +namespace net { +class StreamListenSocketFactory; +class URLRequestContextGetter; +} + +namespace content { + +class DevToolsBrowserTarget; +class DevToolsClientHost; +class RenderViewHost; + +class DevToolsHttpHandlerImpl + : public DevToolsHttpHandler, + public NotificationObserver, + public base::RefCountedThreadSafe<DevToolsHttpHandlerImpl>, + public net::HttpServer::Delegate { + private: + struct PageInfo; + typedef std::vector<PageInfo> PageList; + friend class base::RefCountedThreadSafe<DevToolsHttpHandlerImpl>; + friend class DevToolsHttpHandler; + + static bool SortPageListByTime(const PageInfo& info1, const PageInfo& info2); + + // Takes ownership over |socket_factory|. + DevToolsHttpHandlerImpl(const net::StreamListenSocketFactory* socket_factory, + const std::string& frontend_url, + DevToolsHttpHandlerDelegate* delegate); + virtual ~DevToolsHttpHandlerImpl(); + void Start(); + + // DevToolsHttpHandler implementation. + virtual void Stop() OVERRIDE; + virtual void SetRenderViewHostBinding( + RenderViewHostBinding* binding) OVERRIDE; + virtual GURL GetFrontendURL(RenderViewHost* render_view_host) OVERRIDE; + + // NotificationObserver implementation. + virtual void Observe(int type, + const NotificationSource& source, + const NotificationDetails& details) OVERRIDE; + + // net::HttpServer::Delegate implementation. + virtual void OnHttpRequest(int connection_id, + const net::HttpServerRequestInfo& info) OVERRIDE; + virtual void OnWebSocketRequest( + int connection_id, + const net::HttpServerRequestInfo& info) OVERRIDE; + virtual void OnWebSocketMessage(int connection_id, + const std::string& data) OVERRIDE; + virtual void OnClose(int connection_id) OVERRIDE; + + void OnJsonRequestUI(int connection_id, + const net::HttpServerRequestInfo& info); + void OnThumbnailRequestUI(int connection_id, + const net::HttpServerRequestInfo& info); + void OnDiscoveryPageRequestUI(int connection_id); + + void OnWebSocketRequestUI(int connection_id, + const net::HttpServerRequestInfo& info); + void OnWebSocketMessageUI(int connection_id, const std::string& data); + void OnCloseUI(int connection_id); + + void ResetHandlerThread(); + void ResetHandlerThreadAndRelease(); + + void Init(); + void Teardown(); + + void StartHandlerThread(); + void StopHandlerThread(); + + void SendJson(int connection_id, + net::HttpStatusCode status_code, + base::Value* value, + const std::string& message, + const std::string& jsonp); + void Send200(int connection_id, + const std::string& data, + const std::string& mime_type); + void Send404(int connection_id); + void Send500(int connection_id, + const std::string& message); + void AcceptWebSocket(int connection_id, + const net::HttpServerRequestInfo& request); + + PageList GeneratePageList(); + + // Returns the front end url without the host at the beginning. + std::string GetFrontendURLInternal(const std::string rvh_id, + const std::string& host); + + PageInfo CreatePageInfo(RenderViewHost* rvh); + + base::DictionaryValue* SerializePageInfo(const PageInfo& page_info, + const std::string& host); + + // The thread used by the devtools handler to run server socket. + scoped_ptr<base::Thread> thread_; + + std::string overridden_frontend_url_; + scoped_ptr<const net::StreamListenSocketFactory> socket_factory_; + scoped_refptr<net::HttpServer> server_; + typedef std::map<int, DevToolsClientHost*> ConnectionToClientHostMap; + ConnectionToClientHostMap connection_to_client_host_ui_; + scoped_ptr<DevToolsHttpHandlerDelegate> delegate_; + RenderViewHostBinding* binding_; + scoped_ptr<RenderViewHostBinding> default_binding_; + NotificationRegistrar registrar_; + scoped_ptr<DevToolsBrowserTarget> browser_target_; + DISALLOW_COPY_AND_ASSIGN(DevToolsHttpHandlerImpl); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_HTTP_HANDLER_IMPL_H_ diff --git a/content/browser/devtools/devtools_http_handler_unittest.cc b/content/browser/devtools/devtools_http_handler_unittest.cc new file mode 100644 index 0000000..b01c9d9 --- /dev/null +++ b/content/browser/devtools/devtools_http_handler_unittest.cc @@ -0,0 +1,103 @@ +// Copyright (c) 2012 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 "base/message_loop.h" +#include "base/run_loop.h" +#include "content/browser/browser_thread_impl.h" +#include "content/public/browser/devtools_http_handler.h" +#include "content/public/browser/devtools_http_handler_delegate.h" +#include "net/base/stream_listen_socket.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace content { +namespace { + +using net::StreamListenSocket; + +class DummyListenSocket : public StreamListenSocket, + public StreamListenSocket::Delegate { + public: + DummyListenSocket() + : ALLOW_THIS_IN_INITIALIZER_LIST(StreamListenSocket(0, this)) {} + + // StreamListenSocket::Delegate "implementation" + virtual void DidAccept(StreamListenSocket* server, + StreamListenSocket* connection) OVERRIDE {} + virtual void DidRead(StreamListenSocket* connection, + const char* data, + int len) OVERRIDE {} + virtual void DidClose(StreamListenSocket* sock) OVERRIDE {} + protected: + virtual ~DummyListenSocket() {} + virtual void Accept() OVERRIDE {} +}; + +class DummyListenSocketFactory : public net::StreamListenSocketFactory { + public: + DummyListenSocketFactory( + base::Closure quit_closure_1, base::Closure quit_closure_2) + : quit_closure_1_(quit_closure_1), quit_closure_2_(quit_closure_2) {} + virtual ~DummyListenSocketFactory() { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, quit_closure_2_); + } + + virtual scoped_refptr<StreamListenSocket> CreateAndListen( + StreamListenSocket::Delegate* delegate) const OVERRIDE { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, quit_closure_1_); + return new DummyListenSocket(); + } + private: + base::Closure quit_closure_1_; + base::Closure quit_closure_2_; +}; + +class DummyDelegate : public DevToolsHttpHandlerDelegate { + public: + virtual std::string GetDiscoveryPageHTML() OVERRIDE { return ""; } + virtual bool BundlesFrontendResources() OVERRIDE { return true; } + virtual FilePath GetDebugFrontendDir() OVERRIDE { return FilePath(); } + virtual std::string GetPageThumbnailData(const GURL& url) { return ""; } + virtual RenderViewHost* CreateNewTarget() { return NULL; } +}; + +} + +class DevToolsHttpHandlerTest : public testing::Test { + public: + DevToolsHttpHandlerTest() + : ui_thread_(BrowserThread::UI, &message_loop_) { + } + protected: + virtual void SetUp() { + file_thread_.reset(new BrowserThreadImpl(BrowserThread::FILE)); + file_thread_->Start(); + } + virtual void TearDown() { + file_thread_->Stop(); + } + private: + MessageLoopForIO message_loop_; + BrowserThreadImpl ui_thread_; + scoped_ptr<BrowserThreadImpl> file_thread_; +}; + +TEST_F(DevToolsHttpHandlerTest, TestStartStop) { + base::RunLoop run_loop, run_loop_2; + content::DevToolsHttpHandler* devtools_http_handler_ = + content::DevToolsHttpHandler::Start( + new DummyListenSocketFactory( + run_loop.QuitClosure(), run_loop_2.QuitClosure()), + "", + new DummyDelegate()); + // Our dummy socket factory will post a quit message once the server will + // become ready. + run_loop.Run(); + devtools_http_handler_->Stop(); + // Make sure the handler actually stops. + run_loop_2.Run(); +} + +} // namespace content diff --git a/content/browser/devtools/devtools_manager_impl.cc b/content/browser/devtools/devtools_manager_impl.cc new file mode 100644 index 0000000..b548ba2 --- /dev/null +++ b/content/browser/devtools/devtools_manager_impl.cc @@ -0,0 +1,302 @@ +// Copyright (c) 2012 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 "content/browser/devtools/devtools_manager_impl.h" + +#include <vector> + +#include "base/bind.h" +#include "base/message_loop.h" +#include "content/browser/child_process_security_policy_impl.h" +#include "content/browser/devtools/devtools_netlog_observer.h" +#include "content/browser/devtools/render_view_devtools_agent_host.h" +#include "content/browser/renderer_host/render_view_host_impl.h" +#include "content/browser/web_contents/web_contents_impl.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/devtools_agent_host_registry.h" +#include "content/public/browser/devtools_client_host.h" +#include "googleurl/src/gurl.h" + +namespace content { + +// static +DevToolsManager* DevToolsManager::GetInstance() { + return DevToolsManagerImpl::GetInstance(); +} + +// static +DevToolsManagerImpl* DevToolsManagerImpl::GetInstance() { + return Singleton<DevToolsManagerImpl>::get(); +} + +DevToolsManagerImpl::DevToolsManagerImpl() + : last_orphan_cookie_(0) { +} + +DevToolsManagerImpl::~DevToolsManagerImpl() { + DCHECK(agent_to_client_host_.empty()); + DCHECK(client_to_agent_host_.empty()); + // By the time we destroy devtools manager, all orphan client hosts should + // have been deleted; no need to notify them upon contents closing. + DCHECK(orphan_client_hosts_.empty()); +} + +DevToolsClientHost* DevToolsManagerImpl::GetDevToolsClientHostFor( + DevToolsAgentHost* agent_host) { + AgentToClientHostMap::iterator it = agent_to_client_host_.find(agent_host); + if (it != agent_to_client_host_.end()) + return it->second; + return NULL; +} + +DevToolsAgentHost* DevToolsManagerImpl::GetDevToolsAgentHostFor( + DevToolsClientHost* client_host) { + ClientToAgentHostMap::iterator it = client_to_agent_host_.find(client_host); + if (it != client_to_agent_host_.end()) + return it->second; + return NULL; +} + +void DevToolsManagerImpl::RegisterDevToolsClientHostFor( + DevToolsAgentHost* agent_host, + DevToolsClientHost* client_host) { + BindClientHost(agent_host, client_host); + agent_host->Attach(); +} + +bool DevToolsManagerImpl::DispatchOnInspectorBackend( + DevToolsClientHost* from, + const std::string& message) { + DevToolsAgentHost* agent_host = GetDevToolsAgentHostFor(from); + if (!agent_host) + return false; + + agent_host->DipatchOnInspectorBackend(message); + return true; +} + +void DevToolsManagerImpl::DispatchOnInspectorFrontend( + DevToolsAgentHost* agent_host, + const std::string& message) { + DevToolsClientHost* client_host = GetDevToolsClientHostFor(agent_host); + if (!client_host) { + // Client window was closed while there were messages + // being sent to it. + return; + } + client_host->DispatchOnInspectorFrontend(message); +} + +void DevToolsManagerImpl::SaveAgentRuntimeState(DevToolsAgentHost* agent_host, + const std::string& state) { + agent_runtime_states_[agent_host] = state; +} + +void DevToolsManagerImpl::InspectElement(DevToolsAgentHost* agent_host, + int x, int y) { + agent_host->InspectElement(x, y); +} + +void DevToolsManagerImpl::AddMessageToConsole(DevToolsAgentHost* agent_host, + ConsoleMessageLevel level, + const std::string& message) { + agent_host->AddMessageToConsole(level, message); +} + +void DevToolsManagerImpl::ClientHostClosing(DevToolsClientHost* client_host) { + DevToolsAgentHost* agent_host = GetDevToolsAgentHostFor(client_host); + if (!agent_host) { + // It might be in the list of orphan client hosts, remove it from there. + for (OrphanClientHosts::iterator it = orphan_client_hosts_.begin(); + it != orphan_client_hosts_.end(); ++it) { + if (it->second.first == client_host) { + orphan_client_hosts_.erase(it->first); + return; + } + } + return; + } + + UnbindClientHost(agent_host, client_host); +} + +void DevToolsManagerImpl::AgentHostClosing(DevToolsAgentHost* agent_host) { + UnregisterDevToolsClientHostFor(agent_host); +} + +void DevToolsManagerImpl::UnregisterDevToolsClientHostFor( + DevToolsAgentHost* agent_host) { + DevToolsClientHost* client_host = GetDevToolsClientHostFor(agent_host); + if (!client_host) + return; + UnbindClientHost(agent_host, client_host); + client_host->InspectedContentsClosing(); +} + +void DevToolsManagerImpl::OnNavigatingToPendingEntry( + RenderViewHost* rvh, + RenderViewHost* dest_rvh, + const GURL& gurl) { + if (rvh == dest_rvh && static_cast<RenderViewHostImpl*>( + rvh)->render_view_termination_status() == + base::TERMINATION_STATUS_STILL_RUNNING) + return; + int cookie = DetachClientHost(rvh); + if (cookie != -1) { + // Navigating to URL in the inspected window. + AttachClientHost(cookie, dest_rvh); + + DevToolsAgentHost* dest_agent_host = + DevToolsAgentHostRegistry::GetDevToolsAgentHost(dest_rvh); + DevToolsClientHost* client_host = GetDevToolsClientHostFor( + dest_agent_host); + client_host->FrameNavigating(gurl.spec()); + } +} + +void DevToolsManagerImpl::OnCancelPendingNavigation( + RenderViewHost* pending, + RenderViewHost* current) { + int cookie = DetachClientHost(pending); + if (cookie != -1) { + // Navigating to URL in the inspected window. + AttachClientHost(cookie, current); + } +} + +void DevToolsManagerImpl::ContentsReplaced(WebContents* old_contents, + WebContents* new_contents) { + RenderViewHost* old_rvh = old_contents->GetRenderViewHost(); + if (!DevToolsAgentHostRegistry::HasDevToolsAgentHost(old_rvh)) + return; + + DevToolsAgentHost* old_agent_host = + DevToolsAgentHostRegistry::GetDevToolsAgentHost(old_rvh); + DevToolsClientHost* client_host = GetDevToolsClientHostFor(old_agent_host); + if (!client_host) + return; // Didn't know about old_contents. + int cookie = DetachClientHost(old_rvh); + if (cookie == -1) + return; // Didn't know about old_contents. + + client_host->ContentsReplaced(new_contents); + AttachClientHost(cookie, new_contents->GetRenderViewHost()); +} + +int DevToolsManagerImpl::DetachClientHost(RenderViewHost* from_rvh) { + DevToolsAgentHost* agent_host = + DevToolsAgentHostRegistry::GetDevToolsAgentHost(from_rvh); + return DetachClientHost(agent_host); +} + +int DevToolsManagerImpl::DetachClientHost(DevToolsAgentHost* agent_host) { + DevToolsClientHost* client_host = GetDevToolsClientHostFor(agent_host); + if (!client_host) + return -1; + + int cookie = last_orphan_cookie_++; + orphan_client_hosts_[cookie] = + std::pair<DevToolsClientHost*, std::string>( + client_host, agent_runtime_states_[agent_host]); + + UnbindClientHost(agent_host, client_host); + return cookie; +} + +void DevToolsManagerImpl::AttachClientHost(int client_host_cookie, + RenderViewHost* to_rvh) { + DevToolsAgentHost* agent_host = + DevToolsAgentHostRegistry::GetDevToolsAgentHost(to_rvh); + AttachClientHost(client_host_cookie, agent_host); +} + +void DevToolsManagerImpl::AttachClientHost(int client_host_cookie, + DevToolsAgentHost* agent_host) { + OrphanClientHosts::iterator it = orphan_client_hosts_.find( + client_host_cookie); + if (it == orphan_client_hosts_.end()) + return; + + DevToolsClientHost* client_host = (*it).second.first; + const std::string& state = (*it).second.second; + BindClientHost(agent_host, client_host); + agent_host->Reattach(state); + agent_runtime_states_[agent_host] = state; + + orphan_client_hosts_.erase(it); +} + +void DevToolsManagerImpl::BindClientHost( + DevToolsAgentHost* agent_host, + DevToolsClientHost* client_host) { + DCHECK(agent_to_client_host_.find(agent_host) == + agent_to_client_host_.end()); + DCHECK(client_to_agent_host_.find(client_host) == + client_to_agent_host_.end()); + + if (client_to_agent_host_.empty()) { + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(&DevToolsNetLogObserver::Attach)); + } + agent_to_client_host_[agent_host] = client_host; + client_to_agent_host_[client_host] = agent_host; + agent_host->set_close_listener(this); + + int process_id = agent_host->GetRenderProcessId(); + if (process_id != -1) + ChildProcessSecurityPolicyImpl::GetInstance()->GrantReadRawCookies( + process_id); +} + +void DevToolsManagerImpl::UnbindClientHost(DevToolsAgentHost* agent_host, + DevToolsClientHost* client_host) { + DCHECK(agent_host); + DCHECK(agent_to_client_host_.find(agent_host)->second == + client_host); + DCHECK(client_to_agent_host_.find(client_host)->second == + agent_host); + agent_host->set_close_listener(NULL); + + agent_to_client_host_.erase(agent_host); + client_to_agent_host_.erase(client_host); + agent_runtime_states_.erase(agent_host); + + if (client_to_agent_host_.empty()) { + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(&DevToolsNetLogObserver::Detach)); + } + agent_host->Detach(); + + int process_id = agent_host->GetRenderProcessId(); + if (process_id == -1) + return; + for (AgentToClientHostMap::iterator it = agent_to_client_host_.begin(); + it != agent_to_client_host_.end(); + ++it) { + if (it->first->GetRenderProcessId() == process_id) + return; + } + // We've disconnected from the last renderer -> revoke cookie permissions. + ChildProcessSecurityPolicyImpl::GetInstance()->RevokeReadRawCookies( + process_id); +} + +void DevToolsManagerImpl::CloseAllClientHosts() { + std::vector<DevToolsAgentHost*> agents; + for (AgentToClientHostMap::iterator it = + agent_to_client_host_.begin(); + it != agent_to_client_host_.end(); ++it) { + agents.push_back(it->first); + } + for (std::vector<DevToolsAgentHost*>::iterator it = agents.begin(); + it != agents.end(); ++it) { + UnregisterDevToolsClientHostFor(*it); + } +} + +} // namespace content diff --git a/content/browser/devtools/devtools_manager_impl.h b/content/browser/devtools/devtools_manager_impl.h new file mode 100644 index 0000000..ed213b7 --- /dev/null +++ b/content/browser/devtools/devtools_manager_impl.h @@ -0,0 +1,131 @@ +// Copyright (c) 2012 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 CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_MANAGER_IMPL_H_ +#define CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_MANAGER_IMPL_H_ + +#include <map> +#include <string> + +#include "base/compiler_specific.h" +#include "base/memory/singleton.h" +#include "content/browser/devtools/devtools_agent_host.h" +#include "content/common/content_export.h" +#include "content/public/browser/devtools_client_host.h" +#include "content/public/browser/devtools_manager.h" + +class GURL; + +namespace IPC { +class Message; +} + +namespace content { + +class DevToolsAgentHost; +class RenderViewHost; + +// This class is a singleton that manages DevToolsClientHost instances and +// routes messages between developer tools clients and agents. +// +// Methods below that accept inspected RenderViewHost as a parameter are +// just convenience methods that call corresponding methods accepting +// DevToolAgentHost. +class CONTENT_EXPORT DevToolsManagerImpl + : public DevToolsAgentHost::CloseListener, + public DevToolsManager { + public: + // Returns single instance of this class. The instance is destroyed on the + // browser main loop exit so this method MUST NOT be called after that point. + static DevToolsManagerImpl* GetInstance(); + + DevToolsManagerImpl(); + virtual ~DevToolsManagerImpl(); + + void DispatchOnInspectorFrontend(DevToolsAgentHost* agent_host, + const std::string& message); + + void SaveAgentRuntimeState(DevToolsAgentHost* agent_host, + const std::string& state); + + // Sends 'Attach' message to the agent using |dest_rvh| in case + // there is a DevToolsClientHost registered for the |inspected_rvh|. + void OnNavigatingToPendingEntry(RenderViewHost* inspected_rvh, + RenderViewHost* dest_rvh, + const GURL& gurl); + void OnCancelPendingNavigation(RenderViewHost* pending, + RenderViewHost* current); + + // DevToolsManager implementation + virtual bool DispatchOnInspectorBackend(DevToolsClientHost* from, + const std::string& message) OVERRIDE; + virtual void ContentsReplaced(WebContents* old_contents, + WebContents* new_contents) OVERRIDE; + virtual void CloseAllClientHosts() OVERRIDE; + virtual void AttachClientHost(int client_host_cookie, + DevToolsAgentHost* to_agent) OVERRIDE; + virtual DevToolsClientHost* GetDevToolsClientHostFor( + DevToolsAgentHost* agent_host) OVERRIDE; + virtual DevToolsAgentHost* GetDevToolsAgentHostFor( + DevToolsClientHost* client_host) OVERRIDE; + virtual void RegisterDevToolsClientHostFor( + DevToolsAgentHost* agent_host, + DevToolsClientHost* client_host) OVERRIDE; + virtual void UnregisterDevToolsClientHostFor( + DevToolsAgentHost* agent_host) OVERRIDE; + virtual int DetachClientHost(DevToolsAgentHost* from_agent) OVERRIDE; + virtual void ClientHostClosing(DevToolsClientHost* host) OVERRIDE; + virtual void InspectElement(DevToolsAgentHost* agent_host, + int x, int y) OVERRIDE; + virtual void AddMessageToConsole(DevToolsAgentHost* agent_host, + ConsoleMessageLevel level, + const std::string& message) OVERRIDE; + + private: + friend struct DefaultSingletonTraits<DevToolsManagerImpl>; + + // DevToolsAgentHost::CloseListener implementation. + virtual void AgentHostClosing(DevToolsAgentHost* host) OVERRIDE; + + void BindClientHost(DevToolsAgentHost* agent_host, + DevToolsClientHost* client_host); + void UnbindClientHost(DevToolsAgentHost* agent_host, + DevToolsClientHost* client_host); + + // Detaches client host and returns cookie that can be used in + // AttachClientHost. + int DetachClientHost(RenderViewHost* from_rvh); + + // Attaches orphan client host to new render view host. + void AttachClientHost(int client_host_cookie, + RenderViewHost* to_rvh); + + // These two maps are for tracking dependencies between inspected contents and + // their DevToolsClientHosts. They are useful for routing devtools messages + // and allow us to have at most one devtools client host per contents. + // + // DevToolsManagerImpl starts listening to DevToolsClientHosts when they are + // put into these maps and removes them when they are closing. + typedef std::map<DevToolsAgentHost*, DevToolsClientHost*> + AgentToClientHostMap; + AgentToClientHostMap agent_to_client_host_; + + typedef std::map<DevToolsClientHost*, DevToolsAgentHost*> + ClientToAgentHostMap; + ClientToAgentHostMap client_to_agent_host_; + + typedef std::map<DevToolsAgentHost*, std::string> AgentRuntimeStates; + AgentRuntimeStates agent_runtime_states_; + + typedef std::map<int, std::pair<DevToolsClientHost*, std::string> > + OrphanClientHosts; + OrphanClientHosts orphan_client_hosts_; + int last_orphan_cookie_; + + DISALLOW_COPY_AND_ASSIGN(DevToolsManagerImpl); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_MANAGER_IMPL_H_ diff --git a/content/browser/devtools/devtools_manager_unittest.cc b/content/browser/devtools/devtools_manager_unittest.cc new file mode 100644 index 0000000..d2a47a5 --- /dev/null +++ b/content/browser/devtools/devtools_manager_unittest.cc @@ -0,0 +1,243 @@ +// Copyright (c) 2012 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 "base/basictypes.h" +#include "base/time.h" +#include "content/browser/devtools/devtools_manager_impl.h" +#include "content/browser/devtools/render_view_devtools_agent_host.h" +#include "content/browser/renderer_host/test_render_view_host.h" +#include "content/browser/web_contents/test_web_contents.h" +#include "content/common/view_messages.h" +#include "content/public/browser/content_browser_client.h" +#include "content/public/browser/devtools_agent_host_registry.h" +#include "content/public/browser/devtools_client_host.h" +#include "content/public/browser/web_contents_delegate.h" +#include "content/test/test_content_browser_client.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::TimeDelta; + +namespace content { +namespace { + +class TestDevToolsClientHost : public DevToolsClientHost { + public: + TestDevToolsClientHost() + : last_sent_message(NULL), + closed_(false) { + } + + virtual ~TestDevToolsClientHost() { + EXPECT_TRUE(closed_); + } + + virtual void Close(DevToolsManager* manager) { + EXPECT_FALSE(closed_); + close_counter++; + manager->ClientHostClosing(this); + closed_ = true; + } + virtual void InspectedContentsClosing() { + FAIL(); + } + + virtual void DispatchOnInspectorFrontend(const std::string& message) { + last_sent_message = &message; + } + + virtual void ContentsReplaced(WebContents* new_contents) { + } + + virtual void ReplacedWithAnotherClient() { + } + + static void ResetCounters() { + close_counter = 0; + } + + static int close_counter; + + const std::string* last_sent_message; + + private: + bool closed_; + + virtual void FrameNavigating(const std::string& url) {} + + DISALLOW_COPY_AND_ASSIGN(TestDevToolsClientHost); +}; + +int TestDevToolsClientHost::close_counter = 0; + + +class TestWebContentsDelegate : public WebContentsDelegate { + public: + TestWebContentsDelegate() : renderer_unresponsive_received_(false) {} + + // Notification that the contents is hung. + virtual void RendererUnresponsive(WebContents* source) { + renderer_unresponsive_received_ = true; + } + + bool renderer_unresponsive_received() const { + return renderer_unresponsive_received_; + } + + private: + bool renderer_unresponsive_received_; +}; + +class DevToolsManagerTestBrowserClient : public TestContentBrowserClient { + public: + DevToolsManagerTestBrowserClient() { + } + + virtual bool ShouldSwapProcessesForNavigation( + const GURL& current_url, + const GURL& new_url) OVERRIDE { + return true; + } + + private: + DISALLOW_COPY_AND_ASSIGN(DevToolsManagerTestBrowserClient); +}; + +} // namespace + +class DevToolsManagerTest : public RenderViewHostImplTestHarness { + public: + DevToolsManagerTest() { + } + + protected: + virtual void SetUp() OVERRIDE { + original_browser_client_ = GetContentClient()->browser(); + GetContentClient()->set_browser_for_testing(&browser_client_); + + RenderViewHostImplTestHarness::SetUp(); + TestDevToolsClientHost::ResetCounters(); + } + + virtual void TearDown() OVERRIDE { + RenderViewHostImplTestHarness::TearDown(); + GetContentClient()->set_browser_for_testing(original_browser_client_); + } + + private: + ContentBrowserClient* original_browser_client_; + DevToolsManagerTestBrowserClient browser_client_; +}; + +TEST_F(DevToolsManagerTest, OpenAndManuallyCloseDevToolsClientHost) { + DevToolsManagerImpl manager; + + DevToolsAgentHost* agent = + DevToolsAgentHostRegistry::GetDevToolsAgentHost(rvh()); + DevToolsClientHost* host = manager.GetDevToolsClientHostFor(agent); + EXPECT_TRUE(NULL == host); + + TestDevToolsClientHost client_host; + manager.RegisterDevToolsClientHostFor(agent, &client_host); + // Test that just registered devtools host is returned. + host = manager.GetDevToolsClientHostFor(agent); + EXPECT_TRUE(&client_host == host); + EXPECT_EQ(0, TestDevToolsClientHost::close_counter); + + // Test that the same devtools host is returned. + host = manager.GetDevToolsClientHostFor(agent); + EXPECT_TRUE(&client_host == host); + EXPECT_EQ(0, TestDevToolsClientHost::close_counter); + + client_host.Close(&manager); + EXPECT_EQ(1, TestDevToolsClientHost::close_counter); + host = manager.GetDevToolsClientHostFor(agent); + EXPECT_TRUE(NULL == host); +} + +TEST_F(DevToolsManagerTest, ForwardMessageToClient) { + DevToolsManagerImpl manager; + + TestDevToolsClientHost client_host; + DevToolsAgentHost* agent_host = + DevToolsAgentHostRegistry::GetDevToolsAgentHost(rvh()); + manager.RegisterDevToolsClientHostFor(agent_host, &client_host); + EXPECT_EQ(0, TestDevToolsClientHost::close_counter); + + std::string m = "test message"; + agent_host = DevToolsAgentHostRegistry::GetDevToolsAgentHost(rvh()); + manager.DispatchOnInspectorFrontend(agent_host, m); + EXPECT_TRUE(&m == client_host.last_sent_message); + + client_host.Close(&manager); + EXPECT_EQ(1, TestDevToolsClientHost::close_counter); +} + +TEST_F(DevToolsManagerTest, NoUnresponsiveDialogInInspectedContents) { + TestRenderViewHost* inspected_rvh = test_rvh(); + inspected_rvh->set_render_view_created(true); + EXPECT_FALSE(contents()->GetDelegate()); + TestWebContentsDelegate delegate; + contents()->SetDelegate(&delegate); + + TestDevToolsClientHost client_host; + DevToolsAgentHost* agent_host = + DevToolsAgentHostRegistry::GetDevToolsAgentHost(inspected_rvh); + DevToolsManager::GetInstance()-> + RegisterDevToolsClientHostFor(agent_host, &client_host); + + // Start with a short timeout. + inspected_rvh->StartHangMonitorTimeout(TimeDelta::FromMilliseconds(10)); + // Wait long enough for first timeout and see if it fired. + MessageLoop::current()->PostDelayedTask( + FROM_HERE, MessageLoop::QuitClosure(), TimeDelta::FromMilliseconds(10)); + MessageLoop::current()->Run(); + EXPECT_FALSE(delegate.renderer_unresponsive_received()); + + // Now close devtools and check that the notification is delivered. + client_host.Close(DevToolsManager::GetInstance()); + // Start with a short timeout. + inspected_rvh->StartHangMonitorTimeout(TimeDelta::FromMilliseconds(10)); + // Wait long enough for first timeout and see if it fired. + MessageLoop::current()->PostDelayedTask( + FROM_HERE, MessageLoop::QuitClosure(), TimeDelta::FromMilliseconds(10)); + MessageLoop::current()->Run(); + EXPECT_TRUE(delegate.renderer_unresponsive_received()); + + contents()->SetDelegate(NULL); +} + +TEST_F(DevToolsManagerTest, ReattachOnCancelPendingNavigation) { + contents()->transition_cross_site = true; + // Navigate to URL. First URL should use first RenderViewHost. + const GURL url("http://www.google.com"); + controller().LoadURL( + url, Referrer(), PAGE_TRANSITION_TYPED, std::string()); + contents()->TestDidNavigate(rvh(), 1, url, PAGE_TRANSITION_TYPED); + EXPECT_FALSE(contents()->cross_navigation_pending()); + + TestDevToolsClientHost client_host; + DevToolsManager* devtools_manager = DevToolsManager::GetInstance(); + devtools_manager->RegisterDevToolsClientHostFor( + DevToolsAgentHostRegistry::GetDevToolsAgentHost(rvh()), + &client_host); + + // Navigate to new site which should get a new RenderViewHost. + const GURL url2("http://www.yahoo.com"); + controller().LoadURL( + url2, Referrer(), PAGE_TRANSITION_TYPED, std::string()); + EXPECT_TRUE(contents()->cross_navigation_pending()); + EXPECT_EQ(&client_host, devtools_manager->GetDevToolsClientHostFor( + DevToolsAgentHostRegistry::GetDevToolsAgentHost(pending_rvh()))); + + // Interrupt pending navigation and navigate back to the original site. + controller().LoadURL( + url, Referrer(), PAGE_TRANSITION_TYPED, std::string()); + contents()->TestDidNavigate(rvh(), 1, url, PAGE_TRANSITION_TYPED); + EXPECT_FALSE(contents()->cross_navigation_pending()); + EXPECT_EQ(&client_host, devtools_manager->GetDevToolsClientHostFor( + DevToolsAgentHostRegistry::GetDevToolsAgentHost(rvh()))); + client_host.Close(DevToolsManager::GetInstance()); +} + +} // namespace content diff --git a/content/browser/devtools/devtools_netlog_observer.cc b/content/browser/devtools/devtools_netlog_observer.cc new file mode 100644 index 0000000..f65bc42 --- /dev/null +++ b/content/browser/devtools/devtools_netlog_observer.cc @@ -0,0 +1,329 @@ +// Copyright (c) 2012 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 "content/browser/devtools/devtools_netlog_observer.h" + +#include "base/string_tokenizer.h" +#include "base/string_util.h" +#include "base/values.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/content_browser_client.h" +#include "content/public/common/resource_response.h" +#include "net/base/load_flags.h" +#include "net/http/http_response_headers.h" +#include "net/http/http_util.h" +#include "net/spdy/spdy_header_block.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_netlog_params.h" +#include "webkit/glue/resource_loader_bridge.h" + +namespace content { +const size_t kMaxNumEntries = 1000; + +DevToolsNetLogObserver* DevToolsNetLogObserver::instance_ = NULL; + +DevToolsNetLogObserver::DevToolsNetLogObserver() { +} + +DevToolsNetLogObserver::~DevToolsNetLogObserver() { +} + +DevToolsNetLogObserver::ResourceInfo* +DevToolsNetLogObserver::GetResourceInfo(uint32 id) { + RequestToInfoMap::iterator it = request_to_info_.find(id); + if (it != request_to_info_.end()) + return it->second; + return NULL; +} + +void DevToolsNetLogObserver::OnAddEntry(const net::NetLog::Entry& entry) { + // The events that the Observer is interested in only occur on the IO thread. + if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) + return; + + if (entry.source().type == net::NetLog::SOURCE_URL_REQUEST) + OnAddURLRequestEntry(entry); + else if (entry.source().type == net::NetLog::SOURCE_HTTP_STREAM_JOB) + OnAddHTTPStreamJobEntry(entry); + else if (entry.source().type == net::NetLog::SOURCE_SOCKET) + OnAddSocketEntry(entry); +} + +void DevToolsNetLogObserver::OnAddURLRequestEntry( + const net::NetLog::Entry& entry) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + bool is_begin = entry.phase() == net::NetLog::PHASE_BEGIN; + bool is_end = entry.phase() == net::NetLog::PHASE_END; + + if (entry.type() == net::NetLog::TYPE_URL_REQUEST_START_JOB) { + if (is_begin) { + int load_flags; + scoped_ptr<Value> event_param(entry.ParametersToValue()); + if (!net::StartEventLoadFlagsFromEventParams(event_param.get(), + &load_flags)) { + return; + } + + if (!(load_flags & net::LOAD_REPORT_RAW_HEADERS)) + return; + + if (request_to_info_.size() > kMaxNumEntries) { + LOG(WARNING) << "The raw headers observer url request count has grown " + "larger than expected, resetting"; + request_to_info_.clear(); + } + + request_to_info_[entry.source().id] = new ResourceInfo(); + + if (request_to_encoded_data_length_.size() > kMaxNumEntries) { + LOG(WARNING) << "The encoded data length observer url request count " + "has grown larger than expected, resetting"; + request_to_encoded_data_length_.clear(); + } + + request_to_encoded_data_length_[entry.source().id] = 0; + } + return; + } else if (entry.type() == net::NetLog::TYPE_REQUEST_ALIVE) { + // Cleanup records based on the TYPE_REQUEST_ALIVE entry. + if (is_end) { + request_to_info_.erase(entry.source().id); + request_to_encoded_data_length_.erase(entry.source().id); + } + return; + } + + ResourceInfo* info = GetResourceInfo(entry.source().id); + if (!info) + return; + + switch (entry.type()) { + case net::NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST_HEADERS: { + scoped_ptr<Value> event_params(entry.ParametersToValue()); + std::string request_line; + net::HttpRequestHeaders request_headers; + + if (!net::HttpRequestHeaders::FromNetLogParam(event_params.get(), + &request_headers, + &request_line)) { + NOTREACHED(); + } + + // We need to clear headers in case the same url_request is reused for + // several http requests (e.g. see http://crbug.com/80157). + info->request_headers.clear(); + + for (net::HttpRequestHeaders::Iterator it(request_headers); + it.GetNext();) { + info->request_headers.push_back(std::make_pair(it.name(), it.value())); + } + info->request_headers_text = request_line + request_headers.ToString(); + break; + } + case net::NetLog::TYPE_HTTP_TRANSACTION_SPDY_SEND_REQUEST_HEADERS: { + scoped_ptr<Value> event_params(entry.ParametersToValue()); + net::SpdyHeaderBlock request_headers; + + if (!net::SpdyHeaderBlockFromNetLogParam(event_params.get(), + &request_headers)) { + NOTREACHED(); + } + + // We need to clear headers in case the same url_request is reused for + // several http requests (e.g. see http://crbug.com/80157). + info->request_headers.clear(); + + for (net::SpdyHeaderBlock::const_iterator it = request_headers.begin(); + it != request_headers.end(); ++it) { + info->request_headers.push_back(std::make_pair(it->first, it->second)); + } + info->request_headers_text = ""; + break; + } + case net::NetLog::TYPE_HTTP_TRANSACTION_READ_RESPONSE_HEADERS: { + scoped_ptr<Value> event_params(entry.ParametersToValue()); + + scoped_refptr<net::HttpResponseHeaders> response_headers; + + if (!net::HttpResponseHeaders::FromNetLogParam(event_params.get(), + &response_headers)) { + NOTREACHED(); + } + + info->http_status_code = response_headers->response_code(); + info->http_status_text = response_headers->GetStatusText(); + std::string name, value; + + // We need to clear headers in case the same url_request is reused for + // several http requests (e.g. see http://crbug.com/80157). + info->response_headers.clear(); + + for (void* it = NULL; + response_headers->EnumerateHeaderLines(&it, &name, &value); ) { + info->response_headers.push_back(std::make_pair(name, value)); + } + info->response_headers_text = + net::HttpUtil::ConvertHeadersBackToHTTPResponse( + response_headers->raw_headers()); + break; + } + case net::NetLog::TYPE_HTTP_STREAM_REQUEST_BOUND_TO_JOB: { + scoped_ptr<Value> event_params(entry.ParametersToValue()); + net::NetLog::Source http_stream_job_source; + if (!net::NetLog::Source::FromEventParameters(event_params.get(), + &http_stream_job_source)) { + NOTREACHED(); + break; + } + + uint32 http_stream_job_id = http_stream_job_source.id; + HTTPStreamJobToSocketMap::iterator it = + http_stream_job_to_socket_.find(http_stream_job_id); + if (it == http_stream_job_to_socket_.end()) + return; + uint32 socket_id = it->second; + + if (socket_to_request_.size() > kMaxNumEntries) { + LOG(WARNING) << "The url request observer socket count has grown " + "larger than expected, resetting"; + socket_to_request_.clear(); + } + + socket_to_request_[socket_id] = entry.source().id; + http_stream_job_to_socket_.erase(http_stream_job_id); + break; + } + default: + break; + } +} + +void DevToolsNetLogObserver::OnAddHTTPStreamJobEntry( + const net::NetLog::Entry& entry) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + if (entry.type() == net::NetLog::TYPE_SOCKET_POOL_BOUND_TO_SOCKET) { + scoped_ptr<Value> event_params(entry.ParametersToValue()); + net::NetLog::Source socket_source; + if (!net::NetLog::Source::FromEventParameters(event_params.get(), + &socket_source)) { + NOTREACHED(); + return; + } + + // Prevents us from passively growing the memory unbounded in + // case something went wrong. Should not happen. + if (http_stream_job_to_socket_.size() > kMaxNumEntries) { + LOG(WARNING) << "The load timing observer http stream job count " + "has grown larger than expected, resetting"; + http_stream_job_to_socket_.clear(); + } + http_stream_job_to_socket_[entry.source().id] = socket_source.id; + } +} + +void DevToolsNetLogObserver::OnAddSocketEntry( + const net::NetLog::Entry& entry) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + bool is_end = entry.phase() == net::NetLog::PHASE_END; + + SocketToRequestMap::iterator it = + socket_to_request_.find(entry.source().id); + if (it == socket_to_request_.end()) + return; + uint32 request_id = it->second; + + if (entry.type() == net::NetLog::TYPE_SOCKET_IN_USE) { + if (is_end) + socket_to_request_.erase(entry.source().id); + return; + } + + RequestToEncodedDataLengthMap::iterator encoded_data_length_it = + request_to_encoded_data_length_.find(request_id); + if (encoded_data_length_it == request_to_encoded_data_length_.end()) + return; + + if (net::NetLog::TYPE_SOCKET_BYTES_RECEIVED == entry.type()) { + int byte_count = 0; + scoped_ptr<Value> value(entry.ParametersToValue()); + if (!value->IsType(Value::TYPE_DICTIONARY)) + return; + + DictionaryValue* dValue = static_cast<DictionaryValue*>(value.get()); + if (!dValue->GetInteger("byte_count", &byte_count)) + return; + + encoded_data_length_it->second += byte_count; + } +} + +void DevToolsNetLogObserver::Attach() { + DCHECK(!instance_); + net::NetLog* net_log = GetContentClient()->browser()->GetNetLog(); + if (net_log) { + instance_ = new DevToolsNetLogObserver(); + net_log->AddThreadSafeObserver(instance_, net::NetLog::LOG_ALL_BUT_BYTES); + } +} + +void DevToolsNetLogObserver::Detach() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + if (instance_) { + // Safest not to do this in the destructor to maintain thread safety across + // refactorings. + instance_->net_log()->RemoveThreadSafeObserver(instance_); + delete instance_; + instance_ = NULL; + } +} + +DevToolsNetLogObserver* DevToolsNetLogObserver::GetInstance() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + return instance_; +} + +// static +void DevToolsNetLogObserver::PopulateResponseInfo( + net::URLRequest* request, + ResourceResponse* response) { + if (!(request->load_flags() & net::LOAD_REPORT_RAW_HEADERS)) + return; + + uint32 source_id = request->net_log().source().id; + DevToolsNetLogObserver* dev_tools_net_log_observer = + DevToolsNetLogObserver::GetInstance(); + if (dev_tools_net_log_observer == NULL) + return; + response->head.devtools_info = + dev_tools_net_log_observer->GetResourceInfo(source_id); +} + +// static +int DevToolsNetLogObserver::GetAndResetEncodedDataLength( + net::URLRequest* request) { + if (!(request->load_flags() & net::LOAD_REPORT_RAW_HEADERS)) + return -1; + + uint32 source_id = request->net_log().source().id; + DevToolsNetLogObserver* dev_tools_net_log_observer = + DevToolsNetLogObserver::GetInstance(); + if (dev_tools_net_log_observer == NULL) + return -1; + + RequestToEncodedDataLengthMap::iterator it = + dev_tools_net_log_observer->request_to_encoded_data_length_.find( + source_id); + if (it == dev_tools_net_log_observer->request_to_encoded_data_length_.end()) + return -1; + int encoded_data_length = it->second; + it->second = 0; + return encoded_data_length; +} + +} // namespace content diff --git a/content/browser/devtools/devtools_netlog_observer.h b/content/browser/devtools/devtools_netlog_observer.h new file mode 100644 index 0000000..4e74edd --- /dev/null +++ b/content/browser/devtools/devtools_netlog_observer.h @@ -0,0 +1,70 @@ +// Copyright (c) 2012 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 CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_NETLOG_OBSERVER_H_ +#define CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_NETLOG_OBSERVER_H_ + +#include "base/hash_tables.h" +#include "base/memory/ref_counted.h" +#include "net/base/net_log.h" +#include "webkit/glue/resource_loader_bridge.h" + +namespace net { +class URLRequest; +} // namespace net + +namespace content { +struct ResourceResponse; + +// DevToolsNetLogObserver watches the NetLog event stream and collects the +// stuff that may be of interest to DevTools. Currently, this only includes +// actual HTTP/SPDY headers sent and received over the network. +// +// As DevToolsNetLogObserver shares live data with objects that live on the +// IO Thread, it must also reside on the IO Thread. Only OnAddEntry can be +// called from other threads. +class DevToolsNetLogObserver : public net::NetLog::ThreadSafeObserver { + typedef webkit_glue::ResourceDevToolsInfo ResourceInfo; + + public: + // net::NetLog::ThreadSafeObserver implementation: + virtual void OnAddEntry(const net::NetLog::Entry& entry) OVERRIDE; + + void OnAddURLRequestEntry(const net::NetLog::Entry& entry); + void OnAddHTTPStreamJobEntry(const net::NetLog::Entry& entry); + void OnAddSocketEntry(const net::NetLog::Entry& entry); + + static void Attach(); + static void Detach(); + + // Must be called on the IO thread. May return NULL if no observers + // are active. + static DevToolsNetLogObserver* GetInstance(); + static void PopulateResponseInfo(net::URLRequest*, + ResourceResponse*); + static int GetAndResetEncodedDataLength(net::URLRequest* request); + + private: + static DevToolsNetLogObserver* instance_; + + DevToolsNetLogObserver(); + virtual ~DevToolsNetLogObserver(); + + ResourceInfo* GetResourceInfo(uint32 id); + + typedef base::hash_map<uint32, scoped_refptr<ResourceInfo> > RequestToInfoMap; + typedef base::hash_map<uint32, int> RequestToEncodedDataLengthMap; + typedef base::hash_map<uint32, uint32> HTTPStreamJobToSocketMap; + typedef base::hash_map<uint32, uint32> SocketToRequestMap; + RequestToInfoMap request_to_info_; + RequestToEncodedDataLengthMap request_to_encoded_data_length_; + HTTPStreamJobToSocketMap http_stream_job_to_socket_; + SocketToRequestMap socket_to_request_; + + DISALLOW_COPY_AND_ASSIGN(DevToolsNetLogObserver); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_NETLOG_OBSERVER_H_ diff --git a/content/browser/devtools/devtools_resources.gyp b/content/browser/devtools/devtools_resources.gyp new file mode 100644 index 0000000..9ee2076 --- /dev/null +++ b/content/browser/devtools/devtools_resources.gyp @@ -0,0 +1,58 @@ +# Copyright (c) 2012 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. + +{ + 'variables': { + 'conditions': [ + ['inside_chromium_build==0', { + 'webkit_src_dir': '../../../../../..', + },{ + 'webkit_src_dir': '../../../third_party/WebKit', + }], + ], + }, + 'targets': [ + { + 'target_name': 'devtools_resources', + 'type': 'none', + 'dependencies': [ + '<(webkit_src_dir)/Source/WebKit/chromium/WebKit.gyp:generate_devtools_grd', + ], + 'variables': { + 'grit_out_dir': '<(SHARED_INTERMEDIATE_DIR)/webkit', + }, + 'actions': [ + { + 'action_name': 'devtools_resources', + # This can't use build/grit_action.gypi because the grd file + # is generated at build time, so the trick of using grit_info to get + # the real inputs/outputs at GYP time isn't possible. + 'variables': { + 'grit_cmd': ['python', '../../../tools/grit/grit.py'], + 'grit_grd_file': '<(SHARED_INTERMEDIATE_DIR)/devtools/devtools_resources.grd', + }, + 'inputs': [ + '<(grit_grd_file)', + '<!@pymod_do_main(grit_info --inputs)', + ], + 'outputs': [ + '<(grit_out_dir)/grit/devtools_resources.h', + '<(grit_out_dir)/devtools_resources.pak', + '<(grit_out_dir)/grit/devtools_resources_map.cc', + '<(grit_out_dir)/grit/devtools_resources_map.h', + ], + 'action': ['<@(grit_cmd)', + '-i', '<(grit_grd_file)', 'build', + '-f', 'GRIT_DIR/../gritsettings/resource_ids', + '-o', '<(grit_out_dir)', + '-D', 'SHARED_INTERMEDIATE_DIR=<(SHARED_INTERMEDIATE_DIR)', + '<@(grit_defines)' ], + 'message': 'Generating resources from <(grit_grd_file)', + 'msvs_cygwin_shell': 1, + } + ], + 'includes': [ '../../../build/grit_target.gypi' ], + }, + ], +} diff --git a/content/browser/devtools/devtools_tracing_handler.cc b/content/browser/devtools/devtools_tracing_handler.cc new file mode 100644 index 0000000..5514452 --- /dev/null +++ b/content/browser/devtools/devtools_tracing_handler.cc @@ -0,0 +1,103 @@ +// Copyright (c) 2012 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 "content/browser/devtools/devtools_tracing_handler.h" + +#include "base/bind.h" +#include "base/callback.h" +#include "base/location.h" +#include "base/values.h" +#include "content/browser/devtools/devtools_http_handler_impl.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/trace_controller.h" +#include "content/public/browser/trace_subscriber.h" + +namespace content { + +DevToolsTracingHandler::DevToolsTracingHandler() + : has_completed_(false), + buffer_data_size_(0) { +} + +DevToolsTracingHandler::~DevToolsTracingHandler() { +} + +std::string DevToolsTracingHandler::Domain() { + return "Tracing"; +} + +void DevToolsTracingHandler::OnEndTracingComplete() { + has_completed_ = true; +} + +void DevToolsTracingHandler::OnTraceDataCollected( + const scoped_refptr<base::RefCountedString>& trace_fragment) { + buffer_.push_back(trace_fragment->data()); + buffer_data_size_ += trace_fragment->data().size(); +} + +base::Value* DevToolsTracingHandler::OnProtocolCommand( + const std::string& method, + const base::DictionaryValue* params, + base::Value** error_out) { + if (method == "Tracing.start") + return Start(params); + else if (method == "Tracing.end") + return End(params); + else if (method == "Tracing.hasCompleted") + return HasCompleted(params); + else if (method == "Tracing.getTraceAndReset") + return GetTraceAndReset(params); + + base::DictionaryValue* error_object = new base::DictionaryValue(); + error_object->SetInteger("code", -1); + error_object->SetString("message", "Invalid method"); + + *error_out = error_object; + + return NULL; +} + +base::Value* DevToolsTracingHandler::Start( + const base::DictionaryValue* params) { + std::string categories; + if (params && params->HasKey("categories")) + params->GetString("categories", &categories); + TraceController::GetInstance()->BeginTracing(this, categories); + + return base::Value::CreateBooleanValue(true); +} + +base::Value* DevToolsTracingHandler::End( + const base::DictionaryValue* /* params */) { + TraceController::GetInstance()->EndTracingAsync(this); + + return base::Value::CreateBooleanValue(true); +} + + +base::Value* DevToolsTracingHandler::HasCompleted( + const base::DictionaryValue* /* params */) { + + return base::Value::CreateBooleanValue(has_completed_); +} + +base::Value* DevToolsTracingHandler::GetTraceAndReset( + const base::DictionaryValue* /* params */) { + std::string ret; + ret.reserve(buffer_data_size_); + for (std::vector<std::string>::const_iterator i = buffer_.begin(); + i != buffer_.end(); ++i) { + if (!ret.empty()) + ret.append(","); + ret.append(*i); + } + buffer_.clear(); + has_completed_ = false; + buffer_data_size_ = 0; + + return base::Value::CreateStringValue(ret); +} + +} // namespace content diff --git a/content/browser/devtools/devtools_tracing_handler.h b/content/browser/devtools/devtools_tracing_handler.h new file mode 100644 index 0000000..c01896a --- /dev/null +++ b/content/browser/devtools/devtools_tracing_handler.h @@ -0,0 +1,50 @@ +// Copyright (c) 2012 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 CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_TRACING_HANDLER_H_ +#define CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_TRACING_HANDLER_H_ + +#include "content/browser/devtools/devtools_browser_target.h" +#include "content/public/browser/trace_subscriber.h" + +namespace content { + +// This class bridges DevTools remote debugging server with the trace +// infrastructure. +class DevToolsTracingHandler + : public TraceSubscriber, + public DevToolsBrowserTarget::Handler { + public: + DevToolsTracingHandler(); + virtual ~DevToolsTracingHandler(); + + // TraceSubscriber: + virtual void OnEndTracingComplete() OVERRIDE;; + virtual void OnTraceDataCollected( + const scoped_refptr<base::RefCountedString>& trace_fragment) OVERRIDE; + + // DevToolBrowserTarget::Handler: + virtual std::string Domain() OVERRIDE; + virtual base::Value* OnProtocolCommand( + const std::string& method, + const base::DictionaryValue* params, + base::Value** error_out) OVERRIDE; + + private: + base::Value* Start(const base::DictionaryValue* params); + base::Value* End(const base::DictionaryValue* params); + base::Value* HasCompleted(const base::DictionaryValue* params); + base::Value* GetTraceAndReset(const base::DictionaryValue* params); + + bool has_completed_; + + std::vector<std::string> buffer_; + int buffer_data_size_; + + DISALLOW_COPY_AND_ASSIGN(DevToolsTracingHandler); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_TRACING_HANDLER_H_ diff --git a/content/browser/devtools/render_view_devtools_agent_host.cc b/content/browser/devtools/render_view_devtools_agent_host.cc new file mode 100644 index 0000000..4de7545 --- /dev/null +++ b/content/browser/devtools/render_view_devtools_agent_host.cc @@ -0,0 +1,154 @@ +// Copyright (c) 2012 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 "content/browser/devtools/render_view_devtools_agent_host.h" + +#include "base/basictypes.h" +#include "base/lazy_instance.h" +#include "content/browser/devtools/devtools_manager_impl.h" +#include "content/browser/devtools/render_view_devtools_agent_host.h" +#include "content/browser/renderer_host/render_process_host_impl.h" +#include "content/browser/renderer_host/render_view_host_impl.h" +#include "content/browser/site_instance_impl.h" +#include "content/browser/web_contents/web_contents_impl.h" +#include "content/common/devtools_messages.h" +#include "content/public/browser/content_browser_client.h" +#include "content/public/browser/devtools_agent_host_registry.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_types.h" + +namespace content { + +typedef std::map<RenderViewHost*, RenderViewDevToolsAgentHost*> Instances; + +namespace { +base::LazyInstance<Instances>::Leaky g_instances = LAZY_INSTANCE_INITIALIZER; +} // namespace + +// static +DevToolsAgentHost* DevToolsAgentHostRegistry::GetDevToolsAgentHost( + RenderViewHost* rvh) { + Instances::iterator it = g_instances.Get().find(rvh); + if (it != g_instances.Get().end()) + return it->second; + return new RenderViewDevToolsAgentHost(rvh); +} + +// static +RenderViewHost* DevToolsAgentHostRegistry::GetRenderViewHost( + DevToolsAgentHost* agent_host) { + for (Instances::iterator it = g_instances.Get().begin(); + it != g_instances.Get().end(); ++it) { + if (agent_host == it->second) + return it->first; + } + return NULL; +} + +// static +bool DevToolsAgentHostRegistry::HasDevToolsAgentHost(RenderViewHost* rvh) { + if (g_instances == NULL) + return false; + Instances::iterator it = g_instances.Get().find(rvh); + return it != g_instances.Get().end(); +} + +bool DevToolsAgentHostRegistry::IsDebuggerAttached(WebContents* web_contents) { + if (g_instances == NULL) + return false; + DevToolsManager* devtools_manager = DevToolsManager::GetInstance(); + if (!devtools_manager) + return false; + RenderViewHostDelegate* delegate = + static_cast<WebContentsImpl*>(web_contents); + for (Instances::iterator it = g_instances.Get().begin(); + it != g_instances.Get().end(); ++it) { + if (it->first->GetDelegate() != delegate) + continue; + if (devtools_manager->GetDevToolsClientHostFor(it->second)) + return true; + } + return false; +} + +RenderViewDevToolsAgentHost::RenderViewDevToolsAgentHost( + RenderViewHost* rvh) + : RenderViewHostObserver(rvh), + render_view_host_(rvh) { + g_instances.Get()[rvh] = this; +} + +void RenderViewDevToolsAgentHost::SendMessageToAgent(IPC::Message* msg) { + msg->set_routing_id(render_view_host_->GetRoutingID()); + render_view_host_->Send(msg); +} + +void RenderViewDevToolsAgentHost::NotifyClientAttaching() { + NotificationService::current()->Notify( + NOTIFICATION_DEVTOOLS_AGENT_ATTACHED, + Source<BrowserContext>( + render_view_host_->GetSiteInstance()->GetProcess()-> + GetBrowserContext()), + Details<RenderViewHost>(render_view_host_)); +} + +void RenderViewDevToolsAgentHost::NotifyClientDetaching() { + NotificationService::current()->Notify( + NOTIFICATION_DEVTOOLS_AGENT_DETACHED, + Source<BrowserContext>( + render_view_host_->GetSiteInstance()->GetProcess()-> + GetBrowserContext()), + Details<RenderViewHost>(render_view_host_)); +} + +int RenderViewDevToolsAgentHost::GetRenderProcessId() { + return render_view_host_->GetProcess()->GetID(); +} + +RenderViewDevToolsAgentHost::~RenderViewDevToolsAgentHost() { + g_instances.Get().erase(render_view_host_); +} + +void RenderViewDevToolsAgentHost::RenderViewHostDestroyed( + RenderViewHost* rvh) { + NotifyCloseListener(); + delete this; +} + +bool RenderViewDevToolsAgentHost::OnMessageReceived( + const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(RenderViewDevToolsAgentHost, message) + IPC_MESSAGE_HANDLER(DevToolsClientMsg_DispatchOnInspectorFrontend, + OnDispatchOnInspectorFrontend) + IPC_MESSAGE_HANDLER(DevToolsHostMsg_SaveAgentRuntimeState, + OnSaveAgentRuntimeState) + IPC_MESSAGE_HANDLER(DevToolsHostMsg_ClearBrowserCache, OnClearBrowserCache) + IPC_MESSAGE_HANDLER(DevToolsHostMsg_ClearBrowserCookies, + OnClearBrowserCookies) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void RenderViewDevToolsAgentHost::OnSaveAgentRuntimeState( + const std::string& state) { + DevToolsManagerImpl::GetInstance()->SaveAgentRuntimeState(this, state); +} + +void RenderViewDevToolsAgentHost::OnDispatchOnInspectorFrontend( + const std::string& message) { + DevToolsManagerImpl::GetInstance()->DispatchOnInspectorFrontend( + this, message); +} + +void RenderViewDevToolsAgentHost::OnClearBrowserCache() { + GetContentClient()->browser()->ClearCache(render_view_host_); +} + +void RenderViewDevToolsAgentHost::OnClearBrowserCookies() { + GetContentClient()->browser()->ClearCookies(render_view_host_); +} + +} // namespace content diff --git a/content/browser/devtools/render_view_devtools_agent_host.h b/content/browser/devtools/render_view_devtools_agent_host.h new file mode 100644 index 0000000..afd3173 --- /dev/null +++ b/content/browser/devtools/render_view_devtools_agent_host.h @@ -0,0 +1,51 @@ +// Copyright (c) 2012 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 CONTENT_BROWSER_DEVTOOLS_RENDER_VIEW_DEVTOOLS_AGENT_HOST_H_ +#define CONTENT_BROWSER_DEVTOOLS_RENDER_VIEW_DEVTOOLS_AGENT_HOST_H_ + +#include <map> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "content/browser/devtools/devtools_agent_host.h" +#include "content/common/content_export.h" +#include "content/public/browser/render_view_host_observer.h" + +namespace content { + +class RenderViewHost; + +class CONTENT_EXPORT RenderViewDevToolsAgentHost + : public DevToolsAgentHost, + private RenderViewHostObserver { + public: + RenderViewDevToolsAgentHost(RenderViewHost*); + + private: + virtual ~RenderViewDevToolsAgentHost(); + + // DevToolsAgentHost implementation. + virtual void SendMessageToAgent(IPC::Message* msg) OVERRIDE; + virtual void NotifyClientAttaching() OVERRIDE; + virtual void NotifyClientDetaching() OVERRIDE; + virtual int GetRenderProcessId() OVERRIDE; + + // RenderViewHostObserver overrides. + virtual void RenderViewHostDestroyed(RenderViewHost* rvh) OVERRIDE; + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + + void OnDispatchOnInspectorFrontend(const std::string& message); + void OnSaveAgentRuntimeState(const std::string& state); + void OnClearBrowserCache(); + void OnClearBrowserCookies(); + + RenderViewHost* render_view_host_; + + DISALLOW_COPY_AND_ASSIGN(RenderViewDevToolsAgentHost); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_DEVTOOLS_RENDER_VIEW_DEVTOOLS_AGENT_HOST_H_ diff --git a/content/browser/devtools/worker_devtools_manager.cc b/content/browser/devtools/worker_devtools_manager.cc new file mode 100644 index 0000000..f341c82 --- /dev/null +++ b/content/browser/devtools/worker_devtools_manager.cc @@ -0,0 +1,472 @@ +// Copyright (c) 2011 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 "content/browser/devtools/worker_devtools_manager.h" + +#include <list> +#include <map> + +#include "base/bind.h" +#include "content/browser/devtools/devtools_agent_host.h" +#include "content/browser/devtools/devtools_manager_impl.h" +#include "content/browser/devtools/worker_devtools_message_filter.h" +#include "content/browser/worker_host/worker_service_impl.h" +#include "content/common/devtools_messages.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/child_process_data.h" +#include "content/public/browser/devtools_agent_host_registry.h" +#include "content/public/common/process_type.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebDevToolsAgent.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebCString.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebString.h" + +namespace content { + +// Called on the UI thread. +// static +DevToolsAgentHost* DevToolsAgentHostRegistry::GetDevToolsAgentHostForWorker( + int worker_process_id, + int worker_route_id) { + return WorkerDevToolsManager::GetDevToolsAgentHostForWorker( + worker_process_id, + worker_route_id); +} + +class WorkerDevToolsManager::AgentHosts { +public: + static void Add(WorkerId id, WorkerDevToolsAgentHost* host) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + if (!instance_) + instance_ = new AgentHosts(); + instance_->map_[id] = host; + } + static void Remove(WorkerId id) { + DCHECK(instance_); + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + Instances& map = instance_->map_; + map.erase(id); + if (map.empty()) { + delete instance_; + instance_ = NULL; + } + } + + static WorkerDevToolsAgentHost* GetAgentHost(WorkerId id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + if (!instance_) + return NULL; + Instances& map = instance_->map_; + Instances::iterator it = map.find(id); + if (it == map.end()) + return NULL; + return it->second; + } + +private: + AgentHosts() { + } + ~AgentHosts() {} + + static AgentHosts* instance_; + typedef std::map<WorkerId, WorkerDevToolsAgentHost*> Instances; + Instances map_; +}; + +WorkerDevToolsManager::AgentHosts* + WorkerDevToolsManager::AgentHosts::instance_ = NULL; + + +struct WorkerDevToolsManager::TerminatedInspectedWorker { + TerminatedInspectedWorker(WorkerId id, const GURL& url, const string16& name) + : old_worker_id(id), + worker_url(url), + worker_name(name) {} + WorkerId old_worker_id; + GURL worker_url; + string16 worker_name; +}; + + +class WorkerDevToolsManager::WorkerDevToolsAgentHost + : public DevToolsAgentHost { + public: + explicit WorkerDevToolsAgentHost(WorkerId worker_id) + : worker_id_(worker_id) { + AgentHosts::Add(worker_id, this); + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind( + &RegisterAgent, + worker_id.first, + worker_id.second)); + } + + void WorkerDestroyed() { + NotifyCloseListener(); + delete this; + } + + private: + virtual ~WorkerDevToolsAgentHost() { + AgentHosts::Remove(worker_id_); + } + + static void RegisterAgent( + int worker_process_id, + int worker_route_id) { + WorkerDevToolsManager::GetInstance()->RegisterDevToolsAgentHostForWorker( + worker_process_id, worker_route_id); + } + + static void ForwardToWorkerDevToolsAgent( + int worker_process_id, + int worker_route_id, + IPC::Message* message) { + WorkerDevToolsManager::GetInstance()->ForwardToWorkerDevToolsAgent( + worker_process_id, worker_route_id, *message); + } + + // DevToolsAgentHost implementation. + virtual void SendMessageToAgent(IPC::Message* message) OVERRIDE { + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind( + &WorkerDevToolsAgentHost::ForwardToWorkerDevToolsAgent, + worker_id_.first, + worker_id_.second, + base::Owned(message))); + } + virtual void NotifyClientAttaching() OVERRIDE {} + virtual void NotifyClientDetaching() OVERRIDE {} + virtual int GetRenderProcessId() OVERRIDE { return -1; } + + WorkerId worker_id_; + + DISALLOW_COPY_AND_ASSIGN(WorkerDevToolsAgentHost); +}; + + +class WorkerDevToolsManager::DetachedClientHosts { + public: + static void WorkerReloaded(WorkerId old_id, WorkerId new_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + if (instance_ && instance_->ReattachClient(old_id, new_id)) + return; + RemovePendingWorkerData(old_id); + } + + static void WorkerDestroyed(WorkerId id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + WorkerDevToolsAgentHost* agent = AgentHosts::GetAgentHost(id); + if (!agent) { + RemovePendingWorkerData(id); + return; + } + DevToolsManagerImpl::GetInstance()->DispatchOnInspectorFrontend( + agent, + WebKit::WebDevToolsAgent::workerDisconnectedFromWorkerEvent().utf8()); + int cookie = DevToolsManagerImpl::GetInstance()->DetachClientHost(agent); + agent->WorkerDestroyed(); + if (cookie == -1) { + RemovePendingWorkerData(id); + return; + } + if (!instance_) + new DetachedClientHosts(); + instance_->worker_id_to_cookie_[id] = cookie; + } + + private: + DetachedClientHosts() { + instance_ = this; + } + ~DetachedClientHosts() { + instance_ = NULL; + } + + bool ReattachClient(WorkerId old_id, WorkerId new_id) { + WorkerIdToCookieMap::iterator it = worker_id_to_cookie_.find(old_id); + if (it == worker_id_to_cookie_.end()) + return false; + DevToolsAgentHost* agent = + WorkerDevToolsManager::GetDevToolsAgentHostForWorker( + new_id.first, + new_id.second); + DevToolsManagerImpl::GetInstance()->AttachClientHost( + it->second, + agent); + worker_id_to_cookie_.erase(it); + if (worker_id_to_cookie_.empty()) + delete this; + return true; + } + + static void RemovePendingWorkerData(WorkerId id) { + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&RemoveInspectedWorkerDataOnIOThread, id)); + } + + static void RemoveInspectedWorkerDataOnIOThread(WorkerId id) { + WorkerDevToolsManager::GetInstance()->RemoveInspectedWorkerData(id); + } + + static DetachedClientHosts* instance_; + typedef std::map<WorkerId, int> WorkerIdToCookieMap; + WorkerIdToCookieMap worker_id_to_cookie_; +}; + +WorkerDevToolsManager::DetachedClientHosts* + WorkerDevToolsManager::DetachedClientHosts::instance_ = NULL; + +struct WorkerDevToolsManager::InspectedWorker { + InspectedWorker(WorkerProcessHost* host, int route_id, const GURL& url, + const string16& name) + : host(host), + route_id(route_id), + worker_url(url), + worker_name(name) {} + WorkerProcessHost* const host; + int const route_id; + GURL worker_url; + string16 worker_name; +}; + +// static +WorkerDevToolsManager* WorkerDevToolsManager::GetInstance() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + return Singleton<WorkerDevToolsManager>::get(); +} + +// static +DevToolsAgentHost* WorkerDevToolsManager::GetDevToolsAgentHostForWorker( + int worker_process_id, + int worker_route_id) { + WorkerId id(worker_process_id, worker_route_id); + WorkerDevToolsAgentHost* result = AgentHosts::GetAgentHost(id); + if (!result) + result = new WorkerDevToolsAgentHost(id); + return result; +} + +WorkerDevToolsManager::WorkerDevToolsManager() { +} + +WorkerDevToolsManager::~WorkerDevToolsManager() { +} + +void WorkerDevToolsManager::WorkerCreated( + WorkerProcessHost* worker, + const WorkerProcessHost::WorkerInstance& instance) { + for (TerminatedInspectedWorkers::iterator it = terminated_workers_.begin(); + it != terminated_workers_.end(); ++it) { + if (instance.Matches(it->worker_url, it->worker_name, + instance.partition(), + instance.resource_context())) { + worker->Send(new DevToolsAgentMsg_PauseWorkerContextOnStart( + instance.worker_route_id())); + WorkerId new_worker_id(worker->GetData().id, instance.worker_route_id()); + paused_workers_[new_worker_id] = it->old_worker_id; + terminated_workers_.erase(it); + return; + } + } +} + +void WorkerDevToolsManager::WorkerDestroyed( + WorkerProcessHost* worker, + int worker_route_id) { + InspectedWorkersList::iterator it = FindInspectedWorker( + worker->GetData().id, + worker_route_id); + if (it == inspected_workers_.end()) + return; + + WorkerId worker_id(worker->GetData().id, worker_route_id); + terminated_workers_.push_back(TerminatedInspectedWorker( + worker_id, + it->worker_url, + it->worker_name)); + inspected_workers_.erase(it); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&DetachedClientHosts::WorkerDestroyed, worker_id)); +} + +void WorkerDevToolsManager::WorkerContextStarted(WorkerProcessHost* process, + int worker_route_id) { + WorkerId new_worker_id(process->GetData().id, worker_route_id); + PausedWorkers::iterator it = paused_workers_.find(new_worker_id); + if (it == paused_workers_.end()) + return; + + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind( + &DetachedClientHosts::WorkerReloaded, + it->second, + new_worker_id)); + paused_workers_.erase(it); +} + +void WorkerDevToolsManager::RemoveInspectedWorkerData( + const WorkerId& id) { + for (TerminatedInspectedWorkers::iterator it = terminated_workers_.begin(); + it != terminated_workers_.end(); ++it) { + if (it->old_worker_id == id) { + terminated_workers_.erase(it); + return; + } + } + + for (PausedWorkers::iterator it = paused_workers_.begin(); + it != paused_workers_.end(); ++it) { + if (it->second == id) { + SendResumeToWorker(it->first); + paused_workers_.erase(it); + return; + } + } +} + +WorkerDevToolsManager::InspectedWorkersList::iterator +WorkerDevToolsManager::FindInspectedWorker( + int host_id, int route_id) { + InspectedWorkersList::iterator it = inspected_workers_.begin(); + while (it != inspected_workers_.end()) { + if (it->host->GetData().id == host_id && it->route_id == route_id) + break; + ++it; + } + return it; +} + +static WorkerProcessHost* FindWorkerProcess(int worker_process_id) { + for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) { + if (iter.GetData().id == worker_process_id) + return *iter; + } + return NULL; +} + +void WorkerDevToolsManager::RegisterDevToolsAgentHostForWorker( + int worker_process_id, + int worker_route_id) { + if (WorkerProcessHost* process = FindWorkerProcess(worker_process_id)) { + const WorkerProcessHost::Instances& instances = process->instances(); + for (WorkerProcessHost::Instances::const_iterator i = instances.begin(); + i != instances.end(); ++i) { + if (i->worker_route_id() == worker_route_id) { + DCHECK(FindInspectedWorker(worker_process_id, worker_route_id) == + inspected_workers_.end()); + inspected_workers_.push_back( + InspectedWorker(process, worker_route_id, i->url(), i->name())); + return; + } + } + } + NotifyWorkerDestroyedOnIOThread(worker_process_id, worker_route_id); +} + +void WorkerDevToolsManager::ForwardToDevToolsClient( + int worker_process_id, + int worker_route_id, + const std::string& message) { + if (FindInspectedWorker(worker_process_id, worker_route_id) == + inspected_workers_.end()) { + NOTREACHED(); + return; + } + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind( + &ForwardToDevToolsClientOnUIThread, + worker_process_id, + worker_route_id, + message)); +} + +void WorkerDevToolsManager::SaveAgentRuntimeState(int worker_process_id, + int worker_route_id, + const std::string& state) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind( + &SaveAgentRuntimeStateOnUIThread, + worker_process_id, + worker_route_id, + state)); +} + +void WorkerDevToolsManager::ForwardToWorkerDevToolsAgent( + int worker_process_id, + int worker_route_id, + const IPC::Message& message) { + InspectedWorkersList::iterator it = FindInspectedWorker( + worker_process_id, + worker_route_id); + if (it == inspected_workers_.end()) + return; + IPC::Message* msg = new IPC::Message(message); + msg->set_routing_id(worker_route_id); + it->host->Send(msg); +} + +// static +void WorkerDevToolsManager::ForwardToDevToolsClientOnUIThread( + int worker_process_id, + int worker_route_id, + const std::string& message) { + WorkerDevToolsAgentHost* agent_host = AgentHosts::GetAgentHost(WorkerId( + worker_process_id, + worker_route_id)); + if (!agent_host) + return; + DevToolsManagerImpl::GetInstance()->DispatchOnInspectorFrontend(agent_host, + message); +} + +// static +void WorkerDevToolsManager::SaveAgentRuntimeStateOnUIThread( + int worker_process_id, + int worker_route_id, + const std::string& state) { + WorkerDevToolsAgentHost* agent_host = AgentHosts::GetAgentHost(WorkerId( + worker_process_id, + worker_route_id)); + if (!agent_host) + return; + DevToolsManagerImpl::GetInstance()->SaveAgentRuntimeState(agent_host, state); +} + +// static +void WorkerDevToolsManager::NotifyWorkerDestroyedOnIOThread( + int worker_process_id, + int worker_route_id) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind( + &WorkerDevToolsManager::NotifyWorkerDestroyedOnUIThread, + worker_process_id, + worker_route_id)); +} + +// static +void WorkerDevToolsManager::NotifyWorkerDestroyedOnUIThread( + int worker_process_id, + int worker_route_id) { + WorkerDevToolsAgentHost* host = + AgentHosts::GetAgentHost(WorkerId(worker_process_id, worker_route_id)); + if (host) + host->WorkerDestroyed(); +} + +// static +void WorkerDevToolsManager::SendResumeToWorker(const WorkerId& id) { + if (WorkerProcessHost* process = FindWorkerProcess(id.first)) + process->Send(new DevToolsAgentMsg_ResumeWorkerContext(id.second)); +} + +} // namespace content diff --git a/content/browser/devtools/worker_devtools_manager.h b/content/browser/devtools/worker_devtools_manager.h new file mode 100644 index 0000000..13ef33b --- /dev/null +++ b/content/browser/devtools/worker_devtools_manager.h @@ -0,0 +1,109 @@ +// Copyright (c) 2012 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 CONTENT_BROWSER_DEVTOOLS_WORKER_DEVTOOLS_MANAGER_H_ +#define CONTENT_BROWSER_DEVTOOLS_WORKER_DEVTOOLS_MANAGER_H_ + +#include <list> +#include <map> +#include <string> + +#include "base/basictypes.h" +#include "base/memory/singleton.h" +#include "content/browser/worker_host/worker_process_host.h" +#include "content/common/content_export.h" + +namespace content { + +class DevToolsAgentHost; + +// All methods are supposed to be called on the IO thread. +class WorkerDevToolsManager { + public: + // Returns the WorkerDevToolsManager singleton. + static WorkerDevToolsManager* GetInstance(); + + // Called on the UI thread. + static DevToolsAgentHost* GetDevToolsAgentHostForWorker( + int worker_process_id, + int worker_route_id); + + void ForwardToDevToolsClient(int worker_process_id, + int worker_route_id, + const std::string& message); + void SaveAgentRuntimeState(int worker_process_id, + int worker_route_id, + const std::string& state); + + // Called on the IO thread. + void WorkerCreated( + WorkerProcessHost* process, + const WorkerProcessHost::WorkerInstance& instance); + void WorkerDestroyed(WorkerProcessHost* process, int worker_route_id); + void WorkerContextStarted(WorkerProcessHost* process, int worker_route_id); + + private: + friend struct DefaultSingletonTraits<WorkerDevToolsManager>; + typedef std::pair<int, int> WorkerId; + class AgentHosts; + class DetachedClientHosts; + class WorkerDevToolsAgentHost; + struct InspectedWorker; + typedef std::list<InspectedWorker> InspectedWorkersList; + + WorkerDevToolsManager(); + virtual ~WorkerDevToolsManager(); + + void RemoveInspectedWorkerData(const WorkerId& id); + InspectedWorkersList::iterator FindInspectedWorker(int host_id, int route_id); + + void RegisterDevToolsAgentHostForWorker(int worker_process_id, + int worker_route_id); + void ForwardToWorkerDevToolsAgent(int worker_process_host_id, + int worker_route_id, + const IPC::Message& message); + static void ForwardToDevToolsClientOnUIThread( + int worker_process_id, + int worker_route_id, + const std::string& message); + static void SaveAgentRuntimeStateOnUIThread( + int worker_process_id, + int worker_route_id, + const std::string& state); + static void NotifyWorkerDestroyedOnIOThread(int worker_process_id, + int worker_route_id); + static void NotifyWorkerDestroyedOnUIThread(int worker_process_id, + int worker_route_id); + static void SendResumeToWorker(const WorkerId& id); + + InspectedWorkersList inspected_workers_; + + struct TerminatedInspectedWorker; + typedef std::list<TerminatedInspectedWorker> TerminatedInspectedWorkers; + // List of terminated workers for which there may be a devtools client on + // the UI thread. Worker entry is added into this list when inspected worker + // is terminated and will be removed in one of two cases: + // - shared worker with the same URL and name is started(in wich case we will + // try to reattach existing DevTools client to the new worker). + // - DevTools client which was inspecting terminated worker is closed on the + // UI thread and and WorkerDevToolsManager is notified about that on the IO + // thread. + TerminatedInspectedWorkers terminated_workers_; + + typedef std::map<WorkerId, WorkerId> PausedWorkers; + // Map from old to new worker id for the inspected workers that have been + // terminated and started again in paused state. Worker data will be removed + // from this list in one of two cases: + // - DevTools client is closed on the UI thread, WorkerDevToolsManager was + // notified about that on the IO thread and sent "resume" message to the + // worker. + // - Existing DevTools client was reattached to the new worker. + PausedWorkers paused_workers_; + + DISALLOW_COPY_AND_ASSIGN(WorkerDevToolsManager); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_DEVTOOLS_WORKER_DEVTOOLS_MANAGER_H_ diff --git a/content/browser/devtools/worker_devtools_message_filter.cc b/content/browser/devtools/worker_devtools_message_filter.cc new file mode 100644 index 0000000..3f5553f1 --- /dev/null +++ b/content/browser/devtools/worker_devtools_message_filter.cc @@ -0,0 +1,50 @@ +// Copyright (c) 2011 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 "content/browser/devtools/worker_devtools_message_filter.h" + +#include "content/browser/devtools/worker_devtools_manager.h" +#include "content/common/devtools_messages.h" +#include "content/common/worker_messages.h" + +namespace content { + +WorkerDevToolsMessageFilter::WorkerDevToolsMessageFilter( + int worker_process_host_id) + : worker_process_host_id_(worker_process_host_id), + current_routing_id_(0) { +} + +WorkerDevToolsMessageFilter::~WorkerDevToolsMessageFilter() { +} + +bool WorkerDevToolsMessageFilter::OnMessageReceived( + const IPC::Message& message, + bool* message_was_ok) { + bool handled = true; + current_routing_id_ = message.routing_id(); + IPC_BEGIN_MESSAGE_MAP_EX(WorkerDevToolsMessageFilter, message, + *message_was_ok) + IPC_MESSAGE_HANDLER(DevToolsClientMsg_DispatchOnInspectorFrontend, + OnDispatchOnInspectorFrontend) + IPC_MESSAGE_HANDLER(DevToolsHostMsg_SaveAgentRuntimeState, + OnSaveAgentRumtimeState) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP_EX() + return handled; +} + +void WorkerDevToolsMessageFilter::OnDispatchOnInspectorFrontend( + const std::string& message) { + WorkerDevToolsManager::GetInstance()->ForwardToDevToolsClient( + worker_process_host_id_, current_routing_id_, message); +} + +void WorkerDevToolsMessageFilter::OnSaveAgentRumtimeState( + const std::string& state) { + WorkerDevToolsManager::GetInstance()->SaveAgentRuntimeState( + worker_process_host_id_, current_routing_id_, state); +} + +} // namespace content diff --git a/content/browser/devtools/worker_devtools_message_filter.h b/content/browser/devtools/worker_devtools_message_filter.h new file mode 100644 index 0000000..73ba6f84 --- /dev/null +++ b/content/browser/devtools/worker_devtools_message_filter.h @@ -0,0 +1,35 @@ +// Copyright (c) 2012 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 CONTENT_BROWSER_DEVTOOLS_WORKER_DEVTOOLS_MESSAGE_FILTER_H_ +#define CONTENT_BROWSER_DEVTOOLS_WORKER_DEVTOOLS_MESSAGE_FILTER_H_ + +#include "base/callback_forward.h" +#include "content/public/browser/browser_message_filter.h" + +namespace content { + +class WorkerDevToolsMessageFilter : public BrowserMessageFilter { + public: + explicit WorkerDevToolsMessageFilter(int worker_process_host_id); + + private: + virtual ~WorkerDevToolsMessageFilter(); + + // BrowserMessageFilter implementation. + virtual bool OnMessageReceived(const IPC::Message& message, + bool* message_was_ok) OVERRIDE; + // Message handlers. + void OnDispatchOnInspectorFrontend(const std::string& message); + void OnSaveAgentRumtimeState(const std::string& state); + + int worker_process_host_id_; + int current_routing_id_; + + DISALLOW_COPY_AND_ASSIGN(WorkerDevToolsMessageFilter); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_DEVTOOLS_WORKER_DEVTOOLS_MESSAGE_FILTER_H_ |