Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / content / browser / devtools / protocol / service_worker_handler.cc
blob5e1ac7a3c564465036393db8171fb4cc458a0545
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"
7 #include "base/bind.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"
27 #include "url/gurl.h"
29 // Windows headers will redefine SendMessage.
30 #ifdef SendMessage
31 #undef SendMessage
32 #endif
34 namespace content {
35 namespace devtools {
36 namespace service_worker {
38 using Response = DevToolsProtocolClient::Response;
40 namespace {
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;
61 return std::string();
64 const std::string GetVersionStatusString(
65 content::ServiceWorkerVersion::Status status) {
66 switch (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;
80 return std::string();
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
93 // thread hopping.
94 if (!web_contents)
95 continue;
96 scoped_refptr<DevToolsAgentHost> agent_host(
97 DevToolsAgentHost::GetOrCreateFor(web_contents));
98 if (agent_host)
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));
105 if (agent_host)
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));
123 return version;
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));
138 return registration;
141 scoped_refptr<ServiceWorkerDevToolsAgentHost> GetMatchingServiceWorker(
142 const ServiceWorkerDevToolsAgentHost::List& agent_hosts,
143 const GURL& url) {
144 scoped_refptr<ServiceWorkerDevToolsAgentHost> best_host;
145 std::string best_scope;
146 for (auto host : agent_hosts) {
147 if (host->GetURL().host() != url.host())
148 continue;
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()) {
154 best_host = host;
155 best_scope = scope;
158 return best_host;
161 ServiceWorkerDevToolsAgentHost::Map GetMatchingServiceWorkers(
162 BrowserContext* browser_context,
163 const std::set<GURL>& urls) {
164 ServiceWorkerDevToolsAgentHost::Map result;
165 if (!browser_context)
166 return result;
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);
173 if (host)
174 result[host->GetId()] = host;
176 return result;
179 bool CollectURLs(std::set<GURL>* urls, FrameTreeNode* tree_node) {
180 urls->insert(tree_node->current_url());
181 return false;
184 void StopServiceWorkerOnIO(scoped_refptr<ServiceWorkerContextWrapper> context,
185 int64 version_id) {
186 if (content::ServiceWorkerVersion* version =
187 context->GetLiveVersion(version_id)) {
188 version->StopWorker(base::Bind(&StatusNoOp));
192 void GetDevToolsRouteInfoOnIO(
193 scoped_refptr<ServiceWorkerContextWrapper> context,
194 int64 version_id,
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,
200 base::Bind(
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) {
216 switch (type) {
217 case DevToolsAgentHost::TYPE_WEB_CONTENTS:
218 return "web_contents";
219 case DevToolsAgentHost::TYPE_FRAME:
220 return "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:
226 return "external";
227 case DevToolsAgentHost::TYPE_BROWSER:
228 return "browser";
230 NOTREACHED() << type;
231 return std::string();
234 } // namespace
236 ServiceWorkerHandler::ServiceWorkerHandler()
237 : enabled_(false), render_frame_host_(nullptr), weak_factory_(this) {
240 ServiceWorkerHandler::~ServiceWorkerHandler() {
241 Disable();
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) {
249 context_ = nullptr;
250 return;
252 StoragePartition* partition = BrowserContext::GetStoragePartition(
253 render_frame_host->GetProcess()->GetBrowserContext(),
254 render_frame_host->GetSiteInstance());
255 DCHECK(partition);
256 context_ = static_cast<ServiceWorkerContextWrapper*>(
257 partition->GetServiceWorkerContext());
260 void ServiceWorkerHandler::SetClient(scoped_ptr<Client> client) {
261 client_.swap(client);
264 void ServiceWorkerHandler::UpdateHosts() {
265 if (!enabled_)
266 return;
268 urls_.clear();
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() {
292 Disable();
295 Response ServiceWorkerHandler::Enable() {
296 if (enabled_)
297 return Response::OK();
298 if (!context_)
299 return Response::InternalError("Could not connect to the context");
300 enabled_ = true;
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();
318 UpdateHosts();
319 return Response::OK();
322 Response ServiceWorkerHandler::Disable() {
323 if (!enabled_)
324 return Response::OK();
325 enabled_ = false;
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) {
357 if (!enabled_)
358 return Response::OK();
359 if (!context_)
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) {
366 if (!enabled_)
367 return Response::OK();
368 if (!context_)
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) {
375 if (!enabled_)
376 return Response::OK();
377 if (!context_)
378 return CreateContextErrorResponse();
379 int64 id = 0;
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) {
389 if (!enabled_)
390 return Response::OK();
391 if (!context_)
392 return CreateContextErrorResponse();
393 context_->UpdateRegistration(GURL(scope_url));
394 return Response::OK();
397 Response ServiceWorkerHandler::InspectWorker(const std::string& version_id) {
398 if (!enabled_)
399 return Response::OK();
400 if (!context_)
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) {
415 if (!enabled_)
416 return Response::OK();
417 if (!context_)
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) {
436 if (!context_)
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) {
449 if (!enabled_)
450 return Response::OK();
451 if (!render_frame_host_)
452 return CreateContextErrorResponse();
453 int64 id = 0;
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));
467 if (!agent_host)
468 return Response::InvalidParams("targetId");
469 *target_info =
470 TargetInfo::Create()
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));
481 if (!agent_host)
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())
493 return;
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,
521 int64 version_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.
574 return;
576 UpdateHosts();
579 void ServiceWorkerHandler::WorkerDestroyed(
580 ServiceWorkerDevToolsAgentHost* host) {
581 UpdateHosts();
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())
592 return;
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())
606 return;
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