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