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