blob: 36b7aa04bcbc43e06419812139d31868d356b73a [file] [log] [blame]
// Copyright 2020 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_registry.h"
#include <type_traits>
#include <utility>
#include "base/check_is_test.h"
#include "base/containers/contains.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "components/services/storage/public/cpp/buckets/bucket_info.h"
#include "components/services/storage/public/cpp/buckets/constants.h"
#include "components/services/storage/public/cpp/quota_error_or.h"
#include "components/services/storage/public/mojom/storage_policy_update.mojom.h"
#include "components/services/storage/service_worker/service_worker_storage.h"
#include "components/services/storage/service_worker/service_worker_storage_control_impl.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_info.h"
#include "content/browser/service_worker/service_worker_loader_helpers.h"
#include "content/browser/service_worker/service_worker_registration.h"
#include "content/browser/service_worker/service_worker_version.h"
#include "content/common/service_worker/service_worker_router_evaluator.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/content_features.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "storage/browser/quota/quota_manager_proxy.h"
#include "storage/browser/quota/special_storage_policy.h"
#include "third_party/blink/public/common/service_worker/service_worker_scope_match.h"
#include "third_party/blink/public/common/service_worker/service_worker_status_code.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 "url/origin.h"
namespace content {
namespace {
// Another switch for `kServiceWorkerBackgroundUpdateForRegisteredStorageKeys`
// intended to be controlled from Field Trial (e.g. kill-switch). The original
// flag may be overridden by `AwFieldTrials::RegisterFeatureOverrides`.
BASE_FEATURE(ServiceWorkerBackgroundUpdateForRegisteredStorageKeysFieldTrialControlled,
base::FEATURE_ENABLED_BY_DEFAULT);
BASE_FEATURE(ServiceWorkerBackgroundUpdateForServiceWorkerScopeCache,
base::FEATURE_DISABLED_BY_DEFAULT);
BASE_FEATURE(ServiceWorkerBackgroundUpdateForFindRegistrationForClientUrl,
base::FEATURE_DISABLED_BY_DEFAULT);
BASE_FEATURE(ReduceCallingServiceWorkerRegisteredStorageKeysOnStartup,
base::FEATURE_ENABLED_BY_DEFAULT);
bool ReduceCallingServiceWorkerRegisteredStorageKeysOnStartupEnabled() {
static const bool enabled = base::FeatureList::IsEnabled(
kReduceCallingServiceWorkerRegisteredStorageKeysOnStartup);
return enabled;
}
blink::ServiceWorkerStatusCode DatabaseStatusToStatusCode(
storage::mojom::ServiceWorkerDatabaseStatus status) {
switch (status) {
case storage::mojom::ServiceWorkerDatabaseStatus::kOk:
return blink::ServiceWorkerStatusCode::kOk;
case storage::mojom::ServiceWorkerDatabaseStatus::kErrorNotFound:
return blink::ServiceWorkerStatusCode::kErrorNotFound;
case storage::mojom::ServiceWorkerDatabaseStatus::kErrorDisabled:
return blink::ServiceWorkerStatusCode::kErrorAbort;
case storage::mojom::ServiceWorkerDatabaseStatus::kErrorStorageDisconnected:
return blink::ServiceWorkerStatusCode::kErrorStorageDisconnected;
case storage::mojom::ServiceWorkerDatabaseStatus::kErrorCorrupted:
return blink::ServiceWorkerStatusCode::kErrorStorageDataCorrupted;
default:
return blink::ServiceWorkerStatusCode::kErrorFailed;
}
}
void RunSoon(const base::Location& from_here, base::OnceClosure closure) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
from_here, std::move(closure));
}
void CompleteFindNow(scoped_refptr<ServiceWorkerRegistration> registration,
blink::ServiceWorkerStatusCode status,
ServiceWorkerRegistry::FindRegistrationCallback callback) {
if (registration && registration->is_deleted()) {
// It's past the point of no return and no longer findable.
std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorNotFound,
nullptr);
return;
}
std::move(callback).Run(status, std::move(registration));
}
void CompleteFindSoon(
const base::Location& from_here,
scoped_refptr<ServiceWorkerRegistration> registration,
blink::ServiceWorkerStatusCode status,
ServiceWorkerRegistry::FindRegistrationCallback callback) {
RunSoon(from_here, base::BindOnce(&CompleteFindNow, std::move(registration),
status, std::move(callback)));
}
// Notifies quota manager that a disk write operation failed so that it can
// check for storage pressure.
void CheckForClientWriteFailure(
scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy,
storage::mojom::ServiceWorkerDatabaseStatus status,
const blink::StorageKey& key) {
if (!quota_manager_proxy)
return;
if (status == storage::mojom::ServiceWorkerDatabaseStatus::kErrorFailed ||
status == storage::mojom::ServiceWorkerDatabaseStatus::kErrorIOError) {
quota_manager_proxy->OnClientWriteFailed(key);
}
}
void FindRegistrationForClientUrlTraceEventBegin(int64_t trace_event_id,
const GURL& client_url) {
CHECK(client_url.is_valid());
TRACE_EVENT_BEGIN("ServiceWorker",
"ServiceWorkerRegistry::FindRegistrationForClientUrl",
perfetto::NamedTrack(
"ServiceWorkerRegistry::FindRegistrationForClientUrl",
trace_event_id),
"URL", client_url.spec());
}
void FindRegistrationForClientUrlTraceEventEnd(
int64_t trace_event_id,
blink::ServiceWorkerStatusCode status,
std::optional<std::string> info) {
// ServiceWorkerRegistry::FindRegistrationForClientUrl
if (info) {
TRACE_EVENT_END("ServiceWorker",
perfetto::NamedTrack(
"ServiceWorkerRegistry::FindRegistrationForClientUrl",
trace_event_id),
"Status", blink::ServiceWorkerStatusToString(status),
"Info", *info);
} else {
TRACE_EVENT_END("ServiceWorker",
perfetto::NamedTrack(
"ServiceWorkerRegistry::FindRegistrationForClientUrl",
trace_event_id),
"Status", blink::ServiceWorkerStatusToString(status));
}
}
std::string RouterRulesToString(blink::ServiceWorkerRouterRules rules) {
ServiceWorkerRouterEvaluator e(rules);
if (!e.IsValid()) {
return "data corruption detected";
}
return e.ToString();
}
// The cache size for ServiceWorker's scope cache (https://crbug.com/40254732).
constexpr size_t kServiceWorkerScopeCacheLimitSize = 100;
// The cache size for ServiceWorker's registration cache
// (https://crbug.com/40254732).
constexpr size_t kServiceWorkerRegistrationCacheSize = 100;
} // namespace
// Enables merging duplicate calls of FindRegistrationForClientUrl.
BASE_FEATURE(ServiceWorkerMergeFindRegistrationForClientUrl,
base::FEATURE_ENABLED_BY_DEFAULT);
template <typename... ReplyArgs>
class InflightCallWithInvoker final
: public ServiceWorkerRegistry::InflightCall {
public:
using ReplyCallback = base::OnceCallback<void(ReplyArgs...)>;
// `invoker` is a callback that sends a Mojo IPC over a `mojo::Remote` owned
// by `registry`.
explicit InflightCallWithInvoker(
ServiceWorkerRegistry* registry,
base::RepeatingCallback<void(InflightCallWithInvoker*, ReplyCallback)>
invoker,
ReplyCallback reply_callback)
: registry_(registry),
invoker_(invoker),
reply_callback_(std::move(reply_callback)) {}
~InflightCallWithInvoker() override = default;
void Run() override {
// Insert a reply trampoline to mark the call as completed when the reply
// callback is dispatched. `Unretained` is safe: the Mojo IPC endpoint is
// owned by `registry_`, which also owns `this`. If the Mojo IPC endpoint is
// destroyed, Mojo guarantees the reply callback will not be dispatched.
invoker_.Run(this, base::BindOnce(&InflightCallWithInvoker::DidReply,
base::Unretained(this)));
}
ServiceWorkerRegistry* registry() { return registry_; }
private:
void DidReply(ReplyArgs... reply_args) {
auto reply_callback = std::move(reply_callback_);
// Deletes `this`.
registry_->FinishRemoteCall(this);
std::move(reply_callback).Run(std::move(reply_args)...);
}
// `registry_` owns `this`
const raw_ptr<ServiceWorkerRegistry> registry_;
const base::RepeatingCallback<void(InflightCallWithInvoker*, ReplyCallback)>
invoker_;
base::OnceCallback<void(ReplyArgs...)> reply_callback_;
};
ServiceWorkerRegistry::ServiceWorkerRegistry(
ServiceWorkerContextCore& context,
storage::QuotaManagerProxy* quota_manager_proxy,
storage::SpecialStoragePolicy* special_storage_policy,
base::TimeTicks start_time)
: context_(context),
storage_shared_buffer_(base::MakeRefCounted<
storage::ServiceWorkerStorage::
StorageSharedBuffer>(
base::FeatureList::IsEnabled(
features::
kServiceWorkerBackgroundUpdateForRegisteredStorageKeys) &&
base::FeatureList::IsEnabled(
kServiceWorkerBackgroundUpdateForRegisteredStorageKeysFieldTrialControlled),
base::FeatureList::IsEnabled(
kServiceWorkerBackgroundUpdateForServiceWorkerScopeCache),
base::FeatureList::IsEnabled(
kServiceWorkerBackgroundUpdateForFindRegistrationForClientUrl))),
quota_manager_proxy_(quota_manager_proxy),
special_storage_policy_(special_storage_policy),
registration_scope_cache_(kServiceWorkerScopeCacheLimitSize),
registration_id_cache_(kServiceWorkerRegistrationCacheSize) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
Start(start_time);
}
ServiceWorkerRegistry::ServiceWorkerRegistry(
ServiceWorkerContextCore& context,
ServiceWorkerRegistry& old_registry)
: ServiceWorkerRegistry(
context,
old_registry.quota_manager_proxy_.get(),
old_registry.special_storage_policy_.get(),
// ServiceWorker.Storage.RegisteredStorageKeyCacheInitialization.Time
// uma shouldn't be recorded when ServiceWorkerContextCore is
// recreated. Hence we specify a null TimeTicks here.
base::TimeTicks()) {}
ServiceWorkerRegistry::~ServiceWorkerRegistry() = default;
void ServiceWorkerRegistry::CreateNewRegistration(
blink::mojom::ServiceWorkerRegistrationOptions options,
const blink::StorageKey& key,
blink::mojom::AncestorFrameType ancestor_frame_type,
NewRegistrationCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (quota_manager_proxy_) {
// Can be nullptr in tests.
quota_manager_proxy_->UpdateOrCreateBucket(
storage::BucketInitParams::ForDefaultBucket(key),
base::SingleThreadTaskRunner::GetCurrentDefault(),
base::BindOnce(
&ServiceWorkerRegistry::CreateNewRegistrationWithBucketInfo,
weak_factory_.GetWeakPtr(), std::move(options), key,
ancestor_frame_type, std::move(callback)));
} else {
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::GetNewRegistrationId,
base::BindOnce(&ServiceWorkerRegistry::DidGetNewRegistrationId,
weak_factory_.GetWeakPtr(), std::move(options), key,
ancestor_frame_type, std::move(callback)));
}
}
void ServiceWorkerRegistry::CreateNewRegistrationWithBucketInfo(
blink::mojom::ServiceWorkerRegistrationOptions options,
const blink::StorageKey& key,
blink::mojom::AncestorFrameType ancestor_frame_type,
NewRegistrationCallback callback,
storage::QuotaErrorOr<storage::BucketInfo> result) {
// Return nullptr if `UpdateOrCreateBucket` fails.
if (!result.has_value()) {
std::move(callback).Run(nullptr);
return;
}
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::GetNewRegistrationId,
base::BindOnce(&ServiceWorkerRegistry::DidGetNewRegistrationId,
weak_factory_.GetWeakPtr(), std::move(options), key,
ancestor_frame_type, std::move(callback)));
}
void ServiceWorkerRegistry::CreateNewVersion(
scoped_refptr<ServiceWorkerRegistration> registration,
const GURL& script_url,
blink::mojom::ScriptType script_type,
NewVersionCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(registration);
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::GetNewVersionId,
base::BindOnce(&ServiceWorkerRegistry::DidGetNewVersionId,
weak_factory_.GetWeakPtr(), registration, script_url,
script_type, std::move(callback)));
}
void ServiceWorkerRegistry::FindRegistrationForClientUrl(
Purpose purpose,
const GURL& client_url,
const blink::StorageKey& key,
FindRegistrationCallback callback) {
// To connect this TRACE_EVENT with the callback, Time::Now() is used as a
// trace event id.
int64_t trace_event_id =
base::Time::Now().ToDeltaSinceWindowsEpoch().InMicroseconds();
TRACE_EVENT_WITH_FLOW1(
"ServiceWorker", "ServiceWorkerRegistry::FindRegistrationForClientUrl",
TRACE_ID_WITH_SCOPE("ServiceWorkerRegistry", trace_event_id),
TRACE_EVENT_FLAG_FLOW_OUT, "URL", client_url.spec());
DCHECK_CURRENTLY_ON(BrowserThread::UI);
bool is_mojo_called = false;
base::ScopedClosureRunner run_at_return(
purpose == Purpose::kNavigation
? base::BindOnce(
[](bool* is_mojo_called, bool storage_key_is_first_party) {
base::UmaHistogramBoolean(
"ServiceWorker.FindRegistrationForClientUrl."
"SkippedMojoCall.OnNavigation2",
!(*is_mojo_called));
if (storage_key_is_first_party) {
base::UmaHistogramBoolean(
"ServiceWorker.FindRegistrationForClientUrl."
"SkippedMojoCall.OnNavigation2.StorageKeyIsFirstParty",
!(*is_mojo_called));
}
},
base::Unretained(&is_mojo_called), key.IsFirstPartyContext())
: base::DoNothing());
// The following code implements a performance optimization: it retrieves the
// registration scopes from the `ServiceWorkerStorage` in the thread pool
// without waiting for `DidFindRegistrationForClientUrl()` to be called.
for (const auto& [storage_key, scopes] :
storage_shared_buffer().TakeRegistrationScopes()) {
// Although StorageSharedBuffer ignores the ordering of the
// FindRegistrationForClientUrl mojo calls, we believe this
// is fine since `registration_scope_cache_` is a large enough cache.
registration_scope_cache_.Put(storage_key,
std::set<GURL>(scopes.begin(), scopes.end()));
}
// `registration_scope_cache_` provides a full list of scope URLs for the
// given blink::StorageKey if the matched entry exists. There are the
// following two scenarios after running the ServiceWorkerLongestScopeMatcher
// algorithm.
//
// If there is a key in `registration_scope_cache_`, and if there is no
// matched scope in it, this means that there is no registration for the
// `client_url`. (https://crbug.com/1411197)
//
// If there is a key in `registration_scope_cache_`, and if there is a matched
// scope in it, we can look up `registration_id_cache_`. If we can get
// registration, we can immediately complete with CompleteFindNow() without
// calling FindRegistrationForClientUrl() mojo API.
// (https://crbug.com/1446216)
bool no_registration = false;
std::optional<GURL> matched_scope;
std::optional<std::vector<GURL>> scopes;
auto iter = registration_scope_cache_.Get(key);
if (iter != registration_scope_cache_.end()) {
scopes = std::vector(iter->second.begin(), iter->second.end());
blink::ServiceWorkerLongestScopeMatcher matcher(client_url);
for (const GURL& scope : *scopes) {
if (matcher.MatchLongest(scope)) {
matched_scope = scope;
}
}
no_registration = !matched_scope;
}
base::UmaHistogramBoolean(
"ServiceWorker.FindRegistrationForClientUrl.IsCalledForNavigation",
purpose == Purpose::kNavigation);
if (matched_scope) {
auto it = registration_id_cache_.Get(std::make_pair(*matched_scope, key));
if (it != registration_id_cache_.end()) {
int64_t registration_id = it->second;
std::optional<scoped_refptr<ServiceWorkerRegistration>> registration =
FindFromLiveRegistrationsForId(registration_id);
// Since FindFromLiveRegistrationsForId() can return std::nullopt or
// nullptr, both cases must be checked.
if (registration.has_value() && registration.value()) {
FindRegistrationForClientUrlTraceEventBegin(trace_event_id, client_url);
FindRegistrationForClientUrlTraceEventEnd(
trace_event_id, blink::ServiceWorkerStatusCode::kOk, std::nullopt);
CompleteFindNow(std::move(*registration),
blink::ServiceWorkerStatusCode::kOk,
std::move(callback));
return;
}
}
}
if (base::FeatureList::IsEnabled(
kServiceWorkerMergeFindRegistrationForClientUrl)) {
std::vector<FindRegistrationCallback>& callbacks =
find_registration_callbacks_[std::make_pair(client_url, key)];
callbacks.push_back(std::move(callback));
if (callbacks.size() >= 2) {
// Merges duplicate requests into the preceding in-flight request.
return;
}
// Overwrite with a fake callback here as the actual callback is stored in
// `find_registration_callbacks_`, and will be invoked in
// `RunFindRegistrationCallbacks()`.
callback = base::DoNothing();
}
FindRegistrationForClientUrlTraceEventBegin(trace_event_id, client_url);
if (no_registration) {
DidFindRegistrationForClientUrl(
client_url, key, trace_event_id, std::move(callback),
storage::mojom::ServiceWorkerDatabaseStatus::kErrorNotFound, nullptr,
scopes);
return;
}
storage::mojom::ServiceWorkerFindRegistrationResultPtr preflight_result =
storage_shared_buffer().TakeFindRegistrationResult(client_url, key);
if (!preflight_result.is_null()) {
DidFindRegistrationForClientUrl(
client_url, key, trace_event_id, std::move(callback),
storage::mojom::ServiceWorkerDatabaseStatus::kOk,
std::move(preflight_result), scopes);
return;
}
// TODO(crbug.com/352578800): Consider moving this block before
// kServiceWorkerMergeFindRegistrationForClientUrl check since this block
// will be skipped when no_registration is true.
if (service_worker_loader_helpers::IsEligibleForSyntheticResponse(
context_->wrapper()->browser_context(), client_url)) {
// If `client_url` is eligible for SyntheticResponse, create a fake
// ServiceWorker registration so that the navigation is handled by
// ServiceWorker main resource loader.
is_mojo_called = true;
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::
GetFakeRegistrationForClientUrl,
base::BindOnce(&ServiceWorkerRegistry::DidFindRegistrationForClientUrl,
weak_factory_.GetWeakPtr(), client_url, key,
trace_event_id, std::move(callback)),
client_url, key);
return;
}
is_mojo_called = true;
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::
FindRegistrationForClientUrl,
base::BindOnce(&ServiceWorkerRegistry::DidFindRegistrationForClientUrl,
weak_factory_.GetWeakPtr(), client_url, key,
trace_event_id, std::move(callback)),
client_url, key);
}
void ServiceWorkerRegistry::FindRegistrationForScope(
const GURL& scope,
const blink::StorageKey& key,
FindRegistrationCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (is_storage_disabled_) {
RunSoon(
FROM_HERE,
base::BindOnce(std::move(callback),
blink::ServiceWorkerStatusCode::kErrorAbort, nullptr));
return;
}
// Look up installing registration before checking storage.
scoped_refptr<ServiceWorkerRegistration> installing_registration =
FindInstallingRegistrationForScope(scope, key);
if (installing_registration && !installing_registration->is_deleted()) {
CompleteFindSoon(FROM_HERE, std::move(installing_registration),
blink::ServiceWorkerStatusCode::kOk, std::move(callback));
return;
}
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::FindRegistrationForScope,
base::BindOnce(&ServiceWorkerRegistry::DidFindRegistrationForScope,
weak_factory_.GetWeakPtr(), std::move(callback)),
scope, key);
}
void ServiceWorkerRegistry::FindRegistrationForId(
int64_t registration_id,
const blink::StorageKey& key,
FindRegistrationCallback callback) {
FindRegistrationForIdInternal(registration_id, key, std::move(callback));
}
void ServiceWorkerRegistry::FindRegistrationForIdOnly(
int64_t registration_id,
FindRegistrationCallback callback) {
FindRegistrationForIdInternal(registration_id, /*key=*/std::nullopt,
std::move(callback));
}
void ServiceWorkerRegistry::GetRegistrationsForStorageKey(
const blink::StorageKey& key,
GetRegistrationsCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::
GetRegistrationsForStorageKey,
base::BindOnce(&ServiceWorkerRegistry::DidGetRegistrationsForStorageKey,
weak_factory_.GetWeakPtr(), std::move(callback), key),
key);
}
void ServiceWorkerRegistry::GetStorageUsageForStorageKey(
const blink::StorageKey& key,
GetStorageUsageForStorageKeyCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto wrapped_callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun(
std::move(callback), blink::ServiceWorkerStatusCode::kErrorFailed, 0);
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::GetUsageForStorageKey,
base::BindOnce(&ServiceWorkerRegistry::DidGetStorageUsageForStorageKey,
weak_factory_.GetWeakPtr(), std::move(wrapped_callback)),
key);
}
void ServiceWorkerRegistry::GetAllRegistrationsInfos(
GetRegistrationsInfosCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::
GetAllRegistrationsDeprecated,
base::BindOnce(&ServiceWorkerRegistry::DidGetAllRegistrations,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
ServiceWorkerRegistration* ServiceWorkerRegistry::GetUninstallingRegistration(
const GURL& scope,
const blink::StorageKey& key) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// TODO(bashi): Should we check state of ServiceWorkerStorage?
for (const auto& registration : uninstalling_registrations_) {
if (registration.second->key() == key &&
registration.second->scope() == scope) {
DCHECK(registration.second->is_uninstalling());
return registration.second.get();
}
}
return nullptr;
}
std::vector<scoped_refptr<ServiceWorkerRegistration>>
ServiceWorkerRegistry::GetUninstallingRegistrationsForStorageKey(
const blink::StorageKey& key) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
std::vector<scoped_refptr<ServiceWorkerRegistration>> results;
for (const auto& registration : uninstalling_registrations_) {
if (registration.second->key() == key) {
results.push_back(registration.second);
}
}
return results;
}
void ServiceWorkerRegistry::StoreRegistration(
ServiceWorkerRegistration* registration,
ServiceWorkerVersion* version,
StatusCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(registration);
DCHECK(version);
if (is_storage_disabled_) {
RunSoon(FROM_HERE,
base::BindOnce(std::move(callback),
blink::ServiceWorkerStatusCode::kErrorAbort));
return;
}
DCHECK_NE(version->fetch_handler_existence(),
ServiceWorkerVersion::FetchHandlerExistence::UNKNOWN);
DCHECK_EQ(registration->status(), ServiceWorkerRegistration::Status::kIntact);
auto data = storage::mojom::ServiceWorkerRegistrationData::New();
data->registration_id = registration->id();
data->scope = registration->scope();
data->key = registration->key();
data->script = version->script_url();
data->script_type = version->script_type();
data->update_via_cache = registration->update_via_cache();
data->fetch_handler_type = version->fetch_handler_type();
data->version_id = version->version_id();
data->last_update_check = registration->last_update_check();
data->is_active = (version == registration->active_version());
if (version->origin_trial_tokens())
data->origin_trial_tokens = *version->origin_trial_tokens();
data->navigation_preload_state = blink::mojom::NavigationPreloadState::New();
data->navigation_preload_state->enabled =
registration->navigation_preload_state().enabled;
data->navigation_preload_state->header =
registration->navigation_preload_state().header;
data->script_response_time = version->GetInfo().script_response_time;
for (const blink::mojom::WebFeature feature : version->used_features())
data->used_features.push_back(feature);
data->ancestor_frame_type = registration->ancestor_frame_type();
if (version->router_evaluator()) {
data->router_rules = version->router_evaluator()->rules();
}
// The ServiceWorkerVersion's policy container host might be null if it is
// stored before loading the main script. This happens in many unittests.
data->policy_container_policies =
version->policy_container_host()
? version->policy_container_host()
->policies()
.ToMojoPolicyContainerPolicies()
: blink::mojom::PolicyContainerPolicies::New();
data->has_hid_event_handlers = version->has_hid_event_handlers();
data->has_usb_event_handlers = version->has_usb_event_handlers();
ResourceList resources = version->script_cache_map()->GetResources();
if (resources.empty()) {
RunSoon(FROM_HERE,
base::BindOnce(std::move(callback),
blink::ServiceWorkerStatusCode::kErrorFailed));
return;
}
uint64_t resources_total_size_bytes = 0;
for (const auto& resource : resources) {
DCHECK_GE(resource->size_bytes, 0);
resources_total_size_bytes += resource->size_bytes;
}
data->resources_total_size_bytes = resources_total_size_bytes;
ClearInternalCacheForStorageKey(registration->key());
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::StoreRegistration,
base::BindOnce(&ServiceWorkerRegistry::DidStoreRegistration,
weak_factory_.GetWeakPtr(), registration->id(),
resources_total_size_bytes, registration->scope(),
registration->key(), std::move(callback)),
std::move(data), std::move(resources));
}
void ServiceWorkerRegistry::DeleteRegistration(
scoped_refptr<ServiceWorkerRegistration> registration,
StatusCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (is_storage_disabled_) {
RunSoon(FROM_HERE,
base::BindOnce(std::move(callback),
blink::ServiceWorkerStatusCode::kErrorAbort));
return;
}
DCHECK(!registration->is_deleted())
<< "attempt to delete a registration twice";
ClearInternalCacheForStorageKey(registration->key());
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::DeleteRegistration,
base::BindOnce(&ServiceWorkerRegistry::DidDeleteRegistration,
weak_factory_.GetWeakPtr(), registration->id(),
registration->scope(), registration->key(),
std::move(callback)),
registration->id(), registration->key());
DCHECK(!base::Contains(uninstalling_registrations_, registration->id()));
uninstalling_registrations_[registration->id()] = registration;
registration->SetStatus(ServiceWorkerRegistration::Status::kUninstalling);
}
void ServiceWorkerRegistry::NotifyInstallingRegistration(
ServiceWorkerRegistration* registration) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(installing_registrations_.find(registration->id()) ==
installing_registrations_.end());
installing_registrations_[registration->id()] = registration;
}
void ServiceWorkerRegistry::NotifyDoneInstallingRegistration(
ServiceWorkerRegistration* registration,
ServiceWorkerVersion* version,
blink::ServiceWorkerStatusCode status) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
installing_registrations_.erase(registration->id());
if (status != blink::ServiceWorkerStatusCode::kOk && version) {
ResourceList resources = version->script_cache_map()->GetResources();
std::vector<int64_t> resource_ids;
for (const auto& resource : resources)
resource_ids.push_back(resource->resource_id);
DoomUncommittedResources(resource_ids);
}
}
void ServiceWorkerRegistry::NotifyDoneUninstallingRegistration(
ServiceWorkerRegistration* registration,
ServiceWorkerRegistration::Status new_status) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
registration->SetStatus(new_status);
uninstalling_registrations_.erase(registration->id());
}
void ServiceWorkerRegistry::UpdateToActiveState(int64_t registration_id,
const blink::StorageKey& key,
StatusCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::UpdateToActiveState,
base::BindOnce(&ServiceWorkerRegistry::DidUpdateToActiveState,
weak_factory_.GetWeakPtr(), key, std::move(callback)),
static_cast<const int64_t>(registration_id), key);
}
void ServiceWorkerRegistry::UpdateLastUpdateCheckTime(
int64_t registration_id,
const blink::StorageKey& key,
base::Time last_update_check_time,
StatusCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::UpdateLastUpdateCheckTime,
base::BindOnce(&ServiceWorkerRegistry::DidUpdateRegistration,
weak_factory_.GetWeakPtr(), std::move(callback)),
static_cast<const int64_t>(registration_id), key,
static_cast<const base::Time&>(last_update_check_time));
}
void ServiceWorkerRegistry::UpdateNavigationPreloadEnabled(
int64_t registration_id,
const blink::StorageKey& key,
bool enable,
StatusCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::
UpdateNavigationPreloadEnabled,
base::BindOnce(&ServiceWorkerRegistry::DidUpdateRegistration,
weak_factory_.GetWeakPtr(), std::move(callback)),
static_cast<const int64_t>(registration_id), key,
static_cast<const bool>(enable));
}
void ServiceWorkerRegistry::UpdateNavigationPreloadHeader(
int64_t registration_id,
const blink::StorageKey& key,
const std::string& value,
StatusCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::
UpdateNavigationPreloadHeader,
base::BindOnce(&ServiceWorkerRegistry::DidUpdateRegistration,
weak_factory_.GetWeakPtr(), std::move(callback)),
static_cast<const int64_t>(registration_id), key, value);
}
void ServiceWorkerRegistry::UpdateFetchHandlerType(
int64_t registration_id,
const blink::StorageKey& key,
blink::mojom::ServiceWorkerFetchHandlerType fetch_handler_type,
StatusCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::UpdateFetchHandlerType,
base::BindOnce(&ServiceWorkerRegistry::DidUpdateRegistration,
weak_factory_.GetWeakPtr(), std::move(callback)),
static_cast<const int64_t>(registration_id), key,
static_cast<const blink::mojom::ServiceWorkerFetchHandlerType>(
fetch_handler_type));
}
void ServiceWorkerRegistry::UpdateResourceSha256Checksums(
int64_t registration_id,
const blink::StorageKey& key,
const base::flat_map<int64_t, std::string>& updated_sha256_checksums,
StatusCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::
UpdateResourceSha256Checksums,
base::BindOnce(&ServiceWorkerRegistry::DidUpdateRegistration,
weak_factory_.GetWeakPtr(), std::move(callback)),
static_cast<const int64_t>(registration_id), key,
updated_sha256_checksums);
}
void ServiceWorkerRegistry::StoreUncommittedResourceId(
int64_t resource_id,
const blink::StorageKey& key) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::StoreUncommittedResourceId,
base::BindOnce(&ServiceWorkerRegistry::DidWriteUncommittedResourceIds,
weak_factory_.GetWeakPtr(), key),
static_cast<const int64_t>(resource_id));
}
void ServiceWorkerRegistry::DoomUncommittedResource(int64_t resource_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
std::vector<int64_t> resource_ids = {resource_id};
DoomUncommittedResources(resource_ids);
}
void ServiceWorkerRegistry::GetUserData(int64_t registration_id,
const std::vector<std::string>& keys,
GetUserDataCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::GetUserData,
base::BindOnce(&ServiceWorkerRegistry::DidGetUserData,
weak_factory_.GetWeakPtr(), std::move(callback)),
static_cast<const int64_t>(registration_id), keys);
}
void ServiceWorkerRegistry::GetUserDataByKeyPrefix(
int64_t registration_id,
const std::string& key_prefix,
GetUserDataCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::GetUserDataByKeyPrefix,
base::BindOnce(&ServiceWorkerRegistry::DidGetUserData,
weak_factory_.GetWeakPtr(), std::move(callback)),
static_cast<const int64_t>(registration_id), key_prefix);
}
void ServiceWorkerRegistry::GetUserKeysAndDataByKeyPrefix(
int64_t registration_id,
const std::string& key_prefix,
GetUserKeysAndDataCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::
GetUserKeysAndDataByKeyPrefix,
base::BindOnce(&ServiceWorkerRegistry::DidGetUserKeysAndData,
weak_factory_.GetWeakPtr(), std::move(callback)),
static_cast<const int64_t>(registration_id), key_prefix);
}
void ServiceWorkerRegistry::StoreUserData(
int64_t registration_id,
const blink::StorageKey& key,
const std::vector<std::pair<std::string, std::string>>& key_value_pairs,
StatusCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (registration_id == blink::mojom::kInvalidServiceWorkerRegistrationId ||
key_value_pairs.empty()) {
RunSoon(FROM_HERE,
base::BindOnce(std::move(callback),
blink::ServiceWorkerStatusCode::kErrorFailed));
return;
}
std::vector<storage::mojom::ServiceWorkerUserDataPtr> user_data;
// TODO(crbug.com/40120038): Change this method to take a vector of
// storage::mojom::ServiceWorkerUserDataPtr instead of converting
//|key_value_pairs|.
for (const auto& kv : key_value_pairs) {
user_data.push_back(storage::mojom::ServiceWorkerUserData::New(
registration_id, kv.first, kv.second));
}
auto wrapped_callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun(
std::move(callback), blink::ServiceWorkerStatusCode::kErrorFailed);
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::StoreUserData,
base::BindOnce(&ServiceWorkerRegistry::DidStoreUserData,
weak_factory_.GetWeakPtr(), std::move(wrapped_callback),
key),
registration_id, key, std::move(user_data));
}
void ServiceWorkerRegistry::ClearUserData(int64_t registration_id,
const std::vector<std::string>& keys,
StatusCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto wrapped_callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun(
std::move(callback), blink::ServiceWorkerStatusCode::kErrorFailed);
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::ClearUserData,
base::BindOnce(&ServiceWorkerRegistry::DidClearUserData,
weak_factory_.GetWeakPtr(), std::move(wrapped_callback)),
static_cast<const int64_t>(registration_id), keys);
}
void ServiceWorkerRegistry::ClearUserDataByKeyPrefixes(
int64_t registration_id,
const std::vector<std::string>& key_prefixes,
StatusCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto wrapped_callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun(
std::move(callback), blink::ServiceWorkerStatusCode::kErrorFailed);
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::ClearUserDataByKeyPrefixes,
base::BindOnce(&ServiceWorkerRegistry::DidClearUserData,
weak_factory_.GetWeakPtr(), std::move(wrapped_callback)),
static_cast<const int64_t>(registration_id), key_prefixes);
}
void ServiceWorkerRegistry::ClearUserDataForAllRegistrationsByKeyPrefix(
const std::string& key_prefix,
StatusCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto wrapped_callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun(
std::move(callback), blink::ServiceWorkerStatusCode::kErrorFailed);
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::
ClearUserDataForAllRegistrationsByKeyPrefix,
base::BindOnce(&ServiceWorkerRegistry::DidClearUserData,
weak_factory_.GetWeakPtr(), std::move(wrapped_callback)),
key_prefix);
}
void ServiceWorkerRegistry::GetUserDataForAllRegistrations(
const std::string& key,
GetUserDataForAllRegistrationsCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto wrapped_callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun(
std::move(callback), std::vector<std::pair<int64_t, std::string>>(),
blink::ServiceWorkerStatusCode::kErrorFailed);
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::
GetUserDataForAllRegistrations,
base::BindOnce(&ServiceWorkerRegistry::DidGetUserDataForAllRegistrations,
weak_factory_.GetWeakPtr(), std::move(wrapped_callback)),
key);
}
void ServiceWorkerRegistry::GetUserDataForAllRegistrationsByKeyPrefix(
const std::string& key_prefix,
GetUserDataForAllRegistrationsCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto wrapped_callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun(
std::move(callback), std::vector<std::pair<int64_t, std::string>>(),
blink::ServiceWorkerStatusCode::kErrorFailed);
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::
GetUserDataForAllRegistrationsByKeyPrefix,
base::BindOnce(&ServiceWorkerRegistry::DidGetUserDataForAllRegistrations,
weak_factory_.GetWeakPtr(), std::move(wrapped_callback)),
key_prefix);
}
void ServiceWorkerRegistry::GetRegisteredStorageKeys(
GetRegisteredStorageKeysCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto wrapped_callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun(
std::move(callback), std::vector<blink::StorageKey>());
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::GetRegisteredStorageKeys,
base::BindOnce(&ServiceWorkerRegistry::DidGetRegisteredStorageKeys,
weak_factory_.GetWeakPtr(), std::move(wrapped_callback)));
}
// TODO(crbug.com/422348336): Merge this function into
// `DidGetRegisteredStorageKeysOnStartup()` when
// kReduceCallingServiceWorkerRegisteredStorageKeysOnStartup feature is removed.
void ServiceWorkerRegistry::DidGetRegisteredStorageKeysOnStartupDeprecated(
base::TimeTicks start_time,
const std::vector<blink::StorageKey>& storage_keys) {
TRACE_EVENT(
"ServiceWorker",
"ServiceWorkerRegistry::DidGetRegisteredStorageKeysOnStartupDeprecated");
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Discard RegisteredKeys from `storage_shared_buffer_`.
storage_shared_buffer().TakeRegisteredKeys();
if (!registrations_initialized_) {
SetRegisteredStorageKeys(storage_keys);
}
if (on_registrations_initialized_for_test_) {
std::move(on_registrations_initialized_for_test_).Run();
}
if (!start_time.is_null()) {
base::UmaHistogramMediumTimes(
"ServiceWorker.Storage.RegisteredStorageKeyCacheInitialization.Time2",
base::TimeTicks::Now() - start_time);
}
}
void ServiceWorkerRegistry::SetRegisteredStorageKeys(
const std::vector<blink::StorageKey>& storage_keys) {
TRACE_EVENT("ServiceWorker",
"ServiceWorkerRegistry::SetRegisteredStorageKeys");
CHECK(!registrations_initialized_);
for (const blink::StorageKey& storage_key : storage_keys) {
registered_storage_keys_.insert(storage_key);
}
registrations_initialized_ = true;
}
bool ServiceWorkerRegistry::MaybeHasRegistrationForStorageKey(
const blink::StorageKey& key) {
TRACE_EVENT("ServiceWorker",
"ServiceWorkerRegistry::MaybeHasRegistrationForStorageKey");
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// The following code implements a performance optimization: it retrieves
// `storage_keys` from the `ServiceWorkerStorage` in the thread pool without
// waiting for `DidGetRegisteredStorageKeys()` to be called. This can speed up
// navigation during the browser startup phase.
if (!registrations_initialized_) {
if (std::optional<std::vector<blink::StorageKey>> storage_keys =
storage_shared_buffer().TakeRegisteredKeys()) {
SetRegisteredStorageKeys(*storage_keys);
}
}
if (!registrations_initialized_) {
return true;
}
if (registered_storage_keys_.find(key) != registered_storage_keys_.end()) {
return true;
}
return false;
}
void ServiceWorkerRegistry::
WaitForRegistrationsInitializedForTest() { // IN-TEST
DCHECK_CURRENTLY_ON(BrowserThread::UI);
CHECK_IS_TEST();
if (registrations_initialized_) {
return;
}
base::RunLoop loop;
on_registrations_initialized_for_test_ = loop.QuitClosure();
loop.Run();
}
void ServiceWorkerRegistry::PerformStorageCleanup(base::OnceClosure callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto wrapped_callback =
mojo::WrapCallbackWithDefaultInvokeIfNotRun(std::move(callback));
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::PerformStorageCleanup,
base::BindOnce(&ServiceWorkerRegistry::DidPerformStorageCleanup,
weak_factory_.GetWeakPtr(), std::move(wrapped_callback)));
}
void ServiceWorkerRegistry::PrepareForDeleteAndStartOver() {
should_schedule_delete_and_start_over_ = false;
is_storage_disabled_ = true;
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::Disable,
base::BindOnce(&ServiceWorkerRegistry::DidDisable,
weak_factory_.GetWeakPtr()));
}
void ServiceWorkerRegistry::DeleteAndStartOver(StatusCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::Delete,
base::BindOnce(&ServiceWorkerRegistry::DidDeleteAndStartOver,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void ServiceWorkerRegistry::DisableStorageForTesting(
base::OnceClosure callback) {
GetRemoteStorageControl()->Disable(std::move(callback));
}
void ServiceWorkerRegistry::Start(base::TimeTicks start_time) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (special_storage_policy_) {
storage_policy_observer_.emplace(
base::BindRepeating(&ServiceWorkerRegistry::ApplyPolicyUpdates,
weak_factory_.GetWeakPtr()),
GetIOThreadTaskRunner({}), special_storage_policy_);
GetRegisteredStorageKeys(base::BindOnce(
&ServiceWorkerRegistry::DidGetRegisteredStorageKeysOnStartup,
weak_factory_.GetWeakPtr(), start_time));
}
if (!special_storage_policy_ ||
!ReduceCallingServiceWorkerRegisteredStorageKeysOnStartupEnabled()) {
GetRegisteredStorageKeys(base::BindOnce(
&ServiceWorkerRegistry::DidGetRegisteredStorageKeysOnStartupDeprecated,
weak_factory_.GetWeakPtr(), start_time));
}
}
void ServiceWorkerRegistry::FindRegistrationForIdInternal(
int64_t registration_id,
const std::optional<blink::StorageKey>& key,
FindRegistrationCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Registration lookup is expected to abort when storage is disabled.
if (is_storage_disabled_) {
CompleteFindNow(nullptr, blink::ServiceWorkerStatusCode::kErrorAbort,
std::move(callback));
return;
}
// Lookup live registration first.
std::optional<scoped_refptr<ServiceWorkerRegistration>> registration =
FindFromLiveRegistrationsForId(registration_id);
if (registration) {
blink::ServiceWorkerStatusCode status =
registration.value() ? blink::ServiceWorkerStatusCode::kOk
: blink::ServiceWorkerStatusCode::kErrorNotFound;
// Only notify access for already stored registrations.
if (status == blink::ServiceWorkerStatusCode::kOk &&
(*registration)->IsStored()) {
// Can be nullptr in tests.
if (quota_manager_proxy_) {
// TODO(crbug.com/40213545): pass correct bucket.
quota_manager_proxy_->NotifyBucketAccessed(
storage::BucketLocator::ForDefaultBucket((*registration)->key()),
base::Time::Now());
}
}
CompleteFindNow(std::move(registration.value()), status,
std::move(callback));
return;
}
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::FindRegistrationForId,
base::BindOnce(&ServiceWorkerRegistry::DidFindRegistrationForId,
weak_factory_.GetWeakPtr(), registration_id,
std::move(callback)),
static_cast<const int64_t>(registration_id), key);
}
ServiceWorkerRegistration*
ServiceWorkerRegistry::FindInstallingRegistrationForClientUrl(
const GURL& client_url,
const blink::StorageKey& key) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!client_url.has_ref());
blink::ServiceWorkerLongestScopeMatcher matcher(client_url);
ServiceWorkerRegistration* match = nullptr;
// TODO(nhiroki): This searches over installing registrations linearly and it
// couldn't be scalable. Maybe the regs should be partitioned by origin.
for (const auto& registration : installing_registrations_)
if (registration.second->key() == key &&
matcher.MatchLongest(registration.second->scope()))
match = registration.second.get();
return match;
}
ServiceWorkerRegistration*
ServiceWorkerRegistry::FindInstallingRegistrationForScope(
const GURL& scope,
const blink::StorageKey& key) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
for (const auto& registration : installing_registrations_)
if (registration.second->key() == key &&
registration.second->scope() == scope)
return registration.second.get();
return nullptr;
}
ServiceWorkerRegistration*
ServiceWorkerRegistry::FindInstallingRegistrationForId(
int64_t registration_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
RegistrationRefsById::const_iterator found =
installing_registrations_.find(registration_id);
if (found == installing_registrations_.end())
return nullptr;
return found->second.get();
}
scoped_refptr<ServiceWorkerRegistration>
ServiceWorkerRegistry::GetOrCreateRegistration(
const storage::mojom::ServiceWorkerRegistrationData& data,
const ResourceList& resources,
mojo::PendingRemote<storage::mojom::ServiceWorkerLiveVersionRef>
version_reference) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
scoped_refptr<ServiceWorkerRegistration> registration =
context_->GetLiveRegistration(data.registration_id);
if (registration)
return registration;
blink::mojom::ServiceWorkerRegistrationOptions options(
data.scope, data.script_type, data.update_via_cache);
registration = ServiceWorkerRegistration::Create(
options, data.key, data.registration_id, context_->AsWeakPtr(),
data.ancestor_frame_type);
registration->SetStored();
registration->set_resources_total_size_bytes(data.resources_total_size_bytes);
registration->set_last_update_check(data.last_update_check);
DCHECK(!base::Contains(uninstalling_registrations_, data.registration_id));
scoped_refptr<ServiceWorkerVersion> version =
context_->GetLiveVersion(data.version_id);
if (!version) {
version = base::MakeRefCounted<ServiceWorkerVersion>(
registration.get(), data.script, data.script_type, data.version_id,
std::move(version_reference), context_->AsWeakPtr());
version->set_fetch_handler_type(data.fetch_handler_type);
// `has_hid_event_handlers_` in ServiceWorkerVersion should be set before
// changing the status to ACTIVATED.
version->set_has_hid_event_handlers(data.has_hid_event_handlers);
// `has_usb_event_handlers_` in ServiceWorkerVersion should be set before
// changing the status to ACTIVATED.
version->set_has_usb_event_handlers(data.has_usb_event_handlers);
// Set resources before changing the status to ACTIVATED/INSTALLED.
// |sha256_script_checksum_| in ServiceWorkerVersion should be set before
// changing the status.
version->SetResources(resources);
version->SetStatus(data.is_active ? ServiceWorkerVersion::ACTIVATED
: ServiceWorkerVersion::INSTALLED);
if (data.origin_trial_tokens)
version->SetValidOriginTrialTokens(*data.origin_trial_tokens);
std::set<blink::mojom::WebFeature> used_features(data.used_features.begin(),
data.used_features.end());
version->set_used_features(std::move(used_features));
// policy_container_host could be null for registration restored from old DB
if (data.policy_container_policies) {
version->SetPolicyContainerHost(base::MakeRefCounted<PolicyContainerHost>(
PolicyContainerPolicies(*data.policy_container_policies,
/*is_web_secure_context=*/true)));
}
if (data.router_rules) {
auto error = version->SetupRouterEvaluator(*data.router_rules);
DCHECK_EQ(error, ServiceWorkerRouterEvaluatorErrorEnums::kNoError)
<< "Failed to setup RouterEvaluator from the provided "
<< "rules. Possibly the database is corrupted.";
}
}
version->set_script_response_time_for_devtools(data.script_response_time);
if (version->status() == ServiceWorkerVersion::ACTIVATED) {
registration->SetActiveVersion(version);
} else if (version->status() == ServiceWorkerVersion::INSTALLED) {
registration->SetWaitingVersion(version);
} else {
NOTREACHED();
}
registration->EnableNavigationPreload(data.navigation_preload_state->enabled);
registration->SetNavigationPreloadHeader(
data.navigation_preload_state->header);
return registration;
}
std::optional<scoped_refptr<ServiceWorkerRegistration>>
ServiceWorkerRegistry::FindFromLiveRegistrationsForId(int64_t registration_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
scoped_refptr<ServiceWorkerRegistration> registration =
context_->GetLiveRegistration(registration_id);
if (registration) {
// The registration is considered as findable when it's stored or in
// installing state.
if (registration->IsStored() ||
base::Contains(installing_registrations_, registration_id)) {
return registration;
}
// Otherwise, the registration should not be findable even if it's still
// alive.
return nullptr;
}
// There is no live registration. Storage lookup is required. Returning
// nullopt results in storage lookup.
return std::nullopt;
}
void ServiceWorkerRegistry::DoomUncommittedResources(
const std::vector<int64_t>& resource_ids) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::DoomUncommittedResources,
base::BindOnce(&ServiceWorkerRegistry::DidDoomUncommittedResourceIds,
weak_factory_.GetWeakPtr()),
resource_ids);
}
void ServiceWorkerRegistry::DidFindRegistrationForClientUrl(
const GURL& client_url,
const blink::StorageKey& key,
int64_t trace_event_id,
FindRegistrationCallback callback,
storage::mojom::ServiceWorkerDatabaseStatus database_status,
storage::mojom::ServiceWorkerFindRegistrationResultPtr result,
const std::optional<std::vector<GURL>>& scopes) {
TRACE_EVENT_WITH_FLOW0(
"ServiceWorker", "ServiceWorkerRegistry::DidFindRegistrationForClientUrl",
TRACE_ID_WITH_SCOPE("ServiceWorkerRegistry", trace_event_id),
TRACE_EVENT_FLAG_FLOW_IN);
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Discard RegistrationScopes from storage_shared_buffer.
storage_shared_buffer().TakeRegistrationScopes();
// Discard FindRegistrationResult from storage_shared_buffer.
storage_shared_buffer().TakeFindRegistrationResult(client_url, key);
if (database_status != storage::mojom::ServiceWorkerDatabaseStatus::kOk &&
database_status !=
storage::mojom::ServiceWorkerDatabaseStatus::kErrorNotFound) {
DCHECK(!scopes);
// The following `ScheduleDeleteAndStartOver()` calls
// `ClearAllInternalCache()`. Therefore, no need to call
// `ClearInternalCacheForStorageKey()` here.
ScheduleDeleteAndStartOver();
} else if (scopes && !scopes->empty()) {
registration_scope_cache_.Put(
key, std::set<GURL>(scopes->begin(), scopes->end()));
} else {
ClearInternalCacheForStorageKey(key);
}
const bool kServiceWorkerMergeFindRegistrationForClientUrlEnabled =
base::FeatureList::IsEnabled(
kServiceWorkerMergeFindRegistrationForClientUrl);
blink::ServiceWorkerStatusCode status =
DatabaseStatusToStatusCode(database_status);
if (status == blink::ServiceWorkerStatusCode::kErrorNotFound) {
// Look for something currently being installed.
scoped_refptr<ServiceWorkerRegistration> installing_registration =
FindInstallingRegistrationForClientUrl(client_url, key);
if (installing_registration) {
blink::ServiceWorkerStatusCode installing_status =
installing_registration->is_deleted()
? blink::ServiceWorkerStatusCode::kErrorNotFound
: blink::ServiceWorkerStatusCode::kOk;
FindRegistrationForClientUrlTraceEventEnd(
trace_event_id, status,
(installing_status == blink::ServiceWorkerStatusCode::kOk)
? "Installing registration is found"
: "Any registrations are not found");
if (kServiceWorkerMergeFindRegistrationForClientUrlEnabled) {
RunFindRegistrationCallbacks(client_url, key,
std::move(installing_registration),
installing_status);
} else {
CompleteFindNow(std::move(installing_registration), installing_status,
std::move(callback));
}
return;
}
}
scoped_refptr<ServiceWorkerRegistration> registration;
if (status == blink::ServiceWorkerStatusCode::kOk) {
DCHECK(result);
DCHECK(result->registration);
DCHECK(result->version_reference);
registration =
GetOrCreateRegistration(*(result->registration), result->resources,
std::move(result->version_reference));
registration_id_cache_.Put(std::make_pair(result->registration->scope, key),
result->registration->registration_id);
if (quota_manager_proxy_) {
// Can be nullptr in tests.
quota_manager_proxy_->NotifyBucketAccessed(
storage::BucketLocator::ForDefaultBucket(registration->key()),
base::Time::Now());
}
}
FindRegistrationForClientUrlTraceEventEnd(trace_event_id, status,
std::nullopt);
if (kServiceWorkerMergeFindRegistrationForClientUrlEnabled) {
RunFindRegistrationCallbacks(client_url, key, std::move(registration),
status);
} else {
CompleteFindNow(std::move(registration), status, std::move(callback));
}
}
void ServiceWorkerRegistry::RunFindRegistrationCallbacks(
const GURL& client_url,
const blink::StorageKey& key,
scoped_refptr<ServiceWorkerRegistration> registration,
blink::ServiceWorkerStatusCode status) {
TRACE_EVENT2("ServiceWorker",
"ServiceWorkerRegistry::RunFindRegistrationCallbacks",
"client_url", client_url, "status",
blink::ServiceWorkerStatusToString(status));
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto iter =
find_registration_callbacks_.find(std::make_pair(client_url, key));
CHECK(iter != find_registration_callbacks_.end());
std::vector<FindRegistrationCallback> callbacks = std::move(iter->second);
find_registration_callbacks_.erase(iter);
for (FindRegistrationCallback& callback : callbacks) {
CompleteFindNow(registration, status, std::move(callback));
}
}
void ServiceWorkerRegistry::DidFindRegistrationForScope(
FindRegistrationCallback callback,
storage::mojom::ServiceWorkerDatabaseStatus database_status,
storage::mojom::ServiceWorkerFindRegistrationResultPtr result) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (database_status != storage::mojom::ServiceWorkerDatabaseStatus::kOk &&
database_status !=
storage::mojom::ServiceWorkerDatabaseStatus::kErrorNotFound) {
ScheduleDeleteAndStartOver();
}
blink::ServiceWorkerStatusCode status =
DatabaseStatusToStatusCode(database_status);
scoped_refptr<ServiceWorkerRegistration> registration;
if (status == blink::ServiceWorkerStatusCode::kOk) {
DCHECK(result);
DCHECK(result->registration);
DCHECK(result->version_reference);
registration =
GetOrCreateRegistration(*(result->registration), result->resources,
std::move(result->version_reference));
if (quota_manager_proxy_) {
// Can be nullptr in tests.
quota_manager_proxy_->NotifyBucketAccessed(
storage::BucketLocator::ForDefaultBucket(registration->key()),
base::Time::Now());
}
}
CompleteFindNow(std::move(registration), status, std::move(callback));
}
void ServiceWorkerRegistry::DidFindRegistrationForId(
int64_t registration_id,
FindRegistrationCallback callback,
storage::mojom::ServiceWorkerDatabaseStatus database_status,
storage::mojom::ServiceWorkerFindRegistrationResultPtr result) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (database_status != storage::mojom::ServiceWorkerDatabaseStatus::kOk &&
database_status !=
storage::mojom::ServiceWorkerDatabaseStatus::kErrorNotFound) {
ScheduleDeleteAndStartOver();
}
blink::ServiceWorkerStatusCode status =
DatabaseStatusToStatusCode(database_status);
if (status == blink::ServiceWorkerStatusCode::kErrorNotFound) {
// Look for something currently being installed.
scoped_refptr<ServiceWorkerRegistration> installing_registration =
FindInstallingRegistrationForId(registration_id);
if (installing_registration) {
CompleteFindNow(std::move(installing_registration),
blink::ServiceWorkerStatusCode::kOk, std::move(callback));
return;
}
}
scoped_refptr<ServiceWorkerRegistration> registration;
if (status == blink::ServiceWorkerStatusCode::kOk) {
DCHECK(result);
DCHECK(result->registration);
DCHECK(result->version_reference);
registration =
GetOrCreateRegistration(*(result->registration), result->resources,
std::move(result->version_reference));
if (quota_manager_proxy_) {
// Can be nullptr in tests.
quota_manager_proxy_->NotifyBucketAccessed(
storage::BucketLocator::ForDefaultBucket(registration->key()),
base::Time::Now());
}
}
CompleteFindNow(std::move(registration), status, std::move(callback));
}
void ServiceWorkerRegistry::DidGetRegistrationsForStorageKey(
GetRegistrationsCallback callback,
const blink::StorageKey& key_filter,
storage::mojom::ServiceWorkerDatabaseStatus database_status,
std::vector<storage::mojom::ServiceWorkerFindRegistrationResultPtr>
entries) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
blink::ServiceWorkerStatusCode status =
DatabaseStatusToStatusCode(database_status);
if (status != blink::ServiceWorkerStatusCode::kOk &&
status != blink::ServiceWorkerStatusCode::kErrorNotFound) {
ScheduleDeleteAndStartOver();
std::move(callback).Run(
status, std::vector<scoped_refptr<ServiceWorkerRegistration>>());
return;
}
// Add all stored registrations.
std::set<int64_t> registration_ids;
std::vector<scoped_refptr<ServiceWorkerRegistration>> registrations;
for (const auto& entry : entries) {
DCHECK(entry->registration);
DCHECK(entry->version_reference);
registration_ids.insert(entry->registration->registration_id);
registrations.push_back(
GetOrCreateRegistration(*entry->registration, entry->resources,
std::move(entry->version_reference)));
}
// Add unstored registrations that are being installed.
for (const auto& registration : installing_registrations_) {
if (registration.second->key() != key_filter)
continue;
if (registration_ids.insert(registration.first).second)
registrations.push_back(registration.second);
}
std::move(callback).Run(blink::ServiceWorkerStatusCode::kOk,
std::move(registrations));
}
void ServiceWorkerRegistry::DidGetAllRegistrations(
GetRegistrationsInfosCallback callback,
storage::mojom::ServiceWorkerDatabaseStatus database_status,
RegistrationList registration_data_list) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
blink::ServiceWorkerStatusCode status =
DatabaseStatusToStatusCode(database_status);
if (status != blink::ServiceWorkerStatusCode::kOk &&
status != blink::ServiceWorkerStatusCode::kErrorNotFound) {
ScheduleDeleteAndStartOver();
std::move(callback).Run(status,
std::vector<ServiceWorkerRegistrationInfo>());
return;
}
// Add all stored registrations.
std::set<int64_t> pushed_registrations;
std::vector<ServiceWorkerRegistrationInfo> infos;
for (const auto& registration_data : registration_data_list) {
const bool inserted =
pushed_registrations.insert(registration_data->registration_id).second;
DCHECK(inserted);
scoped_refptr<ServiceWorkerRegistration> registration =
context_->GetLiveRegistration(registration_data->registration_id);
if (registration) {
infos.push_back(registration->GetInfo());
continue;
}
ServiceWorkerRegistrationInfo info;
info.scope = registration_data->scope;
info.key = registration_data->key;
info.update_via_cache = registration_data->update_via_cache;
info.registration_id = registration_data->registration_id;
info.stored_version_size_bytes =
registration_data->resources_total_size_bytes;
info.navigation_preload_enabled =
registration_data->navigation_preload_state->enabled;
info.navigation_preload_header_length =
registration_data->navigation_preload_state->header.size();
if (ServiceWorkerVersion* version =
context_->GetLiveVersion(registration_data->version_id)) {
if (registration_data->is_active)
info.active_version = version->GetInfo();
else
info.waiting_version = version->GetInfo();
infos.push_back(info);
continue;
}
if (registration_data->is_active) {
info.active_version.status = ServiceWorkerVersion::ACTIVATED;
info.active_version.script_url = registration_data->script;
info.active_version.version_id = registration_data->version_id;
info.active_version.registration_id = registration_data->registration_id;
info.active_version.script_response_time =
registration_data->script_response_time;
info.active_version.fetch_handler_type =
registration_data->fetch_handler_type;
info.active_version.navigation_preload_state.enabled =
registration_data->navigation_preload_state->enabled;
info.active_version.navigation_preload_state.header =
registration_data->navigation_preload_state->header;
if (registration_data->router_rules) {
info.active_version.router_rules =
RouterRulesToString(*registration_data->router_rules);
}
} else {
info.waiting_version.status = ServiceWorkerVersion::INSTALLED;
info.waiting_version.script_url = registration_data->script;
info.waiting_version.version_id = registration_data->version_id;
info.waiting_version.registration_id = registration_data->registration_id;
info.waiting_version.script_response_time =
registration_data->script_response_time;
info.waiting_version.fetch_handler_type =
registration_data->fetch_handler_type;
info.waiting_version.navigation_preload_state.enabled =
registration_data->navigation_preload_state->enabled;
info.waiting_version.navigation_preload_state.header =
registration_data->navigation_preload_state->header;
if (registration_data->router_rules) {
info.waiting_version.router_rules =
RouterRulesToString(*registration_data->router_rules);
}
}
infos.push_back(info);
}
// Add unstored registrations that are being installed.
for (const auto& registration : installing_registrations_) {
if (pushed_registrations.insert(registration.first).second)
infos.push_back(registration.second->GetInfo());
}
std::move(callback).Run(blink::ServiceWorkerStatusCode::kOk, infos);
}
void ServiceWorkerRegistry::DidGetStorageUsageForStorageKey(
GetStorageUsageForStorageKeyCallback callback,
storage::mojom::ServiceWorkerDatabaseStatus database_status,
int64_t usage) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
blink::ServiceWorkerStatusCode status =
DatabaseStatusToStatusCode(database_status);
std::move(callback).Run(status, usage);
}
void ServiceWorkerRegistry::DidStoreRegistration(
int64_t stored_registration_id,
uint64_t stored_resources_total_size_bytes,
const GURL& stored_scope,
const blink::StorageKey& key,
StatusCallback callback,
storage::mojom::ServiceWorkerDatabaseStatus database_status,
uint64_t deleted_resources_size) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
blink::ServiceWorkerStatusCode status =
DatabaseStatusToStatusCode(database_status);
CheckForClientWriteFailure(quota_manager_proxy_, database_status, key);
if (status != blink::ServiceWorkerStatusCode::kOk) {
ScheduleDeleteAndStartOver();
std::move(callback).Run(status);
return;
}
auto registration_stored_callback =
base::BindOnce(&ServiceWorkerRegistry::NotifyRegistrationStored,
weak_factory_.GetWeakPtr(), stored_registration_id,
stored_resources_total_size_bytes, stored_scope, key,
std::move(callback));
if (quota_manager_proxy_) {
// Can be nullptr in tests.
quota_manager_proxy_->NotifyBucketModified(
storage::QuotaClientType::kServiceWorker,
storage::BucketLocator::ForDefaultBucket(key),
stored_resources_total_size_bytes - deleted_resources_size,
base::Time::Now(), base::SequencedTaskRunner::GetCurrentDefault(),
std::move(registration_stored_callback));
} else {
std::move(registration_stored_callback).Run();
}
}
void ServiceWorkerRegistry::NotifyRegistrationStored(
int64_t stored_registration_id,
uint64_t stored_resources_total_size_bytes,
const GURL& stored_scope,
const blink::StorageKey& key,
StatusCallback callback) {
registered_storage_keys_.insert(key);
context_->NotifyRegistrationStored(stored_registration_id, stored_scope, key,
stored_resources_total_size_bytes);
if (storage_policy_observer_) {
storage_policy_observer_->StartTrackingOrigin(key.origin());
}
// For all other blink::ServiceWorkerStatusCode entries,
// DidStoreRegistration() schedules deletion and starts over, so the only
// status code that can be called here is the kOk one.
std::move(callback).Run(blink::ServiceWorkerStatusCode::kOk);
}
void ServiceWorkerRegistry::DidDeleteRegistration(
int64_t registration_id,
const GURL& stored_scope,
const blink::StorageKey& key,
StatusCallback callback,
storage::mojom::ServiceWorkerDatabaseStatus database_status,
uint64_t deleted_resources_size,
storage::mojom::ServiceWorkerStorageStorageKeyState storage_key_state) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
blink::ServiceWorkerStatusCode status =
DatabaseStatusToStatusCode(database_status);
if (status != blink::ServiceWorkerStatusCode::kOk) {
ScheduleDeleteAndStartOver();
std::move(callback).Run(status);
return;
}
auto notify_registration_deleted_callback = base::BindOnce(
&ServiceWorkerRegistry::NotifyRegistrationDeletedForStorageKey,
weak_factory_.GetWeakPtr(), registration_id, stored_scope, key,
storage_key_state, std::move(callback));
if (quota_manager_proxy_) {
// Can be nullptr in tests.
quota_manager_proxy_->NotifyBucketModified(
storage::QuotaClientType::kServiceWorker,
storage::BucketLocator::ForDefaultBucket(key), -deleted_resources_size,
base::Time::Now(), base::SequencedTaskRunner::GetCurrentDefault(),
std::move(notify_registration_deleted_callback));
} else {
std::move(notify_registration_deleted_callback).Run();
}
}
void ServiceWorkerRegistry::NotifyRegistrationDeletedForStorageKey(
int64_t registration_id,
const GURL& stored_scope,
const blink::StorageKey& key,
storage::mojom::ServiceWorkerStorageStorageKeyState storage_key_state,
StatusCallback callback) {
scoped_refptr<ServiceWorkerRegistration> registration =
context_->GetLiveRegistration(registration_id);
if (registration)
registration->UnsetStored();
if (storage_key_state ==
storage::mojom::ServiceWorkerStorageStorageKeyState::kDelete) {
registered_storage_keys_.erase(key);
context_->NotifyAllRegistrationsDeletedForStorageKey(key);
if (storage_policy_observer_)
storage_policy_observer_->StopTrackingOrigin(key.origin());
}
// For all other blink::ServiceWorkerStatusCode entries,
// DidDeleteRegistration() schedules deletion and starts over, so the only
// status code that can be called here is the kOk one.
std::move(callback).Run(blink::ServiceWorkerStatusCode::kOk);
}
void ServiceWorkerRegistry::DidUpdateRegistration(
StatusCallback callback,
storage::mojom::ServiceWorkerDatabaseStatus status) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (status != storage::mojom::ServiceWorkerDatabaseStatus::kOk &&
status != storage::mojom::ServiceWorkerDatabaseStatus::kErrorNotFound) {
ScheduleDeleteAndStartOver();
}
std::move(callback).Run(DatabaseStatusToStatusCode(status));
}
void ServiceWorkerRegistry::DidUpdateToActiveState(
const blink::StorageKey& key,
StatusCallback callback,
storage::mojom::ServiceWorkerDatabaseStatus status) {
CheckForClientWriteFailure(quota_manager_proxy_, status, key);
DidUpdateRegistration(std::move(callback), status);
}
void ServiceWorkerRegistry::DidWriteUncommittedResourceIds(
const blink::StorageKey& key,
storage::mojom::ServiceWorkerDatabaseStatus status) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
CheckForClientWriteFailure(quota_manager_proxy_, status, key);
if (status != storage::mojom::ServiceWorkerDatabaseStatus::kOk)
ScheduleDeleteAndStartOver();
}
void ServiceWorkerRegistry::DidDoomUncommittedResourceIds(
storage::mojom::ServiceWorkerDatabaseStatus status) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (status != storage::mojom::ServiceWorkerDatabaseStatus::kOk)
ScheduleDeleteAndStartOver();
}
void ServiceWorkerRegistry::DidGetUserData(
GetUserDataCallback callback,
storage::mojom::ServiceWorkerDatabaseStatus status,
const std::vector<std::string>& data) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (status != storage::mojom::ServiceWorkerDatabaseStatus::kOk &&
status != storage::mojom::ServiceWorkerDatabaseStatus::kErrorNotFound) {
ScheduleDeleteAndStartOver();
}
std::move(callback).Run(data, DatabaseStatusToStatusCode(status));
}
void ServiceWorkerRegistry::DidGetUserKeysAndData(
GetUserKeysAndDataCallback callback,
storage::mojom::ServiceWorkerDatabaseStatus status,
const base::flat_map<std::string, std::string>& data_map) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (status != storage::mojom::ServiceWorkerDatabaseStatus::kOk &&
status != storage::mojom::ServiceWorkerDatabaseStatus::kErrorNotFound) {
ScheduleDeleteAndStartOver();
}
std::move(callback).Run(DatabaseStatusToStatusCode(status), data_map);
}
void ServiceWorkerRegistry::DidStoreUserData(
StatusCallback callback,
const blink::StorageKey& key,
storage::mojom::ServiceWorkerDatabaseStatus status) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
CheckForClientWriteFailure(quota_manager_proxy_, status, key);
// |status| can be NOT_FOUND when the associated registration did not exist in
// the database. In the case, we don't have to schedule the corruption
// recovery.
if (status != storage::mojom::ServiceWorkerDatabaseStatus::kOk &&
status != storage::mojom::ServiceWorkerDatabaseStatus::kErrorNotFound) {
ScheduleDeleteAndStartOver();
}
std::move(callback).Run(DatabaseStatusToStatusCode(status));
}
void ServiceWorkerRegistry::DidClearUserData(
StatusCallback callback,
storage::mojom::ServiceWorkerDatabaseStatus status) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (status != storage::mojom::ServiceWorkerDatabaseStatus::kOk)
ScheduleDeleteAndStartOver();
std::move(callback).Run(DatabaseStatusToStatusCode(status));
}
void ServiceWorkerRegistry::DidGetUserDataForAllRegistrations(
GetUserDataForAllRegistrationsCallback callback,
storage::mojom::ServiceWorkerDatabaseStatus status,
std::vector<storage::mojom::ServiceWorkerUserDataPtr> entries) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// TODO(crbug.com/40120038): Update call sites of
// GetUserDataForAllRegistrations so that we can avoid converting mojo struct
// to a pair.
std::vector<std::pair<int64_t, std::string>> user_data;
if (status != storage::mojom::ServiceWorkerDatabaseStatus::kOk)
ScheduleDeleteAndStartOver();
for (auto& entry : entries) {
user_data.emplace_back(entry->registration_id, entry->value);
}
std::move(callback).Run(user_data, DatabaseStatusToStatusCode(status));
}
void ServiceWorkerRegistry::DidGetNewRegistrationId(
blink::mojom::ServiceWorkerRegistrationOptions options,
const blink::StorageKey& key,
blink::mojom::AncestorFrameType ancestor_frame_type,
NewRegistrationCallback callback,
int64_t registration_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (registration_id == blink::mojom::kInvalidServiceWorkerRegistrationId) {
std::move(callback).Run(nullptr);
return;
}
std::move(callback).Run(ServiceWorkerRegistration::Create(
std::move(options), key, registration_id, context_->AsWeakPtr(),
ancestor_frame_type));
}
void ServiceWorkerRegistry::DidGetNewVersionId(
scoped_refptr<ServiceWorkerRegistration> registration,
const GURL& script_url,
blink::mojom::ScriptType script_type,
NewVersionCallback callback,
int64_t version_id,
mojo::PendingRemote<storage::mojom::ServiceWorkerLiveVersionRef>
version_reference) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (version_id == blink::mojom::kInvalidServiceWorkerVersionId) {
std::move(callback).Run(nullptr);
return;
}
auto version = base::MakeRefCounted<ServiceWorkerVersion>(
registration.get(), script_url, script_type, version_id,
std::move(version_reference), context_->AsWeakPtr());
std::move(callback).Run(std::move(version));
}
void ServiceWorkerRegistry::ScheduleDeleteAndStartOver() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
ClearAllInternalCache();
if (!should_schedule_delete_and_start_over_) {
// Recovery process has already been scheduled.
return;
}
// Ideally, the corruption recovery should not be scheduled if the error
// is transient as it can get healed soon (e.g. IO error). However we
// unconditionally start recovery here for simplicity and low error rates.
DVLOG(1) << "Schedule to delete the context and start over.";
context_->ScheduleDeleteAndStartOver();
// ServiceWorkerContextCore should call PrepareForDeleteAndStartOver().
DCHECK(!should_schedule_delete_and_start_over_);
DCHECK(is_storage_disabled_);
}
void ServiceWorkerRegistry::DidDeleteAndStartOver(
StatusCallback callback,
storage::mojom::ServiceWorkerDatabaseStatus status) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
remote_storage_control_.reset();
ClearAllInternalCache();
std::move(callback).Run(DatabaseStatusToStatusCode(status));
}
void ServiceWorkerRegistry::DidGetRegisteredStorageKeys(
GetRegisteredStorageKeysCallback callback,
const std::vector<blink::StorageKey>& keys) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
std::move(callback).Run(keys);
}
void ServiceWorkerRegistry::DidPerformStorageCleanup(
base::OnceClosure callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
std::move(callback).Run();
}
void ServiceWorkerRegistry::DidDisable() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
void ServiceWorkerRegistry::DidApplyPolicyUpdates(
storage::mojom::ServiceWorkerDatabaseStatus status) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
void ServiceWorkerRegistry::DidGetRegisteredStorageKeysOnStartup(
base::TimeTicks start_time,
const std::vector<blink::StorageKey>& storage_keys) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(special_storage_policy_);
std::vector<url::Origin> origins;
origins.reserve(storage_keys.size());
for (const blink::StorageKey& storage_key : storage_keys) {
origins.push_back(storage_key.origin());
}
storage_policy_observer_->StartTrackingOrigins(origins);
if (ReduceCallingServiceWorkerRegisteredStorageKeysOnStartupEnabled()) {
DidGetRegisteredStorageKeysOnStartupDeprecated(start_time, storage_keys);
}
}
void ServiceWorkerRegistry::ApplyPolicyUpdates(
std::vector<storage::mojom::StoragePolicyUpdatePtr> policy_updates) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (is_storage_disabled_)
return;
if (policy_updates.empty())
return;
CreateInvokerAndStartRemoteCall(
&storage::mojom::ServiceWorkerStorageControl::ApplyPolicyUpdates,
base::BindOnce(&ServiceWorkerRegistry::DidApplyPolicyUpdates,
weak_factory_.GetWeakPtr()),
std::move(policy_updates));
}
bool ServiceWorkerRegistry::ShouldPurgeOnShutdownForTesting(
const blink::StorageKey& key) {
if (!storage_policy_observer_)
return false;
return storage_policy_observer_->ShouldPurgeOnShutdownForTesting( // IN-TEST
key.origin());
}
mojo::Remote<storage::mojom::ServiceWorkerStorageControl>&
ServiceWorkerRegistry::GetRemoteStorageControl() {
// TODO(crbug.com/40813186): Replace CHECK with DCHECK_CURRENTLY_ON
// once the cause is identified.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(!(remote_storage_control_.is_bound() &&
!remote_storage_control_.is_connected()))
<< "Rebinding is not supported yet.";
if (!remote_storage_control_.is_bound()) {
BindStorageControl(remote_storage_control_.BindNewPipeAndPassReceiver(
GetUIThreadTaskRunner(
{BrowserTaskType::kServiceWorkerStorageControlResponse})));
DCHECK(remote_storage_control_.is_bound());
remote_storage_control_.set_disconnect_handler(
base::BindOnce(&ServiceWorkerRegistry::OnRemoteStorageDisconnected,
weak_factory_.GetWeakPtr()));
}
return remote_storage_control_;
}
void ServiceWorkerRegistry::BindStorageControl(
mojo::PendingReceiver<storage::mojom::ServiceWorkerStorageControl>
receiver) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (context_->wrapper()->storage_control_binder_for_test()) {
CHECK_IS_TEST();
context_->wrapper()->storage_control_binder_for_test().Run( // IN-TEST
std::move(receiver));
return;
}
// The database task runner is BLOCK_SHUTDOWN in order to support
// ClearSessionOnlyOrigins() (called due to the "clear on browser exit"
// content setting).
// The ServiceWorkerStorageControl receiver runs on thread pool by using
// |database_task_runner| SequencedTaskRunner.
// TODO(falken): Only block shutdown for that particular task, when someday
// task runners support mixing task shutdown behaviors.
scoped_refptr<base::SequencedTaskRunner> database_task_runner =
base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskShutdownBehavior::BLOCK_SHUTDOWN});
database_task_runner->PostTask(
FROM_HERE,
base::BindOnce(
base::IgnoreResult(&storage::ServiceWorkerStorageControlImpl::Create),
std::move(receiver), context_->wrapper()->user_data_directory(),
storage_shared_buffer_));
}
void ServiceWorkerRegistry::OnRemoteStorageDisconnected() {
const size_t kMaxRetryCounts = 100;
// TODO(crbug.com/40813186): Replace CHECK with DCHECK_CURRENTLY_ON
// once the cause is identified.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
remote_storage_control_.reset();
ClearAllInternalCache();
if (is_storage_disabled_) {
// When the storage is disabled a storage error recovery process is ongoing
// and the storage control will be destroyed soon. Don't try to reconnect
// storage control remote but flush inflight calls. These calls will check
// `is_storage_disabled_` and return errors.
DidRecover();
return;
}
if (connection_state_ == ConnectionState::kRecovering) {
++recovery_retry_counts_;
if (recovery_retry_counts_ > kMaxRetryCounts) {
NOTREACHED() << "The Storage Service consistently crashes.";
}
}
connection_state_ = ConnectionState::kRecovering;
// Collect live version information to recover resource purging state in the
// Storage Service.
std::vector<storage::mojom::ServiceWorkerLiveVersionInfoPtr> versions;
for (auto& it : context_->GetLiveVersions()) {
if (!it.second->is_redundant())
versions.push_back(it.second->RebindStorageReference());
}
GetRemoteStorageControl()->Recover(
std::move(versions), base::BindOnce(&ServiceWorkerRegistry::DidRecover,
weak_factory_.GetWeakPtr()));
}
void ServiceWorkerRegistry::DidRecover() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
recovery_retry_counts_ = 0;
connection_state_ = ConnectionState::kNormal;
// Retry inflight calls.
for (auto& call : inflight_calls_)
call->Run();
}
void ServiceWorkerRegistry::StartRemoteCall(
std::unique_ptr<InflightCall> call) {
auto* raw_call = call.get();
inflight_calls_.insert(std::move(call));
if (connection_state_ == ConnectionState::kNormal) {
raw_call->Run();
}
}
void ServiceWorkerRegistry::FinishRemoteCall(const InflightCall* call) {
DCHECK(base::Contains(inflight_calls_, call));
inflight_calls_.erase(call);
}
void ServiceWorkerRegistry::ClearAllInternalCache() {
registration_scope_cache_.Clear();
registration_id_cache_.Clear();
}
void ServiceWorkerRegistry::ClearInternalCacheForStorageKey(
const blink::StorageKey& storage_key) {
{
auto it = registration_scope_cache_.Peek(storage_key);
if (it != registration_scope_cache_.end()) {
registration_scope_cache_.Erase(it);
}
}
// When `registration_scope_cache_` doesn't have an entry for the given
// `storage_key`, `registration_id_cache_` is not used for the `storage_key`.
// Therefore we remove them all.
for (auto it = registration_id_cache_.begin();
it != registration_id_cache_.end();) {
if (it->first.second == storage_key) {
it = registration_id_cache_.Erase(it);
} else {
++it;
}
}
}
namespace {
template <typename T>
using PassingType = std::conditional_t<std::is_scalar<T>::value, T, const T&>;
template <typename T>
struct RequiresCloneTraits {
static constexpr bool kValue = std::is_copy_constructible<T>::value;
};
// Specialization for vectors with move-only types, since STL does not SFINAE
// disable the copy constructor.
template <typename T>
struct RequiresCloneTraits<std::vector<T>> {
static constexpr bool kValue = std::is_copy_constructible<T>::value;
};
template <typename T,
bool is_copy_constructible = RequiresCloneTraits<T>::kValue>
struct CloneTraits;
template <typename T>
struct CloneTraits<T, true> {
// Simply return a const ref for a copyable type, as the caller can copy if
// needed.
static const T& CloneIfNeeded(const T& in) { return in; }
};
template <typename T>
struct CloneTraits<T, false> {
static T CloneIfNeeded(const T& in) { return mojo::Clone(in); }
};
} // namespace
template <typename Functor, typename... Args, typename... ReplyArgs>
void ServiceWorkerRegistry::CreateInvokerAndStartRemoteCall(
Functor&& f,
base::OnceCallback<void(ReplyArgs...)> reply_callback,
Args&&... args) {
using InflightCallWithInvokerType = InflightCallWithInvoker<ReplyArgs...>;
// This repeating callback (re)issues the Mojo IPC designated by `f`. Note
// that the original passed in `args` end up being owned by `invoker`, and
// ownership is never transferred to the called lambda.
auto invoker = base::BindRepeating(
[](Functor f, PassingType<std::decay_t<Args>>... args,
InflightCallWithInvokerType* inflight_call,
base::OnceCallback<void(ReplyArgs...)> reply_callback) {
DCHECK(inflight_call->registry()
->GetRemoteStorageControl()
.is_connected());
((*inflight_call->registry()->GetRemoteStorageControl()).*f)(
CloneTraits<std::decay_t<Args>>::CloneIfNeeded(args)...,
std::move(reply_callback));
},
std::forward<Functor>(f), std::forward<Args>(args)...);
auto inflight_call = std::make_unique<InflightCallWithInvokerType>(
this, std::move(invoker), std::move(reply_callback));
StartRemoteCall(std::move(inflight_call));
}
} // namespace content