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.
4 
5 #include "content/browser/devtools/protocol/service_worker_handler.h"
6 
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/containers/flat_set.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "base/task/post_task.h"
13 #include "content/browser/background_sync/background_sync_context_impl.h"
14 #include "content/browser/background_sync/background_sync_manager.h"
15 #include "content/browser/devtools/service_worker_devtools_agent_host.h"
16 #include "content/browser/devtools/service_worker_devtools_manager.h"
17 #include "content/browser/devtools/shared_worker_devtools_manager.h"
18 #include "content/browser/frame_host/frame_tree.h"
19 #include "content/browser/frame_host/frame_tree_node.h"
20 #include "content/browser/service_worker/embedded_worker_status.h"
21 #include "content/browser/service_worker/service_worker_context_watcher.h"
22 #include "content/browser/service_worker/service_worker_context_wrapper.h"
23 #include "content/browser/service_worker/service_worker_version.h"
24 #include "content/browser/storage_partition_impl_map.h"
25 #include "content/common/service_worker/service_worker_utils.h"
26 #include "content/public/browser/browser_context.h"
27 #include "content/public/browser/browser_task_traits.h"
28 #include "content/public/browser/browser_thread.h"
29 #include "content/public/browser/devtools_agent_host.h"
30 #include "content/public/browser/render_process_host.h"
31 #include "content/public/browser/service_worker_context.h"
32 #include "content/public/browser/storage_partition.h"
33 #include "content/public/browser/web_contents.h"
34 #include "third_party/blink/public/mojom/push_messaging/push_messaging_status.mojom.h"
35 #include "third_party/blink/public/mojom/service_worker/service_worker_container_type.mojom.h"
36 #include "third_party/blink/public/mojom/service_worker/service_worker_object.mojom.h"
37 #include "url/gurl.h"
38 
39 namespace content {
40 namespace protocol {
41 
42 namespace {
43 
GetVersionRunningStatusString(EmbeddedWorkerStatus running_status)44 const std::string GetVersionRunningStatusString(
45     EmbeddedWorkerStatus running_status) {
46   switch (running_status) {
47     case EmbeddedWorkerStatus::STOPPED:
48       return ServiceWorker::ServiceWorkerVersionRunningStatusEnum::Stopped;
49     case EmbeddedWorkerStatus::STARTING:
50       return ServiceWorker::ServiceWorkerVersionRunningStatusEnum::Starting;
51     case EmbeddedWorkerStatus::RUNNING:
52       return ServiceWorker::ServiceWorkerVersionRunningStatusEnum::Running;
53     case EmbeddedWorkerStatus::STOPPING:
54       return ServiceWorker::ServiceWorkerVersionRunningStatusEnum::Stopping;
55     default:
56       NOTREACHED();
57   }
58   return std::string();
59 }
60 
GetVersionStatusString(content::ServiceWorkerVersion::Status status)61 const std::string GetVersionStatusString(
62     content::ServiceWorkerVersion::Status status) {
63   switch (status) {
64     case content::ServiceWorkerVersion::NEW:
65       return ServiceWorker::ServiceWorkerVersionStatusEnum::New;
66     case content::ServiceWorkerVersion::INSTALLING:
67       return ServiceWorker::ServiceWorkerVersionStatusEnum::Installing;
68     case content::ServiceWorkerVersion::INSTALLED:
69       return ServiceWorker::ServiceWorkerVersionStatusEnum::Installed;
70     case content::ServiceWorkerVersion::ACTIVATING:
71       return ServiceWorker::ServiceWorkerVersionStatusEnum::Activating;
72     case content::ServiceWorkerVersion::ACTIVATED:
73       return ServiceWorker::ServiceWorkerVersionStatusEnum::Activated;
74     case content::ServiceWorkerVersion::REDUNDANT:
75       return ServiceWorker::ServiceWorkerVersionStatusEnum::Redundant;
76     default:
77       NOTREACHED();
78   }
79   return std::string();
80 }
81 
StopServiceWorkerOnCoreThread(scoped_refptr<ServiceWorkerContextWrapper> context,int64_t version_id)82 void StopServiceWorkerOnCoreThread(
83     scoped_refptr<ServiceWorkerContextWrapper> context,
84     int64_t version_id) {
85   if (content::ServiceWorkerVersion* version =
86           context->GetLiveVersion(version_id)) {
87     version->StopWorker(base::DoNothing());
88   }
89 }
90 
GetDevToolsRouteInfoOnCoreThread(scoped_refptr<ServiceWorkerContextWrapper> context,int64_t version_id,base::OnceCallback<void (int,int)> callback)91 void GetDevToolsRouteInfoOnCoreThread(
92     scoped_refptr<ServiceWorkerContextWrapper> context,
93     int64_t version_id,
94     base::OnceCallback<void(int, int)> callback) {
95   if (content::ServiceWorkerVersion* version =
96           context->GetLiveVersion(version_id)) {
97     RunOrPostTaskOnThread(
98         FROM_HERE, BrowserThread::UI,
99         base::BindOnce(
100             std::move(callback), version->embedded_worker()->process_id(),
101             version->embedded_worker()->worker_devtools_agent_route_id()));
102   }
103 }
104 
CreateDomainNotEnabledErrorResponse()105 Response CreateDomainNotEnabledErrorResponse() {
106   return Response::ServerError("ServiceWorker domain not enabled");
107 }
108 
CreateContextErrorResponse()109 Response CreateContextErrorResponse() {
110   return Response::ServerError("Could not connect to the context");
111 }
112 
CreateInvalidVersionIdErrorResponse()113 Response CreateInvalidVersionIdErrorResponse() {
114   return Response::InvalidParams("Invalid version ID");
115 }
116 
DidFindRegistrationForDispatchSyncEventOnCoreThread(scoped_refptr<BackgroundSyncContextImpl> sync_context,const std::string & tag,bool last_chance,blink::ServiceWorkerStatusCode status,scoped_refptr<content::ServiceWorkerRegistration> registration)117 void DidFindRegistrationForDispatchSyncEventOnCoreThread(
118     scoped_refptr<BackgroundSyncContextImpl> sync_context,
119     const std::string& tag,
120     bool last_chance,
121     blink::ServiceWorkerStatusCode status,
122     scoped_refptr<content::ServiceWorkerRegistration> registration) {
123   if (status != blink::ServiceWorkerStatusCode::kOk ||
124       !registration->active_version())
125     return;
126   BackgroundSyncManager* background_sync_manager =
127       sync_context->background_sync_manager();
128   scoped_refptr<content::ServiceWorkerVersion> version(
129       registration->active_version());
130   // Keep the registration while dispatching the sync event.
131   background_sync_manager->EmulateDispatchSyncEvent(
132       tag, std::move(version), last_chance, base::DoNothing());
133 }
134 
DidFindRegistrationForDispatchPeriodicSyncEventOnCoreThread(scoped_refptr<BackgroundSyncContextImpl> sync_context,const std::string & tag,blink::ServiceWorkerStatusCode status,scoped_refptr<content::ServiceWorkerRegistration> registration)135 void DidFindRegistrationForDispatchPeriodicSyncEventOnCoreThread(
136     scoped_refptr<BackgroundSyncContextImpl> sync_context,
137     const std::string& tag,
138     blink::ServiceWorkerStatusCode status,
139     scoped_refptr<content::ServiceWorkerRegistration> registration) {
140   if (status != blink::ServiceWorkerStatusCode::kOk ||
141       !registration->active_version()) {
142     return;
143   }
144 
145   BackgroundSyncManager* background_sync_manager =
146       sync_context->background_sync_manager();
147   scoped_refptr<content::ServiceWorkerVersion> version(
148       registration->active_version());
149   // Keep the registration while dispatching the sync event.
150   background_sync_manager->EmulateDispatchPeriodicSyncEvent(
151       tag, std::move(version), base::DoNothing());
152 }
153 
DispatchSyncEventOnCoreThread(scoped_refptr<ServiceWorkerContextWrapper> context,scoped_refptr<BackgroundSyncContextImpl> sync_context,const GURL & origin,int64_t registration_id,const std::string & tag,bool last_chance)154 void DispatchSyncEventOnCoreThread(
155     scoped_refptr<ServiceWorkerContextWrapper> context,
156     scoped_refptr<BackgroundSyncContextImpl> sync_context,
157     const GURL& origin,
158     int64_t registration_id,
159     const std::string& tag,
160     bool last_chance) {
161   context->FindReadyRegistrationForId(
162       registration_id, origin,
163       base::BindOnce(&DidFindRegistrationForDispatchSyncEventOnCoreThread,
164                      sync_context, tag, last_chance));
165 }
166 
DispatchPeriodicSyncEventOnCoreThread(scoped_refptr<ServiceWorkerContextWrapper> context,scoped_refptr<BackgroundSyncContextImpl> sync_context,const GURL & origin,int64_t registration_id,const std::string & tag)167 void DispatchPeriodicSyncEventOnCoreThread(
168     scoped_refptr<ServiceWorkerContextWrapper> context,
169     scoped_refptr<BackgroundSyncContextImpl> sync_context,
170     const GURL& origin,
171     int64_t registration_id,
172     const std::string& tag) {
173   context->FindReadyRegistrationForId(
174       registration_id, origin,
175       base::BindOnce(
176           &DidFindRegistrationForDispatchPeriodicSyncEventOnCoreThread,
177           sync_context, tag));
178 }
179 
180 }  // namespace
181 
ServiceWorkerHandler(bool allow_inspect_worker)182 ServiceWorkerHandler::ServiceWorkerHandler(bool allow_inspect_worker)
183     : DevToolsDomainHandler(ServiceWorker::Metainfo::domainName),
184       allow_inspect_worker_(allow_inspect_worker),
185       enabled_(false),
186       browser_context_(nullptr),
187       storage_partition_(nullptr) {}
188 
~ServiceWorkerHandler()189 ServiceWorkerHandler::~ServiceWorkerHandler() {
190 }
191 
Wire(UberDispatcher * dispatcher)192 void ServiceWorkerHandler::Wire(UberDispatcher* dispatcher) {
193   frontend_.reset(new ServiceWorker::Frontend(dispatcher->channel()));
194   ServiceWorker::Dispatcher::wire(dispatcher, this);
195 }
196 
SetRenderer(int process_host_id,RenderFrameHostImpl * frame_host)197 void ServiceWorkerHandler::SetRenderer(int process_host_id,
198                                        RenderFrameHostImpl* frame_host) {
199   RenderProcessHost* process_host = RenderProcessHost::FromID(process_host_id);
200   // Do not call UpdateHosts yet, wait for load to commit.
201   if (!process_host) {
202     ClearForceUpdate();
203     context_ = nullptr;
204     return;
205   }
206 
207   storage_partition_ =
208       static_cast<StoragePartitionImpl*>(process_host->GetStoragePartition());
209   DCHECK(storage_partition_);
210   browser_context_ = process_host->GetBrowserContext();
211   context_ = static_cast<ServiceWorkerContextWrapper*>(
212       storage_partition_->GetServiceWorkerContext());
213 }
214 
Enable()215 Response ServiceWorkerHandler::Enable() {
216   if (enabled_)
217     return Response::Success();
218   if (!context_)
219     return CreateContextErrorResponse();
220   enabled_ = true;
221 
222   context_watcher_ = base::MakeRefCounted<ServiceWorkerContextWatcher>(
223       context_,
224       base::BindRepeating(&ServiceWorkerHandler::OnWorkerRegistrationUpdated,
225                           weak_factory_.GetWeakPtr()),
226       base::BindRepeating(&ServiceWorkerHandler::OnWorkerVersionUpdated,
227                           weak_factory_.GetWeakPtr()),
228       base::BindRepeating(&ServiceWorkerHandler::OnErrorReported,
229                           weak_factory_.GetWeakPtr()));
230   context_watcher_->Start();
231 
232   return Response::Success();
233 }
234 
Disable()235 Response ServiceWorkerHandler::Disable() {
236   if (!enabled_)
237     return Response::Success();
238   enabled_ = false;
239 
240   ClearForceUpdate();
241   DCHECK(context_watcher_);
242   context_watcher_->Stop();
243   context_watcher_ = nullptr;
244   return Response::Success();
245 }
246 
Unregister(const std::string & scope_url)247 Response ServiceWorkerHandler::Unregister(const std::string& scope_url) {
248   if (!enabled_)
249     return CreateDomainNotEnabledErrorResponse();
250   if (!context_)
251     return CreateContextErrorResponse();
252   context_->UnregisterServiceWorker(GURL(scope_url), base::DoNothing());
253   return Response::Success();
254 }
255 
StartWorker(const std::string & scope_url)256 Response ServiceWorkerHandler::StartWorker(const std::string& scope_url) {
257   if (!enabled_)
258     return CreateDomainNotEnabledErrorResponse();
259   if (!context_)
260     return CreateContextErrorResponse();
261   context_->StartServiceWorker(GURL(scope_url), base::DoNothing());
262   return Response::Success();
263 }
264 
SkipWaiting(const std::string & scope_url)265 Response ServiceWorkerHandler::SkipWaiting(const std::string& scope_url) {
266   if (!enabled_)
267     return CreateDomainNotEnabledErrorResponse();
268   if (!context_)
269     return CreateContextErrorResponse();
270   context_->SkipWaitingWorker(GURL(scope_url));
271   return Response::Success();
272 }
273 
StopWorker(const std::string & version_id)274 Response ServiceWorkerHandler::StopWorker(const std::string& version_id) {
275   if (!enabled_)
276     return CreateDomainNotEnabledErrorResponse();
277   if (!context_)
278     return CreateContextErrorResponse();
279   int64_t id = 0;
280   if (!base::StringToInt64(version_id, &id))
281     return CreateInvalidVersionIdErrorResponse();
282   RunOrPostTaskOnThread(
283       FROM_HERE, ServiceWorkerContext::GetCoreThreadId(),
284       base::BindOnce(&StopServiceWorkerOnCoreThread, context_, id));
285   return Response::Success();
286 }
287 
StopAllWorkers(std::unique_ptr<StopAllWorkersCallback> callback)288 void ServiceWorkerHandler::StopAllWorkers(
289     std::unique_ptr<StopAllWorkersCallback> callback) {
290   if (!enabled_) {
291     callback->sendFailure(CreateDomainNotEnabledErrorResponse());
292     return;
293   }
294   if (!context_) {
295     callback->sendFailure(CreateContextErrorResponse());
296     return;
297   }
298   context_->StopAllServiceWorkers(base::BindOnce(
299       &StopAllWorkersCallback::sendSuccess, std::move(callback)));
300 }
301 
UpdateRegistration(const std::string & scope_url)302 Response ServiceWorkerHandler::UpdateRegistration(
303     const std::string& scope_url) {
304   if (!enabled_)
305     return CreateDomainNotEnabledErrorResponse();
306   if (!context_)
307     return CreateContextErrorResponse();
308   context_->UpdateRegistration(GURL(scope_url));
309   return Response::Success();
310 }
311 
InspectWorker(const std::string & version_id)312 Response ServiceWorkerHandler::InspectWorker(const std::string& version_id) {
313   if (!enabled_)
314     return CreateDomainNotEnabledErrorResponse();
315   if (!context_)
316     return CreateContextErrorResponse();
317   if (!allow_inspect_worker_)
318     return Response::ServerError("Permission denied");
319   int64_t id = blink::mojom::kInvalidServiceWorkerVersionId;
320   if (!base::StringToInt64(version_id, &id))
321     return CreateInvalidVersionIdErrorResponse();
322   RunOrPostTaskOnThread(
323       FROM_HERE, ServiceWorkerContext::GetCoreThreadId(),
324       base::BindOnce(
325           &GetDevToolsRouteInfoOnCoreThread, context_, id,
326           base::BindOnce(&ServiceWorkerHandler::OpenNewDevToolsWindow,
327                          weak_factory_.GetWeakPtr())));
328   return Response::Success();
329 }
330 
SetForceUpdateOnPageLoad(bool force_update_on_page_load)331 Response ServiceWorkerHandler::SetForceUpdateOnPageLoad(
332     bool force_update_on_page_load) {
333   if (!context_)
334     return CreateContextErrorResponse();
335   context_->SetForceUpdateOnPageLoad(force_update_on_page_load);
336   return Response::Success();
337 }
338 
DeliverPushMessage(const std::string & origin,const std::string & registration_id,const std::string & data)339 Response ServiceWorkerHandler::DeliverPushMessage(
340     const std::string& origin,
341     const std::string& registration_id,
342     const std::string& data) {
343   if (!enabled_)
344     return CreateDomainNotEnabledErrorResponse();
345   if (!browser_context_)
346     return CreateContextErrorResponse();
347   int64_t id = 0;
348   if (!base::StringToInt64(registration_id, &id))
349     return CreateInvalidVersionIdErrorResponse();
350   base::Optional<std::string> payload;
351   if (data.size() > 0)
352     payload = data;
353   BrowserContext::DeliverPushMessage(
354       browser_context_, GURL(origin), id, /* push_message_id= */ std::string(),
355       std::move(payload),
356       base::BindOnce([](blink::mojom::PushDeliveryStatus status) {}));
357 
358   return Response::Success();
359 }
360 
DispatchSyncEvent(const std::string & origin,const std::string & registration_id,const std::string & tag,bool last_chance)361 Response ServiceWorkerHandler::DispatchSyncEvent(
362     const std::string& origin,
363     const std::string& registration_id,
364     const std::string& tag,
365     bool last_chance) {
366   if (!enabled_)
367     return CreateDomainNotEnabledErrorResponse();
368   if (!storage_partition_)
369     return CreateContextErrorResponse();
370   int64_t id = 0;
371   if (!base::StringToInt64(registration_id, &id))
372     return CreateInvalidVersionIdErrorResponse();
373 
374   BackgroundSyncContextImpl* sync_context =
375       storage_partition_->GetBackgroundSyncContext();
376 
377   RunOrPostTaskOnThread(FROM_HERE, ServiceWorkerContext::GetCoreThreadId(),
378                         base::BindOnce(&DispatchSyncEventOnCoreThread, context_,
379                                        base::WrapRefCounted(sync_context),
380                                        GURL(origin), id, tag, last_chance));
381   return Response::Success();
382 }
383 
DispatchPeriodicSyncEvent(const std::string & origin,const std::string & registration_id,const std::string & tag)384 Response ServiceWorkerHandler::DispatchPeriodicSyncEvent(
385     const std::string& origin,
386     const std::string& registration_id,
387     const std::string& tag) {
388   if (!enabled_)
389     return CreateDomainNotEnabledErrorResponse();
390   if (!storage_partition_)
391     return CreateContextErrorResponse();
392   int64_t id = 0;
393   if (!base::StringToInt64(registration_id, &id))
394     return CreateInvalidVersionIdErrorResponse();
395 
396   BackgroundSyncContextImpl* sync_context =
397       storage_partition_->GetBackgroundSyncContext();
398 
399   RunOrPostTaskOnThread(
400       FROM_HERE, ServiceWorkerContext::GetCoreThreadId(),
401       base::BindOnce(&DispatchPeriodicSyncEventOnCoreThread, context_,
402                      base::WrapRefCounted(sync_context), GURL(origin), id,
403                      tag));
404   return Response::Success();
405 }
406 
OpenNewDevToolsWindow(int process_id,int devtools_agent_route_id)407 void ServiceWorkerHandler::OpenNewDevToolsWindow(int process_id,
408                                                  int devtools_agent_route_id) {
409   scoped_refptr<DevToolsAgentHostImpl> agent_host(
410       ServiceWorkerDevToolsManager::GetInstance()
411           ->GetDevToolsAgentHostForWorker(process_id, devtools_agent_route_id));
412   if (!agent_host.get())
413     return;
414   agent_host->Inspect();
415 }
416 
OnWorkerRegistrationUpdated(const std::vector<ServiceWorkerRegistrationInfo> & registrations)417 void ServiceWorkerHandler::OnWorkerRegistrationUpdated(
418     const std::vector<ServiceWorkerRegistrationInfo>& registrations) {
419   using Registration = ServiceWorker::ServiceWorkerRegistration;
420   auto result = std::make_unique<protocol::Array<Registration>>();
421   for (const auto& registration : registrations) {
422     result->emplace_back(
423         Registration::Create()
424             .SetRegistrationId(
425                 base::NumberToString(registration.registration_id))
426             .SetScopeURL(registration.scope.spec())
427             .SetIsDeleted(registration.delete_flag ==
428                           ServiceWorkerRegistrationInfo::IS_DELETED)
429             .Build());
430   }
431   frontend_->WorkerRegistrationUpdated(std::move(result));
432 }
433 
OnWorkerVersionUpdated(const std::vector<ServiceWorkerVersionInfo> & versions)434 void ServiceWorkerHandler::OnWorkerVersionUpdated(
435     const std::vector<ServiceWorkerVersionInfo>& versions) {
436   using Version = ServiceWorker::ServiceWorkerVersion;
437   auto result = std::make_unique<protocol::Array<Version>>();
438   for (const auto& version : versions) {
439     base::flat_set<std::string> client_set;
440 
441     for (const auto& client : version.clients) {
442       if (client.second.type ==
443           blink::mojom::ServiceWorkerContainerType::kForWindow) {
444         // A navigation may not yet be associated with a RenderFrameHost. Use
445         // the |web_contents_getter| instead.
446         WebContents* web_contents =
447             client.second.web_contents_getter
448                 ? client.second.web_contents_getter.Run()
449                 : WebContents::FromRenderFrameHost(RenderFrameHostImpl::FromID(
450                       client.second.process_id, client.second.route_id));
451         // There is a possibility that the frame is already deleted
452         // because of the thread hopping.
453         if (!web_contents)
454           continue;
455         client_set.insert(
456             DevToolsAgentHost::GetOrCreateFor(web_contents)->GetId());
457       }
458     }
459     auto clients = std::make_unique<protocol::Array<std::string>>();
460     for (std::string& client : client_set)
461       clients->emplace_back(std::move(client));
462 
463     std::unique_ptr<Version> version_value =
464         Version::Create()
465             .SetVersionId(base::NumberToString(version.version_id))
466             .SetRegistrationId(base::NumberToString(version.registration_id))
467             .SetScriptURL(version.script_url.spec())
468             .SetRunningStatus(
469                 GetVersionRunningStatusString(version.running_status))
470             .SetStatus(GetVersionStatusString(version.status))
471             .SetScriptLastModified(version.script_last_modified.ToDoubleT())
472             .SetScriptResponseTime(version.script_response_time.ToDoubleT())
473             .SetControlledClients(std::move(clients))
474             .Build();
475     scoped_refptr<DevToolsAgentHostImpl> host(
476         ServiceWorkerDevToolsManager::GetInstance()
477             ->GetDevToolsAgentHostForWorker(
478                 version.process_id,
479                 version.devtools_agent_route_id));
480     if (host)
481       version_value->SetTargetId(host->GetId());
482     result->emplace_back(std::move(version_value));
483   }
484   frontend_->WorkerVersionUpdated(std::move(result));
485 }
486 
OnErrorReported(int64_t registration_id,int64_t version_id,const ServiceWorkerContextCoreObserver::ErrorInfo & info)487 void ServiceWorkerHandler::OnErrorReported(
488     int64_t registration_id,
489     int64_t version_id,
490     const ServiceWorkerContextCoreObserver::ErrorInfo& info) {
491   frontend_->WorkerErrorReported(
492       ServiceWorker::ServiceWorkerErrorMessage::Create()
493           .SetErrorMessage(base::UTF16ToUTF8(info.error_message))
494           .SetRegistrationId(base::NumberToString(registration_id))
495           .SetVersionId(base::NumberToString(version_id))
496           .SetSourceURL(info.source_url.spec())
497           .SetLineNumber(info.line_number)
498           .SetColumnNumber(info.column_number)
499           .Build());
500 }
501 
ClearForceUpdate()502 void ServiceWorkerHandler::ClearForceUpdate() {
503   if (context_)
504     context_->SetForceUpdateOnPageLoad(false);
505 }
506 
507 }  // namespace protocol
508 }  // namespace content
509