// 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 #include #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 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 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::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