1 // Copyright 2015 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.
5 #include "content/browser/devtools/protocol/service_worker_handler.h"
8 #include "base/containers/scoped_ptr_hash_map.h"
9 #include "base/strings/string_number_conversions.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "content/browser/devtools/service_worker_devtools_agent_host.h"
12 #include "content/browser/devtools/service_worker_devtools_manager.h"
13 #include "content/browser/frame_host/frame_tree.h"
14 #include "content/browser/frame_host/frame_tree_node.h"
15 #include "content/browser/frame_host/render_frame_host_impl.h"
16 #include "content/browser/service_worker/service_worker_context_watcher.h"
17 #include "content/browser/service_worker/service_worker_context_wrapper.h"
18 #include "content/browser/service_worker/service_worker_version.h"
19 #include "content/public/browser/browser_context.h"
20 #include "content/public/browser/browser_thread.h"
21 #include "content/public/browser/devtools_agent_host.h"
22 #include "content/public/browser/render_frame_host.h"
23 #include "content/public/browser/render_process_host.h"
24 #include "content/public/browser/storage_partition.h"
25 #include "content/public/common/push_messaging_status.h"
28 // Windows headers will redefine SendMessage.
35 namespace service_worker
{
37 using Response
= DevToolsProtocolClient::Response
;
41 void ResultNoOp(bool success
) {
43 void StatusNoOp(ServiceWorkerStatusCode status
) {
45 void PushDeliveryNoOp(PushDeliveryStatus status
) {
48 const std::string
GetVersionRunningStatusString(
49 content::ServiceWorkerVersion::RunningStatus running_status
) {
50 switch (running_status
) {
51 case content::ServiceWorkerVersion::STOPPED
:
52 return kServiceWorkerVersionRunningStatusStopped
;
53 case content::ServiceWorkerVersion::STARTING
:
54 return kServiceWorkerVersionRunningStatusStarting
;
55 case content::ServiceWorkerVersion::RUNNING
:
56 return kServiceWorkerVersionRunningStatusRunning
;
57 case content::ServiceWorkerVersion::STOPPING
:
58 return kServiceWorkerVersionRunningStatusStopping
;
63 const std::string
GetVersionStatusString(
64 content::ServiceWorkerVersion::Status status
) {
66 case content::ServiceWorkerVersion::NEW
:
67 return kServiceWorkerVersionStatusNew
;
68 case content::ServiceWorkerVersion::INSTALLING
:
69 return kServiceWorkerVersionStatusInstalling
;
70 case content::ServiceWorkerVersion::INSTALLED
:
71 return kServiceWorkerVersionStatusInstalled
;
72 case content::ServiceWorkerVersion::ACTIVATING
:
73 return kServiceWorkerVersionStatusActivating
;
74 case content::ServiceWorkerVersion::ACTIVATED
:
75 return kServiceWorkerVersionStatusActivated
;
76 case content::ServiceWorkerVersion::REDUNDANT
:
77 return kServiceWorkerVersionStatusRedundant
;
82 scoped_refptr
<ServiceWorkerVersion
> CreateVersionDictionaryValue(
83 const ServiceWorkerVersionInfo
& version_info
) {
84 scoped_refptr
<ServiceWorkerVersion
> version(
85 ServiceWorkerVersion::Create()
86 ->set_version_id(base::Int64ToString(version_info
.version_id
))
87 ->set_registration_id(
88 base::Int64ToString(version_info
.registration_id
))
89 ->set_script_url(version_info
.script_url
.spec())
91 GetVersionRunningStatusString(version_info
.running_status
))
92 ->set_status(GetVersionStatusString(version_info
.status
))
93 ->set_script_last_modified(
94 version_info
.script_last_modified
.ToDoubleT())
95 ->set_script_response_time(
96 version_info
.script_response_time
.ToDoubleT()));
100 scoped_refptr
<ServiceWorkerRegistration
> CreateRegistrationDictionaryValue(
101 const ServiceWorkerRegistrationInfo
& registration_info
) {
102 scoped_refptr
<ServiceWorkerRegistration
> registration(
103 ServiceWorkerRegistration::Create()
104 ->set_registration_id(
105 base::Int64ToString(registration_info
.registration_id
))
106 ->set_scope_url(registration_info
.pattern
.spec())
107 ->set_is_deleted(registration_info
.delete_flag
==
108 ServiceWorkerRegistrationInfo::IS_DELETED
));
112 scoped_refptr
<ServiceWorkerDevToolsAgentHost
> GetMatchingServiceWorker(
113 const ServiceWorkerDevToolsAgentHost::List
& agent_hosts
,
115 scoped_refptr
<ServiceWorkerDevToolsAgentHost
> best_host
;
116 std::string best_scope
;
117 for (auto host
: agent_hosts
) {
118 if (host
->GetURL().host() != url
.host())
120 std::string path
= host
->GetURL().path();
121 std::string file
= host
->GetURL().ExtractFileName();
122 std::string scope
= path
.substr(0, path
.length() - file
.length());
123 if (scope
.length() > best_scope
.length()) {
131 ServiceWorkerDevToolsAgentHost::Map
GetMatchingServiceWorkers(
132 const std::set
<GURL
>& urls
) {
133 ServiceWorkerDevToolsAgentHost::List agent_hosts
;
134 ServiceWorkerDevToolsManager::GetInstance()->
135 AddAllAgentHosts(&agent_hosts
);
136 ServiceWorkerDevToolsAgentHost::Map result
;
137 for (const GURL
& url
: urls
) {
138 scoped_refptr
<ServiceWorkerDevToolsAgentHost
> host
=
139 GetMatchingServiceWorker(agent_hosts
, url
);
141 result
[host
->GetId()] = host
;
146 bool CollectURLs(std::set
<GURL
>* urls
, FrameTreeNode
* tree_node
) {
147 urls
->insert(tree_node
->current_url());
151 void StopServiceWorkerOnIO(scoped_refptr
<ServiceWorkerContextWrapper
> context
,
153 if (content::ServiceWorkerVersion
* version
=
154 context
->GetLiveVersion(version_id
)) {
155 version
->StopWorker(base::Bind(&StatusNoOp
));
159 void GetDevToolsRouteInfoOnIO(
160 scoped_refptr
<ServiceWorkerContextWrapper
> context
,
162 const base::Callback
<void(int, int)>& callback
) {
163 if (content::ServiceWorkerVersion
* version
=
164 context
->GetLiveVersion(version_id
)) {
165 BrowserThread::PostTask(
166 BrowserThread::UI
, FROM_HERE
,
168 callback
, version
->embedded_worker()->process_id(),
169 version
->embedded_worker()->worker_devtools_agent_route_id()));
173 Response
CreateContextErrorResponse() {
174 return Response::InternalError("Could not connect to the context");
177 Response
CreateInvalidVersionIdErrorResponse() {
178 return Response::InternalError("Invalid version ID");
183 ServiceWorkerHandler::ServiceWorkerHandler()
184 : enabled_(false), weak_factory_(this) {
187 ServiceWorkerHandler::~ServiceWorkerHandler() {
191 void ServiceWorkerHandler::SetRenderFrameHost(
192 RenderFrameHostImpl
* render_frame_host
) {
193 render_frame_host_
= render_frame_host
;
194 // Do not call UpdateHosts yet, wait for load to commit.
195 if (!render_frame_host
) {
199 StoragePartition
* partition
= BrowserContext::GetStoragePartition(
200 render_frame_host
->GetProcess()->GetBrowserContext(),
201 render_frame_host
->GetSiteInstance());
203 context_
= static_cast<ServiceWorkerContextWrapper
*>(
204 partition
->GetServiceWorkerContext());
207 void ServiceWorkerHandler::SetClient(scoped_ptr
<Client
> client
) {
208 client_
.swap(client
);
211 void ServiceWorkerHandler::UpdateHosts() {
216 if (render_frame_host_
) {
217 render_frame_host_
->frame_tree_node()->frame_tree()->ForEach(
218 base::Bind(&CollectURLs
, &urls_
));
221 ServiceWorkerDevToolsAgentHost::Map old_hosts
= attached_hosts_
;
222 ServiceWorkerDevToolsAgentHost::Map new_hosts
=
223 GetMatchingServiceWorkers(urls_
);
225 for (auto pair
: old_hosts
) {
226 if (new_hosts
.find(pair
.first
) == new_hosts
.end())
227 ReportWorkerTerminated(pair
.second
.get());
230 for (auto pair
: new_hosts
) {
231 if (old_hosts
.find(pair
.first
) == old_hosts
.end())
232 ReportWorkerCreated(pair
.second
.get());
236 void ServiceWorkerHandler::Detached() {
240 Response
ServiceWorkerHandler::Enable() {
242 return Response::OK();
244 return Response::InternalError("Could not connect to the context");
247 ServiceWorkerDevToolsManager::GetInstance()->AddObserver(this);
249 client_
->DebugOnStartUpdated(
250 DebugOnStartUpdatedParams::Create()->set_debug_on_start(
251 ServiceWorkerDevToolsManager::GetInstance()
252 ->debug_service_worker_on_start()));
254 context_watcher_
= new ServiceWorkerContextWatcher(
255 context_
, base::Bind(&ServiceWorkerHandler::OnWorkerRegistrationUpdated
,
256 weak_factory_
.GetWeakPtr()),
257 base::Bind(&ServiceWorkerHandler::OnWorkerVersionUpdated
,
258 weak_factory_
.GetWeakPtr()),
259 base::Bind(&ServiceWorkerHandler::OnErrorReported
,
260 weak_factory_
.GetWeakPtr()));
261 context_watcher_
->Start();
264 return Response::OK();
267 Response
ServiceWorkerHandler::Disable() {
269 return Response::OK();
272 ServiceWorkerDevToolsManager::GetInstance()->RemoveObserver(this);
273 for (const auto& pair
: attached_hosts_
)
274 pair
.second
->DetachClient();
275 attached_hosts_
.clear();
276 DCHECK(context_watcher_
);
277 context_watcher_
->Stop();
278 context_watcher_
= nullptr;
279 return Response::OK();
282 Response
ServiceWorkerHandler::SendMessage(
283 const std::string
& worker_id
,
284 const std::string
& message
) {
285 auto it
= attached_hosts_
.find(worker_id
);
286 if (it
== attached_hosts_
.end())
287 return Response::InternalError("Not connected to the worker");
288 it
->second
->DispatchProtocolMessage(message
);
289 return Response::OK();
292 Response
ServiceWorkerHandler::Stop(
293 const std::string
& worker_id
) {
294 auto it
= attached_hosts_
.find(worker_id
);
295 if (it
== attached_hosts_
.end())
296 return Response::InternalError("Not connected to the worker");
297 it
->second
->UnregisterWorker();
298 return Response::OK();
301 Response
ServiceWorkerHandler::Unregister(const std::string
& scope_url
) {
303 return Response::OK();
305 return CreateContextErrorResponse();
306 context_
->UnregisterServiceWorker(GURL(scope_url
), base::Bind(&ResultNoOp
));
307 return Response::OK();
310 Response
ServiceWorkerHandler::StartWorker(const std::string
& scope_url
) {
312 return Response::OK();
314 return CreateContextErrorResponse();
315 context_
->StartServiceWorker(GURL(scope_url
), base::Bind(&StatusNoOp
));
316 return Response::OK();
319 Response
ServiceWorkerHandler::StopWorker(const std::string
& version_id
) {
321 return Response::OK();
323 return CreateContextErrorResponse();
325 if (!base::StringToInt64(version_id
, &id
))
326 return CreateInvalidVersionIdErrorResponse();
327 BrowserThread::PostTask(BrowserThread::IO
, FROM_HERE
,
328 base::Bind(&StopServiceWorkerOnIO
, context_
, id
));
329 return Response::OK();
332 Response
ServiceWorkerHandler::UpdateRegistration(
333 const std::string
& scope_url
) {
335 return Response::OK();
337 return CreateContextErrorResponse();
338 context_
->UpdateRegistration(GURL(scope_url
));
339 return Response::OK();
342 Response
ServiceWorkerHandler::InspectWorker(const std::string
& version_id
) {
344 return Response::OK();
346 return CreateContextErrorResponse();
349 if (!base::StringToInt64(version_id
, &id
))
350 return CreateInvalidVersionIdErrorResponse();
351 BrowserThread::PostTask(
352 BrowserThread::IO
, FROM_HERE
,
353 base::Bind(&GetDevToolsRouteInfoOnIO
, context_
, id
,
354 base::Bind(&ServiceWorkerHandler::OpenNewDevToolsWindow
,
355 weak_factory_
.GetWeakPtr())));
356 return Response::OK();
359 Response
ServiceWorkerHandler::SkipWaiting(const std::string
& version_id
) {
361 return Response::OK();
363 return CreateContextErrorResponse();
366 if (!base::StringToInt64(version_id
, &id
))
367 return CreateInvalidVersionIdErrorResponse();
368 context_
->SimulateSkipWaiting(id
);
369 return Response::OK();
372 Response
ServiceWorkerHandler::SetDebugOnStart(bool debug_on_start
) {
373 ServiceWorkerDevToolsManager::GetInstance()
374 ->set_debug_service_worker_on_start(debug_on_start
);
375 return Response::OK();
378 Response
ServiceWorkerHandler::DeliverPushMessage(
379 const std::string
& origin
,
380 const std::string
& registration_id
,
381 const std::string
& data
) {
383 return Response::OK();
384 if (!render_frame_host_
)
385 return CreateContextErrorResponse();
387 if (!base::StringToInt64(registration_id
, &id
))
388 return CreateInvalidVersionIdErrorResponse();
389 BrowserContext::DeliverPushMessage(
390 render_frame_host_
->GetProcess()->GetBrowserContext(), GURL(origin
), id
,
391 data
, base::Bind(&PushDeliveryNoOp
));
392 return Response::OK();
395 void ServiceWorkerHandler::OpenNewDevToolsWindow(int process_id
,
396 int devtools_agent_route_id
) {
397 scoped_refptr
<DevToolsAgentHostImpl
> agent_host(
398 ServiceWorkerDevToolsManager::GetInstance()
399 ->GetDevToolsAgentHostForWorker(process_id
, devtools_agent_route_id
));
400 if (!agent_host
.get())
402 agent_host
->Inspect(render_frame_host_
->GetProcess()->GetBrowserContext());
405 void ServiceWorkerHandler::OnWorkerRegistrationUpdated(
406 const std::vector
<ServiceWorkerRegistrationInfo
>& registrations
) {
407 std::vector
<scoped_refptr
<ServiceWorkerRegistration
>> registration_values
;
408 for (const auto& registration
: registrations
) {
409 registration_values
.push_back(
410 CreateRegistrationDictionaryValue(registration
));
412 client_
->WorkerRegistrationUpdated(
413 WorkerRegistrationUpdatedParams::Create()->set_registrations(
414 registration_values
));
417 void ServiceWorkerHandler::OnWorkerVersionUpdated(
418 const std::vector
<ServiceWorkerVersionInfo
>& versions
) {
419 std::vector
<scoped_refptr
<ServiceWorkerVersion
>> version_values
;
420 for (const auto& version
: versions
) {
421 version_values
.push_back(CreateVersionDictionaryValue(version
));
423 client_
->WorkerVersionUpdated(
424 WorkerVersionUpdatedParams::Create()->set_versions(version_values
));
427 void ServiceWorkerHandler::OnErrorReported(
428 int64 registration_id
,
430 const ServiceWorkerContextObserver::ErrorInfo
& info
) {
431 client_
->WorkerErrorReported(
432 WorkerErrorReportedParams::Create()->set_error_message(
433 ServiceWorkerErrorMessage::Create()
434 ->set_error_message(base::UTF16ToUTF8(info
.error_message
))
435 ->set_registration_id(base::Int64ToString(registration_id
))
436 ->set_version_id(base::Int64ToString(version_id
))
437 ->set_source_url(info
.source_url
.spec())
438 ->set_line_number(info
.line_number
)
439 ->set_column_number(info
.column_number
)));
442 void ServiceWorkerHandler::DispatchProtocolMessage(
443 DevToolsAgentHost
* host
,
444 const std::string
& message
) {
446 auto it
= attached_hosts_
.find(host
->GetId());
447 if (it
== attached_hosts_
.end())
448 return; // Already disconnected.
450 client_
->DispatchMessage(
451 DispatchMessageParams::Create()->
452 set_worker_id(host
->GetId())->
453 set_message(message
));
456 void ServiceWorkerHandler::AgentHostClosed(
457 DevToolsAgentHost
* host
,
458 bool replaced_with_another_client
) {
459 client_
->WorkerTerminated(WorkerTerminatedParams::Create()->
460 set_worker_id(host
->GetId()));
461 attached_hosts_
.erase(host
->GetId());
464 void ServiceWorkerHandler::WorkerCreated(
465 ServiceWorkerDevToolsAgentHost
* host
) {
466 auto hosts
= GetMatchingServiceWorkers(urls_
);
467 if (hosts
.find(host
->GetId()) != hosts
.end() && !host
->IsAttached() &&
468 !host
->IsPausedForDebugOnStart())
469 host
->PauseForDebugOnStart();
472 void ServiceWorkerHandler::WorkerReadyForInspection(
473 ServiceWorkerDevToolsAgentHost
* host
) {
474 if (ServiceWorkerDevToolsManager::GetInstance()
475 ->debug_service_worker_on_start()) {
476 // When debug_service_worker_on_start is true, a new DevTools window will
477 // be opend in ServiceWorkerDevToolsManager::WorkerReadyForInspection.
483 void ServiceWorkerHandler::WorkerDestroyed(
484 ServiceWorkerDevToolsAgentHost
* host
) {
488 void ServiceWorkerHandler::DebugOnStartUpdated(bool debug_on_start
) {
489 client_
->DebugOnStartUpdated(
490 DebugOnStartUpdatedParams::Create()->set_debug_on_start(debug_on_start
));
493 void ServiceWorkerHandler::ReportWorkerCreated(
494 ServiceWorkerDevToolsAgentHost
* host
) {
495 if (host
->IsAttached())
497 attached_hosts_
[host
->GetId()] = host
;
498 host
->AttachClient(this);
499 client_
->WorkerCreated(WorkerCreatedParams::Create()->
500 set_worker_id(host
->GetId())->
501 set_url(host
->GetURL().spec()));
504 void ServiceWorkerHandler::ReportWorkerTerminated(
505 ServiceWorkerDevToolsAgentHost
* host
) {
506 auto it
= attached_hosts_
.find(host
->GetId());
507 if (it
== attached_hosts_
.end())
509 host
->DetachClient();
510 client_
->WorkerTerminated(WorkerTerminatedParams::Create()->
511 set_worker_id(host
->GetId()));
512 attached_hosts_
.erase(it
);
515 } // namespace service_worker
516 } // namespace devtools
517 } // namespace content