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/browser/web_contents.h"
26 #include "content/public/common/push_messaging_status.h"
29 // Windows headers will redefine SendMessage.
36 namespace service_worker
{
38 using Response
= DevToolsProtocolClient::Response
;
42 void ResultNoOp(bool success
) {
44 void StatusNoOp(ServiceWorkerStatusCode status
) {
46 void PushDeliveryNoOp(PushDeliveryStatus status
) {
49 const std::string
GetVersionRunningStatusString(
50 content::ServiceWorkerVersion::RunningStatus running_status
) {
51 switch (running_status
) {
52 case content::ServiceWorkerVersion::STOPPED
:
53 return kServiceWorkerVersionRunningStatusStopped
;
54 case content::ServiceWorkerVersion::STARTING
:
55 return kServiceWorkerVersionRunningStatusStarting
;
56 case content::ServiceWorkerVersion::RUNNING
:
57 return kServiceWorkerVersionRunningStatusRunning
;
58 case content::ServiceWorkerVersion::STOPPING
:
59 return kServiceWorkerVersionRunningStatusStopping
;
64 const std::string
GetVersionStatusString(
65 content::ServiceWorkerVersion::Status status
) {
67 case content::ServiceWorkerVersion::NEW
:
68 return kServiceWorkerVersionStatusNew
;
69 case content::ServiceWorkerVersion::INSTALLING
:
70 return kServiceWorkerVersionStatusInstalling
;
71 case content::ServiceWorkerVersion::INSTALLED
:
72 return kServiceWorkerVersionStatusInstalled
;
73 case content::ServiceWorkerVersion::ACTIVATING
:
74 return kServiceWorkerVersionStatusActivating
;
75 case content::ServiceWorkerVersion::ACTIVATED
:
76 return kServiceWorkerVersionStatusActivated
;
77 case content::ServiceWorkerVersion::REDUNDANT
:
78 return kServiceWorkerVersionStatusRedundant
;
83 scoped_refptr
<ServiceWorkerVersion
> CreateVersionDictionaryValue(
84 const ServiceWorkerVersionInfo
& version_info
) {
85 std::vector
<std::string
> clients
;
86 for (const auto& client
: version_info
.clients
) {
87 if (client
.second
.type
== SERVICE_WORKER_PROVIDER_FOR_WINDOW
) {
88 RenderFrameHostImpl
* render_frame_host
= RenderFrameHostImpl::FromID(
89 client
.second
.process_id
, client
.second
.route_id
);
90 WebContents
* web_contents
=
91 WebContents::FromRenderFrameHost(render_frame_host
);
92 // There is a possibility that the frame is already deleted because of the
96 scoped_refptr
<DevToolsAgentHost
> agent_host(
97 DevToolsAgentHost::GetOrCreateFor(web_contents
));
99 clients
.push_back(agent_host
->GetId());
100 } else if (client
.second
.type
==
101 SERVICE_WORKER_PROVIDER_FOR_SHARED_WORKER
) {
102 scoped_refptr
<DevToolsAgentHost
> agent_host(
103 DevToolsAgentHost::GetForWorker(client
.second
.process_id
,
104 client
.second
.route_id
));
106 clients
.push_back(agent_host
->GetId());
109 scoped_refptr
<ServiceWorkerVersion
> version(
110 ServiceWorkerVersion::Create()
111 ->set_version_id(base::Int64ToString(version_info
.version_id
))
112 ->set_registration_id(
113 base::Int64ToString(version_info
.registration_id
))
114 ->set_script_url(version_info
.script_url
.spec())
115 ->set_running_status(
116 GetVersionRunningStatusString(version_info
.running_status
))
117 ->set_status(GetVersionStatusString(version_info
.status
))
118 ->set_script_last_modified(
119 version_info
.script_last_modified
.ToDoubleT())
120 ->set_script_response_time(
121 version_info
.script_response_time
.ToDoubleT())
122 ->set_controlled_clients(clients
));
126 scoped_refptr
<ServiceWorkerRegistration
> CreateRegistrationDictionaryValue(
127 const ServiceWorkerRegistrationInfo
& registration_info
) {
128 scoped_refptr
<ServiceWorkerRegistration
> registration(
129 ServiceWorkerRegistration::Create()
130 ->set_registration_id(
131 base::Int64ToString(registration_info
.registration_id
))
132 ->set_scope_url(registration_info
.pattern
.spec())
133 ->set_is_deleted(registration_info
.delete_flag
==
134 ServiceWorkerRegistrationInfo::IS_DELETED
));
138 scoped_refptr
<ServiceWorkerDevToolsAgentHost
> GetMatchingServiceWorker(
139 const ServiceWorkerDevToolsAgentHost::List
& agent_hosts
,
141 scoped_refptr
<ServiceWorkerDevToolsAgentHost
> best_host
;
142 std::string best_scope
;
143 for (auto host
: agent_hosts
) {
144 if (host
->GetURL().host() != url
.host())
146 std::string path
= host
->GetURL().path();
147 std::string file
= host
->GetURL().ExtractFileName();
148 std::string scope
= path
.substr(0, path
.length() - file
.length());
149 if (scope
.length() > best_scope
.length()) {
157 ServiceWorkerDevToolsAgentHost::Map
GetMatchingServiceWorkers(
158 BrowserContext
* browser_context
,
159 const std::set
<GURL
>& urls
) {
160 ServiceWorkerDevToolsAgentHost::Map result
;
161 if (!browser_context
)
163 ServiceWorkerDevToolsAgentHost::List agent_hosts
;
164 ServiceWorkerDevToolsManager::GetInstance()
165 ->AddAllAgentHostsForBrowserContext(browser_context
, &agent_hosts
);
166 for (const GURL
& url
: urls
) {
167 scoped_refptr
<ServiceWorkerDevToolsAgentHost
> host
=
168 GetMatchingServiceWorker(agent_hosts
, url
);
170 result
[host
->GetId()] = host
;
175 bool CollectURLs(std::set
<GURL
>* urls
, FrameTreeNode
* tree_node
) {
176 urls
->insert(tree_node
->current_url());
180 void StopServiceWorkerOnIO(scoped_refptr
<ServiceWorkerContextWrapper
> context
,
182 if (content::ServiceWorkerVersion
* version
=
183 context
->GetLiveVersion(version_id
)) {
184 version
->StopWorker(base::Bind(&StatusNoOp
));
188 void GetDevToolsRouteInfoOnIO(
189 scoped_refptr
<ServiceWorkerContextWrapper
> context
,
191 const base::Callback
<void(int, int)>& callback
) {
192 if (content::ServiceWorkerVersion
* version
=
193 context
->GetLiveVersion(version_id
)) {
194 BrowserThread::PostTask(
195 BrowserThread::UI
, FROM_HERE
,
197 callback
, version
->embedded_worker()->process_id(),
198 version
->embedded_worker()->worker_devtools_agent_route_id()));
202 Response
CreateContextErrorResponse() {
203 return Response::InternalError("Could not connect to the context");
206 Response
CreateInvalidVersionIdErrorResponse() {
207 return Response::InternalError("Invalid version ID");
210 const std::string
GetDevToolsAgentHostTypeString(
211 content::DevToolsAgentHost::Type type
) {
213 case DevToolsAgentHost::TYPE_WEB_CONTENTS
:
214 return "web_contents";
215 case DevToolsAgentHost::TYPE_FRAME
:
217 case DevToolsAgentHost::TYPE_SHARED_WORKER
:
218 return "shared_worker";
219 case DevToolsAgentHost::TYPE_SERVICE_WORKER
:
220 return "service_worker";
221 case DevToolsAgentHost::TYPE_EXTERNAL
:
223 case DevToolsAgentHost::TYPE_BROWSER
:
226 NOTREACHED() << type
;
227 return std::string();
232 ServiceWorkerHandler::ServiceWorkerHandler()
233 : enabled_(false), render_frame_host_(nullptr), weak_factory_(this) {
236 ServiceWorkerHandler::~ServiceWorkerHandler() {
240 void ServiceWorkerHandler::SetRenderFrameHost(
241 RenderFrameHostImpl
* render_frame_host
) {
242 render_frame_host_
= render_frame_host
;
243 // Do not call UpdateHosts yet, wait for load to commit.
244 if (!render_frame_host
) {
248 StoragePartition
* partition
= BrowserContext::GetStoragePartition(
249 render_frame_host
->GetProcess()->GetBrowserContext(),
250 render_frame_host
->GetSiteInstance());
252 context_
= static_cast<ServiceWorkerContextWrapper
*>(
253 partition
->GetServiceWorkerContext());
256 void ServiceWorkerHandler::SetClient(scoped_ptr
<Client
> client
) {
257 client_
.swap(client
);
260 void ServiceWorkerHandler::UpdateHosts() {
265 BrowserContext
* browser_context
= nullptr;
266 if (render_frame_host_
) {
267 render_frame_host_
->frame_tree_node()->frame_tree()->ForEach(
268 base::Bind(&CollectURLs
, &urls_
));
269 browser_context
= render_frame_host_
->GetProcess()->GetBrowserContext();
272 ServiceWorkerDevToolsAgentHost::Map old_hosts
= attached_hosts_
;
273 ServiceWorkerDevToolsAgentHost::Map new_hosts
=
274 GetMatchingServiceWorkers(browser_context
, urls_
);
276 for (auto pair
: old_hosts
) {
277 if (new_hosts
.find(pair
.first
) == new_hosts
.end())
278 ReportWorkerTerminated(pair
.second
.get());
281 for (auto pair
: new_hosts
) {
282 if (old_hosts
.find(pair
.first
) == old_hosts
.end())
283 ReportWorkerCreated(pair
.second
.get());
287 void ServiceWorkerHandler::Detached() {
291 Response
ServiceWorkerHandler::Enable() {
293 return Response::OK();
295 return Response::InternalError("Could not connect to the context");
298 ServiceWorkerDevToolsManager::GetInstance()->AddObserver(this);
300 client_
->DebugOnStartUpdated(
301 DebugOnStartUpdatedParams::Create()->set_debug_on_start(
302 ServiceWorkerDevToolsManager::GetInstance()
303 ->debug_service_worker_on_start()));
305 context_watcher_
= new ServiceWorkerContextWatcher(
306 context_
, base::Bind(&ServiceWorkerHandler::OnWorkerRegistrationUpdated
,
307 weak_factory_
.GetWeakPtr()),
308 base::Bind(&ServiceWorkerHandler::OnWorkerVersionUpdated
,
309 weak_factory_
.GetWeakPtr()),
310 base::Bind(&ServiceWorkerHandler::OnErrorReported
,
311 weak_factory_
.GetWeakPtr()));
312 context_watcher_
->Start();
315 return Response::OK();
318 Response
ServiceWorkerHandler::Disable() {
320 return Response::OK();
323 ServiceWorkerDevToolsManager::GetInstance()->RemoveObserver(this);
324 for (const auto& pair
: attached_hosts_
)
325 pair
.second
->DetachClient();
326 attached_hosts_
.clear();
327 DCHECK(context_watcher_
);
328 context_watcher_
->Stop();
329 context_watcher_
= nullptr;
330 return Response::OK();
333 Response
ServiceWorkerHandler::SendMessage(
334 const std::string
& worker_id
,
335 const std::string
& message
) {
336 auto it
= attached_hosts_
.find(worker_id
);
337 if (it
== attached_hosts_
.end())
338 return Response::InternalError("Not connected to the worker");
339 it
->second
->DispatchProtocolMessage(message
);
340 return Response::OK();
343 Response
ServiceWorkerHandler::Stop(
344 const std::string
& worker_id
) {
345 auto it
= attached_hosts_
.find(worker_id
);
346 if (it
== attached_hosts_
.end())
347 return Response::InternalError("Not connected to the worker");
348 it
->second
->UnregisterWorker();
349 return Response::OK();
352 Response
ServiceWorkerHandler::Unregister(const std::string
& scope_url
) {
354 return Response::OK();
356 return CreateContextErrorResponse();
357 context_
->UnregisterServiceWorker(GURL(scope_url
), base::Bind(&ResultNoOp
));
358 return Response::OK();
361 Response
ServiceWorkerHandler::StartWorker(const std::string
& scope_url
) {
363 return Response::OK();
365 return CreateContextErrorResponse();
366 context_
->StartServiceWorker(GURL(scope_url
), base::Bind(&StatusNoOp
));
367 return Response::OK();
370 Response
ServiceWorkerHandler::StopWorker(const std::string
& version_id
) {
372 return Response::OK();
374 return CreateContextErrorResponse();
376 if (!base::StringToInt64(version_id
, &id
))
377 return CreateInvalidVersionIdErrorResponse();
378 BrowserThread::PostTask(BrowserThread::IO
, FROM_HERE
,
379 base::Bind(&StopServiceWorkerOnIO
, context_
, id
));
380 return Response::OK();
383 Response
ServiceWorkerHandler::UpdateRegistration(
384 const std::string
& scope_url
) {
386 return Response::OK();
388 return CreateContextErrorResponse();
389 context_
->UpdateRegistration(GURL(scope_url
));
390 return Response::OK();
393 Response
ServiceWorkerHandler::InspectWorker(const std::string
& version_id
) {
395 return Response::OK();
397 return CreateContextErrorResponse();
400 if (!base::StringToInt64(version_id
, &id
))
401 return CreateInvalidVersionIdErrorResponse();
402 BrowserThread::PostTask(
403 BrowserThread::IO
, FROM_HERE
,
404 base::Bind(&GetDevToolsRouteInfoOnIO
, context_
, id
,
405 base::Bind(&ServiceWorkerHandler::OpenNewDevToolsWindow
,
406 weak_factory_
.GetWeakPtr())));
407 return Response::OK();
410 Response
ServiceWorkerHandler::SkipWaiting(const std::string
& version_id
) {
412 return Response::OK();
414 return CreateContextErrorResponse();
417 if (!base::StringToInt64(version_id
, &id
))
418 return CreateInvalidVersionIdErrorResponse();
419 context_
->SimulateSkipWaiting(id
);
420 return Response::OK();
423 Response
ServiceWorkerHandler::SetDebugOnStart(bool debug_on_start
) {
424 ServiceWorkerDevToolsManager::GetInstance()
425 ->set_debug_service_worker_on_start(debug_on_start
);
426 return Response::OK();
429 Response
ServiceWorkerHandler::DeliverPushMessage(
430 const std::string
& origin
,
431 const std::string
& registration_id
,
432 const std::string
& data
) {
434 return Response::OK();
435 if (!render_frame_host_
)
436 return CreateContextErrorResponse();
438 if (!base::StringToInt64(registration_id
, &id
))
439 return CreateInvalidVersionIdErrorResponse();
440 BrowserContext::DeliverPushMessage(
441 render_frame_host_
->GetProcess()->GetBrowserContext(), GURL(origin
), id
,
442 data
, base::Bind(&PushDeliveryNoOp
));
443 return Response::OK();
446 Response
ServiceWorkerHandler::GetTargetInfo(
447 const std::string
& target_id
,
448 scoped_refptr
<TargetInfo
>* target_info
) {
449 scoped_refptr
<DevToolsAgentHost
> agent_host(
450 DevToolsAgentHost::GetForId(target_id
));
452 return Response::InvalidParams("targetId");
455 ->set_id(agent_host
->GetId())
456 ->set_type(GetDevToolsAgentHostTypeString(agent_host
->GetType()))
457 ->set_title(agent_host
->GetTitle())
458 ->set_url(agent_host
->GetURL().spec());
459 return Response::OK();
462 Response
ServiceWorkerHandler::ActivateTarget(const std::string
& target_id
) {
463 scoped_refptr
<DevToolsAgentHost
> agent_host(
464 DevToolsAgentHost::GetForId(target_id
));
466 return Response::InvalidParams("targetId");
467 agent_host
->Activate();
468 return Response::OK();
471 void ServiceWorkerHandler::OpenNewDevToolsWindow(int process_id
,
472 int devtools_agent_route_id
) {
473 scoped_refptr
<DevToolsAgentHostImpl
> agent_host(
474 ServiceWorkerDevToolsManager::GetInstance()
475 ->GetDevToolsAgentHostForWorker(process_id
, devtools_agent_route_id
));
476 if (!agent_host
.get())
478 agent_host
->Inspect(render_frame_host_
->GetProcess()->GetBrowserContext());
481 void ServiceWorkerHandler::OnWorkerRegistrationUpdated(
482 const std::vector
<ServiceWorkerRegistrationInfo
>& registrations
) {
483 std::vector
<scoped_refptr
<ServiceWorkerRegistration
>> registration_values
;
484 for (const auto& registration
: registrations
) {
485 registration_values
.push_back(
486 CreateRegistrationDictionaryValue(registration
));
488 client_
->WorkerRegistrationUpdated(
489 WorkerRegistrationUpdatedParams::Create()->set_registrations(
490 registration_values
));
493 void ServiceWorkerHandler::OnWorkerVersionUpdated(
494 const std::vector
<ServiceWorkerVersionInfo
>& versions
) {
495 std::vector
<scoped_refptr
<ServiceWorkerVersion
>> version_values
;
496 for (const auto& version
: versions
) {
497 version_values
.push_back(CreateVersionDictionaryValue(version
));
499 client_
->WorkerVersionUpdated(
500 WorkerVersionUpdatedParams::Create()->set_versions(version_values
));
503 void ServiceWorkerHandler::OnErrorReported(
504 int64 registration_id
,
506 const ServiceWorkerContextObserver::ErrorInfo
& info
) {
507 client_
->WorkerErrorReported(
508 WorkerErrorReportedParams::Create()->set_error_message(
509 ServiceWorkerErrorMessage::Create()
510 ->set_error_message(base::UTF16ToUTF8(info
.error_message
))
511 ->set_registration_id(base::Int64ToString(registration_id
))
512 ->set_version_id(base::Int64ToString(version_id
))
513 ->set_source_url(info
.source_url
.spec())
514 ->set_line_number(info
.line_number
)
515 ->set_column_number(info
.column_number
)));
518 void ServiceWorkerHandler::DispatchProtocolMessage(
519 DevToolsAgentHost
* host
,
520 const std::string
& message
) {
522 auto it
= attached_hosts_
.find(host
->GetId());
523 if (it
== attached_hosts_
.end())
524 return; // Already disconnected.
526 client_
->DispatchMessage(
527 DispatchMessageParams::Create()->
528 set_worker_id(host
->GetId())->
529 set_message(message
));
532 void ServiceWorkerHandler::AgentHostClosed(
533 DevToolsAgentHost
* host
,
534 bool replaced_with_another_client
) {
535 client_
->WorkerTerminated(WorkerTerminatedParams::Create()->
536 set_worker_id(host
->GetId()));
537 attached_hosts_
.erase(host
->GetId());
540 void ServiceWorkerHandler::WorkerCreated(
541 ServiceWorkerDevToolsAgentHost
* host
) {
542 BrowserContext
* browser_context
= nullptr;
543 if (render_frame_host_
)
544 browser_context
= render_frame_host_
->GetProcess()->GetBrowserContext();
546 auto hosts
= GetMatchingServiceWorkers(browser_context
, urls_
);
547 if (hosts
.find(host
->GetId()) != hosts
.end() && !host
->IsAttached() &&
548 !host
->IsPausedForDebugOnStart())
549 host
->PauseForDebugOnStart();
552 void ServiceWorkerHandler::WorkerReadyForInspection(
553 ServiceWorkerDevToolsAgentHost
* host
) {
554 if (ServiceWorkerDevToolsManager::GetInstance()
555 ->debug_service_worker_on_start()) {
556 // When debug_service_worker_on_start is true, a new DevTools window will
557 // be opend in ServiceWorkerDevToolsManager::WorkerReadyForInspection.
563 void ServiceWorkerHandler::WorkerDestroyed(
564 ServiceWorkerDevToolsAgentHost
* host
) {
568 void ServiceWorkerHandler::DebugOnStartUpdated(bool debug_on_start
) {
569 client_
->DebugOnStartUpdated(
570 DebugOnStartUpdatedParams::Create()->set_debug_on_start(debug_on_start
));
573 void ServiceWorkerHandler::ReportWorkerCreated(
574 ServiceWorkerDevToolsAgentHost
* host
) {
575 if (host
->IsAttached())
577 attached_hosts_
[host
->GetId()] = host
;
578 host
->AttachClient(this);
579 client_
->WorkerCreated(WorkerCreatedParams::Create()->
580 set_worker_id(host
->GetId())->
581 set_url(host
->GetURL().spec()));
584 void ServiceWorkerHandler::ReportWorkerTerminated(
585 ServiceWorkerDevToolsAgentHost
* host
) {
586 auto it
= attached_hosts_
.find(host
->GetId());
587 if (it
== attached_hosts_
.end())
589 host
->DetachClient();
590 client_
->WorkerTerminated(WorkerTerminatedParams::Create()->
591 set_worker_id(host
->GetId()));
592 attached_hosts_
.erase(it
);
595 } // namespace service_worker
596 } // namespace devtools
597 } // namespace content