• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
SendMessageToWorker(const EmbeddedWorkerDevToolsManager::WorkerId & worker_id,IPC::Message * message)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 
ServiceWorkerIdentifier(const ServiceWorkerContextCore * const service_worker_context,int64 service_worker_version_id)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 
ServiceWorkerIdentifier(const ServiceWorkerIdentifier & other)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 
Matches(const ServiceWorkerIdentifier & other) const50 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 
WorkerInfo(const SharedWorkerInstance & instance)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 
WorkerInfo(const ServiceWorkerIdentifier & service_worker_id)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 
Matches(const SharedWorkerInstance & other)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 
Matches(const ServiceWorkerIdentifier & other)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 
~WorkerInfo()84 EmbeddedWorkerDevToolsManager::WorkerInfo::~WorkerInfo() {
85 }
86 
87 class EmbeddedWorkerDevToolsManager::EmbeddedWorkerDevToolsAgentHost
88     : public IPCDevToolsAgentHost,
89       public IPC::Listener {
90  public:
EmbeddedWorkerDevToolsAgentHost(WorkerId worker_id)91   explicit EmbeddedWorkerDevToolsAgentHost(WorkerId worker_id)
92       : worker_id_(worker_id), worker_attached_(false) {
93     AttachToWorker();
94   }
95 
96   // DevToolsAgentHost override.
IsWorker() const97   virtual bool IsWorker() const OVERRIDE { return true; }
98 
99   // IPCDevToolsAgentHost implementation.
SendMessageToAgent(IPC::Message * message)100   virtual void SendMessageToAgent(IPC::Message* message) OVERRIDE {
101     if (worker_attached_)
102       SendMessageToWorker(worker_id_, message);
103     else
104       delete message;
105   }
Attach()106   virtual void Attach() OVERRIDE {
107     AttachToWorker();
108     IPCDevToolsAgentHost::Attach();
109   }
OnClientAttached()110   virtual void OnClientAttached() OVERRIDE {}
OnClientDetached()111   virtual void OnClientDetached() OVERRIDE { DetachFromWorker(); }
112 
113   // IPC::Listener implementation.
OnMessageReceived(const IPC::Message & msg)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 
ReattachToWorker(WorkerId worker_id)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 
DetachFromWorker()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 
worker_id() const145   WorkerId worker_id() const { return worker_id_; }
146 
147  private:
~EmbeddedWorkerDevToolsAgentHost()148   virtual ~EmbeddedWorkerDevToolsAgentHost() {
149     CHECK(!worker_attached_);
150     EmbeddedWorkerDevToolsManager::GetInstance()->RemoveInspectedWorkerData(
151         this);
152   }
153 
OnDispatchOnInspectorFrontend(const std::string & message)154   void OnDispatchOnInspectorFrontend(const std::string& message) {
155     DevToolsManagerImpl::GetInstance()->DispatchOnInspectorFrontend(this,
156                                                                     message);
157   }
158 
OnSaveAgentRuntimeState(const std::string & state)159   void OnSaveAgentRuntimeState(const std::string& state) { state_ = state; }
160 
AttachToWorker()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
GetInstance()177 EmbeddedWorkerDevToolsManager* EmbeddedWorkerDevToolsManager::GetInstance() {
178   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
179   return Singleton<EmbeddedWorkerDevToolsManager>::get();
180 }
181 
GetDevToolsAgentHostForWorker(int worker_process_id,int worker_route_id)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*
GetDevToolsAgentHostForServiceWorker(const ServiceWorkerIdentifier & service_worker_id)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 
EmbeddedWorkerDevToolsManager()213 EmbeddedWorkerDevToolsManager::EmbeddedWorkerDevToolsManager()
214     : debug_service_worker_on_start_(false) {
215 }
216 
~EmbeddedWorkerDevToolsManager()217 EmbeddedWorkerDevToolsManager::~EmbeddedWorkerDevToolsManager() {
218 }
219 
SharedWorkerCreated(int worker_process_id,int worker_route_id,const SharedWorkerInstance & instance)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 
ServiceWorkerCreated(int worker_process_id,int worker_route_id,const ServiceWorkerIdentifier & service_worker_id)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 
WorkerDestroyed(int worker_process_id,int worker_route_id)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 
WorkerContextStarted(int worker_process_id,int worker_route_id)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 
RemoveInspectedWorkerData(EmbeddedWorkerDevToolsAgentHost * agent_host)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
FindExistingSharedWorkerInfo(const SharedWorkerInstance & instance)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
FindExistingServiceWorkerInfo(const ServiceWorkerIdentifier & service_worker_id)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 
MoveToPausedState(const WorkerId & id,const WorkerInfoMap::iterator & it)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 
ResetForTesting()375 void EmbeddedWorkerDevToolsManager::ResetForTesting() {
376   workers_.clear();
377 }
378 
379 }  // namespace content
380