Home | History | Annotate | Download | only in devtools
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "content/browser/devtools/worker_devtools_manager.h"
      6 
      7 #include <list>
      8 #include <map>
      9 
     10 #include "base/bind.h"
     11 #include "base/lazy_instance.h"
     12 #include "content/browser/devtools/devtools_manager_impl.h"
     13 #include "content/browser/devtools/devtools_protocol.h"
     14 #include "content/browser/devtools/devtools_protocol_constants.h"
     15 #include "content/browser/devtools/ipc_devtools_agent_host.h"
     16 #include "content/browser/devtools/worker_devtools_message_filter.h"
     17 #include "content/browser/worker_host/worker_service_impl.h"
     18 #include "content/common/devtools_messages.h"
     19 #include "content/public/browser/browser_thread.h"
     20 #include "content/public/browser/child_process_data.h"
     21 #include "content/public/common/process_type.h"
     22 
     23 namespace content {
     24 
     25 // Called on the UI thread.
     26 // static
     27 scoped_refptr<DevToolsAgentHost> DevToolsAgentHost::GetForWorker(
     28     int worker_process_id,
     29     int worker_route_id) {
     30   return WorkerDevToolsManager::GetDevToolsAgentHostForWorker(
     31       worker_process_id,
     32       worker_route_id);
     33 }
     34 
     35 namespace {
     36 
     37 typedef std::map<WorkerDevToolsManager::WorkerId,
     38                  WorkerDevToolsManager::WorkerDevToolsAgentHost*> AgentHosts;
     39 base::LazyInstance<AgentHosts>::Leaky g_agent_map = LAZY_INSTANCE_INITIALIZER;
     40 base::LazyInstance<AgentHosts>::Leaky g_orphan_map = LAZY_INSTANCE_INITIALIZER;
     41 
     42 }  // namespace
     43 
     44 struct WorkerDevToolsManager::TerminatedInspectedWorker {
     45   TerminatedInspectedWorker(WorkerId id, const GURL& url, const string16& name)
     46       : old_worker_id(id),
     47         worker_url(url),
     48         worker_name(name) {}
     49   WorkerId old_worker_id;
     50   GURL worker_url;
     51   string16 worker_name;
     52 };
     53 
     54 
     55 class WorkerDevToolsManager::WorkerDevToolsAgentHost
     56     : public IPCDevToolsAgentHost {
     57  public:
     58   explicit WorkerDevToolsAgentHost(WorkerId worker_id)
     59       : has_worker_id_(false) {
     60     SetWorkerId(worker_id, false);
     61   }
     62 
     63   void SetWorkerId(WorkerId worker_id, bool reattach) {
     64     worker_id_ = worker_id;
     65     if (!has_worker_id_)
     66       AddRef();  //  Balanced in ResetWorkerId.
     67     has_worker_id_ = true;
     68     g_agent_map.Get()[worker_id_] = this;
     69 
     70     BrowserThread::PostTask(
     71         BrowserThread::IO,
     72         FROM_HERE,
     73         base::Bind(
     74             &ConnectToWorker,
     75             worker_id.first,
     76             worker_id.second));
     77 
     78     if (reattach)
     79       Reattach(state_);
     80   }
     81 
     82   void ResetWorkerId() {
     83     g_agent_map.Get().erase(worker_id_);
     84     has_worker_id_ = false;
     85     Release();  //  Balanced in SetWorkerId.
     86   }
     87 
     88   void SaveAgentRuntimeState(const std::string& state) {
     89     state_ = state;
     90   }
     91 
     92   void ConnectionFailed() {
     93     NotifyCloseListener();
     94     // Object can be deleted here.
     95   }
     96 
     97  private:
     98   virtual ~WorkerDevToolsAgentHost();
     99 
    100   static void ConnectToWorker(
    101       int worker_process_id,
    102       int worker_route_id) {
    103     WorkerDevToolsManager::GetInstance()->ConnectDevToolsAgentHostToWorker(
    104         worker_process_id, worker_route_id);
    105   }
    106 
    107   static void ForwardToWorkerDevToolsAgent(
    108       int worker_process_id,
    109       int worker_route_id,
    110       IPC::Message* message) {
    111     WorkerDevToolsManager::GetInstance()->ForwardToWorkerDevToolsAgent(
    112         worker_process_id, worker_route_id, *message);
    113   }
    114 
    115   // IPCDevToolsAgentHost implementation.
    116   virtual void SendMessageToAgent(IPC::Message* message) OVERRIDE {
    117     if (!has_worker_id_) {
    118       delete message;
    119       return;
    120     }
    121     BrowserThread::PostTask(
    122         BrowserThread::IO, FROM_HERE,
    123         base::Bind(
    124             &WorkerDevToolsAgentHost::ForwardToWorkerDevToolsAgent,
    125             worker_id_.first,
    126             worker_id_.second,
    127             base::Owned(message)));
    128   }
    129 
    130   virtual void OnClientAttached() OVERRIDE {}
    131   virtual void OnClientDetached() OVERRIDE {}
    132 
    133   bool has_worker_id_;
    134   WorkerId worker_id_;
    135   std::string state_;
    136 
    137   DISALLOW_COPY_AND_ASSIGN(WorkerDevToolsAgentHost);
    138 };
    139 
    140 
    141 class WorkerDevToolsManager::DetachedClientHosts {
    142  public:
    143   static void WorkerReloaded(WorkerId old_id, WorkerId new_id) {
    144     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    145     AgentHosts::iterator it = g_orphan_map.Get().find(old_id);
    146     if (it != g_orphan_map.Get().end()) {
    147       it->second->SetWorkerId(new_id, true);
    148       g_orphan_map.Get().erase(old_id);
    149       return;
    150     }
    151     RemovePendingWorkerData(old_id);
    152   }
    153 
    154   static void WorkerDestroyed(WorkerId id) {
    155     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    156     AgentHosts::iterator it = g_agent_map.Get().find(id);
    157     if (it == g_agent_map.Get().end()) {
    158       RemovePendingWorkerData(id);
    159       return;
    160     }
    161 
    162     WorkerDevToolsAgentHost* agent = it->second;
    163     DevToolsManagerImpl* devtools_manager = DevToolsManagerImpl::GetInstance();
    164     if (!agent->IsAttached()) {
    165       // Agent has no client hosts -> delete it.
    166       RemovePendingWorkerData(id);
    167       return;
    168     }
    169 
    170     // Client host is debugging this worker agent host.
    171     std::string notification = DevToolsProtocol::CreateNotification(
    172         devtools::Worker::disconnectedFromWorker::kName, NULL)->Serialize();
    173     devtools_manager->DispatchOnInspectorFrontend(agent, notification);
    174     g_orphan_map.Get()[id] = agent;
    175     agent->ResetWorkerId();
    176   }
    177 
    178   static void RemovePendingWorkerData(WorkerId id) {
    179     BrowserThread::PostTask(
    180         BrowserThread::IO, FROM_HERE,
    181         base::Bind(&RemoveInspectedWorkerDataOnIOThread, id));
    182   }
    183 
    184  private:
    185   DetachedClientHosts() {}
    186   ~DetachedClientHosts() {}
    187 
    188   static void RemoveInspectedWorkerDataOnIOThread(WorkerId id) {
    189     WorkerDevToolsManager::GetInstance()->RemoveInspectedWorkerData(id);
    190   }
    191 };
    192 
    193 struct WorkerDevToolsManager::InspectedWorker {
    194   InspectedWorker(WorkerProcessHost* host, int route_id, const GURL& url,
    195                   const string16& name)
    196       : host(host),
    197         route_id(route_id),
    198         worker_url(url),
    199         worker_name(name) {}
    200   WorkerProcessHost* const host;
    201   int const route_id;
    202   GURL worker_url;
    203   string16 worker_name;
    204 };
    205 
    206 // static
    207 WorkerDevToolsManager* WorkerDevToolsManager::GetInstance() {
    208   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    209   return Singleton<WorkerDevToolsManager>::get();
    210 }
    211 
    212 // static
    213 DevToolsAgentHost* WorkerDevToolsManager::GetDevToolsAgentHostForWorker(
    214     int worker_process_id,
    215     int worker_route_id) {
    216   WorkerId id(worker_process_id, worker_route_id);
    217   AgentHosts::iterator it = g_agent_map.Get().find(id);
    218   if (it == g_agent_map.Get().end())
    219     return new WorkerDevToolsAgentHost(id);
    220   return it->second;
    221 }
    222 
    223 WorkerDevToolsManager::WorkerDevToolsManager() {
    224 }
    225 
    226 WorkerDevToolsManager::~WorkerDevToolsManager() {
    227 }
    228 
    229 void WorkerDevToolsManager::WorkerCreated(
    230     WorkerProcessHost* worker,
    231     const WorkerProcessHost::WorkerInstance& instance) {
    232   for (TerminatedInspectedWorkers::iterator it = terminated_workers_.begin();
    233        it != terminated_workers_.end(); ++it) {
    234     if (instance.Matches(it->worker_url, it->worker_name,
    235                          instance.partition(),
    236                          instance.resource_context())) {
    237       worker->Send(new DevToolsAgentMsg_PauseWorkerContextOnStart(
    238           instance.worker_route_id()));
    239       WorkerId new_worker_id(worker->GetData().id, instance.worker_route_id());
    240       paused_workers_[new_worker_id] = it->old_worker_id;
    241       terminated_workers_.erase(it);
    242       return;
    243     }
    244   }
    245 }
    246 
    247 void WorkerDevToolsManager::WorkerDestroyed(
    248     WorkerProcessHost* worker,
    249     int worker_route_id) {
    250   InspectedWorkersList::iterator it = FindInspectedWorker(
    251       worker->GetData().id,
    252       worker_route_id);
    253   if (it == inspected_workers_.end())
    254     return;
    255 
    256   WorkerId worker_id(worker->GetData().id, worker_route_id);
    257   terminated_workers_.push_back(TerminatedInspectedWorker(
    258       worker_id,
    259       it->worker_url,
    260       it->worker_name));
    261   inspected_workers_.erase(it);
    262   BrowserThread::PostTask(
    263       BrowserThread::UI, FROM_HERE,
    264       base::Bind(&DetachedClientHosts::WorkerDestroyed, worker_id));
    265 }
    266 
    267 void WorkerDevToolsManager::WorkerContextStarted(WorkerProcessHost* process,
    268                                                  int worker_route_id) {
    269   WorkerId new_worker_id(process->GetData().id, worker_route_id);
    270   PausedWorkers::iterator it = paused_workers_.find(new_worker_id);
    271   if (it == paused_workers_.end())
    272     return;
    273 
    274   BrowserThread::PostTask(
    275       BrowserThread::UI, FROM_HERE,
    276       base::Bind(
    277           &DetachedClientHosts::WorkerReloaded,
    278           it->second,
    279           new_worker_id));
    280   paused_workers_.erase(it);
    281 }
    282 
    283 void WorkerDevToolsManager::RemoveInspectedWorkerData(
    284     const WorkerId& id) {
    285   for (TerminatedInspectedWorkers::iterator it = terminated_workers_.begin();
    286        it != terminated_workers_.end(); ++it) {
    287     if (it->old_worker_id == id) {
    288       terminated_workers_.erase(it);
    289       return;
    290     }
    291   }
    292 
    293   for (PausedWorkers::iterator it = paused_workers_.begin();
    294        it != paused_workers_.end(); ++it) {
    295     if (it->second == id) {
    296       SendResumeToWorker(it->first);
    297       paused_workers_.erase(it);
    298       return;
    299     }
    300   }
    301 }
    302 
    303 WorkerDevToolsManager::InspectedWorkersList::iterator
    304 WorkerDevToolsManager::FindInspectedWorker(
    305     int host_id, int route_id) {
    306   InspectedWorkersList::iterator it = inspected_workers_.begin();
    307   while (it != inspected_workers_.end()) {
    308     if (it->host->GetData().id == host_id && it->route_id == route_id)
    309       break;
    310     ++it;
    311   }
    312   return it;
    313 }
    314 
    315 static WorkerProcessHost* FindWorkerProcess(int worker_process_id) {
    316   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
    317     if (iter.GetData().id == worker_process_id)
    318       return *iter;
    319   }
    320   return NULL;
    321 }
    322 
    323 void WorkerDevToolsManager::ConnectDevToolsAgentHostToWorker(
    324     int worker_process_id,
    325     int worker_route_id) {
    326   if (WorkerProcessHost* process = FindWorkerProcess(worker_process_id)) {
    327     const WorkerProcessHost::Instances& instances = process->instances();
    328     for (WorkerProcessHost::Instances::const_iterator i = instances.begin();
    329          i != instances.end(); ++i) {
    330       if (i->worker_route_id() == worker_route_id) {
    331         DCHECK(FindInspectedWorker(worker_process_id, worker_route_id) ==
    332                inspected_workers_.end());
    333         inspected_workers_.push_back(
    334             InspectedWorker(process, worker_route_id, i->url(), i->name()));
    335         return;
    336       }
    337     }
    338   }
    339   NotifyConnectionFailedOnIOThread(worker_process_id, worker_route_id);
    340 }
    341 
    342 void WorkerDevToolsManager::ForwardToDevToolsClient(
    343     int worker_process_id,
    344     int worker_route_id,
    345     const std::string& message) {
    346   if (FindInspectedWorker(worker_process_id, worker_route_id) ==
    347           inspected_workers_.end()) {
    348       NOTREACHED();
    349       return;
    350   }
    351   BrowserThread::PostTask(
    352       BrowserThread::UI, FROM_HERE,
    353       base::Bind(
    354           &ForwardToDevToolsClientOnUIThread,
    355           worker_process_id,
    356           worker_route_id,
    357           message));
    358 }
    359 
    360 void WorkerDevToolsManager::SaveAgentRuntimeState(int worker_process_id,
    361                                                   int worker_route_id,
    362                                                   const std::string& state) {
    363   BrowserThread::PostTask(
    364       BrowserThread::UI, FROM_HERE,
    365       base::Bind(
    366           &SaveAgentRuntimeStateOnUIThread,
    367           worker_process_id,
    368           worker_route_id,
    369           state));
    370 }
    371 
    372 void WorkerDevToolsManager::ForwardToWorkerDevToolsAgent(
    373     int worker_process_id,
    374     int worker_route_id,
    375     const IPC::Message& message) {
    376   InspectedWorkersList::iterator it = FindInspectedWorker(
    377       worker_process_id,
    378       worker_route_id);
    379   if (it == inspected_workers_.end())
    380     return;
    381   IPC::Message* msg = new IPC::Message(message);
    382   msg->set_routing_id(worker_route_id);
    383   it->host->Send(msg);
    384 }
    385 
    386 // static
    387 void WorkerDevToolsManager::ForwardToDevToolsClientOnUIThread(
    388     int worker_process_id,
    389     int worker_route_id,
    390     const std::string& message) {
    391   AgentHosts::iterator it = g_agent_map.Get().find(WorkerId(worker_process_id,
    392                                                             worker_route_id));
    393   if (it == g_agent_map.Get().end())
    394     return;
    395   DevToolsManagerImpl::GetInstance()->DispatchOnInspectorFrontend(it->second,
    396                                                                   message);
    397 }
    398 
    399 // static
    400 void WorkerDevToolsManager::SaveAgentRuntimeStateOnUIThread(
    401     int worker_process_id,
    402     int worker_route_id,
    403     const std::string& state) {
    404   AgentHosts::iterator it = g_agent_map.Get().find(WorkerId(worker_process_id,
    405                                                             worker_route_id));
    406   if (it == g_agent_map.Get().end())
    407     return;
    408   it->second->SaveAgentRuntimeState(state);
    409 }
    410 
    411 // static
    412 void WorkerDevToolsManager::NotifyConnectionFailedOnIOThread(
    413     int worker_process_id,
    414     int worker_route_id) {
    415   BrowserThread::PostTask(
    416       BrowserThread::UI, FROM_HERE,
    417       base::Bind(
    418           &WorkerDevToolsManager::NotifyConnectionFailedOnUIThread,
    419           worker_process_id,
    420           worker_route_id));
    421 }
    422 
    423 // static
    424 void WorkerDevToolsManager::NotifyConnectionFailedOnUIThread(
    425     int worker_process_id,
    426     int worker_route_id) {
    427   AgentHosts::iterator it = g_agent_map.Get().find(WorkerId(worker_process_id,
    428                                                             worker_route_id));
    429   if (it != g_agent_map.Get().end())
    430     it->second->ConnectionFailed();
    431 }
    432 
    433 // static
    434 void WorkerDevToolsManager::SendResumeToWorker(const WorkerId& id) {
    435   if (WorkerProcessHost* process = FindWorkerProcess(id.first))
    436     process->Send(new DevToolsAgentMsg_ResumeWorkerContext(id.second));
    437 }
    438 
    439 WorkerDevToolsManager::WorkerDevToolsAgentHost::~WorkerDevToolsAgentHost() {
    440   DetachedClientHosts::RemovePendingWorkerData(worker_id_);
    441   g_agent_map.Get().erase(worker_id_);
    442   g_orphan_map.Get().erase(worker_id_);
    443 }
    444 
    445 }  // namespace content
    446