blob: 2029648b8726e83b2b1cacc2d965a04aaba26e34 [file] [log] [blame]
// 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/debugger/worker_devtools_manager.h"
#include <list>
#include <map>
#include "base/bind.h"
#include "content/browser/debugger/devtools_agent_host.h"
#include "content/browser/debugger/devtools_manager_impl.h"
#include "content/browser/debugger/worker_devtools_message_filter.h"
#include "content/browser/worker_host/worker_process_host.h"
#include "content/browser/worker_host/worker_service.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/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_types.h"
#include "content/public/common/process_type.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebCString.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebDevToolsAgent.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebString.h"
using content::BrowserThread;
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
: private content::NotificationObserver {
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() {
registrar_.Add(this, content::NOTIFICATION_APP_TERMINATING,
content::NotificationService::AllSources());
}
~AgentHosts() {}
// content::NotificationObserver implementation.
virtual void Observe(int type,
const content::NotificationSource&,
const content::NotificationDetails&) OVERRIDE;
static AgentHosts* instance_;
typedef std::map<WorkerId, WorkerDevToolsAgentHost*> Instances;
Instances map_;
content::NotificationRegistrar registrar_;
};
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,
const 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,
*message));
}
virtual void NotifyClientClosing() 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::disconnectEventAsText().utf8());
int cookie = DevToolsManagerImpl::GetInstance()->DetachClientHost(agent);
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;
void WorkerDevToolsManager::AgentHosts::Observe(
int type,
const content::NotificationSource&,
const content::NotificationDetails&) {
DCHECK(type == content::NOTIFICATION_APP_TERMINATING);
Instances copy(map_);
for (Instances::iterator it = copy.begin(); it != copy.end(); ++it)
it->second->WorkerDestroyed();
DCHECK(!instance_);
}
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() {
WorkerService::GetInstance()->AddObserver(this);
}
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.resource_context())) {
worker->Send(new DevToolsAgentMsg_PauseWorkerContextOnStart(
instance.worker_route_id()));
WorkerId new_worker_id(worker->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->id(),
worker_route_id);
if (it == inspected_workers_.end())
return;
WorkerId worker_id(worker->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->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->id() == host_id && it->route_id == route_id)
break;
++it;
}
return it;
}
static WorkerProcessHost* FindWorkerProcess(int worker_process_id) {
BrowserChildProcessHost::Iterator iter(content::PROCESS_TYPE_WORKER);
for (; !iter.Done(); ++iter) {
if (iter->id() == worker_process_id)
return static_cast<WorkerProcessHost*>(*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