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