blob: 4e77faa38d269abacb883418bb424cd0f4361f86 [file] [log] [blame]
// Copyright 2019 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/service_worker/service_worker_container_host.h"
#include <set>
#include <utility>
#include "base/containers/adapters.h"
#include "base/containers/contains.h"
#include "base/debug/crash_logging.h"
#include "base/functional/callback_helpers.h"
#include "base/functional/overloaded.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.h"
#include "base/uuid.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/service_worker/service_worker_consts.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/browser/service_worker/service_worker_host.h"
#include "content/browser/service_worker/service_worker_object_host.h"
#include "content/browser/service_worker/service_worker_registration_object_host.h"
#include "content/browser/service_worker/service_worker_security_utils.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/content_navigation_policy.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/common/content_client.h"
#include "content/public/common/origin_util.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "mojo/public/cpp/bindings/remote_set.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/service_worker/service_worker_scope_match.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_registration_options.mojom.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_running_status_callback.mojom.h"
namespace content {
namespace {
void RunCallbacks(
std::vector<ServiceWorkerContainerHost::ExecutionReadyCallback> callbacks) {
for (auto& callback : callbacks) {
std::move(callback).Run();
}
}
ServiceWorkerMetrics::EventType PurposeToEventType(
blink::mojom::ControllerServiceWorkerPurpose purpose) {
switch (purpose) {
case blink::mojom::ControllerServiceWorkerPurpose::FETCH_SUB_RESOURCE:
return ServiceWorkerMetrics::EventType::FETCH_SUB_RESOURCE;
}
NOTREACHED();
return ServiceWorkerMetrics::EventType::UNKNOWN;
}
storage::mojom::CacheStorageControl* GetCacheStorageControl(
scoped_refptr<ServiceWorkerVersion> version) {
DCHECK(version);
if (!version->context()) {
return nullptr;
}
auto* storage_partition = version->context()->wrapper()->storage_partition();
if (!storage_partition) {
return nullptr;
}
auto* control = storage_partition->GetCacheStorageControl();
if (!control) {
return nullptr;
}
return control;
}
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
// TODO(crbug.com/40918057): remove this metrics if we confirm that
// kContainerNotReady prevents calling the CountFeature IPC.
enum class CountFeatureDropOutReason {
kOk = 0,
kContainerNotReady = 1,
kExecutionNotReady = 2,
kNotBoundOrNotConnected = 3,
kMaxValue = kNotBoundOrNotConnected,
};
// Max number of messages that can be sent before |container_| gets ready.
// I believe messages may not be sent in that situation for regular way, but
// we technically do not prevent finding a client and send a message in that
// phase.
// 128 is picked randomly. We may need to run a experiment to decide the precise
// number.
constexpr size_t kMaxBufferedMessageSize = 128;
} // namespace
// RAII helper class for keeping track of versions waiting for an update hint
// from the renderer.
//
// This class is move-only.
class ServiceWorkerContainerHost::PendingUpdateVersion {
public:
explicit PendingUpdateVersion(scoped_refptr<ServiceWorkerVersion> version)
: version_(std::move(version)) {
version_->IncrementPendingUpdateHintCount();
}
PendingUpdateVersion(PendingUpdateVersion&& other) {
version_ = std::move(other.version_);
}
PendingUpdateVersion(const PendingUpdateVersion&) = delete;
PendingUpdateVersion& operator=(const PendingUpdateVersion&) = delete;
~PendingUpdateVersion() {
if (version_)
version_->DecrementPendingUpdateHintCount();
}
PendingUpdateVersion& operator=(PendingUpdateVersion&& other) {
version_ = std::move(other.version_);
return *this;
}
// Needed for base::flat_set.
bool operator<(const PendingUpdateVersion& other) const {
return version_ < other.version_;
}
private:
scoped_refptr<ServiceWorkerVersion> version_;
};
// To realize the running status condition in the ServiceWorker static routing
// API, the latest ServiceWorker running status is notified to all renderers
// that execute ServiceWorker subresource loaders and controlled by the
// ServiceWorker.
//
// This is an observer to receive the ServiceWorker running status change,
// and make the update notified to all the renderers via mojo IPC.
//
// See:
// https://w3c.github.io/ServiceWorker/#dom-routercondition-runningstatus
class ServiceWorkerContainerHost::ServiceWorkerRunningStatusObserver final
: public ServiceWorkerVersion::Observer {
public:
void OnRunningStateChanged(ServiceWorkerVersion* version) override {
Notify(version->running_status());
}
void Notify(blink::EmbeddedWorkerStatus status) {
for (const auto& callback : callbacks_) {
callback->OnStatusChanged(status);
}
}
void AddCallback(
mojo::PendingRemote<blink::mojom::ServiceWorkerRunningStatusCallback>
callback) {
callbacks_.Add(std::move(callback));
}
private:
mojo::RemoteSet<blink::mojom::ServiceWorkerRunningStatusCallback> callbacks_;
};
ServiceWorkerContainerHost::ServiceWorkerContainerHost(
base::WeakPtr<ServiceWorkerContextCore> context,
bool is_parent_frame_secure,
mojo::PendingAssociatedRemote<blink::mojom::ServiceWorkerContainer>
container_remote,
int process_id_for_worker_client)
: context_(std::move(context)),
create_time_(base::TimeTicks::Now()),
is_parent_frame_secure_(is_parent_frame_secure),
container_(std::move(container_remote)),
process_id_for_worker_client_(process_id_for_worker_client) {
DCHECK(context_);
}
ServiceWorkerContainerHostForServiceWorker::
ServiceWorkerContainerHostForServiceWorker(
base::WeakPtr<ServiceWorkerContextCore> context,
ServiceWorkerHost* service_worker_host,
const GURL& url,
const blink::StorageKey& storage_key)
: ServiceWorkerContainerHost(
std::move(context),
/*is_parent_frame_secure=*/true,
/*container_remote=*/{},
/*process_id_for_worker_client=*/ChildProcessHost::kInvalidUniqueID),
service_worker_host_(service_worker_host) {
url_ = url;
key_ = storage_key;
top_frame_origin_ = url::Origin::Create(key_.top_level_site().GetURL());
CHECK(!url_.has_ref());
service_worker_security_utils::CheckOnUpdateUrls(url_, key_);
}
ServiceWorkerContainerHostForClient::ServiceWorkerContainerHostForClient(
base::WeakPtr<ServiceWorkerContextCore> context,
bool is_parent_frame_secure,
mojo::PendingAssociatedRemote<blink::mojom::ServiceWorkerContainer>
container_remote,
int frame_tree_node_id)
: ServiceWorkerContainerHost(
std::move(context),
is_parent_frame_secure,
std::move(container_remote),
/*process_id_for_worker_client=*/ChildProcessHost::kInvalidUniqueID) {
client_uuid_ = base::Uuid::GenerateRandomV4().AsLowercaseString();
client_info_ = ServiceWorkerClientInfo();
ongoing_navigation_frame_tree_node_id_ = frame_tree_node_id;
DCHECK(IsContainerForWindowClient());
DCHECK(container_.is_bound());
}
ServiceWorkerContainerHostForClient::ServiceWorkerContainerHostForClient(
base::WeakPtr<ServiceWorkerContextCore> context,
int process_id,
mojo::PendingAssociatedRemote<blink::mojom::ServiceWorkerContainer>
container_remote,
ServiceWorkerClientInfo client_info)
: ServiceWorkerContainerHost(std::move(context),
/*is_parent_frame_secure=*/true,
std::move(container_remote),
process_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
client_uuid_ = base::Uuid::GenerateRandomV4().AsLowercaseString();
client_info_ = client_info;
DCHECK(IsContainerForWorkerClient());
DCHECK_NE(process_id_for_worker_client_, ChildProcessHost::kInvalidUniqueID);
DCHECK(container_.is_bound());
}
ServiceWorkerContainerHost::~ServiceWorkerContainerHost() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (IsContainerForWindowClient()) {
auto* rfh = RenderFrameHostImpl::FromID(GetRenderFrameHostId());
if (rfh)
rfh->RemoveServiceWorkerContainerHost(client_uuid());
}
if (controller_) {
DCHECK(IsContainerForClient());
controller_->Uncontrol(client_uuid());
if (running_status_observer_) {
controller_->RemoveObserver(running_status_observer_.get());
running_status_observer_.reset();
}
}
// Remove |this| as an observer of ServiceWorkerRegistrations.
// TODO(falken): Use base::ScopedObservation instead of this explicit call.
controller_.reset();
controller_registration_.reset();
// Ensure callbacks awaiting execution ready are notified.
if (IsContainerForClient())
RunExecutionReadyCallbacks();
RemoveAllMatchingRegistrations();
}
ServiceWorkerContainerHostForClient::~ServiceWorkerContainerHostForClient() =
default;
ServiceWorkerContainerHostForServiceWorker::
~ServiceWorkerContainerHostForServiceWorker() = default;
void ServiceWorkerContainerHostForClient::Register(
const GURL& script_url,
blink::mojom::ServiceWorkerRegistrationOptionsPtr options,
blink::mojom::FetchClientSettingsObjectPtr
outside_fetch_client_settings_object,
RegisterCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!CanServeContainerHostMethods(
&callback, options->scope, script_url,
base::StringPrintf(
ServiceWorkerConsts::kServiceWorkerRegisterErrorPrefix,
options->scope.spec().c_str(), script_url.spec().c_str())
.c_str(),
nullptr)) {
return;
}
if (!IsContainerForWindowClient()) {
mojo::ReportBadMessage(ServiceWorkerConsts::kBadMessageFromNonWindow);
std::move(callback).Run(blink::mojom::ServiceWorkerErrorType::kUnknown,
std::string(), nullptr);
return;
}
std::vector<GURL> urls = {url_, options->scope, script_url};
if (!service_worker_security_utils::AllOriginsMatchAndCanAccessServiceWorkers(
urls)) {
mojo::ReportBadMessage(ServiceWorkerConsts::kBadMessageImproperOrigins);
// ReportBadMessage() will terminate the renderer process, but Mojo
// complains if the callback is not run. Just run it with nonsense
// arguments.
std::move(callback).Run(blink::mojom::ServiceWorkerErrorType::kUnknown,
std::string(), nullptr);
return;
}
if (!service_worker_security_utils::
OriginCanRegisterServiceWorkerFromJavascript(url_)) {
mojo::ReportBadMessage(ServiceWorkerConsts::kBadMessageImproperOrigins);
// ReportBadMessage() will terminate the renderer process, but Mojo
// complains if the callback is not run. Just run it with nonsense
// arguments.
std::move(callback).Run(blink::mojom::ServiceWorkerErrorType::kUnknown,
std::string(), nullptr);
return;
}
int64_t trace_id = base::TimeTicks::Now().since_origin().InMicroseconds();
TRACE_EVENT_NESTABLE_ASYNC_BEGIN2(
"ServiceWorker", "ServiceWorkerContainerHost::Register",
TRACE_ID_WITH_SCOPE("ServiceWorkerContainerHost::Register", trace_id),
"Scope", options->scope.spec(), "Script URL", script_url.spec());
// Wrap the callback with default invoke before passing it, since
// RegisterServiceWorker() can drop the callback on service worker
// context core shutdown (i.e., browser session shutdown or
// DeleteAndStartOver()) and a DCHECK would happen.
// TODO(crbug.com/40646828): Remove this wrapper and have the Mojo connections
// drop during shutdown, so the callback can be dropped without crash. Note
// that we currently would need to add this WrapCallback to *ALL* Mojo
// callbacks that go through ServiceWorkerContextCore or its members like
// ServiceWorkerStorage. We're only adding it to Register() now because a
// browser test fails without it.
auto wrapped_callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun(
std::move(callback), blink::mojom::ServiceWorkerErrorType::kUnknown,
std::string(), nullptr);
// We pass the requesting frame host id, so that we can use this context for
// things like printing console error if the service worker does not have a
// process yet. This must be after commit so it should be populated, while
// it's possible the RenderFrameHost has already been destroyed due to IPC
// ordering.
GlobalRenderFrameHostId global_frame_id = GetRenderFrameHostId();
DCHECK_NE(global_frame_id.child_id, ChildProcessHost::kInvalidUniqueID);
DCHECK_NE(global_frame_id.frame_routing_id, MSG_ROUTING_NONE);
// Registrations could come from different origins when "disable-web-security"
// is active, we need to make sure we get the correct key.
const blink::StorageKey key =
service_worker_security_utils::GetCorrectStorageKeyForWebSecurityState(
key_, options->scope);
context_->RegisterServiceWorker(
script_url, key, *options,
std::move(outside_fetch_client_settings_object),
base::BindOnce(&ServiceWorkerContainerHostForClient::RegistrationComplete,
base::AsWeakPtr(this), GURL(script_url),
GURL(options->scope), std::move(wrapped_callback),
trace_id, mojo::GetBadMessageCallback()),
global_frame_id, policy_container_policies_.value());
}
void ServiceWorkerContainerHostForClient::GetRegistration(
const GURL& client_url,
GetRegistrationCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!CanServeContainerHostMethods(
&callback, url_, GURL(),
ServiceWorkerConsts::kServiceWorkerGetRegistrationErrorPrefix,
nullptr)) {
return;
}
std::string error_message;
if (!IsValidGetRegistrationMessage(client_url, &error_message)) {
mojo::ReportBadMessage(error_message);
// ReportBadMessage() will kill the renderer process, but Mojo complains if
// the callback is not run. Just run it with nonsense arguments.
std::move(callback).Run(blink::mojom::ServiceWorkerErrorType::kUnknown,
std::string(), nullptr);
return;
}
int64_t trace_id = base::TimeTicks::Now().since_origin().InMicroseconds();
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(
"ServiceWorker", "ServiceWorkerContainerHost::GetRegistration",
TRACE_ID_WITH_SCOPE("ServiceWorkerContainerHost::GetRegistration",
trace_id),
"Client URL", client_url.spec());
// The client_url may be cross-origin if "disable-web-security" is active,
// make sure we get the correct key.
const blink::StorageKey key =
service_worker_security_utils::GetCorrectStorageKeyForWebSecurityState(
key_, client_url);
context_->registry()->FindRegistrationForClientUrl(
ServiceWorkerRegistry::Purpose::kNotForNavigation, client_url, key,
base::BindOnce(
&ServiceWorkerContainerHostForClient::GetRegistrationComplete,
base::AsWeakPtr(this), std::move(callback), trace_id));
}
void ServiceWorkerContainerHostForClient::GetRegistrations(
GetRegistrationsCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!CanServeContainerHostMethods(
&callback, url_, GURL(),
ServiceWorkerConsts::kServiceWorkerGetRegistrationsErrorPrefix,
std::nullopt)) {
return;
}
std::string error_message;
if (!IsValidGetRegistrationsMessage(&error_message)) {
mojo::ReportBadMessage(error_message);
// ReportBadMessage() will kill the renderer process, but Mojo complains if
// the callback is not run. Just run it with nonsense arguments.
std::move(callback).Run(blink::mojom::ServiceWorkerErrorType::kUnknown,
std::string(), std::nullopt);
return;
}
int64_t trace_id = base::TimeTicks::Now().since_origin().InMicroseconds();
TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(
"ServiceWorker", "ServiceWorkerContainerHost::GetRegistrations",
TRACE_ID_WITH_SCOPE("ServiceWorkerContainerHost::GetRegistrations",
trace_id));
context_->registry()->GetRegistrationsForStorageKey(
key_, base::BindOnce(
&ServiceWorkerContainerHostForClient::GetRegistrationsComplete,
base::AsWeakPtr(this), std::move(callback), trace_id));
}
void ServiceWorkerContainerHostForClient::GetRegistrationForReady(
GetRegistrationForReadyCallback callback) {
std::string error_message;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!IsValidGetRegistrationForReadyMessage(&error_message)) {
mojo::ReportBadMessage(error_message);
// ReportBadMessage() will kill the renderer process, but Mojo complains if
// the callback is not run. Just run it with nonsense arguments.
std::move(callback).Run(nullptr);
return;
}
TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(
"ServiceWorker", "ServiceWorkerContainerHost::GetRegistrationForReady",
TRACE_ID_LOCAL(this));
DCHECK(!get_ready_callback_);
get_ready_callback_ =
std::make_unique<GetRegistrationForReadyCallback>(std::move(callback));
ReturnRegistrationForReadyIfNeeded();
}
void ServiceWorkerContainerHostForClient::EnsureControllerServiceWorker(
mojo::PendingReceiver<blink::mojom::ControllerServiceWorker> receiver,
blink::mojom::ControllerServiceWorkerPurpose purpose) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// TODO(kinuko): Log the reasons we drop the request.
if (!context_ || !controller_)
return;
controller_->RunAfterStartWorker(
PurposeToEventType(purpose),
base::BindOnce(
&ServiceWorkerContainerHostForClient::StartControllerComplete,
base::AsWeakPtr(this), std::move(receiver)));
}
void ServiceWorkerContainerHost::CloneContainerHost(
mojo::PendingReceiver<blink::mojom::ServiceWorkerContainerHost> receiver) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
additional_receivers_.Add(this, std::move(receiver));
}
void ServiceWorkerContainerHostForClient::HintToUpdateServiceWorker() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(IsContainerForClient());
// The destructors notify the ServiceWorkerVersions to update.
versions_to_update_.clear();
}
void ServiceWorkerContainerHostForClient::EnsureFileAccess(
const std::vector<base::FilePath>& file_paths,
EnsureFileAccessCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ServiceWorkerVersion* version =
controller_registration_ ? controller_registration_->active_version()
: nullptr;
// The controller might have legitimately been lost due to
// NotifyControllerLost(), so don't ReportBadMessage() here.
if (version) {
int controller_process_id = version->embedded_worker()->process_id();
ChildProcessSecurityPolicyImpl* policy =
ChildProcessSecurityPolicyImpl::GetInstance();
for (const auto& file : file_paths) {
if (!policy->CanReadFile(GetProcessId(), file)) {
mojo::ReportBadMessage(
"The renderer doesn't have access to the file "
"but it tried to grant access to the controller.");
return;
}
if (!policy->CanReadFile(controller_process_id, file))
policy->GrantReadFile(controller_process_id, file);
}
}
std::move(callback).Run();
}
void ServiceWorkerContainerHostForClient::OnExecutionReady() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Since `OnExecutionReady()` is a part of `ServiceWorkerContainerHost`,
// this method is called only if `is_container_ready_` is true.
CHECK(is_container_ready_);
CHECK(IsContainerForClient());
if (is_execution_ready()) {
mojo::ReportBadMessage("SWPH_OER_ALREADY_READY");
return;
}
// The controller was sent on navigation commit but we must send it again here
// because 1) the controller might have changed since navigation commit due to
// skipWaiting(), and 2) the UseCounter might have changed since navigation
// commit, in such cases the updated information was prevented being sent due
// to false is_execution_ready().
// TODO(leonhsl): Create some layout tests covering the above case 1), in
// which case we may also need to set |notify_controllerchange| correctly.
SendSetControllerServiceWorker(false /* notify_controllerchange */);
SetExecutionReady();
}
void ServiceWorkerContainerHostForServiceWorker::Register(
const GURL& script_url,
blink::mojom::ServiceWorkerRegistrationOptionsPtr options,
blink::mojom::FetchClientSettingsObjectPtr
outside_fetch_client_settings_object,
RegisterCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
mojo::ReportBadMessage(ServiceWorkerConsts::kBadMessageFromNonWindow);
std::move(callback).Run(blink::mojom::ServiceWorkerErrorType::kUnknown,
std::string(), nullptr);
}
void ServiceWorkerContainerHostForServiceWorker::GetRegistration(
const GURL& client_url,
GetRegistrationCallback callback) {
mojo::ReportBadMessage(ServiceWorkerConsts::kBadMessageFromNonWindow);
// ReportBadMessage() will kill the renderer process, but Mojo complains if
// the callback is not run. Just run it with nonsense arguments.
std::move(callback).Run(blink::mojom::ServiceWorkerErrorType::kUnknown,
std::string(), nullptr);
}
void ServiceWorkerContainerHostForServiceWorker::GetRegistrations(
GetRegistrationsCallback callback) {
mojo::ReportBadMessage(ServiceWorkerConsts::kBadMessageFromNonWindow);
// ReportBadMessage() will kill the renderer process, but Mojo complains if
// the callback is not run. Just run it with nonsense arguments.
std::move(callback).Run(blink::mojom::ServiceWorkerErrorType::kUnknown,
std::string(), std::nullopt);
}
void ServiceWorkerContainerHostForServiceWorker::GetRegistrationForReady(
GetRegistrationForReadyCallback callback) {
std::string error_message;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
mojo::ReportBadMessage(ServiceWorkerConsts::kBadMessageFromNonWindow);
// ReportBadMessage() will kill the renderer process, but Mojo complains if
// the callback is not run. Just run it with nonsense arguments.
std::move(callback).Run(nullptr);
}
void ServiceWorkerContainerHostForServiceWorker::EnsureControllerServiceWorker(
mojo::PendingReceiver<blink::mojom::ControllerServiceWorker> receiver,
blink::mojom::ControllerServiceWorkerPurpose purpose) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void ServiceWorkerContainerHostForServiceWorker::HintToUpdateServiceWorker() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
mojo::ReportBadMessage("SWPH_HTUSW_NOT_CLIENT");
}
void ServiceWorkerContainerHostForServiceWorker::EnsureFileAccess(
const std::vector<base::FilePath>& file_paths,
EnsureFileAccessCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::move(callback).Run();
}
void ServiceWorkerContainerHostForServiceWorker::OnExecutionReady() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
mojo::ReportBadMessage("SWPH_OER_NOT_CLIENT");
}
void ServiceWorkerContainerHost::OnVersionAttributesChanged(
ServiceWorkerRegistration* registration,
blink::mojom::ChangedServiceWorkerObjectsMaskPtr changed_mask) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!get_ready_callback_ || get_ready_callback_->is_null())
return;
if (changed_mask->active && registration->active_version()) {
// Wait until the state change so we don't send the get for ready
// registration complete message before set version attributes message.
registration->active_version()->RegisterStatusChangeCallback(base::BindOnce(
&ServiceWorkerContainerHost::ReturnRegistrationForReadyIfNeeded,
base::AsWeakPtr(this)));
}
}
void ServiceWorkerContainerHost::OnRegistrationFailed(
ServiceWorkerRegistration* registration) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
RemoveMatchingRegistration(registration);
}
void ServiceWorkerContainerHost::OnRegistrationFinishedUninstalling(
ServiceWorkerRegistration* registration) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
RemoveMatchingRegistration(registration);
}
void ServiceWorkerContainerHost::OnSkippedWaiting(
ServiceWorkerRegistration* registration) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (controller_registration_ != registration)
return;
#if DCHECK_IS_ON()
DCHECK(controller_);
ServiceWorkerVersion* active = controller_registration_->active_version();
DCHECK(active);
DCHECK_NE(active, controller_.get());
DCHECK_EQ(active->status(), ServiceWorkerVersion::ACTIVATING);
#endif // DCHECK_IS_ON()
if (IsBackForwardCacheEnabled() && IsInBackForwardCache()) {
// This ServiceWorkerContainerHost is evicted from BackForwardCache in
// |ActivateWaitingVersion|, but not deleted yet. This can happen because
// asynchronous eviction and |OnSkippedWaiting| are in the same task.
// The controller does not have to be updated because |this| will be evicted
// from BackForwardCache.
// TODO(yuzus): Wire registration with ServiceWorkerContainerHost so that we
// can check on the caller side.
return;
}
UpdateController(true /* notify_controllerchange */);
}
void ServiceWorkerContainerHost::AddMatchingRegistration(
ServiceWorkerRegistration* registration) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(blink::ServiceWorkerScopeMatches(registration->scope(),
GetUrlForScopeMatch()));
DCHECK(registration->key() == key());
if (!IsEligibleForServiceWorkerController())
return;
size_t key = registration->scope().spec().size();
if (base::Contains(matching_registrations_, key))
return;
registration->AddListener(this);
matching_registrations_[key] = registration;
ReturnRegistrationForReadyIfNeeded();
}
void ServiceWorkerContainerHost::RemoveMatchingRegistration(
ServiceWorkerRegistration* registration) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_NE(controller_registration_, registration);
#if DCHECK_IS_ON()
DCHECK(IsMatchingRegistration(registration));
#endif // DCHECK_IS_ON()
registration->RemoveListener(this);
size_t key = registration->scope().spec().size();
matching_registrations_.erase(key);
}
ServiceWorkerRegistration* ServiceWorkerContainerHost::MatchRegistration()
const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (const auto& registration : base::Reversed(matching_registrations_)) {
if (registration.second->is_uninstalled())
continue;
if (registration.second->is_uninstalling())
return nullptr;
return registration.second.get();
}
return nullptr;
}
void ServiceWorkerContainerHost::AddServiceWorkerToUpdate(
scoped_refptr<ServiceWorkerVersion> version) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// This is only called for windows now, but it should be called for all
// clients someday.
DCHECK(IsContainerForWindowClient());
versions_to_update_.emplace(std::move(version));
}
void ServiceWorkerContainerHost::PostMessageToClient(
ServiceWorkerVersion* version,
blink::TransferableMessage message) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsContainerForClient());
base::WeakPtr<ServiceWorkerObjectHost> object_host =
version_object_manager().GetOrCreateHost(version);
if (!is_container_ready_) {
if (buffered_messages_.size() < kMaxBufferedMessageSize) {
buffered_messages_.emplace_back(object_host, std::move(message));
}
return;
}
blink::mojom::ServiceWorkerObjectInfoPtr info;
if (object_host)
info = object_host->CreateCompleteObjectInfoToSend();
container_->PostMessageToClient(std::move(info), std::move(message));
}
void ServiceWorkerContainerHost::CountFeature(
blink::mojom::WebFeature feature) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
SCOPED_CRASH_KEY_NUMBER("SWCH_CF", "feature", static_cast<int32_t>(feature));
SCOPED_CRASH_KEY_NUMBER("SWCH_CF", "client_type",
static_cast<int32_t>(GetClientType()));
constexpr char kDropOutMetrics[] = "ServiceWorker.CountFeature.DropOut";
// CountFeature is a message about the client's controller. It should be sent
// only for clients.
DCHECK(IsContainerForClient());
// `container_` can be used only if ServiceWorkerContainerInfoForClient has
// been passed to the renderer process. Otherwise, the method call will crash
// inside the mojo library (See crbug.com/40918057).
if (!is_container_ready_) {
base::UmaHistogramEnumeration(
kDropOutMetrics, CountFeatureDropOutReason::kContainerNotReady);
buffered_used_features_.insert(feature);
return;
}
// And only when loading finished so the controller is really settled.
if (!is_execution_ready()) {
base::UmaHistogramEnumeration(
kDropOutMetrics, CountFeatureDropOutReason::kExecutionNotReady);
buffered_used_features_.insert(feature);
return;
}
// `container_` shouldn't be disconnected during the lifetime of `this` but
// there seems a situation where `container_` is disconnected or unbound.
// TODO(crbug.com/1136843, crbug.com/40918057): Figure out the cause and
// remove this check.
if (!container_.is_bound() || !container_.is_connected()) {
base::UmaHistogramEnumeration(
kDropOutMetrics, CountFeatureDropOutReason::kNotBoundOrNotConnected);
return;
}
base::UmaHistogramEnumeration(kDropOutMetrics,
CountFeatureDropOutReason::kOk);
container_->CountFeature(feature);
}
blink::mojom::ControllerServiceWorkerInfoPtr
ServiceWorkerContainerHost::CreateControllerServiceWorkerInfo() {
CHECK(controller());
auto controller_info = blink::mojom::ControllerServiceWorkerInfo::New();
controller_info->client_id = client_uuid();
controller_info->mode = GetControllerMode();
controller_info->fetch_handler_type = controller()->fetch_handler_type();
controller_info->fetch_handler_bypass_option =
controller()->fetch_handler_bypass_option();
controller_info->sha256_script_checksum =
controller()->sha256_script_checksum();
controller_info->need_router_evaluate = controller()->NeedRouterEvaluate();
if (controller()->router_evaluator()) {
controller_info->router_data = blink::mojom::ServiceWorkerRouterData::New();
controller_info->router_data->router_rules =
controller()->router_evaluator()->rules();
// Pass an endpoint for the cache storage.
mojo::PendingRemote<blink::mojom::CacheStorage> remote_cache_storage =
GetRemoteCacheStorage();
if (remote_cache_storage) {
controller_info->router_data->remote_cache_storage =
std::move(remote_cache_storage);
}
if (controller()->router_evaluator()->need_running_status()) {
controller_info->router_data->running_status_receiver =
GetRunningStatusCallbackReceiver();
controller_info->router_data->initial_running_status =
controller()->running_status();
}
}
// Note that |controller_info->remote_controller| is null if the controller
// has no fetch event handler. In that case the renderer frame won't get the
// controller pointer upon the navigation commit, and subresource loading will
// not be intercepted. (It might get intercepted later if the controller
// changes due to skipWaiting() so SetController is sent.)
mojo::Remote<blink::mojom::ControllerServiceWorker> remote =
GetRemoteControllerServiceWorker();
if (remote.is_bound()) {
controller_info->remote_controller = remote.Unbind();
}
if (fetch_request_window_id()) {
controller_info->fetch_request_window_id =
std::make_optional(fetch_request_window_id());
}
// Populate used features for UseCounter purposes.
for (const auto feature : controller()->used_features()) {
controller_info->used_features.push_back(feature);
}
return controller_info;
}
void ServiceWorkerContainerHost::SendSetControllerServiceWorker(
bool notify_controllerchange) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsContainerForClient());
CHECK(is_container_ready_);
if (!controller_ || !context_) {
// Do not set |fetch_request_window_id| when |controller_| is not available.
// Setting |fetch_request_window_id| should not affect correctness, however,
// we have the extensions bug, https://crbug.com/963748, which we don't yet
// understand. That is why we don't set |fetch_request_window_id| if there
// is no controller, at least, until we can fix the extension bug.
//
// Also check if |context_| is not null. This is a speculative fix for
// crbug.com/324559079. When |controller_info->fetch_request_window_id|
// is set, the renderer expects that |controller_info->object_info| is also
// set as a controller. |controller_info->object_info| is set in
// `version_object_manager().GetOrCreateHost()`, but that may return null if
// |context_| does not exist. To avoid the potential inconsistency with the
// renderer side, setController as no-controller.
auto controller_info = blink::mojom::ControllerServiceWorkerInfo::New();
controller_info->client_id = client_uuid();
container_->SetController(std::move(controller_info),
notify_controllerchange);
return;
}
DCHECK(controller_registration());
DCHECK_EQ(controller_registration_->active_version(), controller_.get());
auto controller_info = CreateControllerServiceWorkerInfo();
// Set the info for the JavaScript ServiceWorkerContainer#controller object.
if (base::WeakPtr<ServiceWorkerObjectHost> object_host =
version_object_manager().GetOrCreateHost(controller())) {
controller_info->object_info =
object_host->CreateCompleteObjectInfoToSend();
}
// TODO(crbug.com/331279951): Remove these crash keys after investigation.
SCOPED_CRASH_KEY_NUMBER("SWCH_SC", "client_type",
static_cast<int32_t>(GetClientType()));
SCOPED_CRASH_KEY_BOOL("SWCH_SC", "is_bound", container_.is_bound());
SCOPED_CRASH_KEY_BOOL("SWCH_SC", "is_connected",
container_.is_bound() && container_.is_connected());
SCOPED_CRASH_KEY_BOOL("SWCH_SC", "notify_controllerchange",
notify_controllerchange);
SCOPED_CRASH_KEY_BOOL("SWCH_SC", "IsContainerForClient",
IsContainerForClient());
SCOPED_CRASH_KEY_BOOL("SWCH_SC", "is_execution_ready", is_execution_ready());
SCOPED_CRASH_KEY_BOOL("SWCH_SC", "is_container_ready", is_container_ready_);
container_->SetController(std::move(controller_info),
notify_controllerchange);
}
void ServiceWorkerContainerHost::NotifyControllerLost() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (IsBackForwardCacheEnabled() && IsInBackForwardCache()) {
// The controller was unregistered, which usually does not happen while it
// has controllees. Since the document is in the back/forward cache, it does
// not count as a controllee. However, this means if it were to be restored,
// the page would be in an unexpected state, so evict the bfcache entry.
EvictFromBackForwardCache(BackForwardCacheMetrics::NotRestoredReason::
kServiceWorkerUnregistration);
}
SetControllerRegistration(nullptr, true /* notify_controllerchange */);
}
void ServiceWorkerContainerHost::ClaimedByRegistration(
scoped_refptr<ServiceWorkerRegistration> registration) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsContainerForClient());
DCHECK(registration->active_version());
DCHECK(is_execution_ready());
// TODO(falken): This should just early return, or DCHECK. claim() should have
// no effect on a page that's already using the registration.
if (registration == controller_registration_) {
UpdateController(true /* notify_controllerchange */);
return;
}
SetControllerRegistration(registration, true /* notify_controllerchange */);
}
ServiceWorkerRegistrationObjectManager::ServiceWorkerRegistrationObjectManager(
ServiceWorkerContainerHost* container_host)
: container_host_(*container_host) {}
ServiceWorkerRegistrationObjectManager::
~ServiceWorkerRegistrationObjectManager() = default;
blink::mojom::ServiceWorkerRegistrationObjectInfoPtr
ServiceWorkerRegistrationObjectManager::CreateInfo(
scoped_refptr<ServiceWorkerRegistration> registration) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
int64_t registration_id = registration->id();
auto existing_host = registration_object_hosts_.find(registration_id);
if (existing_host != registration_object_hosts_.end()) {
return existing_host->second->CreateObjectInfo();
}
registration_object_hosts_[registration_id] =
std::make_unique<ServiceWorkerRegistrationObjectHost>(
container_host_->context(), &container_host_.get(),
std::move(registration));
return registration_object_hosts_[registration_id]->CreateObjectInfo();
}
void ServiceWorkerRegistrationObjectManager::RemoveHost(
int64_t registration_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(base::Contains(registration_object_hosts_, registration_id));
// This is a workaround for a really unfavorable ownership structure of
// service worker content code. This boils down to the following ownership
// cycle:
// 1. This class owns ServiceWorkerRegistrationObjectHost via std::unique_ptr
// in registration_object_hosts_.
// 2. The ServiceWorkerRegistrationObjectHost has a
// scoped_refptr<ServiceWorkerRegistration> registration_ member.
// 3. The ServiceWorkerRegistration has multiple
// scoped_refptr<ServiceWorkerVersion> members.
// 4. ServiceWorkerVersion has a std::unique_ptr<ServiceWorkerHost>
// worker_host_ member.
// 5. ServiceWorkerHost in turn owns an instance of this class via
// its worker_host_ member.
// What this all means is that erasing the registration_id here can actually
// lead to "this" ending up being destroyed after we exit from the erase
// call. This might not seem fatal, but is when using libstdc++. Apparently
// the C++ standard does not define when the destructor of the value from the
// map gets called. In libcxx its called after the key has been removed from
// the map, while in libstdc++ the destructor gets called first and then
// the key is removed before the erase call returns. This means that in
// case of libstdc++ the value we're removing from the map in the erase call
// can be deleted a second time when registration_object_hosts_ destructor
// gets called in ~ServiceWorkerContainerHost.
auto to_be_deleted = std::move(registration_object_hosts_[registration_id]);
registration_object_hosts_.erase(registration_id);
}
ServiceWorkerObjectManager::ServiceWorkerObjectManager(
ServiceWorkerContainerHost* container_host)
: container_host_(*container_host) {}
ServiceWorkerObjectManager::~ServiceWorkerObjectManager() = default;
blink::mojom::ServiceWorkerObjectInfoPtr
ServiceWorkerObjectManager::CreateInfoToSend(
scoped_refptr<ServiceWorkerVersion> version) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
int64_t version_id = version->version_id();
auto existing_object_host = service_worker_object_hosts_.find(version_id);
if (existing_object_host != service_worker_object_hosts_.end()) {
return existing_object_host->second->CreateCompleteObjectInfoToSend();
}
service_worker_object_hosts_[version_id] =
std::make_unique<ServiceWorkerObjectHost>(container_host_->context(),
container_host_->AsWeakPtr(),
std::move(version));
return service_worker_object_hosts_[version_id]
->CreateCompleteObjectInfoToSend();
}
base::WeakPtr<ServiceWorkerObjectHost>
ServiceWorkerObjectManager::GetOrCreateHost(
scoped_refptr<ServiceWorkerVersion> version) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!container_host_->context() || !version) {
return nullptr;
}
const int64_t version_id = version->version_id();
auto existing_object_host = service_worker_object_hosts_.find(version_id);
if (existing_object_host != service_worker_object_hosts_.end())
return existing_object_host->second->AsWeakPtr();
service_worker_object_hosts_[version_id] =
std::make_unique<ServiceWorkerObjectHost>(container_host_->context(),
container_host_->AsWeakPtr(),
std::move(version));
return service_worker_object_hosts_[version_id]->AsWeakPtr();
}
void ServiceWorkerObjectManager::RemoveHost(int64_t version_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(base::Contains(service_worker_object_hosts_, version_id));
// ServiceWorkerObjectHost to be deleted may have the last reference to
// ServiceWorkerVersion that indirectly owns this ServiceWorkerContainerHost.
// If we erase the object host directly from the map, |this| could be deleted
// during the map operation and may crash. To avoid the case, we take the
// ownership of the object host from the map first, and then erase the entry
// from the map. See https://crbug.com/1056598 for details.
std::unique_ptr<ServiceWorkerObjectHost> to_be_deleted =
std::move(service_worker_object_hosts_[version_id]);
DCHECK(to_be_deleted);
service_worker_object_hosts_.erase(version_id);
}
bool ServiceWorkerContainerHost::IsContainerForClient() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return client_info_ != std::nullopt;
}
blink::mojom::ServiceWorkerClientType
ServiceWorkerContainerHost::GetClientType() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(client_info_);
return absl::visit(
base::Overloaded(
[](GlobalRenderFrameHostId render_frame_host_id) {
return blink::mojom::ServiceWorkerClientType::kWindow;
},
[](blink::DedicatedWorkerToken dedicated_worker_token) {
return blink::mojom::ServiceWorkerClientType::kDedicatedWorker;
},
[](blink::SharedWorkerToken shared_worker_token) {
return blink::mojom::ServiceWorkerClientType::kSharedWorker;
}),
*client_info_);
}
bool ServiceWorkerContainerHost::IsContainerForWindowClient() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return client_info_ &&
absl::holds_alternative<GlobalRenderFrameHostId>(*client_info_);
}
bool ServiceWorkerContainerHost::IsContainerForWorkerClient() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
using blink::mojom::ServiceWorkerClientType;
if (!client_info_)
return false;
return absl::holds_alternative<blink::DedicatedWorkerToken>(*client_info_) ||
absl::holds_alternative<blink::SharedWorkerToken>(*client_info_);
}
ServiceWorkerClientInfo ServiceWorkerContainerHost::GetServiceWorkerClientInfo()
const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsContainerForClient());
return *client_info_;
}
void ServiceWorkerContainerHost::OnBeginNavigationCommit(
const GlobalRenderFrameHostId& rfh_id,
const PolicyContainerPolicies& policy_container_policies,
mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter>
coep_reporter,
ukm::SourceId document_ukm_source_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsContainerForWindowClient());
ongoing_navigation_frame_tree_node_id_ = RenderFrameHost::kNoFrameTreeNodeId;
client_info_ = rfh_id;
if (controller_)
controller_->UpdateForegroundPriority();
DCHECK(!policy_container_policies_.has_value());
policy_container_policies_ = policy_container_policies.Clone();
coep_reporter_.Bind(std::move(coep_reporter));
mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter>
coep_reporter_to_be_passed;
coep_reporter_->Clone(
coep_reporter_to_be_passed.InitWithNewPipeAndPassReceiver());
if (controller_ && controller_->fetch_handler_existence() ==
ServiceWorkerVersion::FetchHandlerExistence::EXISTS) {
DCHECK(pending_controller_receiver_);
controller_->controller()->Clone(
std::move(pending_controller_receiver_),
policy_container_policies_->cross_origin_embedder_policy,
std::move(coep_reporter_to_be_passed));
}
auto* rfh = RenderFrameHostImpl::FromID(rfh_id);
// `rfh` may be null in tests (but it should not happen in production).
if (rfh)
rfh->AddServiceWorkerContainerHost(client_uuid(), base::AsWeakPtr(this));
DCHECK_EQ(ukm_source_id_, ukm::kInvalidSourceId);
ukm_source_id_ = document_ukm_source_id;
TransitionToClientPhase(ClientPhase::kResponseCommitted);
}
void ServiceWorkerContainerHost::OnEndNavigationCommit() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsContainerForWindowClient());
DCHECK(!navigation_commit_ended_);
navigation_commit_ended_ = true;
if (controller_) {
controller_->OnControlleeNavigationCommitted(client_uuid_,
GetRenderFrameHostId());
}
}
void ServiceWorkerContainerHost::CompleteWebWorkerPreparation(
const PolicyContainerPolicies& policy_container_policies,
ukm::SourceId worker_ukm_source_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsContainerForWorkerClient());
DCHECK(!policy_container_policies_);
policy_container_policies_ = policy_container_policies.Clone();
if (controller_ && controller_->fetch_handler_existence() ==
ServiceWorkerVersion::FetchHandlerExistence::EXISTS) {
DCHECK(pending_controller_receiver_);
// TODO(crbug.com/41478971): Plumb the COEP reporter.
controller_->controller()->Clone(
std::move(pending_controller_receiver_),
policy_container_policies_->cross_origin_embedder_policy,
mojo::NullRemote());
}
DCHECK_EQ(ukm_source_id_, ukm::kInvalidSourceId);
ukm_source_id_ = worker_ukm_source_id;
TransitionToClientPhase(ClientPhase::kResponseCommitted);
SetExecutionReady();
}
void ServiceWorkerContainerHostForServiceWorker::UpdateUrls(
const GURL& url,
const std::optional<url::Origin>& top_frame_origin,
const blink::StorageKey& storage_key) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!url.has_ref());
url_ = url;
top_frame_origin_ = top_frame_origin;
key_ = storage_key;
service_worker_security_utils::CheckOnUpdateUrls(url, key_);
}
void ServiceWorkerContainerHostForClient::UpdateUrls(
const GURL& url,
const std::optional<url::Origin>& top_frame_origin,
const blink::StorageKey& storage_key) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!url.has_ref());
GURL previous_url = url_;
url_ = url;
top_frame_origin_ = top_frame_origin;
key_ = storage_key;
service_worker_security_utils::CheckOnUpdateUrls(GetUrlForScopeMatch(), key_);
if (previous_url != url) {
// Revoke the token on URL change since any service worker holding the token
// may no longer be the potential controller of this frame and shouldn't
// have the power to display SSL dialogs for it.
if (IsContainerForWindowClient())
fetch_request_window_id_ = base::UnguessableToken::Create();
}
// Update client id on cross origin redirects. This corresponds to the HTML
// standard's "process a navigation fetch" algorithm's step for discarding
// |reservedEnvironment|.
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#process-a-navigate-fetch
// "If |reservedEnvironment| is not null and |currentURL|'s origin is not the
// same as |reservedEnvironment|'s creation URL's origin, then:
// 1. Run the environment discarding steps for |reservedEnvironment|.
// 2. Set |reservedEnvironment| to null."
if (previous_url.is_valid() && !url::IsSameOriginWith(previous_url, url)) {
// Remove old controller since we know the controller is definitely
// changed. We need to remove |this| from |controller_|'s controllee before
// updating UUID since ServiceWorkerVersion has a map from uuid to provider
// hosts.
SetControllerRegistration(nullptr, false /* notify_controllerchange */);
// Set UUID to the new one.
std::string previous_client_uuid = client_uuid_;
client_uuid_ = base::Uuid::GenerateRandomV4().AsLowercaseString();
if (context_)
context_->UpdateContainerHostClientID(previous_client_uuid, client_uuid_);
}
SyncMatchingRegistrations();
}
void ServiceWorkerContainerHost::SetControllerRegistration(
scoped_refptr<ServiceWorkerRegistration> controller_registration,
bool notify_controllerchange) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsContainerForClient());
if (controller_registration) {
CHECK(IsEligibleForServiceWorkerController());
DCHECK(controller_registration->active_version());
#if DCHECK_IS_ON()
DCHECK(IsMatchingRegistration(controller_registration.get()));
#endif // DCHECK_IS_ON()
}
controller_registration_ = controller_registration;
UpdateController(notify_controllerchange);
}
mojo::Remote<blink::mojom::ControllerServiceWorker>
ServiceWorkerContainerHost::GetRemoteControllerServiceWorker() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsContainerForClient());
DCHECK(controller_);
if (controller_->fetch_handler_existence() ==
ServiceWorkerVersion::FetchHandlerExistence::DOES_NOT_EXIST) {
return mojo::Remote<blink::mojom::ControllerServiceWorker>();
}
mojo::Remote<blink::mojom::ControllerServiceWorker> remote_controller;
if (!is_response_committed()) {
// The receiver will be connected to the controller in
// OnBeginNavigationCommit() or CompleteWebWorkerPreparation(). The pair of
// Mojo endpoints is created on each main resource response including
// redirect. The final Mojo endpoint which is corresponding to the OK
// response will be sent to the service worker.
pending_controller_receiver_ =
remote_controller.BindNewPipeAndPassReceiver();
} else {
mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter>
coep_reporter_to_be_passed;
if (coep_reporter_) {
DCHECK(IsContainerForWindowClient());
coep_reporter_->Clone(
coep_reporter_to_be_passed.InitWithNewPipeAndPassReceiver());
} else {
// TODO(crbug.com/41478971): Implement DedicatedWorker and
// SharedWorker cases.
DCHECK(IsContainerForWorkerClient());
}
controller_->controller()->Clone(
remote_controller.BindNewPipeAndPassReceiver(),
policy_container_policies_->cross_origin_embedder_policy,
std::move(coep_reporter_to_be_passed));
}
return remote_controller;
}
bool ServiceWorkerContainerHostForClient::AllowServiceWorker(
const GURL& scope,
const GURL& script_url) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(context_);
auto* browser_context = context_->wrapper()->browser_context();
// Check that the browser context is not nullptr. It becomes nullptr
// when the service worker process manager is being shutdown.
if (!browser_context) {
return false;
}
AllowServiceWorkerResult allowed =
GetContentClient()->browser()->AllowServiceWorker(
scope, service_worker_security_utils::site_for_cookies(key_),
top_frame_origin(), script_url, browser_context);
if (IsContainerForWindowClient()) {
auto* rfh = RenderFrameHostImpl::FromID(GetRenderFrameHostId());
auto* web_contents =
static_cast<WebContentsImpl*>(WebContents::FromRenderFrameHost(rfh));
if (web_contents)
web_contents->OnServiceWorkerAccessed(rfh, scope, allowed);
}
return allowed;
}
bool ServiceWorkerContainerHostForServiceWorker::AllowServiceWorker(
const GURL& scope,
const GURL& script_url) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(context_);
auto* browser_context = context_->wrapper()->browser_context();
// Check that the browser context is not nullptr. It becomes nullptr
// when the service worker process manager is being shutdown.
if (!browser_context) {
return false;
}
return GetContentClient()->browser()->AllowServiceWorker(
scope, service_worker_security_utils::site_for_cookies(key_),
top_frame_origin(), script_url, browser_context);
}
bool ServiceWorkerContainerHost::IsEligibleForServiceWorkerController() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsContainerForClient());
if (!url_.is_valid())
return false;
// Pass GetUrlForScopeMatch() instead of `url_` because we cannot take the
// origin of `url_` when it's a blob URL (see https://crbug.com/1144717). It's
// guaranteed that the URL returned by GetURLForScopeMatch() has the same
// logical origin as `url_`.
// TODO(asamidoi): Add url::Origin member for ServiceWorkerContainerHost and
// use it as the argument of OriginCanAccessServiceWorkers().
if (!OriginCanAccessServiceWorkers(GetUrlForScopeMatch()))
return false;
if (is_parent_frame_secure_)
return true;
std::set<std::string> schemes;
GetContentClient()->browser()->GetSchemesBypassingSecureContextCheckAllowlist(
&schemes);
return schemes.find(url_.scheme()) != schemes.end();
}
bool ServiceWorkerContainerHost::is_response_committed() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsContainerForClient());
switch (client_phase_) {
case ClientPhase::kInitial:
return false;
case ClientPhase::kResponseCommitted:
case ClientPhase::kExecutionReady:
return true;
}
NOTREACHED();
return false;
}
void ServiceWorkerContainerHost::AddExecutionReadyCallback(
ExecutionReadyCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsContainerForClient());
DCHECK(!is_execution_ready());
execution_ready_callbacks_.push_back(std::move(callback));
}
bool ServiceWorkerContainerHost::is_execution_ready() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsContainerForClient());
return client_phase_ == ClientPhase::kExecutionReady;
}
GlobalRenderFrameHostId ServiceWorkerContainerHost::GetRenderFrameHostId()
const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsContainerForWindowClient());
return absl::get<GlobalRenderFrameHostId>(*client_info_);
}
int ServiceWorkerContainerHost::GetProcessId() const {
if (IsContainerForWindowClient()) {
return GetRenderFrameHostId().child_id;
}
DCHECK(IsContainerForWorkerClient());
return process_id_for_worker_client_;
}
NavigationRequest*
ServiceWorkerContainerHost::GetOngoingNavigationRequestBeforeCommit(
base::PassKey<StoragePartitionImpl>) const {
DCHECK(IsContainerForWindowClient());
DCHECK_NE(ongoing_navigation_frame_tree_node_id_,
RenderFrameHost::kNoFrameTreeNodeId);
DCHECK(!GetRenderFrameHostId());
// It is safe to use `ongoing_navigation_frame_tree_node_id_` to obtain the
// corresponding navigation request without being concerned about the case
// that a new navigation had started and the old navigation had been deleted,
// because the owner of this instance will reset the key that can be used to
// retrieve this instance, which makes the old key stale and cannot locate
// this instance. This mechanism guarantees that this instance would always be
// associated with the latest navigation.
// However, this design requires callers to carefully get the
// `ServiceWorkerContainerHost` instance from scratch instead of using a
// stored one, and it would be better to optimize the design when possible.
FrameTreeNode* frame_tree_node =
FrameTreeNode::GloballyFindByID(ongoing_navigation_frame_tree_node_id_);
return frame_tree_node ? frame_tree_node->navigation_request() : nullptr;
}
const std::string& ServiceWorkerContainerHost::client_uuid() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsContainerForClient());
return client_uuid_;
}
blink::mojom::ControllerServiceWorkerMode
ServiceWorkerContainerHost::GetControllerMode() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsContainerForClient());
if (!controller_)
return blink::mojom::ControllerServiceWorkerMode::kNoController;
switch (controller_->fetch_handler_existence()) {
case ServiceWorkerVersion::FetchHandlerExistence::DOES_NOT_EXIST:
return blink::mojom::ControllerServiceWorkerMode::kNoFetchEventHandler;
case ServiceWorkerVersion::FetchHandlerExistence::EXISTS:
return blink::mojom::ControllerServiceWorkerMode::kControlled;
case ServiceWorkerVersion::FetchHandlerExistence::UNKNOWN:
// UNKNOWN means the controller is still installing. It's not possible to
// have a controller that hasn't finished installing.
NOTREACHED();
}
NOTREACHED();
return blink::mojom::ControllerServiceWorkerMode::kNoController;
}
ServiceWorkerVersion* ServiceWorkerContainerHost::controller() const {
#if DCHECK_IS_ON()
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CheckControllerConsistency(false);
#endif // DCHECK_IS_ON()
return controller_.get();
}
ServiceWorkerRegistration* ServiceWorkerContainerHost::controller_registration()
const {
#if DCHECK_IS_ON()
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CheckControllerConsistency(false);
#endif // DCHECK_IS_ON()
return controller_registration_.get();
}
ServiceWorkerHost*
ServiceWorkerContainerHostForServiceWorker::service_worker_host() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return service_worker_host_;
}
bool ServiceWorkerContainerHost::IsInBackForwardCache() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return is_in_back_forward_cache_;
}
void ServiceWorkerContainerHost::EvictFromBackForwardCache(
BackForwardCacheMetrics::NotRestoredReason reason) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsBackForwardCacheEnabled());
DCHECK(IsContainerForClient());
is_in_back_forward_cache_ = false;
if (!IsContainerForWindowClient())
return;
auto* rfh = RenderFrameHostImpl::FromID(GetRenderFrameHostId());
// |rfh| could be evicted before this function is called.
if (rfh && rfh->IsInBackForwardCache())
rfh->EvictFromBackForwardCacheWithReason(reason);
}
void ServiceWorkerContainerHost::OnEnterBackForwardCache() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsBackForwardCacheEnabled());
DCHECK(IsContainerForClient());
if (controller_)
controller_->MoveControlleeToBackForwardCacheMap(client_uuid());
is_in_back_forward_cache_ = true;
}
void ServiceWorkerContainerHost::OnRestoreFromBackForwardCache() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsBackForwardCacheEnabled());
DCHECK(IsContainerForClient());
if (controller_)
controller_->RestoreControlleeFromBackForwardCacheMap(client_uuid());
is_in_back_forward_cache_ = false;
}
void ServiceWorkerContainerHost::SyncMatchingRegistrations() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!controller_registration_);
RemoveAllMatchingRegistrations();
if (!context_)
return;
const auto& registrations = context_->GetLiveRegistrations();
for (const auto& key_registration : registrations) {
ServiceWorkerRegistration* registration = key_registration.second;
if (!registration->is_uninstalled() && registration->key() == key() &&
blink::ServiceWorkerScopeMatches(registration->scope(),
GetUrlForScopeMatch())) {
AddMatchingRegistration(registration);
}
}
}
#if DCHECK_IS_ON()
bool ServiceWorkerContainerHost::IsMatchingRegistration(
ServiceWorkerRegistration* registration) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::string spec = registration->scope().spec();
size_t key = spec.size();
auto iter = matching_registrations_.find(key);
if (iter == matching_registrations_.end())
return false;
if (iter->second.get() != registration)
return false;
return true;
}
#endif // DCHECK_IS_ON()
void ServiceWorkerContainerHost::RemoveAllMatchingRegistrations() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!controller_registration_);
for (const auto& it : matching_registrations_) {
ServiceWorkerRegistration* registration = it.second.get();
registration->RemoveListener(this);
}
matching_registrations_.clear();
}
void ServiceWorkerContainerHost::ReturnRegistrationForReadyIfNeeded() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!get_ready_callback_ || get_ready_callback_->is_null())
return;
ServiceWorkerRegistration* registration = MatchRegistration();
if (!registration || !registration->active_version())
return;
TRACE_EVENT_NESTABLE_ASYNC_END1(
"ServiceWorker", "ServiceWorkerContainerHost::GetRegistrationForReady",
TRACE_ID_LOCAL(this), "Registration ID", registration->id());
if (!context_) {
// Here no need to run or destroy |get_ready_callback_|, which will destroy
// together with |receiver_| when |this| destroys.
return;
}
std::move(*get_ready_callback_)
.Run(registration_object_manager().CreateInfo(
scoped_refptr<ServiceWorkerRegistration>(registration)));
}
void ServiceWorkerContainerHost::SetExecutionReady() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!is_execution_ready());
TransitionToClientPhase(ClientPhase::kExecutionReady);
RunExecutionReadyCallbacks();
if (context_)
context_->NotifyClientIsExecutionReady(*this);
FlushFeatures();
}
void ServiceWorkerContainerHost::RunExecutionReadyCallbacks() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsContainerForClient());
std::vector<ExecutionReadyCallback> callbacks;
execution_ready_callbacks_.swap(callbacks);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&RunCallbacks, std::move(callbacks)));
}
void ServiceWorkerContainerHost::TransitionToClientPhase(
ClientPhase new_phase) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (client_phase_ == new_phase)
return;
switch (client_phase_) {
case ClientPhase::kInitial:
DCHECK_EQ(new_phase, ClientPhase::kResponseCommitted);
break;
case ClientPhase::kResponseCommitted:
DCHECK_EQ(new_phase, ClientPhase::kExecutionReady);
break;
case ClientPhase::kExecutionReady:
NOTREACHED();
break;
}
client_phase_ = new_phase;
}
void ServiceWorkerContainerHost::UpdateController(
bool notify_controllerchange) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ServiceWorkerVersion* version =
controller_registration_ ? controller_registration_->active_version()
: nullptr;
CHECK(!version || IsEligibleForServiceWorkerController());
if (version == controller_.get())
return;
scoped_refptr<ServiceWorkerVersion> previous_version = controller_;
controller_ = version;
if (version) {
// TODO(crbug.com/330928087): remove check when this issue resolved.
SCOPED_CRASH_KEY_NUMBER("SWV_RCFBCM", "client_type",
static_cast<int32_t>(GetClientType()));
SCOPED_CRASH_KEY_BOOL("SWV_RCFBCM", "is_execution_ready",
is_execution_ready());
SCOPED_CRASH_KEY_BOOL("SWV_RCFBCM", "is_blob_url",
url() != GetUrlForScopeMatch());
SCOPED_CRASH_KEY_BOOL("SWV_RCFBCM", "is_inherited", is_inherited());
CHECK(!version->BFCacheContainsControllee(client_uuid()));
version->AddControllee(this);
if (IsBackForwardCacheEnabled() && IsInBackForwardCache()) {
// |this| was not |version|'s controllee when |OnEnterBackForwardCache|
// was called.
version->MoveControlleeToBackForwardCacheMap(client_uuid());
}
if (running_status_observer_) {
version->AddObserver(running_status_observer_.get());
running_status_observer_->Notify(version->running_status());
}
}
if (previous_version) {
previous_version->Uncontrol(client_uuid());
if (running_status_observer_) {
previous_version->RemoveObserver(running_status_observer_.get());
}
}
// SetController message should be sent only for clients.
DCHECK(IsContainerForClient());
// No need to `SetController` if the container is not ready because
// when the container gets ready, `ControllerServiceWorkerInfoPtr` is also
// sent in the same IPC call. Moreover, it is harmful to resend the past
// SetController to the renderer because it moves the controller in the
// renderer to the past one.
if (!is_container_ready_) {
return;
}
if (!is_execution_ready()) {
return;
}
SendSetControllerServiceWorker(notify_controllerchange);
}
#if DCHECK_IS_ON()
void ServiceWorkerContainerHost::CheckControllerConsistency(
bool should_crash) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!controller_) {
DCHECK(!controller_registration_);
return;
}
DCHECK(IsContainerForClient());
DCHECK(controller_registration_);
DCHECK_EQ(controller_->registration_id(), controller_registration_->id());
switch (controller_->status()) {
case ServiceWorkerVersion::NEW:
case ServiceWorkerVersion::INSTALLING:
case ServiceWorkerVersion::INSTALLED:
if (should_crash) {
ServiceWorkerVersion::Status status = controller_->status();
base::debug::Alias(&status);
CHECK(false) << "Controller service worker has a bad status: "
<< ServiceWorkerVersion::VersionStatusToString(status);
}
break;
case ServiceWorkerVersion::REDUNDANT: {
if (should_crash) {
CHECK(false);
}
break;
}
case ServiceWorkerVersion::ACTIVATING:
case ServiceWorkerVersion::ACTIVATED:
// Valid status, controller is being activated.
break;
}
}
#endif // DCHECK_IS_ON()
void ServiceWorkerContainerHostForClient::StartControllerComplete(
mojo::PendingReceiver<blink::mojom::ControllerServiceWorker> receiver,
blink::ServiceWorkerStatusCode status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsContainerForClient());
if (status == blink::ServiceWorkerStatusCode::kOk) {
DCHECK(is_response_committed());
mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter>
coep_reporter_to_be_passed;
if (coep_reporter_) {
DCHECK(IsContainerForWindowClient());
coep_reporter_->Clone(
coep_reporter_to_be_passed.InitWithNewPipeAndPassReceiver());
} else {
// TODO(crbug.com/41478971): Implement DedicatedWorker and
// SharedWorker cases.
DCHECK(IsContainerForWorkerClient());
}
controller_->controller()->Clone(
std::move(receiver),
policy_container_policies_->cross_origin_embedder_policy,
std::move(coep_reporter_to_be_passed));
}
}
void ServiceWorkerContainerHostForClient::RegistrationComplete(
const GURL& script_url,
const GURL& scope,
RegisterCallback callback,
int64_t trace_id,
mojo::ReportBadMessageCallback bad_message_callback,
blink::ServiceWorkerStatusCode status,
const std::string& status_message,
int64_t registration_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
TRACE_EVENT_NESTABLE_ASYNC_END2(
"ServiceWorker", "ServiceWorkerContainerHost::Register",
TRACE_ID_WITH_SCOPE("ServiceWorkerContainerHost::Register", trace_id),
"Status", blink::ServiceWorkerStatusToString(status), "Registration ID",
registration_id);
// kErrorInvalidArguments means the renderer gave unexpectedly bad arguments,
// so terminate it.
if (status == blink::ServiceWorkerStatusCode::kErrorInvalidArguments) {
std::move(bad_message_callback).Run(status_message);
// |bad_message_callback| will kill the renderer process, but Mojo complains
// if the callback is not run. Just run it with nonsense arguments.
std::move(callback).Run(blink::mojom::ServiceWorkerErrorType::kUnknown,
std::string(), nullptr);
return;
}
if (!context_) {
std::move(callback).Run(
blink::mojom::ServiceWorkerErrorType::kAbort,
base::StringPrintf(
ServiceWorkerConsts::kServiceWorkerRegisterErrorPrefix,
scope.spec().c_str(), script_url.spec().c_str()) +
std::string(ServiceWorkerConsts::kShutdownErrorMessage),
nullptr);
return;
}
if (status != blink::ServiceWorkerStatusCode::kOk) {
std::string error_message;
blink::mojom::ServiceWorkerErrorType error_type;
GetServiceWorkerErrorTypeForRegistration(status, status_message,
&error_type, &error_message);
std::move(callback).Run(
error_type,
base::StringPrintf(
ServiceWorkerConsts::kServiceWorkerRegisterErrorPrefix,
scope.spec().c_str(), script_url.spec().c_str()) +
error_message,
nullptr);
return;
}
scoped_refptr<ServiceWorkerRegistration> registration =
context_->GetLiveRegistration(registration_id);
// ServiceWorkerRegisterJob calls its completion callback, which results in
// this function being called, while the registration is live.
DCHECK(registration);
std::move(callback).Run(
blink::mojom::ServiceWorkerErrorType::kNone, std::nullopt,
registration_object_manager().CreateInfo(
scoped_refptr<ServiceWorkerRegistration>(registration)));
}
void ServiceWorkerContainerHostForClient::GetRegistrationComplete(
GetRegistrationCallback callback,
int64_t trace_id,
blink::ServiceWorkerStatusCode status,
scoped_refptr<ServiceWorkerRegistration> registration) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
TRACE_EVENT_NESTABLE_ASYNC_END2(
"ServiceWorker", "ServiceWorkerContainerHost::GetRegistration",
TRACE_ID_WITH_SCOPE("ServiceWorkerContainerHost::GetRegistration",
trace_id),
"Status", blink::ServiceWorkerStatusToString(status), "Registration ID",
registration ? registration->id()
: blink::mojom::kInvalidServiceWorkerRegistrationId);
if (!context_) {
std::move(callback).Run(
blink::mojom::ServiceWorkerErrorType::kAbort,
std::string(
ServiceWorkerConsts::kServiceWorkerGetRegistrationErrorPrefix) +
std::string(ServiceWorkerConsts::kShutdownErrorMessage),
nullptr);
return;
}
if (status != blink::ServiceWorkerStatusCode::kOk &&
status != blink::ServiceWorkerStatusCode::kErrorNotFound) {
std::string error_message;
blink::mojom::ServiceWorkerErrorType error_type;
GetServiceWorkerErrorTypeForRegistration(status, std::string(), &error_type,
&error_message);
std::move(callback).Run(
error_type,
ServiceWorkerConsts::kServiceWorkerGetRegistrationErrorPrefix +
error_message,
nullptr);
return;
}
DCHECK(status != blink::ServiceWorkerStatusCode::kOk || registration);
blink::mojom::ServiceWorkerRegistrationObjectInfoPtr info;
if (status == blink::ServiceWorkerStatusCode::kOk &&
!registration->is_uninstalling()) {
info = registration_object_manager().CreateInfo(std::move(registration));
}
std::move(callback).Run(blink::mojom::ServiceWorkerErrorType::kNone,
std::nullopt, std::move(info));
}
void ServiceWorkerContainerHostForClient::GetRegistrationsComplete(
GetRegistrationsCallback callback,
int64_t trace_id,
blink::ServiceWorkerStatusCode status,
const std::vector<scoped_refptr<ServiceWorkerRegistration>>&
registrations) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
TRACE_EVENT_NESTABLE_ASYNC_END1(
"ServiceWorker", "ServiceWorkerContainerHost::GetRegistrations",
TRACE_ID_WITH_SCOPE("ServiceWorkerContainerHost::GetRegistrations",
trace_id),
"Status", blink::ServiceWorkerStatusToString(status));
if (!context_) {
std::move(callback).Run(
blink::mojom::ServiceWorkerErrorType::kAbort,
std::string(
ServiceWorkerConsts::kServiceWorkerGetRegistrationsErrorPrefix) +
std::string(ServiceWorkerConsts::kShutdownErrorMessage),
std::nullopt);
return;
}
if (status != blink::ServiceWorkerStatusCode::kOk) {
std::string error_message;
blink::mojom::ServiceWorkerErrorType error_type;
GetServiceWorkerErrorTypeForRegistration(status, std::string(), &error_type,
&error_message);
std::move(callback).Run(
error_type,
ServiceWorkerConsts::kServiceWorkerGetRegistrationsErrorPrefix +
error_message,
std::nullopt);
return;
}
std::vector<blink::mojom::ServiceWorkerRegistrationObjectInfoPtr>
object_infos;
for (const auto& registration : registrations) {
DCHECK(registration.get());
if (!registration->is_uninstalling()) {
object_infos.push_back(
registration_object_manager().CreateInfo(std::move(registration)));
}
}
// Sort by Insertion order. Detail discussion can be found in:
// https://github.com/w3c/ServiceWorker/issues/1465
std::sort(
object_infos.begin(), object_infos.end(),
[](const blink::mojom::ServiceWorkerRegistrationObjectInfoPtr& ptr1,
const blink::mojom::ServiceWorkerRegistrationObjectInfoPtr& ptr2) {
return ptr1->registration_id < ptr2->registration_id;
});
std::move(callback).Run(blink::mojom::ServiceWorkerErrorType::kNone,
std::nullopt, std::move(object_infos));
}
bool ServiceWorkerContainerHostForClient::IsValidGetRegistrationMessage(
const GURL& client_url,
std::string* out_error) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!IsContainerForWindowClient()) {
*out_error = ServiceWorkerConsts::kBadMessageFromNonWindow;
return false;
}
if (!client_url.is_valid()) {
*out_error = ServiceWorkerConsts::kBadMessageInvalidURL;
return false;
}
std::vector<GURL> urls = {url_, client_url};
if (!service_worker_security_utils::AllOriginsMatchAndCanAccessServiceWorkers(
urls)) {
*out_error = ServiceWorkerConsts::kBadMessageImproperOrigins;
return false;
}
return true;
}
bool ServiceWorkerContainerHostForClient::IsValidGetRegistrationsMessage(
std::string* out_error) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!IsContainerForWindowClient()) {
*out_error = ServiceWorkerConsts::kBadMessageFromNonWindow;
return false;
}
if (!OriginCanAccessServiceWorkers(url_)) {
*out_error = ServiceWorkerConsts::kBadMessageImproperOrigins;
return false;
}
return true;
}
bool ServiceWorkerContainerHostForClient::IsValidGetRegistrationForReadyMessage(
std::string* out_error) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!IsContainerForWindowClient()) {
*out_error = ServiceWorkerConsts::kBadMessageFromNonWindow;
return false;
}
if (get_ready_callback_) {
*out_error =
ServiceWorkerConsts::kBadMessageGetRegistrationForReadyDuplicated;
return false;
}
return true;
}
template <typename CallbackType, typename... Args>
bool ServiceWorkerContainerHostForClient::CanServeContainerHostMethods(
CallbackType* callback,
const GURL& scope,
const GURL& script_url,
const char* error_prefix,
Args... args) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!context_) {
std::move(*callback).Run(
blink::mojom::ServiceWorkerErrorType::kAbort,
std::string(error_prefix) +
std::string(ServiceWorkerConsts::kShutdownErrorMessage),
args...);
return false;
}
// TODO(falken): This check can be removed once crbug.com/439697 is fixed.
// (Also see crbug.com/776408)
if (url_.is_empty()) {
std::move(*callback).Run(
blink::mojom::ServiceWorkerErrorType::kSecurity,
std::string(error_prefix) +
std::string(ServiceWorkerConsts::kNoDocumentURLErrorMessage),
args...);
return false;
}
if (!AllowServiceWorker(scope, script_url)) {
std::move(*callback).Run(
blink::mojom::ServiceWorkerErrorType::kDisabled,
std::string(error_prefix) +
std::string(ServiceWorkerConsts::kUserDeniedPermissionMessage),
args...);
return false;
}
return true;
}
const GURL& ServiceWorkerContainerHost::GetUrlForScopeMatch() const {
DCHECK(IsContainerForClient());
if (!scope_match_url_for_blob_client_.is_empty())
return scope_match_url_for_blob_client_;
return url_;
}
void ServiceWorkerContainerHost::InheritControllerFrom(
ServiceWorkerContainerHost& creator_host,
const GURL& blob_url) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsContainerForClient());
DCHECK(base::FeatureList::IsEnabled(kSharedWorkerBlobURLFix) ||
blink::mojom::ServiceWorkerClientType::kDedicatedWorker ==
GetClientType());
DCHECK(blob_url.SchemeIsBlob());
UpdateUrls(blob_url, creator_host.top_frame_origin(), creator_host.key());
// Let `scope_match_url_for_blob_client_` be the creator's url for scope match
// because a client should be handled by the service worker of its creator.
scope_match_url_for_blob_client_ = creator_host.GetUrlForScopeMatch();
// Inherit the controller of the creator.
if (creator_host.controller_registration()) {
AddMatchingRegistration(creator_host.controller_registration());
SetControllerRegistration(creator_host.controller_registration(),
false /* notify_controllerchange */);
}
creator_host.SetInherited();
}
mojo::PendingRemote<blink::mojom::CacheStorage>
ServiceWorkerContainerHost::GetRemoteCacheStorage() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsContainerForClient());
DCHECK(controller_);
auto* control = GetCacheStorageControl(controller_);
if (!control) {
return mojo::NullRemote();
}
// Since this is offloading the cache storage API access in ServiceWorker,
// we need to follow COEP used there.
// The reason why COEP is enforced to the cache storage API can be seen in:
// crbug.com/991428.
const network::CrossOriginEmbedderPolicy* coep =
controller_->cross_origin_embedder_policy();
if (!coep) {
return mojo::NullRemote();
}
mojo::PendingRemote<blink::mojom::CacheStorage> remote;
control->AddReceiver(
*coep, controller_->embedded_worker()->GetCoepReporter(),
storage::BucketLocator::ForDefaultBucket(controller_->key()),
storage::mojom::CacheStorageOwner::kCacheAPI,
remote.InitWithNewPipeAndPassReceiver());
return remote;
}
mojo::PendingReceiver<blink::mojom::ServiceWorkerRunningStatusCallback>
ServiceWorkerContainerHost::GetRunningStatusCallbackReceiver() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(controller_);
if (!running_status_observer_) {
running_status_observer_ = absl::make_unique<
ServiceWorkerContainerHost::ServiceWorkerRunningStatusObserver>();
controller_->AddObserver(running_status_observer_.get());
}
mojo::PendingRemote<blink::mojom::ServiceWorkerRunningStatusCallback>
remote_callback;
auto receiver = remote_callback.InitWithNewPipeAndPassReceiver();
running_status_observer_->AddCallback(std::move(remote_callback));
return receiver;
}
SubresourceLoaderParams
ServiceWorkerContainerHost::MaybeCreateSubresourceLoaderParams(
base::WeakPtr<ServiceWorkerContainerHost> container_host) {
// We didn't find a matching service worker for this request, and
// ServiceWorkerContainerHost::SetControllerRegistration() was not called.
if (!container_host || !container_host->controller()) {
return {};
}
// Otherwise let's send the controller service worker information along
// with the navigation commit.
SubresourceLoaderParams params;
params.controller_service_worker_info =
container_host->CreateControllerServiceWorkerInfo();
if (base::WeakPtr<ServiceWorkerObjectHost> object_host =
container_host->version_object_manager().GetOrCreateHost(
container_host->controller())) {
params.controller_service_worker_object_host = object_host;
params.controller_service_worker_info->object_info =
object_host->CreateIncompleteObjectInfo();
}
params.container_host = container_host;
return params;
}
void ServiceWorkerContainerHost::SetContainerReady() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
is_container_ready_ = true;
std::vector<std::tuple<base::WeakPtr<ServiceWorkerObjectHost>,
blink::TransferableMessage>>
messages;
messages.swap(buffered_messages_);
base::UmaHistogramCounts1000("ServiceWorker.PostMessage.QueueSize",
messages.size());
for (auto& [object_host, message] : messages) {
blink::mojom::ServiceWorkerObjectInfoPtr info;
if (object_host) {
info = object_host->CreateCompleteObjectInfoToSend();
}
container_->PostMessageToClient(std::move(info), std::move(message));
}
CHECK(buffered_messages_.empty());
FlushFeatures();
}
void ServiceWorkerContainerHost::FlushFeatures() {
std::set<blink::mojom::WebFeature> features;
features.swap(buffered_used_features_);
for (const auto& feature : features) {
CountFeature(feature);
}
}
namespace {
using StatusCallback = base::OnceCallback<void(blink::ServiceWorkerStatusCode)>;
using PrepareExtendableMessageEventCallback =
base::OnceCallback<bool(blink::mojom::ExtendableMessageEventPtr*)>;
void DispatchExtendableMessageEventAfterStartWorker(
scoped_refptr<ServiceWorkerVersion> worker,
blink::TransferableMessage message,
const url::Origin& source_origin,
const std::optional<base::TimeDelta>& timeout,
StatusCallback callback,
PrepareExtendableMessageEventCallback prepare_callback,
blink::ServiceWorkerStatusCode start_worker_status) {
if (start_worker_status != blink::ServiceWorkerStatusCode::kOk) {
std::move(callback).Run(start_worker_status);
return;
}
blink::mojom::ExtendableMessageEventPtr event =
blink::mojom::ExtendableMessageEvent::New();
event->message = std::move(message);
event->source_origin = source_origin;
if (!std::move(prepare_callback).Run(&event)) {
std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorFailed);
return;
}
int request_id;
if (timeout) {
request_id = worker->StartRequestWithCustomTimeout(
ServiceWorkerMetrics::EventType::MESSAGE, std::move(callback), *timeout,
ServiceWorkerVersion::CONTINUE_ON_TIMEOUT);
} else {
request_id = worker->StartRequest(ServiceWorkerMetrics::EventType::MESSAGE,
std::move(callback));
}
worker->endpoint()->DispatchExtendableMessageEvent(
std::move(event), worker->CreateSimpleEventCallback(request_id));
}
void StartWorkerToDispatchExtendableMessageEvent(
scoped_refptr<ServiceWorkerVersion> worker,
blink::TransferableMessage message,
const url::Origin& source_origin,
const std::optional<base::TimeDelta>& timeout,
StatusCallback callback,
PrepareExtendableMessageEventCallback prepare_callback) {
// If not enough time is left to actually process the event don't even
// bother starting the worker and sending the event.
if (timeout && *timeout < base::Milliseconds(100)) {
std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorTimeout);
return;
}
// Abort if redundant. This is not strictly needed since RunAfterStartWorker
// does the same, but avoids logging UMA about failed startups.
if (worker->is_redundant()) {
std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorRedundant);
return;
}
// As we don't track tasks between workers and renderers, we can nullify the
// message's parent task ID.
message.parent_task_id = std::nullopt;
worker->RunAfterStartWorker(
ServiceWorkerMetrics::EventType::MESSAGE,
base::BindOnce(&DispatchExtendableMessageEventAfterStartWorker, worker,
std::move(message), source_origin, timeout,
std::move(callback), std::move(prepare_callback)));
}
bool PrepareExtendableMessageEventFromClient(
base::WeakPtr<ServiceWorkerContextCore> context,
int64_t registration_id,
blink::mojom::ServiceWorkerClientInfoPtr source_client_info,
blink::mojom::ExtendableMessageEventPtr* event) {
if (!context) {
return false;
}
DCHECK(source_client_info && !source_client_info->client_uuid.empty());
(*event)->source_info_for_client = std::move(source_client_info);
// Hide the client url if the client has a unique origin.
if ((*event)->source_origin.opaque()) {
(*event)->source_info_for_client->url = GURL();
}
// Reset |registration->self_update_delay| iff postMessage is coming from a
// client, to prevent workers from postMessage to another version to reset
// the delay (https://crbug.com/805496).
scoped_refptr<ServiceWorkerRegistration> registration =
context->GetLiveRegistration(registration_id);
DCHECK(registration) << "running workers should have a live registration";
registration->set_self_update_delay(base::TimeDelta());
return true;
}
// The output |event| must be sent over Mojo immediately after this function
// returns. See ServiceWorkerObjectHost::CreateCompleteObjectInfoToSend() for
// details.
bool PrepareExtendableMessageEventFromServiceWorker(
scoped_refptr<ServiceWorkerVersion> worker,
base::WeakPtr<ServiceWorkerContainerHostForServiceWorker>
source_container_host,
blink::mojom::ExtendableMessageEventPtr* event) {
// The service worker execution context may have been destroyed by the time we
// get here.
if (!source_container_host) {
return false;
}
blink::mojom::ServiceWorkerObjectInfoPtr source_worker_info;
base::WeakPtr<ServiceWorkerObjectHost> service_worker_object_host =
worker->worker_host()
->container_host()
->version_object_manager()
.GetOrCreateHost(
source_container_host->service_worker_host()->version());
if (service_worker_object_host) {
// CreateCompleteObjectInfoToSend() is safe because |source_worker_info|
// will be sent immediately by the caller of this function.
source_worker_info =
service_worker_object_host->CreateCompleteObjectInfoToSend();
}
(*event)->source_info_for_service_worker = std::move(source_worker_info);
// Hide the service worker url if the service worker has a unique origin.
if ((*event)->source_origin.opaque()) {
(*event)->source_info_for_service_worker->url = GURL();
}
return true;
}
void DispatchExtendableMessageEventFromClient(
base::WeakPtr<ServiceWorkerContextCore> context,
scoped_refptr<ServiceWorkerVersion> worker,
blink::TransferableMessage message,
const url::Origin& source_origin,
StatusCallback callback,
blink::mojom::ServiceWorkerClientInfoPtr source_client_info) {
if (!context) {
std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorAbort);
return;
}
// |source_client_info| may be null if a client sent the message but its
// info could not be retrieved.
if (!source_client_info) {
std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorFailed);
return;
}
StartWorkerToDispatchExtendableMessageEvent(
worker, std::move(message), source_origin, std::nullopt /* timeout */,
std::move(callback),
base::BindOnce(&PrepareExtendableMessageEventFromClient, context,
worker->registration_id(), std::move(source_client_info)));
}
void DispatchExtendableMessageEventFromServiceWorker(
scoped_refptr<ServiceWorkerVersion> worker,
blink::TransferableMessage message,
const url::Origin& source_origin,
const std::optional<base::TimeDelta>& timeout,
StatusCallback callback,
base::WeakPtr<ServiceWorkerContainerHostForServiceWorker>
source_container_host) {
if (!source_container_host) {
std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorFailed);
return;
}
StartWorkerToDispatchExtendableMessageEvent(
worker, std::move(message), source_origin, timeout, std::move(callback),
base::BindOnce(&PrepareExtendableMessageEventFromServiceWorker, worker,
std::move(source_container_host)));
}
} // namespace
void ServiceWorkerContainerHostForServiceWorker::DispatchExtendableMessageEvent(
scoped_refptr<ServiceWorkerVersion> version,
::blink::TransferableMessage message,
StatusCallback callback) {
// Clamp timeout to the sending worker's remaining timeout, to prevent
// postMessage from keeping workers alive forever.
base::TimeDelta timeout =
service_worker_host()->version()->remaining_timeout();
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&DispatchExtendableMessageEventFromServiceWorker,
std::move(version), std::move(message),
url::Origin::Create(url()), std::make_optional(timeout),
std::move(callback), base::AsWeakPtr(this)));
}
void ServiceWorkerContainerHostForClient::DispatchExtendableMessageEvent(
scoped_refptr<ServiceWorkerVersion> version,
::blink::TransferableMessage message,
StatusCallback callback) {
if (IsContainerForWindowClient()) {
service_worker_client_utils::GetClient(
this, base::BindOnce(&DispatchExtendableMessageEventFromClient,
context(), std::move(version), std::move(message),
url::Origin::Create(url()), std::move(callback)));
} else {
DCHECK(IsContainerForWorkerClient());
// Web workers don't yet have access to ServiceWorker objects, so they
// can't postMessage to one (https://crbug.com/371690).
NOTREACHED();
}
}
void ServiceWorkerContainerHostForClient::Update(
scoped_refptr<ServiceWorkerRegistration> registration,
blink::mojom::FetchClientSettingsObjectPtr
outside_fetch_client_settings_object,
blink::mojom::ServiceWorkerRegistrationObjectHost::UpdateCallback
callback) {
// Don't delay update() if called by non-ServiceWorkers.
registration->ExecuteUpdate(std::move(outside_fetch_client_settings_object),
std::move(callback));
}
void ServiceWorkerContainerHostForServiceWorker::Update(
scoped_refptr<ServiceWorkerRegistration> registration,
blink::mojom::FetchClientSettingsObjectPtr
outside_fetch_client_settings_object,
blink::mojom::ServiceWorkerRegistrationObjectHost::UpdateCallback
callback) {
ServiceWorkerVersion* version = service_worker_host()->version();
DCHECK(version);
registration->DelayUpdate(*version,
std::move(outside_fetch_client_settings_object),
std::move(callback));
}
// If a blob URL is used for a SharedWorker script's URL, a controller will be
// inherited.
BASE_FEATURE(kSharedWorkerBlobURLFix,
"SharedWorkerBlobURLFix",
base::FEATURE_DISABLED_BY_DEFAULT);
} // namespace content