blob: 7962787ecd57170445625a5b3222fcdb0c13d277 [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/flat_set.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/devtools/shared_worker_devtools_manager.h"
#include "content/browser/frame_host/frame_tree.h"
#include "content/browser/frame_host/frame_tree_node.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_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.mojom.h"
#include "third_party/WebKit/common/service_worker/service_worker_object.mojom.h"
#include "third_party/WebKit/common/service_worker/service_worker_provider_type.mojom.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) {
}
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::BindOnce(&base::DoNothing));
}
}
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::BindOnce(
callback, version->embedded_worker()->process_id(),
version->embedded_worker()->worker_devtools_agent_route_id()));
}
}
Response CreateDomainNotEnabledErrorResponse() {
return Response::Error("ServiceWorker domain not enabled");
}
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::BindOnce(&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),
browser_context_(nullptr),
storage_partition_(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::SetRenderer(int process_host_id,
RenderFrameHostImpl* frame_host) {
RenderProcessHost* process_host = RenderProcessHost::FromID(process_host_id);
// Do not call UpdateHosts yet, wait for load to commit.
if (!process_host) {
ClearForceUpdate();
context_ = nullptr;
return;
}
storage_partition_ =
static_cast<StoragePartitionImpl*>(process_host->GetStoragePartition());
DCHECK(storage_partition_);
context_ = static_cast<ServiceWorkerContextWrapper*>(
storage_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 CreateDomainNotEnabledErrorResponse();
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 CreateDomainNotEnabledErrorResponse();
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 CreateDomainNotEnabledErrorResponse();
if (!context_)
return CreateContextErrorResponse();
context_->SkipWaitingWorker(GURL(scope_url));
return Response::OK();
}
Response ServiceWorkerHandler::StopWorker(const std::string& version_id) {
if (!enabled_)
return CreateDomainNotEnabledErrorResponse();
if (!context_)
return CreateContextErrorResponse();
int64_t id = 0;
if (!base::StringToInt64(version_id, &id))
return CreateInvalidVersionIdErrorResponse();
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::BindOnce(&StopServiceWorkerOnIO, context_, id));
return Response::OK();
}
void ServiceWorkerHandler::StopAllWorkers(
std::unique_ptr<StopAllWorkersCallback> callback) {
if (!enabled_) {
callback->sendFailure(CreateDomainNotEnabledErrorResponse());
return;
}
if (!context_) {
callback->sendFailure(CreateContextErrorResponse());
return;
}
context_->StopAllServiceWorkers(base::BindOnce(
&StopAllWorkersCallback::sendSuccess, std::move(callback)));
}
Response ServiceWorkerHandler::UpdateRegistration(
const std::string& scope_url) {
if (!enabled_)
return CreateDomainNotEnabledErrorResponse();
if (!context_)
return CreateContextErrorResponse();
context_->UpdateRegistration(GURL(scope_url));
return Response::OK();
}
Response ServiceWorkerHandler::InspectWorker(const std::string& version_id) {
if (!enabled_)
return CreateDomainNotEnabledErrorResponse();
if (!context_)
return CreateContextErrorResponse();
int64_t id = blink::mojom::kInvalidServiceWorkerVersionId;
if (!base::StringToInt64(version_id, &id))
return CreateInvalidVersionIdErrorResponse();
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(&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 CreateDomainNotEnabledErrorResponse();
if (!browser_context_)
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(
browser_context_, GURL(origin), id, payload,
base::BindRepeating([](mojom::PushDeliveryStatus status) {}));
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 CreateDomainNotEnabledErrorResponse();
if (!storage_partition_)
return CreateContextErrorResponse();
int64_t id = 0;
if (!base::StringToInt64(registration_id, &id))
return CreateInvalidVersionIdErrorResponse();
BackgroundSyncContext* sync_context =
storage_partition_->GetBackgroundSyncContext();
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::BindOnce(&DispatchSyncEventOnIO, context_,
base::WrapRefCounted(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) {
base::flat_set<std::string> client_set;
for (const auto& client : version.clients) {
if (client.second.type ==
blink::mojom::ServiceWorkerProviderType::kForWindow) {
// PlzNavigate: a navigation may not yet be associated with a
// RenderFrameHost. Use the |web_contents_getter| instead.
WebContents* web_contents =
client.second.web_contents_getter
? client.second.web_contents_getter.Run()
: WebContents::FromRenderFrameHost(RenderFrameHostImpl::FromID(
client.second.process_id, client.second.route_id));
// There is a possibility that the frame is already deleted
// because of the thread hopping.
if (!web_contents)
continue;
client_set.insert(
DevToolsAgentHost::GetOrCreateFor(web_contents)->GetId());
}
}
std::unique_ptr<protocol::Array<std::string>> clients =
protocol::Array<std::string>::create();
for (auto& c : client_set)
clients->addItem(c);
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 ServiceWorkerContextCoreObserver::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