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
)
135 ->set_force_update_on_page_load(
136 registration_info
.force_update_on_page_load
==
137 ServiceWorkerRegistrationInfo::IS_FORCED
));
141 scoped_refptr
<ServiceWorkerDevToolsAgentHost
> GetMatchingServiceWorker(
142 const ServiceWorkerDevToolsAgentHost::List
& agent_hosts
,
144 scoped_refptr
<ServiceWorkerDevToolsAgentHost
> best_host
;
145 std::string best_scope
;
146 for (auto host
: agent_hosts
) {
147 if (host
->GetURL().host() != url
.host())
149 std::string path
= host
->GetURL().path();
150 std::string file
= host
->GetURL().ExtractFileName();
151 std::string scope
= path
.substr(0, path
.length() - file
.length());
152 // Choose the latest, longest scope match worker.
153 if (scope
.length() >= best_scope
.length()) {
161 ServiceWorkerDevToolsAgentHost::Map
GetMatchingServiceWorkers(
162 BrowserContext
* browser_context
,
163 const std::set
<GURL
>& urls
) {
164 ServiceWorkerDevToolsAgentHost::Map result
;
165 if (!browser_context
)
167 ServiceWorkerDevToolsAgentHost::List agent_hosts
;
168 ServiceWorkerDevToolsManager::GetInstance()
169 ->AddAllAgentHostsForBrowserContext(browser_context
, &agent_hosts
);
170 for (const GURL
& url
: urls
) {
171 scoped_refptr
<ServiceWorkerDevToolsAgentHost
> host
=
172 GetMatchingServiceWorker(agent_hosts
, url
);
174 result
[host
->GetId()] = host
;
179 bool CollectURLs(std::set
<GURL
>* urls
, FrameTreeNode
* tree_node
) {
180 urls
->insert(tree_node
->current_url());
184 void StopServiceWorkerOnIO(scoped_refptr
<ServiceWorkerContextWrapper
> context
,
186 if (content::ServiceWorkerVersion
* version
=
187 context
->GetLiveVersion(version_id
)) {
188 version
->StopWorker(base::Bind(&StatusNoOp
));
192 void GetDevToolsRouteInfoOnIO(
193 scoped_refptr
<ServiceWorkerContextWrapper
> context
,
195 const base::Callback
<void(int, int)>& callback
) {
196 if (content::ServiceWorkerVersion
* version
=
197 context
->GetLiveVersion(version_id
)) {
198 BrowserThread::PostTask(
199 BrowserThread::UI
, FROM_HERE
,
201 callback
, version
->embedded_worker()->process_id(),
202 version
->embedded_worker()->worker_devtools_agent_route_id()));
206 Response
CreateContextErrorResponse() {
207 return Response::InternalError("Could not connect to the context");
210 Response
CreateInvalidVersionIdErrorResponse() {
211 return Response::InternalError("Invalid version ID");
214 const std::string
GetDevToolsAgentHostTypeString(
215 content::DevToolsAgentHost::Type type
) {
217 case DevToolsAgentHost::TYPE_WEB_CONTENTS
:
218 return "web_contents";
219 case DevToolsAgentHost::TYPE_FRAME
:
221 case DevToolsAgentHost::TYPE_SHARED_WORKER
:
222 return "shared_worker";
223 case DevToolsAgentHost::TYPE_SERVICE_WORKER
:
224 return "service_worker";
225 case DevToolsAgentHost::TYPE_EXTERNAL
:
227 case DevToolsAgentHost::TYPE_BROWSER
:
230 NOTREACHED() << type
;
231 return std::string();
236 ServiceWorkerHandler::ServiceWorkerHandler()
237 : enabled_(false), render_frame_host_(nullptr), weak_factory_(this) {
240 ServiceWorkerHandler::~ServiceWorkerHandler() {
244 void ServiceWorkerHandler::SetRenderFrameHost(
245 RenderFrameHostImpl
* render_frame_host
) {
246 render_frame_host_
= render_frame_host
;
247 // Do not call UpdateHosts yet, wait for load to commit.
248 if (!render_frame_host
) {
252 StoragePartition
* partition
= BrowserContext::GetStoragePartition(
253 render_frame_host
->GetProcess()->GetBrowserContext(),
254 render_frame_host
->GetSiteInstance());
256 context_
= static_cast<ServiceWorkerContextWrapper
*>(
257 partition
->GetServiceWorkerContext());
260 void ServiceWorkerHandler::SetClient(scoped_ptr
<Client
> client
) {
261 client_
.swap(client
);
264 void ServiceWorkerHandler::UpdateHosts() {
269 BrowserContext
* browser_context
= nullptr;
270 if (render_frame_host_
) {
271 render_frame_host_
->frame_tree_node()->frame_tree()->ForEach(
272 base::Bind(&CollectURLs
, &urls_
));
273 browser_context
= render_frame_host_
->GetProcess()->GetBrowserContext();
276 ServiceWorkerDevToolsAgentHost::Map old_hosts
= attached_hosts_
;
277 ServiceWorkerDevToolsAgentHost::Map new_hosts
=
278 GetMatchingServiceWorkers(browser_context
, urls_
);
280 for (auto pair
: old_hosts
) {
281 if (new_hosts
.find(pair
.first
) == new_hosts
.end())
282 ReportWorkerTerminated(pair
.second
.get());
285 for (auto pair
: new_hosts
) {
286 if (old_hosts
.find(pair
.first
) == old_hosts
.end())
287 ReportWorkerCreated(pair
.second
.get());
291 void ServiceWorkerHandler::Detached() {
295 Response
ServiceWorkerHandler::Enable() {
297 return Response::OK();
299 return Response::InternalError("Could not connect to the context");
302 ServiceWorkerDevToolsManager::GetInstance()->AddObserver(this);
304 client_
->DebugOnStartUpdated(
305 DebugOnStartUpdatedParams::Create()->set_debug_on_start(
306 ServiceWorkerDevToolsManager::GetInstance()
307 ->debug_service_worker_on_start()));
309 context_watcher_
= new ServiceWorkerContextWatcher(
310 context_
, base::Bind(&ServiceWorkerHandler::OnWorkerRegistrationUpdated
,
311 weak_factory_
.GetWeakPtr()),
312 base::Bind(&ServiceWorkerHandler::OnWorkerVersionUpdated
,
313 weak_factory_
.GetWeakPtr()),
314 base::Bind(&ServiceWorkerHandler::OnErrorReported
,
315 weak_factory_
.GetWeakPtr()));
316 context_watcher_
->Start();
319 return Response::OK();
322 Response
ServiceWorkerHandler::Disable() {
324 return Response::OK();
327 ServiceWorkerDevToolsManager::GetInstance()->RemoveObserver(this);
328 for (const auto& pair
: attached_hosts_
)
329 pair
.second
->DetachClient();
330 attached_hosts_
.clear();
331 DCHECK(context_watcher_
);
332 context_watcher_
->Stop();
333 context_watcher_
= nullptr;
334 return Response::OK();
337 Response
ServiceWorkerHandler::SendMessage(
338 const std::string
& worker_id
,
339 const std::string
& message
) {
340 auto it
= attached_hosts_
.find(worker_id
);
341 if (it
== attached_hosts_
.end())
342 return Response::InternalError("Not connected to the worker");
343 it
->second
->DispatchProtocolMessage(message
);
344 return Response::OK();
347 Response
ServiceWorkerHandler::Stop(
348 const std::string
& worker_id
) {
349 auto it
= attached_hosts_
.find(worker_id
);
350 if (it
== attached_hosts_
.end())
351 return Response::InternalError("Not connected to the worker");
352 it
->second
->UnregisterWorker();
353 return Response::OK();
356 Response
ServiceWorkerHandler::Unregister(const std::string
& scope_url
) {
358 return Response::OK();
360 return CreateContextErrorResponse();
361 context_
->UnregisterServiceWorker(GURL(scope_url
), base::Bind(&ResultNoOp
));
362 return Response::OK();
365 Response
ServiceWorkerHandler::StartWorker(const std::string
& scope_url
) {
367 return Response::OK();
369 return CreateContextErrorResponse();
370 context_
->StartServiceWorker(GURL(scope_url
), base::Bind(&StatusNoOp
));
371 return Response::OK();
374 Response
ServiceWorkerHandler::StopWorker(const std::string
& version_id
) {
376 return Response::OK();
378 return CreateContextErrorResponse();
380 if (!base::StringToInt64(version_id
, &id
))
381 return CreateInvalidVersionIdErrorResponse();
382 BrowserThread::PostTask(BrowserThread::IO
, FROM_HERE
,
383 base::Bind(&StopServiceWorkerOnIO
, context_
, id
));
384 return Response::OK();
387 Response
ServiceWorkerHandler::UpdateRegistration(
388 const std::string
& scope_url
) {
390 return Response::OK();
392 return CreateContextErrorResponse();
393 context_
->UpdateRegistration(GURL(scope_url
));
394 return Response::OK();
397 Response
ServiceWorkerHandler::InspectWorker(const std::string
& version_id
) {
399 return Response::OK();
401 return CreateContextErrorResponse();
403 int64 id
= kInvalidServiceWorkerVersionId
;
404 if (!base::StringToInt64(version_id
, &id
))
405 return CreateInvalidVersionIdErrorResponse();
406 BrowserThread::PostTask(
407 BrowserThread::IO
, FROM_HERE
,
408 base::Bind(&GetDevToolsRouteInfoOnIO
, context_
, id
,
409 base::Bind(&ServiceWorkerHandler::OpenNewDevToolsWindow
,
410 weak_factory_
.GetWeakPtr())));
411 return Response::OK();
414 Response
ServiceWorkerHandler::SkipWaiting(const std::string
& version_id
) {
416 return Response::OK();
418 return CreateContextErrorResponse();
420 int64 id
= kInvalidServiceWorkerVersionId
;
421 if (!base::StringToInt64(version_id
, &id
))
422 return CreateInvalidVersionIdErrorResponse();
423 context_
->SimulateSkipWaiting(id
);
424 return Response::OK();
427 Response
ServiceWorkerHandler::SetDebugOnStart(bool debug_on_start
) {
428 ServiceWorkerDevToolsManager::GetInstance()
429 ->set_debug_service_worker_on_start(debug_on_start
);
430 return Response::OK();
433 Response
ServiceWorkerHandler::SetForceUpdateOnPageLoad(
434 const std::string
& registration_id
,
435 bool force_update_on_page_load
) {
437 return CreateContextErrorResponse();
438 int64 id
= kInvalidServiceWorkerRegistrationId
;
439 if (!base::StringToInt64(registration_id
, &id
))
440 return CreateInvalidVersionIdErrorResponse();
441 context_
->SetForceUpdateOnPageLoad(id
, force_update_on_page_load
);
442 return Response::OK();
445 Response
ServiceWorkerHandler::DeliverPushMessage(
446 const std::string
& origin
,
447 const std::string
& registration_id
,
448 const std::string
& data
) {
450 return Response::OK();
451 if (!render_frame_host_
)
452 return CreateContextErrorResponse();
454 if (!base::StringToInt64(registration_id
, &id
))
455 return CreateInvalidVersionIdErrorResponse();
456 BrowserContext::DeliverPushMessage(
457 render_frame_host_
->GetProcess()->GetBrowserContext(), GURL(origin
), id
,
458 data
, base::Bind(&PushDeliveryNoOp
));
459 return Response::OK();
462 Response
ServiceWorkerHandler::GetTargetInfo(
463 const std::string
& target_id
,
464 scoped_refptr
<TargetInfo
>* target_info
) {
465 scoped_refptr
<DevToolsAgentHost
> agent_host(
466 DevToolsAgentHost::GetForId(target_id
));
468 return Response::InvalidParams("targetId");
471 ->set_id(agent_host
->GetId())
472 ->set_type(GetDevToolsAgentHostTypeString(agent_host
->GetType()))
473 ->set_title(agent_host
->GetTitle())
474 ->set_url(agent_host
->GetURL().spec());
475 return Response::OK();
478 Response
ServiceWorkerHandler::ActivateTarget(const std::string
& target_id
) {
479 scoped_refptr
<DevToolsAgentHost
> agent_host(
480 DevToolsAgentHost::GetForId(target_id
));
482 return Response::InvalidParams("targetId");
483 agent_host
->Activate();
484 return Response::OK();
487 void ServiceWorkerHandler::OpenNewDevToolsWindow(int process_id
,
488 int devtools_agent_route_id
) {
489 scoped_refptr
<DevToolsAgentHostImpl
> agent_host(
490 ServiceWorkerDevToolsManager::GetInstance()
491 ->GetDevToolsAgentHostForWorker(process_id
, devtools_agent_route_id
));
492 if (!agent_host
.get())
494 agent_host
->Inspect(render_frame_host_
->GetProcess()->GetBrowserContext());
497 void ServiceWorkerHandler::OnWorkerRegistrationUpdated(
498 const std::vector
<ServiceWorkerRegistrationInfo
>& registrations
) {
499 std::vector
<scoped_refptr
<ServiceWorkerRegistration
>> registration_values
;
500 for (const auto& registration
: registrations
) {
501 registration_values
.push_back(
502 CreateRegistrationDictionaryValue(registration
));
504 client_
->WorkerRegistrationUpdated(
505 WorkerRegistrationUpdatedParams::Create()->set_registrations(
506 registration_values
));
509 void ServiceWorkerHandler::OnWorkerVersionUpdated(
510 const std::vector
<ServiceWorkerVersionInfo
>& versions
) {
511 std::vector
<scoped_refptr
<ServiceWorkerVersion
>> version_values
;
512 for (const auto& version
: versions
) {
513 version_values
.push_back(CreateVersionDictionaryValue(version
));
515 client_
->WorkerVersionUpdated(
516 WorkerVersionUpdatedParams::Create()->set_versions(version_values
));
519 void ServiceWorkerHandler::OnErrorReported(
520 int64 registration_id
,
522 const ServiceWorkerContextObserver::ErrorInfo
& info
) {
523 client_
->WorkerErrorReported(
524 WorkerErrorReportedParams::Create()->set_error_message(
525 ServiceWorkerErrorMessage::Create()
526 ->set_error_message(base::UTF16ToUTF8(info
.error_message
))
527 ->set_registration_id(base::Int64ToString(registration_id
))
528 ->set_version_id(base::Int64ToString(version_id
))
529 ->set_source_url(info
.source_url
.spec())
530 ->set_line_number(info
.line_number
)
531 ->set_column_number(info
.column_number
)));
534 void ServiceWorkerHandler::DispatchProtocolMessage(
535 DevToolsAgentHost
* host
,
536 const std::string
& message
) {
538 auto it
= attached_hosts_
.find(host
->GetId());
539 if (it
== attached_hosts_
.end())
540 return; // Already disconnected.
542 client_
->DispatchMessage(
543 DispatchMessageParams::Create()->
544 set_worker_id(host
->GetId())->
545 set_message(message
));
548 void ServiceWorkerHandler::AgentHostClosed(
549 DevToolsAgentHost
* host
,
550 bool replaced_with_another_client
) {
551 client_
->WorkerTerminated(WorkerTerminatedParams::Create()->
552 set_worker_id(host
->GetId()));
553 attached_hosts_
.erase(host
->GetId());
556 void ServiceWorkerHandler::WorkerCreated(
557 ServiceWorkerDevToolsAgentHost
* host
) {
558 BrowserContext
* browser_context
= nullptr;
559 if (render_frame_host_
)
560 browser_context
= render_frame_host_
->GetProcess()->GetBrowserContext();
562 auto hosts
= GetMatchingServiceWorkers(browser_context
, urls_
);
563 if (hosts
.find(host
->GetId()) != hosts
.end() && !host
->IsAttached() &&
564 !host
->IsPausedForDebugOnStart())
565 host
->PauseForDebugOnStart();
568 void ServiceWorkerHandler::WorkerReadyForInspection(
569 ServiceWorkerDevToolsAgentHost
* host
) {
570 if (ServiceWorkerDevToolsManager::GetInstance()
571 ->debug_service_worker_on_start()) {
572 // When debug_service_worker_on_start is true, a new DevTools window will
573 // be opend in ServiceWorkerDevToolsManager::WorkerReadyForInspection.
579 void ServiceWorkerHandler::WorkerDestroyed(
580 ServiceWorkerDevToolsAgentHost
* host
) {
584 void ServiceWorkerHandler::DebugOnStartUpdated(bool debug_on_start
) {
585 client_
->DebugOnStartUpdated(
586 DebugOnStartUpdatedParams::Create()->set_debug_on_start(debug_on_start
));
589 void ServiceWorkerHandler::ReportWorkerCreated(
590 ServiceWorkerDevToolsAgentHost
* host
) {
591 if (host
->IsAttached())
593 attached_hosts_
[host
->GetId()] = host
;
594 host
->AttachClient(this);
595 client_
->WorkerCreated(WorkerCreatedParams::Create()
596 ->set_worker_id(host
->GetId())
597 ->set_url(host
->GetURL().spec())
598 ->set_version_id(base::Int64ToString(
599 host
->service_worker_version_id())));
602 void ServiceWorkerHandler::ReportWorkerTerminated(
603 ServiceWorkerDevToolsAgentHost
* host
) {
604 auto it
= attached_hosts_
.find(host
->GetId());
605 if (it
== attached_hosts_
.end())
607 host
->DetachClient();
608 client_
->WorkerTerminated(WorkerTerminatedParams::Create()->
609 set_worker_id(host
->GetId()));
610 attached_hosts_
.erase(it
);
613 } // namespace service_worker
614 } // namespace devtools
615 } // namespace content