blob: 242d33e6d9beb955a9e7f113594da5a9daf7bf02 [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/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 protocol {
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 ServiceWorker::ServiceWorkerVersionRunningStatusEnum::Stopped;
case EmbeddedWorkerStatus::STARTING:
return ServiceWorker::ServiceWorkerVersionRunningStatusEnum::Starting;
case EmbeddedWorkerStatus::RUNNING:
return ServiceWorker::ServiceWorkerVersionRunningStatusEnum::Running;
case EmbeddedWorkerStatus::STOPPING:
return ServiceWorker::ServiceWorkerVersionRunningStatusEnum::Stopping;
default:
NOTREACHED();
}
return std::string();
}
const std::string GetVersionStatusString(
content::ServiceWorkerVersion::Status status) {
switch (status) {
case content::ServiceWorkerVersion::NEW:
return ServiceWorker::ServiceWorkerVersionStatusEnum::New;
case content::ServiceWorkerVersion::INSTALLING:
return ServiceWorker::ServiceWorkerVersionStatusEnum::Installing;
case content::ServiceWorkerVersion::INSTALLED:
return ServiceWorker::ServiceWorkerVersionStatusEnum::Installed;
case content::ServiceWorkerVersion::ACTIVATING:
return ServiceWorker::ServiceWorkerVersionStatusEnum::Activating;
case content::ServiceWorkerVersion::ACTIVATED:
return ServiceWorker::ServiceWorkerVersionStatusEnum::Activated;
case content::ServiceWorkerVersion::REDUNDANT:
return ServiceWorker::ServiceWorkerVersionStatusEnum::Redundant;
default:
NOTREACHED();
}
return std::string();
}
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::Error("Could not connect to the context");
}
Response CreateInvalidVersionIdErrorResponse() {
return Response::InvalidParams("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()
: DevToolsDomainHandler(ServiceWorker::Metainfo::domainName),
enabled_(false),
render_frame_host_(nullptr),
weak_factory_(this) {
}
ServiceWorkerHandler::~ServiceWorkerHandler() {
}
void ServiceWorkerHandler::Wire(UberDispatcher* dispatcher) {
frontend_.reset(new ServiceWorker::Frontend(dispatcher->channel()));
ServiceWorker::Dispatcher::wire(dispatcher, this);
}
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());
}
Response ServiceWorkerHandler::Enable() {
if (enabled_)
return Response::OK();
if (!context_)
return CreateContextErrorResponse();
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) {
using Registration = ServiceWorker::ServiceWorkerRegistration;
std::unique_ptr<protocol::Array<Registration>> result =
protocol::Array<Registration>::create();
for (const auto& registration : registrations) {
result->addItem(Registration::Create()
.SetRegistrationId(
base::Int64ToString(registration.registration_id))
.SetScopeURL(registration.pattern.spec())
.SetIsDeleted(registration.delete_flag ==
ServiceWorkerRegistrationInfo::IS_DELETED)
.Build());
}
frontend_->WorkerRegistrationUpdated(std::move(result));
}
void ServiceWorkerHandler::OnWorkerVersionUpdated(
const std::vector<ServiceWorkerVersionInfo>& versions) {
using Version = ServiceWorker::ServiceWorkerVersion;
std::unique_ptr<protocol::Array<Version>> result =
protocol::Array<Version>::create();
for (const auto& version : versions) {
std::unique_ptr<protocol::Array<std::string>> clients =
protocol::Array<std::string>::create();
for (const auto& client : version.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;
clients->addItem(
DevToolsAgentHost::GetOrCreateFor(web_contents)->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->addItem(agent_host->GetId());
}
}
std::unique_ptr<Version> version_value = Version::Create()
.SetVersionId(base::Int64ToString(version.version_id))
.SetRegistrationId(
base::Int64ToString(version.registration_id))
.SetScriptURL(version.script_url.spec())
.SetRunningStatus(
GetVersionRunningStatusString(version.running_status))
.SetStatus(GetVersionStatusString(version.status))
.SetScriptLastModified(
version.script_last_modified.ToDoubleT())
.SetScriptResponseTime(
version.script_response_time.ToDoubleT())
.SetControlledClients(std::move(clients))
.Build();
scoped_refptr<DevToolsAgentHostImpl> host(
ServiceWorkerDevToolsManager::GetInstance()
->GetDevToolsAgentHostForWorker(
version.process_id,
version.devtools_agent_route_id));
if (host)
version_value->SetTargetId(host->GetId());
result->addItem(std::move(version_value));
}
frontend_->WorkerVersionUpdated(std::move(result));
}
void ServiceWorkerHandler::OnErrorReported(
int64_t registration_id,
int64_t version_id,
const ServiceWorkerContextObserver::ErrorInfo& info) {
frontend_->WorkerErrorReported(
ServiceWorker::ServiceWorkerErrorMessage::Create()
.SetErrorMessage(base::UTF16ToUTF8(info.error_message))
.SetRegistrationId(base::Int64ToString(registration_id))
.SetVersionId(base::Int64ToString(version_id))
.SetSourceURL(info.source_url.spec())
.SetLineNumber(info.line_number)
.SetColumnNumber(info.column_number)
.Build());
}
void ServiceWorkerHandler::ClearForceUpdate() {
if (context_)
context_->SetForceUpdateOnPageLoad(false);
}
} // namespace protocol
} // namespace content