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 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 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 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 50 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 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 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 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 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 84 EmbeddedWorkerDevToolsManager::WorkerInfo::~WorkerInfo() { 85 } 86 87 class EmbeddedWorkerDevToolsManager::EmbeddedWorkerDevToolsAgentHost 88 : public IPCDevToolsAgentHost, 89 public IPC::Listener { 90 public: 91 explicit EmbeddedWorkerDevToolsAgentHost(WorkerId worker_id) 92 : worker_id_(worker_id), worker_attached_(false) { 93 AttachToWorker(); 94 } 95 96 // DevToolsAgentHost override. 97 virtual bool IsWorker() const OVERRIDE { return true; } 98 99 // IPCDevToolsAgentHost implementation. 100 virtual void SendMessageToAgent(IPC::Message* message) OVERRIDE { 101 if (worker_attached_) 102 SendMessageToWorker(worker_id_, message); 103 else 104 delete message; 105 } 106 virtual void Attach() OVERRIDE { 107 AttachToWorker(); 108 IPCDevToolsAgentHost::Attach(); 109 } 110 virtual void OnClientAttached() OVERRIDE {} 111 virtual void OnClientDetached() OVERRIDE { DetachFromWorker(); } 112 113 // IPC::Listener implementation. 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 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 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 145 WorkerId worker_id() const { return worker_id_; } 146 147 private: 148 virtual ~EmbeddedWorkerDevToolsAgentHost() { 149 CHECK(!worker_attached_); 150 EmbeddedWorkerDevToolsManager::GetInstance()->RemoveInspectedWorkerData( 151 this); 152 } 153 154 void OnDispatchOnInspectorFrontend(const std::string& message) { 155 DevToolsManagerImpl::GetInstance()->DispatchOnInspectorFrontend(this, 156 message); 157 } 158 159 void OnSaveAgentRuntimeState(const std::string& state) { state_ = state; } 160 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 177 EmbeddedWorkerDevToolsManager* EmbeddedWorkerDevToolsManager::GetInstance() { 178 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 179 return Singleton<EmbeddedWorkerDevToolsManager>::get(); 180 } 181 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* 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 213 EmbeddedWorkerDevToolsManager::EmbeddedWorkerDevToolsManager() 214 : debug_service_worker_on_start_(false) { 215 } 216 217 EmbeddedWorkerDevToolsManager::~EmbeddedWorkerDevToolsManager() { 218 } 219 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 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 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 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 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 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 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 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 375 void EmbeddedWorkerDevToolsManager::ResetForTesting() { 376 workers_.clear(); 377 } 378 379 } // namespace content 380