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
GetForWorker(int worker_process_id,int worker_route_id)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
HasForWorker(int worker_process_id,int worker_route_id)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 {
TerminatedInspectedWorkercontent::WorkerDevToolsManager::TerminatedInspectedWorker55 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:
WorkerDevToolsAgentHost(WorkerId worker_id)70 explicit WorkerDevToolsAgentHost(WorkerId worker_id)
71 : has_worker_id_(false) {
72 SetWorkerId(worker_id, false);
73 }
74
SetWorkerId(WorkerId worker_id,bool reattach)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
ResetWorkerId()94 void ResetWorkerId() {
95 g_agent_map.Get().erase(worker_id_);
96 has_worker_id_ = false;
97 Release(); // Balanced in SetWorkerId.
98 }
99
SaveAgentRuntimeState(const std::string & state)100 void SaveAgentRuntimeState(const std::string& state) {
101 state_ = state;
102 }
103
ConnectionFailed()104 void ConnectionFailed() {
105 NotifyCloseListener();
106 // Object can be deleted here.
107 }
108
109 private:
110 virtual ~WorkerDevToolsAgentHost();
111
ConnectToWorker(int worker_process_id,int worker_route_id)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
ForwardToWorkerDevToolsAgent(int worker_process_id,int worker_route_id,IPC::Message * message)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.
SendMessageToAgent(IPC::Message * message)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
OnClientAttached()142 virtual void OnClientAttached() OVERRIDE {}
OnClientDetached()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:
WorkerReloaded(WorkerId old_id,WorkerId new_id)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
WorkerDestroyed(WorkerId id)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
RemovePendingWorkerData(WorkerId id)190 static void RemovePendingWorkerData(WorkerId id) {
191 BrowserThread::PostTask(
192 BrowserThread::IO, FROM_HERE,
193 base::Bind(&RemoveInspectedWorkerDataOnIOThread, id));
194 }
195
196 private:
DetachedClientHosts()197 DetachedClientHosts() {}
~DetachedClientHosts()198 ~DetachedClientHosts() {}
199
RemoveInspectedWorkerDataOnIOThread(WorkerId id)200 static void RemoveInspectedWorkerDataOnIOThread(WorkerId id) {
201 WorkerDevToolsManager::GetInstance()->RemoveInspectedWorkerData(id);
202 }
203 };
204
205 struct WorkerDevToolsManager::InspectedWorker {
InspectedWorkercontent::WorkerDevToolsManager::InspectedWorker206 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
GetInstance()219 WorkerDevToolsManager* WorkerDevToolsManager::GetInstance() {
220 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
221 return Singleton<WorkerDevToolsManager>::get();
222 }
223
224 // static
GetDevToolsAgentHostForWorker(int worker_process_id,int worker_route_id)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
HasDevToolsAgentHostForWorker(int worker_process_id,int worker_route_id)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
WorkerDevToolsManager()243 WorkerDevToolsManager::WorkerDevToolsManager() {
244 }
245
~WorkerDevToolsManager()246 WorkerDevToolsManager::~WorkerDevToolsManager() {
247 }
248
WorkerCreated(WorkerProcessHost * worker,const WorkerProcessHost::WorkerInstance & instance)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
WorkerDestroyed(WorkerProcessHost * worker,int worker_route_id)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
WorkerContextStarted(WorkerProcessHost * process,int worker_route_id)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
RemoveInspectedWorkerData(const WorkerId & id)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
FindInspectedWorker(int host_id,int route_id)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
FindWorkerProcess(int worker_process_id)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
ConnectDevToolsAgentHostToWorker(int worker_process_id,int worker_route_id)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
ForwardToDevToolsClient(int worker_process_id,int worker_route_id,const std::string & message)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
SaveAgentRuntimeState(int worker_process_id,int worker_route_id,const std::string & state)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
ForwardToWorkerDevToolsAgent(int worker_process_id,int worker_route_id,const IPC::Message & message)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
ForwardToDevToolsClientOnUIThread(int worker_process_id,int worker_route_id,const std::string & message)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
SaveAgentRuntimeStateOnUIThread(int worker_process_id,int worker_route_id,const std::string & state)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
NotifyConnectionFailedOnIOThread(int worker_process_id,int worker_route_id)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
NotifyConnectionFailedOnUIThread(int worker_process_id,int worker_route_id)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
SendResumeToWorker(const WorkerId & id)454 void WorkerDevToolsManager::SendResumeToWorker(const WorkerId& id) {
455 if (WorkerProcessHost* process = FindWorkerProcess(id.first))
456 process->Send(new DevToolsAgentMsg_ResumeWorkerContext(id.second));
457 }
458
~WorkerDevToolsAgentHost()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