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