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