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