Home | History | Annotate | Download | only in devtools
      1 // Copyright 2014 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/embedded_worker_devtools_manager.h"
      6 
      7 #include "content/browser/devtools/devtools_manager_impl.h"
      8 #include "content/browser/devtools/devtools_protocol.h"
      9 #include "content/browser/devtools/devtools_protocol_constants.h"
     10 #include "content/browser/devtools/ipc_devtools_agent_host.h"
     11 #include "content/browser/shared_worker/shared_worker_instance.h"
     12 #include "content/common/devtools_messages.h"
     13 #include "content/public/browser/browser_thread.h"
     14 #include "content/public/browser/render_process_host.h"
     15 #include "content/public/browser/worker_service.h"
     16 #include "ipc/ipc_listener.h"
     17 
     18 namespace content {
     19 
     20 namespace {
     21 
     22 bool SendMessageToWorker(
     23     const EmbeddedWorkerDevToolsManager::WorkerId& worker_id,
     24     IPC::Message* message) {
     25   RenderProcessHost* host = RenderProcessHost::FromID(worker_id.first);
     26   if (!host) {
     27     delete message;
     28     return false;
     29   }
     30   message->set_routing_id(worker_id.second);
     31   host->Send(message);
     32   return true;
     33 }
     34 
     35 }  // namespace
     36 
     37 EmbeddedWorkerDevToolsManager::ServiceWorkerIdentifier::ServiceWorkerIdentifier(
     38     const ServiceWorkerContextCore* const service_worker_context,
     39     int64 service_worker_version_id)
     40     : service_worker_context_(service_worker_context),
     41       service_worker_version_id_(service_worker_version_id) {
     42 }
     43 
     44 EmbeddedWorkerDevToolsManager::ServiceWorkerIdentifier::ServiceWorkerIdentifier(
     45     const ServiceWorkerIdentifier& other)
     46     : service_worker_context_(other.service_worker_context_),
     47       service_worker_version_id_(other.service_worker_version_id_) {
     48 }
     49 
     50 bool EmbeddedWorkerDevToolsManager::ServiceWorkerIdentifier::Matches(
     51     const ServiceWorkerIdentifier& other) const {
     52   return service_worker_context_ == other.service_worker_context_ &&
     53          service_worker_version_id_ == other.service_worker_version_id_;
     54 }
     55 
     56 EmbeddedWorkerDevToolsManager::WorkerInfo::WorkerInfo(
     57     const SharedWorkerInstance& instance)
     58     : shared_worker_instance_(new SharedWorkerInstance(instance)),
     59       state_(WORKER_UNINSPECTED),
     60       agent_host_(NULL) {
     61 }
     62 
     63 EmbeddedWorkerDevToolsManager::WorkerInfo::WorkerInfo(
     64     const ServiceWorkerIdentifier& service_worker_id)
     65     : service_worker_id_(new ServiceWorkerIdentifier(service_worker_id)),
     66       state_(WORKER_UNINSPECTED),
     67       agent_host_(NULL) {
     68 }
     69 
     70 bool EmbeddedWorkerDevToolsManager::WorkerInfo::Matches(
     71     const SharedWorkerInstance& other) {
     72   if (!shared_worker_instance_)
     73     return false;
     74   return shared_worker_instance_->Matches(other);
     75 }
     76 
     77 bool EmbeddedWorkerDevToolsManager::WorkerInfo::Matches(
     78     const ServiceWorkerIdentifier& other) {
     79   if (!service_worker_id_)
     80     return false;
     81   return service_worker_id_->Matches(other);
     82 }
     83 
     84 EmbeddedWorkerDevToolsManager::WorkerInfo::~WorkerInfo() {
     85 }
     86 
     87 class EmbeddedWorkerDevToolsManager::EmbeddedWorkerDevToolsAgentHost
     88     : public IPCDevToolsAgentHost,
     89       public IPC::Listener {
     90  public:
     91   explicit EmbeddedWorkerDevToolsAgentHost(WorkerId worker_id)
     92       : worker_id_(worker_id), worker_attached_(false) {
     93     AttachToWorker();
     94   }
     95 
     96   // DevToolsAgentHost override.
     97   virtual bool IsWorker() const OVERRIDE { return true; }
     98 
     99   // IPCDevToolsAgentHost implementation.
    100   virtual void SendMessageToAgent(IPC::Message* message) OVERRIDE {
    101     if (worker_attached_)
    102       SendMessageToWorker(worker_id_, message);
    103     else
    104       delete message;
    105   }
    106   virtual void Attach() OVERRIDE {
    107     AttachToWorker();
    108     IPCDevToolsAgentHost::Attach();
    109   }
    110   virtual void OnClientAttached() OVERRIDE {}
    111   virtual void OnClientDetached() OVERRIDE { DetachFromWorker(); }
    112 
    113   // IPC::Listener implementation.
    114   virtual bool OnMessageReceived(const IPC::Message& msg) OVERRIDE {
    115     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    116     bool handled = true;
    117     IPC_BEGIN_MESSAGE_MAP(EmbeddedWorkerDevToolsAgentHost, msg)
    118     IPC_MESSAGE_HANDLER(DevToolsClientMsg_DispatchOnInspectorFrontend,
    119                         OnDispatchOnInspectorFrontend)
    120     IPC_MESSAGE_HANDLER(DevToolsHostMsg_SaveAgentRuntimeState,
    121                         OnSaveAgentRuntimeState)
    122     IPC_MESSAGE_UNHANDLED(handled = false)
    123     IPC_END_MESSAGE_MAP()
    124     return handled;
    125   }
    126 
    127   void ReattachToWorker(WorkerId worker_id) {
    128     CHECK(!worker_attached_);
    129     worker_id_ = worker_id;
    130     if (!IsAttached())
    131       return;
    132     AttachToWorker();
    133     Reattach(state_);
    134   }
    135 
    136   void DetachFromWorker() {
    137     if (!worker_attached_)
    138       return;
    139     worker_attached_ = false;
    140     if (RenderProcessHost* host = RenderProcessHost::FromID(worker_id_.first))
    141       host->RemoveRoute(worker_id_.second);
    142     Release();
    143   }
    144 
    145   WorkerId worker_id() const { return worker_id_; }
    146 
    147  private:
    148   virtual ~EmbeddedWorkerDevToolsAgentHost() {
    149     CHECK(!worker_attached_);
    150     EmbeddedWorkerDevToolsManager::GetInstance()->RemoveInspectedWorkerData(
    151         this);
    152   }
    153 
    154   void OnDispatchOnInspectorFrontend(const std::string& message) {
    155     DevToolsManagerImpl::GetInstance()->DispatchOnInspectorFrontend(this,
    156                                                                     message);
    157   }
    158 
    159   void OnSaveAgentRuntimeState(const std::string& state) { state_ = state; }
    160 
    161   void AttachToWorker() {
    162     if (worker_attached_)
    163       return;
    164     worker_attached_ = true;
    165     AddRef();
    166     if (RenderProcessHost* host = RenderProcessHost::FromID(worker_id_.first))
    167       host->AddRoute(worker_id_.second, this);
    168   }
    169 
    170   WorkerId worker_id_;
    171   bool worker_attached_;
    172   std::string state_;
    173   DISALLOW_COPY_AND_ASSIGN(EmbeddedWorkerDevToolsAgentHost);
    174 };
    175 
    176 // static
    177 EmbeddedWorkerDevToolsManager* EmbeddedWorkerDevToolsManager::GetInstance() {
    178   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    179   return Singleton<EmbeddedWorkerDevToolsManager>::get();
    180 }
    181 
    182 DevToolsAgentHost* EmbeddedWorkerDevToolsManager::GetDevToolsAgentHostForWorker(
    183     int worker_process_id,
    184     int worker_route_id) {
    185   WorkerId id(worker_process_id, worker_route_id);
    186 
    187   WorkerInfoMap::iterator it = workers_.find(id);
    188   if (it == workers_.end())
    189     return NULL;
    190 
    191   WorkerInfo* info = it->second;
    192   if (info->state() != WORKER_UNINSPECTED &&
    193       info->state() != WORKER_PAUSED_FOR_DEBUG_ON_START) {
    194     return info->agent_host();
    195   }
    196 
    197   EmbeddedWorkerDevToolsAgentHost* agent_host =
    198       new EmbeddedWorkerDevToolsAgentHost(id);
    199   info->set_agent_host(agent_host);
    200   info->set_state(WORKER_INSPECTED);
    201   return agent_host;
    202 }
    203 
    204 DevToolsAgentHost*
    205 EmbeddedWorkerDevToolsManager::GetDevToolsAgentHostForServiceWorker(
    206     const ServiceWorkerIdentifier& service_worker_id) {
    207   WorkerInfoMap::iterator it = FindExistingServiceWorkerInfo(service_worker_id);
    208   if (it == workers_.end())
    209     return NULL;
    210   return GetDevToolsAgentHostForWorker(it->first.first, it->first.second);
    211 }
    212 
    213 EmbeddedWorkerDevToolsManager::EmbeddedWorkerDevToolsManager()
    214     : debug_service_worker_on_start_(false) {
    215 }
    216 
    217 EmbeddedWorkerDevToolsManager::~EmbeddedWorkerDevToolsManager() {
    218 }
    219 
    220 bool EmbeddedWorkerDevToolsManager::SharedWorkerCreated(
    221     int worker_process_id,
    222     int worker_route_id,
    223     const SharedWorkerInstance& instance) {
    224   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    225   const WorkerId id(worker_process_id, worker_route_id);
    226   WorkerInfoMap::iterator it = FindExistingSharedWorkerInfo(instance);
    227   if (it == workers_.end()) {
    228     scoped_ptr<WorkerInfo> info(new WorkerInfo(instance));
    229     workers_.set(id, info.Pass());
    230     return false;
    231   }
    232   MoveToPausedState(id, it);
    233   return true;
    234 }
    235 
    236 bool EmbeddedWorkerDevToolsManager::ServiceWorkerCreated(
    237     int worker_process_id,
    238     int worker_route_id,
    239     const ServiceWorkerIdentifier& service_worker_id) {
    240   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    241   const WorkerId id(worker_process_id, worker_route_id);
    242   WorkerInfoMap::iterator it = FindExistingServiceWorkerInfo(service_worker_id);
    243   if (it == workers_.end()) {
    244     scoped_ptr<WorkerInfo> info(new WorkerInfo(service_worker_id));
    245     if (debug_service_worker_on_start_)
    246       info->set_state(WORKER_PAUSED_FOR_DEBUG_ON_START);
    247     workers_.set(id, info.Pass());
    248     return debug_service_worker_on_start_;
    249   }
    250   MoveToPausedState(id, it);
    251   return true;
    252 }
    253 
    254 void EmbeddedWorkerDevToolsManager::WorkerDestroyed(int worker_process_id,
    255                                                     int worker_route_id) {
    256   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    257   const WorkerId id(worker_process_id, worker_route_id);
    258   WorkerInfoMap::iterator it = workers_.find(id);
    259   DCHECK(it != workers_.end());
    260   WorkerInfo* info = it->second;
    261   switch (info->state()) {
    262     case WORKER_UNINSPECTED:
    263     case WORKER_PAUSED_FOR_DEBUG_ON_START:
    264       workers_.erase(it);
    265       break;
    266     case WORKER_INSPECTED: {
    267       EmbeddedWorkerDevToolsAgentHost* agent_host = info->agent_host();
    268       info->set_state(WORKER_TERMINATED);
    269       if (!agent_host->IsAttached()) {
    270         agent_host->DetachFromWorker();
    271         return;
    272       }
    273       // Client host is debugging this worker agent host.
    274       std::string notification =
    275           DevToolsProtocol::CreateNotification(
    276               devtools::Worker::disconnectedFromWorker::kName, NULL)
    277               ->Serialize();
    278       DevToolsManagerImpl::GetInstance()->DispatchOnInspectorFrontend(
    279           agent_host, notification);
    280       agent_host->DetachFromWorker();
    281       break;
    282     }
    283     case WORKER_TERMINATED:
    284       NOTREACHED();
    285       break;
    286     case WORKER_PAUSED_FOR_REATTACH: {
    287       scoped_ptr<WorkerInfo> worker_info = workers_.take_and_erase(it);
    288       worker_info->set_state(WORKER_TERMINATED);
    289       const WorkerId old_id = worker_info->agent_host()->worker_id();
    290       workers_.set(old_id, worker_info.Pass());
    291       break;
    292     }
    293   }
    294 }
    295 
    296 void EmbeddedWorkerDevToolsManager::WorkerContextStarted(int worker_process_id,
    297                                                          int worker_route_id) {
    298   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    299   const WorkerId id(worker_process_id, worker_route_id);
    300   WorkerInfoMap::iterator it = workers_.find(id);
    301   DCHECK(it != workers_.end());
    302   WorkerInfo* info = it->second;
    303   if (info->state() == WORKER_PAUSED_FOR_DEBUG_ON_START) {
    304     RenderProcessHost* rph = RenderProcessHost::FromID(worker_process_id);
    305     scoped_refptr<DevToolsAgentHost> agent_host(
    306         GetDevToolsAgentHostForWorker(worker_process_id, worker_route_id));
    307     DevToolsManagerImpl::GetInstance()->Inspect(rph->GetBrowserContext(),
    308                                                 agent_host.get());
    309   } else if (info->state() == WORKER_PAUSED_FOR_REATTACH) {
    310     info->agent_host()->ReattachToWorker(id);
    311     info->set_state(WORKER_INSPECTED);
    312   }
    313 }
    314 
    315 void EmbeddedWorkerDevToolsManager::RemoveInspectedWorkerData(
    316     EmbeddedWorkerDevToolsAgentHost* agent_host) {
    317   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    318   const WorkerId id(agent_host->worker_id());
    319   scoped_ptr<WorkerInfo> worker_info = workers_.take_and_erase(id);
    320   if (worker_info) {
    321     DCHECK_EQ(worker_info->agent_host(), agent_host);
    322     if (worker_info->state() == WORKER_TERMINATED)
    323       return;
    324     DCHECK_EQ(worker_info->state(), WORKER_INSPECTED);
    325     worker_info->set_agent_host(NULL);
    326     worker_info->set_state(WORKER_UNINSPECTED);
    327     workers_.set(id, worker_info.Pass());
    328     return;
    329   }
    330   for (WorkerInfoMap::iterator it = workers_.begin(); it != workers_.end();
    331        ++it) {
    332     if (it->second->agent_host() == agent_host) {
    333       DCHECK_EQ(WORKER_PAUSED_FOR_REATTACH, it->second->state());
    334       SendMessageToWorker(
    335           it->first,
    336           new DevToolsAgentMsg_ResumeWorkerContext(it->first.second));
    337       it->second->set_agent_host(NULL);
    338       it->second->set_state(WORKER_UNINSPECTED);
    339       return;
    340     }
    341   }
    342 }
    343 
    344 EmbeddedWorkerDevToolsManager::WorkerInfoMap::iterator
    345 EmbeddedWorkerDevToolsManager::FindExistingSharedWorkerInfo(
    346     const SharedWorkerInstance& instance) {
    347   WorkerInfoMap::iterator it = workers_.begin();
    348   for (; it != workers_.end(); ++it) {
    349     if (it->second->Matches(instance))
    350       break;
    351   }
    352   return it;
    353 }
    354 
    355 EmbeddedWorkerDevToolsManager::WorkerInfoMap::iterator
    356 EmbeddedWorkerDevToolsManager::FindExistingServiceWorkerInfo(
    357     const ServiceWorkerIdentifier& service_worker_id) {
    358   WorkerInfoMap::iterator it = workers_.begin();
    359   for (; it != workers_.end(); ++it) {
    360     if (it->second->Matches(service_worker_id))
    361       break;
    362   }
    363   return it;
    364 }
    365 
    366 void EmbeddedWorkerDevToolsManager::MoveToPausedState(
    367     const WorkerId& id,
    368     const WorkerInfoMap::iterator& it) {
    369   DCHECK_EQ(WORKER_TERMINATED, it->second->state());
    370   scoped_ptr<WorkerInfo> info = workers_.take_and_erase(it);
    371   info->set_state(WORKER_PAUSED_FOR_REATTACH);
    372   workers_.set(id, info.Pass());
    373 }
    374 
    375 void EmbeddedWorkerDevToolsManager::ResetForTesting() {
    376   workers_.clear();
    377 }
    378 
    379 }  // namespace content
    380