blob: c34d15a1ec1847012b17943d55162a3037f1d831 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/devtools/protocol/service_worker_handler.h"
#include "base/bind.h"
#include "base/containers/scoped_ptr_hash_map.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "content/browser/background_sync/background_sync_context.h"
#include "content/browser/background_sync/background_sync_manager.h"
#include "content/browser/devtools/service_worker_devtools_agent_host.h"
#include "content/browser/devtools/service_worker_devtools_manager.h"
#include "content/browser/frame_host/frame_tree.h"
#include "content/browser/frame_host/frame_tree_node.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/browser/service_worker/embedded_worker_status.h"
#include "content/browser/service_worker/service_worker_context_watcher.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/browser/service_worker/service_worker_version.h"
#include "content/browser/storage_partition_impl_map.h"
#include "content/common/service_worker/service_worker_utils.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/devtools_agent_host.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/push_event_payload.h"
#include "content/public/common/push_messaging_status.h"
#include "url/gurl.h"
namespace content {
namespace devtools {
namespace service_worker {
using Response = DevToolsProtocolClient::Response;
namespace {
void ResultNoOp(bool success) {
}
void StatusNoOp(ServiceWorkerStatusCode status) {
}
void StatusNoOpKeepingRegistration(
scoped_refptr<content::ServiceWorkerRegistration> protect,
ServiceWorkerStatusCode status) {
}
void PushDeliveryNoOp(PushDeliveryStatus status) {
}
const std::string GetVersionRunningStatusString(
EmbeddedWorkerStatus running_status) {
switch (running_status) {
case EmbeddedWorkerStatus::STOPPED:
return kServiceWorkerVersionRunningStatusStopped;
case EmbeddedWorkerStatus::STARTING:
return kServiceWorkerVersionRunningStatusStarting;
case EmbeddedWorkerStatus::RUNNING:
return kServiceWorkerVersionRunningStatusRunning;
case EmbeddedWorkerStatus::STOPPING:
return kServiceWorkerVersionRunningStatusStopping;
}
return std::string();
}
const std::string GetVersionStatusString(
content::ServiceWorkerVersion::Status status) {
switch (status) {
case content::ServiceWorkerVersion::NEW:
return kServiceWorkerVersionStatusNew;
case content::ServiceWorkerVersion::INSTALLING:
return kServiceWorkerVersionStatusInstalling;
case content::ServiceWorkerVersion::INSTALLED:
return kServiceWorkerVersionStatusInstalled;
case content::ServiceWorkerVersion::ACTIVATING:
return kServiceWorkerVersionStatusActivating;
case content::ServiceWorkerVersion::ACTIVATED:
return kServiceWorkerVersionStatusActivated;
case content::ServiceWorkerVersion::REDUNDANT:
return kServiceWorkerVersionStatusRedundant;
}
return std::string();
}
scoped_refptr<ServiceWorkerVersion> CreateVersionDictionaryValue(
const ServiceWorkerVersionInfo& version_info) {
std::vector<std::string> clients;
for (const auto& client : version_info.clients) {
if (client.second.type == SERVICE_WORKER_PROVIDER_FOR_WINDOW) {
RenderFrameHostImpl* render_frame_host = RenderFrameHostImpl::FromID(
client.second.process_id, client.second.route_id);
WebContents* web_contents =
WebContents::FromRenderFrameHost(render_frame_host);
// There is a possibility that the frame is already deleted because of the
// thread hopping.
if (!web_contents)
continue;
scoped_refptr<DevToolsAgentHost> agent_host(
DevToolsAgentHost::GetOrCreateFor(web_contents));
if (agent_host)
clients.push_back(agent_host->GetId());
} else if (client.second.type ==
SERVICE_WORKER_PROVIDER_FOR_SHARED_WORKER) {
scoped_refptr<DevToolsAgentHost> agent_host(
DevToolsAgentHost::GetForWorker(client.second.process_id,
client.second.route_id));
if (agent_host)
clients.push_back(agent_host->GetId());
}
}
scoped_refptr<ServiceWorkerVersion> version(
ServiceWorkerVersion::Create()
->set_version_id(base::Int64ToString(version_info.version_id))
->set_registration_id(
base::Int64ToString(version_info.registration_id))
->set_script_url(version_info.script_url.spec())
->set_running_status(
GetVersionRunningStatusString(version_info.running_status))
->set_status(GetVersionStatusString(version_info.status))
->set_script_last_modified(
version_info.script_last_modified.ToDoubleT())
->set_script_response_time(
version_info.script_response_time.ToDoubleT())
->set_controlled_clients(clients));
scoped_refptr<DevToolsAgentHostImpl> host(
ServiceWorkerDevToolsManager::GetInstance()
->GetDevToolsAgentHostForWorker(
version_info.process_id,
version_info.devtools_agent_route_id));
if (host)
version->set_target_id(host->GetId());
return version;
}
scoped_refptr<ServiceWorkerRegistration> CreateRegistrationDictionaryValue(
const ServiceWorkerRegistrationInfo& registration_info) {
scoped_refptr<ServiceWorkerRegistration> registration(
ServiceWorkerRegistration::Create()
->set_registration_id(
base::Int64ToString(registration_info.registration_id))
->set_scope_url(registration_info.pattern.spec())
->set_is_deleted(registration_info.delete_flag ==
ServiceWorkerRegistrationInfo::IS_DELETED));
return registration;
}
void StopServiceWorkerOnIO(scoped_refptr<ServiceWorkerContextWrapper> context,
int64_t version_id) {
if (content::ServiceWorkerVersion* version =
context->GetLiveVersion(version_id)) {
version->StopWorker(base::Bind(&StatusNoOp));
}
}
void GetDevToolsRouteInfoOnIO(
scoped_refptr<ServiceWorkerContextWrapper> context,
int64_t version_id,
const base::Callback<void(int, int)>& callback) {
if (content::ServiceWorkerVersion* version =
context->GetLiveVersion(version_id)) {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(
callback, version->embedded_worker()->process_id(),
version->embedded_worker()->worker_devtools_agent_route_id()));
}
}
Response CreateContextErrorResponse() {
return Response::InternalError("Could not connect to the context");
}
Response CreateInvalidVersionIdErrorResponse() {
return Response::InternalError("Invalid version ID");
}
void DidFindRegistrationForDispatchSyncEventOnIO(
scoped_refptr<BackgroundSyncContext> sync_context,
const std::string& tag,
bool last_chance,
ServiceWorkerStatusCode status,
scoped_refptr<content::ServiceWorkerRegistration> registration) {
if (status != SERVICE_WORKER_OK || !registration->active_version())
return;
BackgroundSyncManager* background_sync_manager =
sync_context->background_sync_manager();
scoped_refptr<content::ServiceWorkerVersion> version(
registration->active_version());
// Keep the registration while dispatching the sync event.
background_sync_manager->EmulateDispatchSyncEvent(
tag, std::move(version), last_chance,
base::Bind(&StatusNoOpKeepingRegistration, std::move(registration)));
}
void DispatchSyncEventOnIO(scoped_refptr<ServiceWorkerContextWrapper> context,
scoped_refptr<BackgroundSyncContext> sync_context,
const GURL& origin,
int64_t registration_id,
const std::string& tag,
bool last_chance) {
context->FindReadyRegistrationForId(
registration_id, origin,
base::Bind(&DidFindRegistrationForDispatchSyncEventOnIO, sync_context,
tag, last_chance));
}
} // namespace
ServiceWorkerHandler::ServiceWorkerHandler()
: enabled_(false), render_frame_host_(nullptr), weak_factory_(this) {
}
ServiceWorkerHandler::~ServiceWorkerHandler() {
Disable();
}
void ServiceWorkerHandler::SetRenderFrameHost(
RenderFrameHostImpl* render_frame_host) {
render_frame_host_ = render_frame_host;
// Do not call UpdateHosts yet, wait for load to commit.
if (!render_frame_host) {
ClearForceUpdate();
context_ = nullptr;
return;
}
StoragePartition* partition = BrowserContext::GetStoragePartition(
render_frame_host->GetProcess()->GetBrowserContext(),
render_frame_host->GetSiteInstance());
DCHECK(partition);
context_ = static_cast<ServiceWorkerContextWrapper*>(
partition->GetServiceWorkerContext());
}
void ServiceWorkerHandler::SetClient(std::unique_ptr<Client> client) {
client_.swap(client);
}
void ServiceWorkerHandler::Detached() {
Disable();
}
Response ServiceWorkerHandler::Enable() {
if (enabled_)
return Response::OK();
if (!context_)
return Response::InternalError("Could not connect to the context");
enabled_ = true;
context_watcher_ = new ServiceWorkerContextWatcher(
context_, base::Bind(&ServiceWorkerHandler::OnWorkerRegistrationUpdated,
weak_factory_.GetWeakPtr()),
base::Bind(&ServiceWorkerHandler::OnWorkerVersionUpdated,
weak_factory_.GetWeakPtr()),
base::Bind(&ServiceWorkerHandler::OnErrorReported,
weak_factory_.GetWeakPtr()));
context_watcher_->Start();
return Response::OK();
}
Response ServiceWorkerHandler::Disable() {
if (!enabled_)
return Response::OK();
enabled_ = false;
ClearForceUpdate();
DCHECK(context_watcher_);
context_watcher_->Stop();
context_watcher_ = nullptr;
return Response::OK();
}
Response ServiceWorkerHandler::Unregister(const std::string& scope_url) {
if (!enabled_)
return Response::OK();
if (!context_)
return CreateContextErrorResponse();
context_->UnregisterServiceWorker(GURL(scope_url), base::Bind(&ResultNoOp));
return Response::OK();
}
Response ServiceWorkerHandler::StartWorker(const std::string& scope_url) {
if (!enabled_)
return Response::OK();
if (!context_)
return CreateContextErrorResponse();
context_->StartServiceWorker(GURL(scope_url), base::Bind(&StatusNoOp));
return Response::OK();
}
Response ServiceWorkerHandler::SkipWaiting(const std::string& scope_url) {
if (!enabled_)
return Response::OK();
if (!context_)
return CreateContextErrorResponse();
context_->SkipWaitingWorker(GURL(scope_url));
return Response::OK();
}
Response ServiceWorkerHandler::StopWorker(const std::string& version_id) {
if (!enabled_)
return Response::OK();
if (!context_)
return CreateContextErrorResponse();
int64_t id = 0;
if (!base::StringToInt64(version_id, &id))
return CreateInvalidVersionIdErrorResponse();
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::Bind(&StopServiceWorkerOnIO, context_, id));
return Response::OK();
}
Response ServiceWorkerHandler::UpdateRegistration(
const std::string& scope_url) {
if (!enabled_)
return Response::OK();
if (!context_)
return CreateContextErrorResponse();
context_->UpdateRegistration(GURL(scope_url));
return Response::OK();
}
Response ServiceWorkerHandler::InspectWorker(const std::string& version_id) {
if (!enabled_)
return Response::OK();
if (!context_)
return CreateContextErrorResponse();
int64_t id = kInvalidServiceWorkerVersionId;
if (!base::StringToInt64(version_id, &id))
return CreateInvalidVersionIdErrorResponse();
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&GetDevToolsRouteInfoOnIO, context_, id,
base::Bind(&ServiceWorkerHandler::OpenNewDevToolsWindow,
weak_factory_.GetWeakPtr())));
return Response::OK();
}
Response ServiceWorkerHandler::SetForceUpdateOnPageLoad(
bool force_update_on_page_load) {
if (!context_)
return CreateContextErrorResponse();
context_->SetForceUpdateOnPageLoad(force_update_on_page_load);
return Response::OK();
}
Response ServiceWorkerHandler::DeliverPushMessage(
const std::string& origin,
const std::string& registration_id,
const std::string& data) {
if (!enabled_)
return Response::OK();
if (!render_frame_host_)
return CreateContextErrorResponse();
int64_t id = 0;
if (!base::StringToInt64(registration_id, &id))
return CreateInvalidVersionIdErrorResponse();
PushEventPayload payload;
if (data.size() > 0)
payload.setData(data);
BrowserContext::DeliverPushMessage(
render_frame_host_->GetProcess()->GetBrowserContext(), GURL(origin), id,
payload, base::Bind(&PushDeliveryNoOp));
return Response::OK();
}
Response ServiceWorkerHandler::DispatchSyncEvent(
const std::string& origin,
const std::string& registration_id,
const std::string& tag,
bool last_chance) {
if (!enabled_)
return Response::OK();
if (!render_frame_host_)
return CreateContextErrorResponse();
int64_t id = 0;
if (!base::StringToInt64(registration_id, &id))
return CreateInvalidVersionIdErrorResponse();
StoragePartitionImpl* partition =
static_cast<StoragePartitionImpl*>(BrowserContext::GetStoragePartition(
render_frame_host_->GetProcess()->GetBrowserContext(),
render_frame_host_->GetSiteInstance()));
BackgroundSyncContext* sync_context = partition->GetBackgroundSyncContext();
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::Bind(&DispatchSyncEventOnIO, context_,
make_scoped_refptr(sync_context),
GURL(origin), id, tag, last_chance));
return Response::OK();
}
void ServiceWorkerHandler::OpenNewDevToolsWindow(int process_id,
int devtools_agent_route_id) {
scoped_refptr<DevToolsAgentHostImpl> agent_host(
ServiceWorkerDevToolsManager::GetInstance()
->GetDevToolsAgentHostForWorker(process_id, devtools_agent_route_id));
if (!agent_host.get())
return;
agent_host->Inspect();
}
void ServiceWorkerHandler::OnWorkerRegistrationUpdated(
const std::vector<ServiceWorkerRegistrationInfo>& registrations) {
std::vector<scoped_refptr<ServiceWorkerRegistration>> registration_values;
for (const auto& registration : registrations) {
registration_values.push_back(
CreateRegistrationDictionaryValue(registration));
}
client_->WorkerRegistrationUpdated(
WorkerRegistrationUpdatedParams::Create()->set_registrations(
registration_values));
}
void ServiceWorkerHandler::OnWorkerVersionUpdated(
const std::vector<ServiceWorkerVersionInfo>& versions) {
std::vector<scoped_refptr<ServiceWorkerVersion>> version_values;
for (const auto& version : versions) {
version_values.push_back(CreateVersionDictionaryValue(version));
}
client_->WorkerVersionUpdated(
WorkerVersionUpdatedParams::Create()->set_versions(version_values));
}
void ServiceWorkerHandler::OnErrorReported(
int64_t registration_id,
int64_t version_id,
const ServiceWorkerContextObserver::ErrorInfo& info) {
client_->WorkerErrorReported(
WorkerErrorReportedParams::Create()->set_error_message(
ServiceWorkerErrorMessage::Create()
->set_error_message(base::UTF16ToUTF8(info.error_message))
->set_registration_id(base::Int64ToString(registration_id))
->set_version_id(base::Int64ToString(version_id))
->set_source_url(info.source_url.spec())
->set_line_number(info.line_number)
->set_column_number(info.column_number)));
}
void ServiceWorkerHandler::ClearForceUpdate() {
if (context_)
context_->SetForceUpdateOnPageLoad(false);
}
} // namespace service_worker
} // namespace devtools
} // namespace content