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