blob: 5dc28b77ba72eeb040c0dd90d8ff801498a6f5ce [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// 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 <memory>
#include <variant>
#include "base/containers/flat_set.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "content/browser/background_sync/background_sync_context_impl.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/renderer_host/frame_tree.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/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.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/service_worker_context.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "third_party/blink/public/common/service_worker/embedded_worker_status.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/push_messaging/push_messaging_status.mojom.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_object.mojom.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace content {
namespace protocol {
namespace {
const std::string GetVersionRunningStatusString(
blink::EmbeddedWorkerStatus running_status) {
switch (running_status) {
case blink::EmbeddedWorkerStatus::kStopped:
return ServiceWorker::ServiceWorkerVersionRunningStatusEnum::Stopped;
case blink::EmbeddedWorkerStatus::kStarting:
return ServiceWorker::ServiceWorkerVersionRunningStatusEnum::Starting;
case blink::EmbeddedWorkerStatus::kRunning:
return ServiceWorker::ServiceWorkerVersionRunningStatusEnum::Running;
case blink::EmbeddedWorkerStatus::kStopping:
return ServiceWorker::ServiceWorkerVersionRunningStatusEnum::Stopping;
default:
NOTREACHED();
}
}
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();
}
}
Response CreateDomainNotEnabledErrorResponse() {
return Response::ServerError("ServiceWorker domain not enabled");
}
Response CreateContextErrorResponse() {
return Response::ServerError("Could not connect to the context");
}
Response CreateInvalidVersionIdErrorResponse() {
return Response::InvalidParams("Invalid version ID");
}
void DidFindRegistrationForDispatchSyncEvent(
scoped_refptr<BackgroundSyncContextImpl> sync_context,
const std::string& tag,
bool last_chance,
blink::ServiceWorkerStatusCode status,
scoped_refptr<content::ServiceWorkerRegistration> registration) {
if (status != blink::ServiceWorkerStatusCode::kOk ||
!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::DoNothing());
}
void DidFindRegistrationForDispatchPeriodicSyncEvent(
scoped_refptr<BackgroundSyncContextImpl> sync_context,
const std::string& tag,
blink::ServiceWorkerStatusCode status,
scoped_refptr<content::ServiceWorkerRegistration> registration) {
if (status != blink::ServiceWorkerStatusCode::kOk ||
!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->EmulateDispatchPeriodicSyncEvent(
tag, std::move(version), base::DoNothing());
}
} // namespace
ServiceWorkerHandler::ServiceWorkerHandler()
: DevToolsDomainHandler(ServiceWorker::Metainfo::domainName),
enabled_(false),
browser_context_(nullptr),
storage_partition_(nullptr) {}
ServiceWorkerHandler::~ServiceWorkerHandler() = default;
void ServiceWorkerHandler::Wire(UberDispatcher* dispatcher) {
frontend_ = std::make_unique<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_);
browser_context_ = process_host->GetBrowserContext();
context_ = static_cast<ServiceWorkerContextWrapper*>(
storage_partition_->GetServiceWorkerContext());
}
Response ServiceWorkerHandler::Enable() {
if (enabled_)
return Response::Success();
if (!context_)
return CreateContextErrorResponse();
enabled_ = true;
context_watcher_ = base::MakeRefCounted<ServiceWorkerContextWatcher>(
context_,
base::BindRepeating(&ServiceWorkerHandler::OnWorkerRegistrationUpdated,
weak_factory_.GetWeakPtr()),
base::BindRepeating(&ServiceWorkerHandler::OnWorkerVersionUpdated,
weak_factory_.GetWeakPtr()),
base::BindRepeating(&ServiceWorkerHandler::OnErrorReported,
weak_factory_.GetWeakPtr()));
context_watcher_->Start();
return Response::Success();
}
Response ServiceWorkerHandler::Disable() {
if (!enabled_)
return Response::Success();
enabled_ = false;
ClearForceUpdate();
DCHECK(context_watcher_);
context_watcher_->Stop();
context_watcher_ = nullptr;
return Response::Success();
}
Response ServiceWorkerHandler::Unregister(const std::string& scope_url) {
if (!enabled_)
return CreateDomainNotEnabledErrorResponse();
if (!context_)
return CreateContextErrorResponse();
GURL url(scope_url);
const blink::StorageKey key =
blink::StorageKey::CreateFirstParty(url::Origin::Create(url));
context_->UnregisterServiceWorker(url, key, base::DoNothing());
return Response::Success();
}
Response ServiceWorkerHandler::StartWorker(const std::string& scope_url) {
if (!enabled_)
return CreateDomainNotEnabledErrorResponse();
if (!context_)
return CreateContextErrorResponse();
context_->StartActiveServiceWorker(
GURL(scope_url),
blink::StorageKey::CreateFirstParty(url::Origin::Create(GURL(scope_url))),
base::DoNothing());
return Response::Success();
}
Response ServiceWorkerHandler::SkipWaiting(const std::string& scope_url) {
if (!enabled_)
return CreateDomainNotEnabledErrorResponse();
if (!context_)
return CreateContextErrorResponse();
context_->SkipWaitingWorker(GURL(scope_url),
blink::StorageKey::CreateFirstParty(
url::Origin::Create(GURL(scope_url))));
return Response::Success();
}
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();
if (content::ServiceWorkerVersion* version = context_->GetLiveVersion(id)) {
version->StopWorker(base::DoNothing());
}
return Response::Success();
}
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),
blink::StorageKey::CreateFirstParty(
url::Origin::Create(GURL(scope_url))));
return Response::Success();
}
Response ServiceWorkerHandler::SetForceUpdateOnPageLoad(
bool force_update_on_page_load) {
if (!context_)
return CreateContextErrorResponse();
context_->SetForceUpdateOnPageLoad(force_update_on_page_load);
return Response::Success();
}
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();
std::optional<std::string> payload;
if (data.size() > 0)
payload = data;
browser_context_->DeliverPushMessage(
GURL(origin), id,
/* message_id= */ std::string(), std::move(payload),
/* record_network_requests= */ false, base::DoNothing());
return Response::Success();
}
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();
scoped_refptr<BackgroundSyncContextImpl> sync_context =
base::WrapRefCounted(storage_partition_->GetBackgroundSyncContext());
context_->FindReadyRegistrationForId(
id,
blink::StorageKey::CreateFirstParty(url::Origin::Create(GURL(origin))),
base::BindOnce(&DidFindRegistrationForDispatchSyncEvent,
std::move(sync_context), tag, last_chance));
return Response::Success();
}
Response ServiceWorkerHandler::DispatchPeriodicSyncEvent(
const std::string& origin,
const std::string& registration_id,
const std::string& tag) {
if (!enabled_)
return CreateDomainNotEnabledErrorResponse();
if (!storage_partition_)
return CreateContextErrorResponse();
int64_t id = 0;
if (!base::StringToInt64(registration_id, &id))
return CreateInvalidVersionIdErrorResponse();
scoped_refptr<BackgroundSyncContextImpl> sync_context =
base::WrapRefCounted(storage_partition_->GetBackgroundSyncContext());
context_->FindReadyRegistrationForId(
id,
blink::StorageKey::CreateFirstParty(url::Origin::Create(GURL(origin))),
base::BindOnce(&DidFindRegistrationForDispatchPeriodicSyncEvent,
std::move(sync_context), tag));
return Response::Success();
}
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;
auto result = std::make_unique<protocol::Array<Registration>>();
for (const auto& registration : registrations) {
result->emplace_back(
Registration::Create()
.SetRegistrationId(
base::NumberToString(registration.registration_id))
.SetScopeURL(registration.scope.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;
auto result = std::make_unique<protocol::Array<Version>>();
for (const auto& version : versions) {
base::flat_set<std::string> client_set;
for (const auto& client : version.clients) {
if (std::holds_alternative<GlobalRenderFrameHostId>(client.second)) {
WebContents* web_contents = WebContentsImpl::FromRenderFrameHostID(
std::get<GlobalRenderFrameHostId>(client.second));
// 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());
}
}
auto clients = std::make_unique<protocol::Array<std::string>>();
for (std::string& client : client_set)
clients->emplace_back(std::move(client));
std::unique_ptr<Version> version_value =
Version::Create()
.SetVersionId(base::NumberToString(version.version_id))
.SetRegistrationId(base::NumberToString(version.registration_id))
.SetScriptURL(version.script_url.spec())
.SetRunningStatus(
GetVersionRunningStatusString(version.running_status))
.SetStatus(GetVersionStatusString(version.status))
.SetScriptLastModified(
version.script_last_modified.InSecondsFSinceUnixEpoch())
.SetScriptResponseTime(
version.script_response_time.InSecondsFSinceUnixEpoch())
.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());
}
if (version.router_rules) {
version_value->SetRouterRules(*version.router_rules);
}
result->emplace_back(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::NumberToString(registration_id))
.SetVersionId(base::NumberToString(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