blob: c81b74079fb4f4ccb831701703fe542e0e06d4a5 [file] [log] [blame]
// Copyright 2014 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/cache_storage/cache_storage.h"
#include <stddef.h>
#include <memory>
#include <set>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "base/barrier_closure.h"
#include "base/containers/contains.h"
#include "base/containers/span.h"
#include "base/files/file_util.h"
#include "base/files/memory_mapped_file.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/hash/sha1.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/string_view_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/traced_value.h"
#include "base/uuid.h"
#include "build/build_config.h"
#include "components/services/storage/public/cpp/buckets/bucket_locator.h"
#include "content/browser/cache_storage/cache_storage.pb.h"
#include "content/browser/cache_storage/cache_storage_cache.h"
#include "content/browser/cache_storage/cache_storage_cache_handle.h"
#include "content/browser/cache_storage/cache_storage_histogram_utils.h"
#include "content/browser/cache_storage/cache_storage_index.h"
#include "content/browser/cache_storage/cache_storage_manager.h"
#include "content/browser/cache_storage/cache_storage_quota_client.h"
#include "content/browser/cache_storage/cache_storage_scheduler.h"
#include "content/browser/cache_storage/cache_storage_trace_utils.h"
#include "content/common/background_fetch/background_fetch_types.h"
#include "net/base/directory_lister.h"
#include "net/base/net_errors.h"
#include "storage/browser/blob/blob_storage_context.h"
#include "storage/browser/quota/quota_manager_proxy.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
using blink::mojom::CacheStorageError;
namespace content {
namespace {
std::string HexedHash(const std::string& value) {
std::string value_hash = base::SHA1HashString(value);
return base::ToLowerASCII(base::HexEncode(value_hash));
}
void SizeRetrievedFromAllCaches(std::unique_ptr<int64_t> accumulator,
CacheStorage::SizeCallback callback) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), *accumulator));
}
} // namespace
const char CacheStorage::kIndexFileName[] = "index.txt";
constexpr char16_t kReplacementCharacter = 0xFFFD;
struct CacheStorage::CacheMatchResponse {
CacheMatchResponse() = default;
~CacheMatchResponse() = default;
CacheStorageError error;
blink::mojom::FetchAPIResponsePtr response;
};
std::u16string CacheStorage::ConvertUTF16BytesStringToU16String(
const std::string& utf16_bytes,
bool correct_encoding) {
std::u16string cache_name;
// Interpret `utf16_bytes` as a sequence of raw bytes.
base::span<const uint8_t> serialized_cache_name_bytes =
base::as_byte_span(utf16_bytes);
std::vector<uint8_t> corrected_bytes(serialized_cache_name_bytes.begin(),
serialized_cache_name_bytes.end());
// Validate that the number of bytes in the `serialized_cache_name_bytes`
// (or `corrected_bytes`) is a multiple of 2. Each UTF-16 character
// is 2 bytes, so an odd number of bytes indicates potential data
// corruption or an encoding error.
//
// If an odd number of bytes is detected, the last byte is removed and
// replaced with the 2-byte `kReplacementCharacter` to maintain valid
// UTF-16 encoding. This ensures that the string remains correctly
// formed and can be safely processed.
if (correct_encoding && corrected_bytes.size() % 2 != 0) {
corrected_bytes.pop_back();
base::span<const uint8_t> replacement_char_bytes =
base::byte_span_from_ref(kReplacementCharacter);
corrected_bytes.insert(corrected_bytes.end(),
replacement_char_bytes.begin(),
replacement_char_bytes.end());
}
cache_name.resize(corrected_bytes.size() / sizeof(char16_t));
base::span<uint8_t> cache_name_bytes =
base::as_writable_byte_span(cache_name);
cache_name_bytes.copy_from(corrected_bytes);
return cache_name;
}
// Handles the loading and clean up of CacheStorageCache objects.
class CacheStorage::CacheLoader {
public:
using CacheAndErrorCallback =
base::OnceCallback<void(std::unique_ptr<CacheStorageCache>,
CacheStorageError status)>;
using BoolCallback = base::OnceCallback<void(bool)>;
using CacheStorageIndexLoadCallback =
base::OnceCallback<void(std::unique_ptr<CacheStorageIndex>)>;
CacheLoader(base::SequencedTaskRunner* cache_task_runner,
scoped_refptr<base::SequencedTaskRunner> scheduler_task_runner,
scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy,
scoped_refptr<BlobStorageContextWrapper> blob_storage_context,
CacheStorage* cache_storage,
const storage::BucketLocator& bucket_locator,
storage::mojom::CacheStorageOwner owner)
: cache_task_runner_(cache_task_runner),
scheduler_task_runner_(std::move(scheduler_task_runner)),
quota_manager_proxy_(std::move(quota_manager_proxy)),
blob_storage_context_(std::move(blob_storage_context)),
cache_storage_(cache_storage),
bucket_locator_(bucket_locator),
owner_(owner) {
DCHECK(!bucket_locator_.storage_key.origin().opaque());
}
virtual ~CacheLoader() = default;
// Creates a CacheStorageCache with the given name. It does not attempt to
// load the backend, that happens lazily when the cache is used.
virtual std::unique_ptr<CacheStorageCache> CreateCache(
const std::u16string& cache_name,
int64_t cache_size,
int64_t cache_padding) = 0;
// Deletes any pre-existing cache of the same name and then loads it.
virtual void PrepareNewCacheDestination(const std::u16string& cache_name,
CacheAndErrorCallback callback) = 0;
// After the backend has been deleted, do any extra house keeping such as
// removing the cache's directory.
virtual void CleanUpDeletedCache(CacheStorageCache* cache) = 0;
// Writes the cache index to disk if applicable.
virtual void WriteIndex(const CacheStorageIndex& index,
BoolCallback callback) = 0;
// Loads the cache index from disk if applicable.
virtual void LoadIndex(CacheStorageIndexLoadCallback callback) = 0;
// Called when CacheStorage has created a cache. Used to hold onto a handle to
// the cache if necessary.
virtual void NotifyCacheCreated(const std::u16string& cache_name,
CacheStorageCacheHandle cache_handle) {}
// Notification that the cache for |cache_handle| has been doomed. If the
// loader is holding a handle to the cache, it should drop it now.
virtual void NotifyCacheDoomed(CacheStorageCacheHandle cache_handle) {}
protected:
const scoped_refptr<base::SequencedTaskRunner> cache_task_runner_;
const scoped_refptr<base::SequencedTaskRunner> scheduler_task_runner_;
// Owned by CacheStorage which owns this. This is guaranteed to outlive
// CacheLoader, but we store a reference to keep it alive for callbacks.
scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy_;
scoped_refptr<BlobStorageContextWrapper> blob_storage_context_;
// Raw pointer is safe because this object is owned by cache_storage_.
raw_ptr<CacheStorage> cache_storage_;
const storage::BucketLocator bucket_locator_;
const storage::mojom::CacheStorageOwner owner_;
};
// Creates memory-only ServiceWorkerCaches. Because these caches have no
// persistent storage it is not safe to free them from memory if they might be
// used again. Therefore this class holds a reference to each cache until the
// cache is doomed.
class CacheStorage::MemoryLoader : public CacheStorage::CacheLoader {
public:
MemoryLoader(base::SequencedTaskRunner* cache_task_runner,
scoped_refptr<base::SequencedTaskRunner> scheduler_task_runner,
scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy,
scoped_refptr<BlobStorageContextWrapper> blob_storage_context,
CacheStorage* cache_storage,
const storage::BucketLocator& bucket_locator,
storage::mojom::CacheStorageOwner owner)
: CacheLoader(cache_task_runner,
std::move(scheduler_task_runner),
std::move(quota_manager_proxy),
std::move(blob_storage_context),
cache_storage,
bucket_locator,
owner) {}
std::unique_ptr<CacheStorageCache> CreateCache(
const std::u16string& cache_name,
int64_t cache_size,
int64_t cache_padding) override {
return CacheStorageCache::CreateMemoryCache(
bucket_locator_, owner_, cache_name, cache_storage_,
scheduler_task_runner_, quota_manager_proxy_, blob_storage_context_);
}
void PrepareNewCacheDestination(const std::u16string& cache_name,
CacheAndErrorCallback callback) override {
std::unique_ptr<CacheStorageCache> cache =
CreateCache(cache_name, /*cache_size=*/0, /*cache_padding=*/0);
std::move(callback).Run(std::move(cache), CacheStorageError::kSuccess);
}
void CleanUpDeletedCache(CacheStorageCache* cache) override {}
void WriteIndex(const CacheStorageIndex& index,
BoolCallback callback) override {
std::move(callback).Run(true);
}
void LoadIndex(CacheStorageIndexLoadCallback callback) override {
std::move(callback).Run(std::make_unique<CacheStorageIndex>());
}
void NotifyCacheCreated(const std::u16string& cache_name,
CacheStorageCacheHandle cache_handle) override {
DCHECK(!base::Contains(cache_handles_, cache_name));
cache_handles_.insert(std::make_pair(cache_name, std::move(cache_handle)));
}
void NotifyCacheDoomed(CacheStorageCacheHandle cache_handle) override {
auto* impl = CacheStorageCache::From(cache_handle);
DCHECK(base::Contains(cache_handles_, impl->cache_name()));
cache_handles_.erase(impl->cache_name());
}
private:
typedef std::map<std::u16string, CacheStorageCacheHandle> CacheHandles;
~MemoryLoader() override = default;
// Keep a reference to each cache to ensure that it's not freed before the
// client calls CacheStorage::Delete or the CacheStorage is
// freed.
CacheHandles cache_handles_;
};
class CacheStorage::SimpleCacheLoader : public CacheStorage::CacheLoader {
public:
SimpleCacheLoader(
const base::FilePath& directory_path,
base::SequencedTaskRunner* cache_task_runner,
scoped_refptr<base::SequencedTaskRunner> scheduler_task_runner,
scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy,
scoped_refptr<BlobStorageContextWrapper> blob_storage_context,
CacheStorage* cache_storage,
const storage::BucketLocator& bucket_locator,
storage::mojom::CacheStorageOwner owner)
: CacheLoader(cache_task_runner,
std::move(scheduler_task_runner),
std::move(quota_manager_proxy),
std::move(blob_storage_context),
cache_storage,
bucket_locator,
owner),
directory_path_(directory_path) {}
std::unique_ptr<CacheStorageCache> CreateCache(
const std::u16string& cache_name,
int64_t cache_size,
int64_t cache_padding) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(base::Contains(cache_name_to_cache_dir_, cache_name));
std::string cache_dir = cache_name_to_cache_dir_[cache_name];
base::FilePath cache_path = directory_path_.AppendASCII(cache_dir);
return CacheStorageCache::CreatePersistentCache(
bucket_locator_, owner_, cache_name, cache_storage_, cache_path,
scheduler_task_runner_, quota_manager_proxy_, blob_storage_context_,
cache_size, cache_padding);
}
void PrepareNewCacheDestination(const std::u16string& cache_name,
CacheAndErrorCallback callback) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
cache_task_runner_->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&SimpleCacheLoader::PrepareNewCacheDirectoryInPool,
directory_path_),
base::BindOnce(&SimpleCacheLoader::PrepareNewCacheCreateCache,
weak_ptr_factory_.GetWeakPtr(), cache_name,
std::move(callback)));
}
// Runs on the cache_task_runner_.
static std::tuple<CacheStorageError, std::string>
PrepareNewCacheDirectoryInPool(const base::FilePath& directory_path) {
std::string cache_dir;
base::FilePath cache_path;
do {
cache_dir = base::Uuid::GenerateRandomV4().AsLowercaseString();
cache_path = directory_path.AppendASCII(cache_dir);
} while (base::PathExists(cache_path));
base::File::Error error = base::File::FILE_OK;
if (base::CreateDirectoryAndGetError(cache_path, &error)) {
return std::make_tuple(CacheStorageError::kSuccess, cache_dir);
} else {
CacheStorageError status =
error == base::File::FILE_ERROR_NO_SPACE
? CacheStorageError::kErrorQuotaExceeded
: MakeErrorStorage(ErrorStorageType::kDidCreateNullCache);
return std::make_tuple(status, cache_dir);
}
}
void PrepareNewCacheCreateCache(
const std::u16string& cache_name,
CacheAndErrorCallback callback,
const std::tuple<CacheStorageError, std::string>& result) {
const auto& [status, cache_dir] = result;
if (status != CacheStorageError::kSuccess) {
std::move(callback).Run(nullptr, status);
return;
}
DCHECK(!cache_dir.empty());
cache_name_to_cache_dir_[cache_name] = cache_dir;
std::move(callback).Run(CreateCache(cache_name, CacheStorage::kSizeUnknown,
CacheStorage::kSizeUnknown),
CacheStorageError::kSuccess);
}
void CleanUpDeletedCache(CacheStorageCache* cache) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(base::Contains(doomed_cache_to_path_, cache));
base::FilePath cache_path =
directory_path_.AppendASCII(doomed_cache_to_path_[cache]);
doomed_cache_to_path_.erase(cache);
cache_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&SimpleCacheLoader::CleanUpDeleteCacheDirInPool,
cache_path));
}
static void CleanUpDeleteCacheDirInPool(const base::FilePath& cache_path) {
base::DeletePathRecursively(cache_path);
}
void WriteIndex(const CacheStorageIndex& index,
BoolCallback callback) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// 1. Create the index file as a string. (WriteIndex)
// 2. Write the file to disk. (WriteIndexWriteToFileInPool)
proto::CacheStorageIndex protobuf_index;
// GetURL().spec() is used here rather than Serialize() to ensure
// backwards compatibility with older data. The serializations are
// subtly different, e.g. Origin does not include a trailing "/".
// TODO(crbug.com/41368964): Add a test for validating fields in the proto
// TODO(crbug.com/40177656): Stop setting the origin field once
// `CacheStorageManager` no longer uses the origin as a fallback for
// getting the storage key associated with each cache (for more info, see
// `GetStorageKeysAndLastModifiedOnTaskRunner`).
protobuf_index.set_origin(
bucket_locator_.storage_key.origin().GetURL().spec());
protobuf_index.set_storage_key(bucket_locator_.storage_key.Serialize());
protobuf_index.set_bucket_id(bucket_locator_.id.value());
protobuf_index.set_bucket_is_default(bucket_locator_.is_default);
for (const auto& cache_metadata : index.ordered_cache_metadata()) {
DCHECK(base::Contains(cache_name_to_cache_dir_, cache_metadata.name));
proto::CacheStorageIndex::Cache* index_cache = protobuf_index.add_cache();
index_cache->set_name(base::UTF16ToUTF8(cache_metadata.name));
// Protobuf does not support UTF16 string. Store the cache name as
// byte array.
base::span<const uint8_t> byte_span =
base::as_byte_span(cache_metadata.name);
std::string_view utf16_string_view = base::as_string_view(byte_span);
index_cache->set_u16string_name(std::string(utf16_string_view));
index_cache->set_cache_dir(cache_name_to_cache_dir_[cache_metadata.name]);
if (cache_metadata.size == CacheStorage::kSizeUnknown)
index_cache->clear_size();
else
index_cache->set_size(cache_metadata.size);
index_cache->set_padding(cache_metadata.padding);
index_cache->set_padding_version(
CacheStorageCache::GetResponsePaddingVersion());
}
std::string serialized;
bool success = protobuf_index.SerializeToString(&serialized);
DCHECK(success);
base::FilePath tmp_path = directory_path_.AppendASCII("index.txt.tmp");
base::FilePath index_path =
directory_path_.AppendASCII(CacheStorage::kIndexFileName);
cache_task_runner_->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&SimpleCacheLoader::WriteIndexWriteToFileInPool,
tmp_path, index_path, serialized, quota_manager_proxy_,
bucket_locator_),
std::move(callback));
}
static bool WriteIndexWriteToFileInPool(
const base::FilePath& tmp_path,
const base::FilePath& index_path,
const std::string& data,
scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy,
const storage::BucketLocator& bucket_locator) {
if (!base::WriteFile(tmp_path, data)) {
base::DeleteFile(tmp_path);
quota_manager_proxy->OnClientWriteFailed(bucket_locator.storage_key);
return false;
}
// Atomically rename the temporary index file to become the real one.
return base::ReplaceFile(tmp_path, index_path, nullptr);
}
void LoadIndex(CacheStorageIndexLoadCallback callback) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
cache_task_runner_->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&SimpleCacheLoader::ReadAndMigrateIndexInPool,
directory_path_, quota_manager_proxy_, bucket_locator_),
base::BindOnce(&SimpleCacheLoader::LoadIndexDidReadIndex,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void LoadIndexDidReadIndex(CacheStorageIndexLoadCallback callback,
proto::CacheStorageIndex protobuf_index) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::unique_ptr<std::set<std::string>> cache_dirs(
new std::set<std::string>);
auto index = std::make_unique<CacheStorageIndex>();
for (int i = 0, max = protobuf_index.cache_size(); i < max; ++i) {
const proto::CacheStorageIndex::Cache& cache = protobuf_index.cache(i);
DCHECK(cache.has_cache_dir());
int64_t cache_size =
cache.has_size() ? cache.size() : CacheStorage::kSizeUnknown;
int64_t cache_padding;
if (cache.has_padding()) {
if (cache.has_padding_version() &&
cache.padding_version() ==
CacheStorageCache::GetResponsePaddingVersion()) {
cache_padding = cache.padding();
} else {
// The padding algorithm version changed so set to unknown to force
// recalculation.
cache_padding = CacheStorage::kSizeUnknown;
}
} else {
cache_padding = CacheStorage::kSizeUnknown;
}
// Added support for backward compatibility to handle cache names encoded
// in UTF-8. This change ensures proper handling and storage of both UTF-8
// and UTF-16 encoded cache names. All new cache names are stored as
// UTF-16 encoded strings. Existing cache names that are already stored
// in UTF-8 format need to be read and processed in their original format.
// (crbug.com/41142654).
//
// TODO(crbug.com/401016018): Track UTF-8 cache name usage with metrics.
// Once usage drops below a defined threshold, we can safely remove
// support for UTF-8 cache names and rely solely on UTF-16.
std::u16string cache_name;
if (!cache.u16string_name().empty()) {
std::string cache_name_utf8 = cache.u16string_name();
cache_name = CacheStorage::ConvertUTF16BytesStringToU16String(
cache_name_utf8,
/*correct_encoding=*/true);
} else {
cache_name = base::UTF8ToUTF16(cache.name());
}
index->Insert(CacheStorageIndex::CacheMetadata(cache_name, cache_size,
cache_padding));
cache_name_to_cache_dir_[cache_name] = cache.cache_dir();
cache_dirs->insert(cache.cache_dir());
}
cache_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&DeleteUnreferencedCachesInPool,
directory_path_, std::move(cache_dirs)));
std::move(callback).Run(std::move(index));
}
void NotifyCacheDoomed(CacheStorageCacheHandle cache_handle) override {
auto* impl = CacheStorageCache::From(cache_handle);
DCHECK(base::Contains(cache_name_to_cache_dir_, impl->cache_name()));
auto iter = cache_name_to_cache_dir_.find(impl->cache_name());
doomed_cache_to_path_[cache_handle.value()] = iter->second;
cache_name_to_cache_dir_.erase(iter);
}
private:
friend class MigratedLegacyCacheDirectoryNameTest;
~SimpleCacheLoader() override = default;
// Iterates over the caches and deletes any directory not found in
// |cache_dirs|. Runs on cache_task_runner_
static void DeleteUnreferencedCachesInPool(
const base::FilePath& cache_base_dir,
std::unique_ptr<std::set<std::string>> cache_dirs) {
base::FileEnumerator file_enum(cache_base_dir, false /* recursive */,
base::FileEnumerator::DIRECTORIES);
std::vector<base::FilePath> dirs_to_delete;
{
base::FilePath cache_path;
while (!(cache_path = file_enum.Next()).empty()) {
if (!base::Contains(*cache_dirs, cache_path.BaseName().AsUTF8Unsafe()))
dirs_to_delete.push_back(cache_path);
}
}
for (const base::FilePath& cache_path : dirs_to_delete)
base::DeletePathRecursively(cache_path);
}
// Runs on cache_task_runner_
static proto::CacheStorageIndex ReadAndMigrateIndexInPool(
const base::FilePath& directory_path,
scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy,
const storage::BucketLocator& bucket_locator) {
const base::FilePath index_path =
directory_path.AppendASCII(CacheStorage::kIndexFileName);
proto::CacheStorageIndex index;
std::string body;
if (!base::ReadFileToString(index_path, &body) ||
!index.ParseFromString(body))
return proto::CacheStorageIndex();
body.clear();
base::File::Info file_info;
base::Time index_last_modified;
if (GetFileInfo(index_path, &file_info))
index_last_modified = file_info.last_modified;
bool index_modified = false;
// Look for caches that have no cache_dir. Give any such caches a directory
// with a random name and move them there. Then, rewrite the index file.
// Additionally invalidate the size of any index entries where the cache was
// modified after the index (making it out-of-date). We'll assume that any
// unmigrated index files predate the buckets integration and leave them in
// the directory for first-party Cache Storage instances in the default
// bucket.
for (int i = 0, max = index.cache_size(); i < max; ++i) {
const proto::CacheStorageIndex::Cache& cache = index.cache(i);
if (cache.has_cache_dir()) {
if (cache.has_size()) {
base::FilePath cache_dir =
directory_path.AppendASCII(cache.cache_dir());
if (!GetFileInfo(cache_dir, &file_info) ||
index_last_modified <= file_info.last_modified) {
// Index is older than this cache, so invalidate index entries that
// may change as a result of cache operations.
index.mutable_cache(i)->clear_size();
}
}
} else {
// Find a new home for the caches that don't have a directory (legacy
// caches) since they predate the change where u16string `cache_names`
// were added.
base::FilePath legacy_cache_path =
directory_path.AppendASCII(HexedHash(cache.name()));
std::string cache_dir;
base::FilePath cache_path;
do {
cache_dir = base::Uuid::GenerateRandomV4().AsLowercaseString();
cache_path = directory_path.AppendASCII(cache_dir);
} while (base::PathExists(cache_path));
if (!base::Move(legacy_cache_path, cache_path)) {
// If the move fails then the cache is in a bad state. Return an empty
// index so that the CacheStorage can start fresh. The unreferenced
// caches will be discarded later in initialization.
return proto::CacheStorageIndex();
}
index.mutable_cache(i)->set_cache_dir(cache_dir);
index.mutable_cache(i)->clear_size();
index_modified = true;
}
}
if (!index.has_storage_key()) {
DCHECK(bucket_locator.storage_key.origin().GetURL().spec() ==
index.origin());
index.set_storage_key(bucket_locator.storage_key.Serialize());
index_modified = true;
}
if (!index.has_bucket_id()) {
index.set_bucket_id(bucket_locator.id.value());
index.set_bucket_is_default(bucket_locator.is_default);
index_modified = true;
}
if (index_modified) {
base::FilePath tmp_path = directory_path.AppendASCII("index.txt.tmp");
if (!index.SerializeToString(&body) ||
!WriteIndexWriteToFileInPool(tmp_path, index_path, body,
std::move(quota_manager_proxy),
bucket_locator)) {
return proto::CacheStorageIndex();
}
}
return index;
}
const base::FilePath directory_path_;
std::map<std::u16string, std::string> cache_name_to_cache_dir_;
std::map<CacheStorageCache*, std::string> doomed_cache_to_path_;
SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtrFactory<SimpleCacheLoader> weak_ptr_factory_{this};
};
CacheStorage::CacheStorage(
const base::FilePath& path,
bool memory_only,
base::SequencedTaskRunner* cache_task_runner,
scoped_refptr<base::SequencedTaskRunner> scheduler_task_runner,
scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy,
scoped_refptr<BlobStorageContextWrapper> blob_storage_context,
CacheStorageManager* cache_storage_manager,
const storage::BucketLocator& bucket_locator,
storage::mojom::CacheStorageOwner owner)
: bucket_locator_(bucket_locator),
memory_only_(memory_only),
scheduler_(
new CacheStorageScheduler(CacheStorageSchedulerClient::kStorage,
scheduler_task_runner)),
directory_path_(path),
cache_task_runner_(cache_task_runner),
quota_manager_proxy_(std::move(quota_manager_proxy)),
blob_storage_context_(std::move(blob_storage_context)),
owner_(owner),
cache_storage_manager_(cache_storage_manager) {
if (memory_only) {
cache_loader_ = base::WrapUnique<CacheLoader>(
new MemoryLoader(cache_task_runner_.get(),
std::move(scheduler_task_runner), quota_manager_proxy_,
blob_storage_context_, this, bucket_locator_, owner));
return;
}
cache_loader_ = base::WrapUnique<CacheLoader>(new SimpleCacheLoader(
directory_path_, cache_task_runner_.get(),
std::move(scheduler_task_runner), quota_manager_proxy_,
blob_storage_context_, this, bucket_locator_, owner));
#if BUILDFLAG(IS_ANDROID)
app_status_listener_ =
base::android::ApplicationStatusListener::New(base::BindRepeating(
&CacheStorage::OnApplicationStateChange, weak_factory_.GetWeakPtr()));
#endif
}
CacheStorage::~CacheStorage() {
FlushIndexIfDirty();
}
CacheStorageHandle CacheStorage::CreateHandle() {
return CacheStorageHandle(weak_factory_.GetWeakPtr());
}
void CacheStorage::AddHandleRef() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
handle_ref_count_ += 1;
}
void CacheStorage::DropHandleRef() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_GT(handle_ref_count_, 0U);
handle_ref_count_ -= 1;
if (!handle_ref_count_ && cache_storage_manager_) {
ReleaseUnreferencedCaches();
cache_storage_manager_->CacheStorageUnreferenced(this, bucket_locator_,
owner_);
}
}
void CacheStorage::Init() {
if (!initialized_)
LazyInit();
}
void CacheStorage::OpenCache(const std::u16string& cache_name,
int64_t trace_id,
CacheAndErrorCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!initialized_)
LazyInit();
quota_manager_proxy_->NotifyBucketAccessed(bucket_locator_,
base::Time::Now());
// TODO: Hold a handle to this CacheStorage instance while executing
// operations to better support use by internal code that may
// start a single operation without explicitly maintaining a
// handle.
auto id = scheduler_->CreateId();
scheduler_->ScheduleOperation(
id, CacheStorageSchedulerMode::kExclusive, CacheStorageSchedulerOp::kOpen,
CacheStorageSchedulerPriority::kNormal,
base::BindOnce(
&CacheStorage::OpenCacheImpl, weak_factory_.GetWeakPtr(), cache_name,
trace_id,
scheduler_->WrapCallbackToRunNext(id, std::move(callback))));
}
void CacheStorage::HasCache(const std::u16string& cache_name,
int64_t trace_id,
BoolAndErrorCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!initialized_)
LazyInit();
quota_manager_proxy_->NotifyBucketAccessed(bucket_locator_,
base::Time::Now());
auto id = scheduler_->CreateId();
scheduler_->ScheduleOperation(
id, CacheStorageSchedulerMode::kShared, CacheStorageSchedulerOp::kHas,
CacheStorageSchedulerPriority::kNormal,
base::BindOnce(
&CacheStorage::HasCacheImpl, weak_factory_.GetWeakPtr(), cache_name,
trace_id,
scheduler_->WrapCallbackToRunNext(id, std::move(callback))));
}
void CacheStorage::DoomCache(const std::u16string& cache_name,
int64_t trace_id,
ErrorCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!initialized_)
LazyInit();
quota_manager_proxy_->NotifyBucketAccessed(bucket_locator_,
base::Time::Now());
auto id = scheduler_->CreateId();
scheduler_->ScheduleOperation(
id, CacheStorageSchedulerMode::kExclusive,
CacheStorageSchedulerOp::kDelete, CacheStorageSchedulerPriority::kNormal,
base::BindOnce(
&CacheStorage::DoomCacheImpl, weak_factory_.GetWeakPtr(), cache_name,
trace_id,
scheduler_->WrapCallbackToRunNext(id, std::move(callback))));
}
void CacheStorage::EnumerateCaches(int64_t trace_id,
EnumerateCachesCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!initialized_)
LazyInit();
quota_manager_proxy_->NotifyBucketAccessed(bucket_locator_,
base::Time::Now());
auto id = scheduler_->CreateId();
scheduler_->ScheduleOperation(
id, CacheStorageSchedulerMode::kShared, CacheStorageSchedulerOp::kKeys,
CacheStorageSchedulerPriority::kNormal,
base::BindOnce(
&CacheStorage::EnumerateCachesImpl, weak_factory_.GetWeakPtr(),
trace_id,
scheduler_->WrapCallbackToRunNext(id, std::move(callback))));
}
void CacheStorage::MatchCache(const std::u16string& cache_name,
blink::mojom::FetchAPIRequestPtr request,
blink::mojom::CacheQueryOptionsPtr match_options,
CacheStorageSchedulerPriority priority,
int64_t trace_id,
CacheStorageCache::ResponseCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!initialized_)
LazyInit();
quota_manager_proxy_->NotifyBucketAccessed(bucket_locator_,
base::Time::Now());
auto id = scheduler_->CreateId();
scheduler_->ScheduleOperation(
id, CacheStorageSchedulerMode::kShared, CacheStorageSchedulerOp::kMatch,
CacheStorageSchedulerPriority::kNormal,
base::BindOnce(
&CacheStorage::MatchCacheImpl, weak_factory_.GetWeakPtr(), cache_name,
std::move(request), std::move(match_options), priority, trace_id,
scheduler_->WrapCallbackToRunNext(id, std::move(callback))));
}
void CacheStorage::MatchAllCaches(
blink::mojom::FetchAPIRequestPtr request,
blink::mojom::CacheQueryOptionsPtr match_options,
CacheStorageSchedulerPriority priority,
int64_t trace_id,
CacheStorageCache::ResponseCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!initialized_)
LazyInit();
quota_manager_proxy_->NotifyBucketAccessed(bucket_locator_,
base::Time::Now());
auto id = scheduler_->CreateId();
scheduler_->ScheduleOperation(
id, CacheStorageSchedulerMode::kShared,
CacheStorageSchedulerOp::kMatchAll,
CacheStorageSchedulerPriority::kNormal,
base::BindOnce(
&CacheStorage::MatchAllCachesImpl, weak_factory_.GetWeakPtr(),
std::move(request), std::move(match_options), priority, trace_id,
scheduler_->WrapCallbackToRunNext(id, std::move(callback))));
}
void CacheStorage::WriteToCache(const std::u16string& cache_name,
blink::mojom::FetchAPIRequestPtr request,
blink::mojom::FetchAPIResponsePtr response,
int64_t trace_id,
CacheStorage::ErrorCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!initialized_)
LazyInit();
quota_manager_proxy_->NotifyBucketAccessed(bucket_locator_,
base::Time::Now());
// Note, this is a shared operation since it only reads CacheStorage data.
// The CacheStorageCache is responsible for making its put operation
// exclusive.
auto id = scheduler_->CreateId();
scheduler_->ScheduleOperation(
id, CacheStorageSchedulerMode::kShared, CacheStorageSchedulerOp::kPut,
CacheStorageSchedulerPriority::kNormal,
base::BindOnce(
&CacheStorage::WriteToCacheImpl, weak_factory_.GetWeakPtr(),
cache_name, std::move(request), std::move(response), trace_id,
scheduler_->WrapCallbackToRunNext(id, std::move(callback))));
}
void CacheStorage::GetSizeThenCloseAllCaches(SizeCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!initialized_)
LazyInit();
// Note, this is a shared operation since it only reads CacheStorage data.
// The CacheStorageCache is responsible for making its close operation
// exclusive.
auto id = scheduler_->CreateId();
scheduler_->ScheduleOperation(
id, CacheStorageSchedulerMode::kShared,
CacheStorageSchedulerOp::kSizeThenClose,
CacheStorageSchedulerPriority::kNormal,
base::BindOnce(
&CacheStorage::GetSizeThenCloseAllCachesImpl,
weak_factory_.GetWeakPtr(),
scheduler_->WrapCallbackToRunNext(id, std::move(callback))));
}
void CacheStorage::Size(CacheStorage::SizeCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!initialized_)
LazyInit();
auto id = scheduler_->CreateId();
scheduler_->ScheduleOperation(
id, CacheStorageSchedulerMode::kShared, CacheStorageSchedulerOp::kSize,
CacheStorageSchedulerPriority::kNormal,
base::BindOnce(
&CacheStorage::SizeImpl, weak_factory_.GetWeakPtr(),
scheduler_->WrapCallbackToRunNext(id, std::move(callback))));
}
void CacheStorage::ResetManager() {
cache_storage_manager_ = nullptr;
}
void CacheStorage::NotifyCacheContentChanged(const std::u16string& cache_name) {
if (cache_storage_manager_)
cache_storage_manager_->NotifyCacheContentChanged(bucket_locator_,
cache_name);
}
void CacheStorage::ScheduleWriteIndex() {
// These values are chosen to be equal or greater than the simple disk_cache
// index write delays. We want the cache_storage index to be written last.
static const int64_t kWriteIndexDelayMilliseconds = 20050;
static const int64_t kWriteIndexBackgroundDelayMilliseconds = 150;
int64_t delay_ms = app_on_background_ ? kWriteIndexBackgroundDelayMilliseconds
: kWriteIndexDelayMilliseconds;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
index_write_task_.Reset(base::BindOnce(&CacheStorage::WriteIndex,
weak_factory_.GetWeakPtr(),
base::DoNothing()));
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, index_write_task_.callback(), base::Milliseconds(delay_ms));
}
void CacheStorage::WriteIndex(base::OnceCallback<void(bool)> callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto id = scheduler_->CreateId();
scheduler_->ScheduleOperation(
id, CacheStorageSchedulerMode::kExclusive,
CacheStorageSchedulerOp::kWriteIndex,
CacheStorageSchedulerPriority::kNormal,
base::BindOnce(
&CacheStorage::WriteIndexImpl, weak_factory_.GetWeakPtr(),
scheduler_->WrapCallbackToRunNext(id, std::move(callback))));
}
void CacheStorage::WriteIndexImpl(base::OnceCallback<void(bool)> callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(scheduler_->IsRunningExclusiveOperation());
cache_loader_->WriteIndex(*cache_index_, std::move(callback));
}
bool CacheStorage::InitiateScheduledIndexWriteForTest(
base::OnceCallback<void(bool)> callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (index_write_pending()) {
index_write_task_.Cancel();
WriteIndex(std::move(callback));
return true;
}
std::move(callback).Run(true /* success */);
return false;
}
void CacheStorage::CacheSizeUpdated(const CacheStorageCache* cache) {
// Should not be called for doomed caches.
DCHECK(
!base::Contains(doomed_caches_, const_cast<CacheStorageCache*>(cache)));
DCHECK_NE(cache->cache_padding(), kSizeUnknown);
bool size_changed =
cache_index_->SetCacheSize(cache->cache_name(), cache->cache_size());
bool padding_changed = cache_index_->SetCachePadding(cache->cache_name(),
cache->cache_padding());
if (size_changed || padding_changed)
ScheduleWriteIndex();
}
void CacheStorage::ReleaseUnreferencedCaches() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& entry : cache_map_) {
if (entry.second && entry.second->IsUnreferenced())
entry.second.reset();
}
}
void CacheStorage::CacheUnreferenced(CacheStorageCache* cache) {
DCHECK(cache);
DCHECK(cache->IsUnreferenced());
auto doomed_caches_it = doomed_caches_.find(cache);
if (doomed_caches_it != doomed_caches_.end()) {
// The last reference to a doomed cache is gone, perform clean up.
DeleteCacheFinalize(cache);
return;
}
// Opportunistically keep warmed caches open when the CacheStorage is
// still actively referenced. Repeatedly opening and closing simple
// disk_cache backends can be quite slow. This is easy to trigger when
// a site uses caches.match() frequently because the a Cache object is
// never exposed to script to explicitly hold the backend open.
if (handle_ref_count_)
return;
// The CacheStorage is not actively being referenced. Close the cache
// immediately.
auto cache_map_it = cache_map_.find(cache->cache_name());
CHECK(cache_map_it != cache_map_.end());
cache_map_it->second.reset();
}
CacheStorageSchedulerId CacheStorage::StartAsyncOperationForTesting() {
auto id = scheduler_->CreateId();
scheduler_->ScheduleOperation(
id, CacheStorageSchedulerMode::kExclusive, CacheStorageSchedulerOp::kTest,
CacheStorageSchedulerPriority::kNormal, base::DoNothing());
return id;
}
void CacheStorage::CompleteAsyncOperationForTesting(
CacheStorageSchedulerId id) {
scheduler_->CompleteOperationAndRunNext(id);
}
// Init is run lazily so that it is called on the proper MessageLoop.
void CacheStorage::LazyInit() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!initialized_);
if (initializing_)
return;
DCHECK(!scheduler_->ScheduledOperations());
initializing_ = true;
init_id_ = scheduler_->CreateId();
scheduler_->ScheduleOperation(
init_id_, CacheStorageSchedulerMode::kExclusive,
CacheStorageSchedulerOp::kInit, CacheStorageSchedulerPriority::kNormal,
base::BindOnce(&CacheStorage::LazyInitImpl, weak_factory_.GetWeakPtr()));
}
void CacheStorage::LazyInitImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!initialized_);
DCHECK(initializing_);
// 1. Get the cache index (async call)
// 2. For each cache name, load the cache (async call)
// 3. Once each load is complete, update the map variables.
// 4. Call the list of waiting callbacks.
DCHECK(scheduler_->IsRunningExclusiveOperation());
cache_loader_->LoadIndex(base::BindOnce(&CacheStorage::LazyInitDidLoadIndex,
weak_factory_.GetWeakPtr()));
}
void CacheStorage::LazyInitDidLoadIndex(
std::unique_ptr<CacheStorageIndex> index) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(cache_map_.empty());
for (const auto& cache_metadata : index->ordered_cache_metadata()) {
cache_map_.insert(std::make_pair(cache_metadata.name, nullptr));
}
DCHECK(!cache_index_);
cache_index_ = std::move(index);
initializing_ = false;
initialized_ = true;
scheduler_->CompleteOperationAndRunNext(init_id_);
}
void CacheStorage::OpenCacheImpl(const std::u16string& cache_name,
int64_t trace_id,
CacheAndErrorCallback callback) {
TRACE_EVENT_WITH_FLOW1("CacheStorage", "CacheStorage::OpenCacheImpl",
TRACE_ID_GLOBAL(trace_id),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
"cache_name", cache_name);
CacheStorageCacheHandle cache_handle = GetLoadedCache(cache_name);
if (cache_handle.value()) {
std::move(callback).Run(std::move(cache_handle),
CacheStorageError::kSuccess);
return;
}
DCHECK(scheduler_->IsRunningExclusiveOperation());
cache_loader_->PrepareNewCacheDestination(
cache_name, base::BindOnce(&CacheStorage::CreateCacheDidCreateCache,
weak_factory_.GetWeakPtr(), cache_name,
trace_id, std::move(callback)));
}
void CacheStorage::CreateCacheDidCreateCache(
const std::u16string& cache_name,
int64_t trace_id,
CacheAndErrorCallback callback,
std::unique_ptr<CacheStorageCache> cache,
CacheStorageError status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
TRACE_EVENT_WITH_FLOW0("CacheStorage",
"CacheStorage::CreateCacheDidCreateCache",
TRACE_ID_GLOBAL(trace_id),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
if (status != CacheStorageError::kSuccess) {
std::move(callback).Run(CacheStorageCacheHandle(), status);
return;
}
CacheStorageCache* cache_ptr = cache.get();
cache_map_.insert(std::make_pair(cache_name, std::move(cache)));
cache_index_->Insert(CacheStorageIndex::CacheMetadata(
cache_name, cache_ptr->cache_size(), cache_ptr->cache_padding()));
CacheStorageCacheHandle handle = cache_ptr->CreateHandle();
index_write_task_.Cancel();
cache_loader_->WriteIndex(
*cache_index_,
base::BindOnce(&CacheStorage::CreateCacheDidWriteIndex,
weak_factory_.GetWeakPtr(), std::move(callback),
cache_ptr->CreateHandle(), trace_id));
cache_loader_->NotifyCacheCreated(cache_name, std::move(handle));
if (cache_storage_manager_)
cache_storage_manager_->NotifyCacheListChanged(bucket_locator_);
}
void CacheStorage::CreateCacheDidWriteIndex(
CacheAndErrorCallback callback,
CacheStorageCacheHandle cache_handle,
int64_t trace_id,
bool success) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(cache_handle.value());
TRACE_EVENT_WITH_FLOW0("CacheStorage",
"CacheStorage::CreateCacheDidWriteIndex",
TRACE_ID_GLOBAL(trace_id),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
// TODO(jkarlin): Handle !success.
std::move(callback).Run(std::move(cache_handle), CacheStorageError::kSuccess);
}
void CacheStorage::HasCacheImpl(const std::u16string& cache_name,
int64_t trace_id,
BoolAndErrorCallback callback) {
TRACE_EVENT_WITH_FLOW1("CacheStorage", "CacheStorage::HasCacheImpl",
TRACE_ID_GLOBAL(trace_id),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
"cache_name", cache_name);
bool has_cache = base::Contains(cache_map_, cache_name);
std::move(callback).Run(has_cache, CacheStorageError::kSuccess);
}
void CacheStorage::DoomCacheImpl(const std::u16string& cache_name,
int64_t trace_id,
ErrorCallback callback) {
TRACE_EVENT_WITH_FLOW1("CacheStorage", "CacheStorage::DoomCacheImpl",
TRACE_ID_GLOBAL(trace_id),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
"cache_name", cache_name);
CacheStorageCacheHandle cache_handle = GetLoadedCache(cache_name);
if (!cache_handle.value()) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback), CacheStorageError::kErrorNotFound));
return;
}
DCHECK(scheduler_->IsRunningExclusiveOperation());
CacheStorageCache::From(cache_handle)->SetObserver(nullptr);
cache_index_->DoomCache(cache_name);
index_write_task_.Cancel();
cache_loader_->WriteIndex(
*cache_index_,
base::BindOnce(&CacheStorage::DeleteCacheDidWriteIndex,
weak_factory_.GetWeakPtr(), std::move(cache_handle),
std::move(callback), trace_id));
}
void CacheStorage::DeleteCacheDidWriteIndex(
CacheStorageCacheHandle cache_handle,
ErrorCallback callback,
int64_t trace_id,
bool success) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto* impl = CacheStorageCache::From(cache_handle);
TRACE_EVENT_WITH_FLOW0("CacheStorage",
"CacheStorage::DeleteCacheDidWriteIndex",
TRACE_ID_GLOBAL(trace_id),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
if (!success) {
// Undo any changes if the index couldn't be written to disk.
cache_index_->RestoreDoomedCache();
impl->SetObserver(this);
std::move(callback).Run(
MakeErrorStorage(ErrorStorageType::kDeleteCacheFailed));
return;
}
cache_index_->FinalizeDoomedCache();
auto map_iter = cache_map_.find(impl->cache_name());
CHECK(map_iter != cache_map_.end());
doomed_caches_.insert(
std::make_pair(map_iter->second.get(), std::move(map_iter->second)));
cache_map_.erase(map_iter);
cache_loader_->NotifyCacheDoomed(std::move(cache_handle));
if (cache_storage_manager_)
cache_storage_manager_->NotifyCacheListChanged(bucket_locator_);
std::move(callback).Run(CacheStorageError::kSuccess);
}
// Call this once the last handle to a doomed cache is gone. It's okay if this
// doesn't get to complete before shutdown, the cache will be removed from disk
// on next startup in that case.
void CacheStorage::DeleteCacheFinalize(CacheStorageCache* doomed_cache) {
doomed_cache->Size(base::BindOnce(&CacheStorage::DeleteCacheDidGetSize,
weak_factory_.GetWeakPtr(), doomed_cache));
}
void CacheStorage::DeleteCacheDidGetSize(CacheStorageCache* doomed_cache,
int64_t cache_size) {
quota_manager_proxy_->NotifyBucketModified(
CacheStorageQuotaClient::GetClientTypeFromOwner(owner_), bucket_locator_,
-cache_size, base::Time::Now(),
base::SequencedTaskRunner::GetCurrentDefault(), base::DoNothing());
cache_loader_->CleanUpDeletedCache(doomed_cache);
auto doomed_caches_iter = doomed_caches_.find(doomed_cache);
CHECK(doomed_caches_iter != doomed_caches_.end());
doomed_caches_.erase(doomed_caches_iter);
}
void CacheStorage::EnumerateCachesImpl(int64_t trace_id,
EnumerateCachesCallback callback) {
TRACE_EVENT_WITH_FLOW0("CacheStorage", "CacheStorage::EnumerateCachesImpl",
TRACE_ID_GLOBAL(trace_id),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
std::vector<std::u16string> list;
for (const auto& metadata : cache_index_->ordered_cache_metadata()) {
list.push_back(metadata.name);
}
std::move(callback).Run(std::move(list));
}
void CacheStorage::MatchCacheImpl(
const std::u16string& cache_name,
blink::mojom::FetchAPIRequestPtr request,
blink::mojom::CacheQueryOptionsPtr match_options,
CacheStorageSchedulerPriority priority,
int64_t trace_id,
CacheStorageCache::ResponseCallback callback) {
TRACE_EVENT_WITH_FLOW2(
"CacheStorage", "CacheStorage::MatchCacheImpl", TRACE_ID_GLOBAL(trace_id),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "cache_name",
cache_name, "request", CacheStorageTracedValue(request));
CacheStorageCacheHandle cache_handle = GetLoadedCache(cache_name);
if (!cache_handle.value()) {
std::move(callback).Run(CacheStorageError::kErrorCacheNameNotFound,
nullptr);
return;
}
// Pass the cache handle along to the callback to keep the cache open until
// match is done.
CacheStorageCache* cache_ptr = cache_handle.value();
cache_ptr->Match(
std::move(request), std::move(match_options), priority, trace_id,
base::BindOnce(&CacheStorage::MatchCacheDidMatch,
weak_factory_.GetWeakPtr(), std::move(cache_handle),
trace_id, std::move(callback)));
}
void CacheStorage::MatchCacheDidMatch(
CacheStorageCacheHandle cache_handle,
int64_t trace_id,
CacheStorageCache::ResponseCallback callback,
CacheStorageError error,
blink::mojom::FetchAPIResponsePtr response) {
TRACE_EVENT_WITH_FLOW0("CacheStorage", "CacheStorage::MatchCacheDidMatch",
TRACE_ID_GLOBAL(trace_id),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
std::move(callback).Run(error, std::move(response));
}
void CacheStorage::MatchAllCachesImpl(
blink::mojom::FetchAPIRequestPtr request,
blink::mojom::CacheQueryOptionsPtr match_options,
CacheStorageSchedulerPriority priority,
int64_t trace_id,
CacheStorageCache::ResponseCallback callback) {
TRACE_EVENT_WITH_FLOW0("CacheStorage", "CacheStorage::MatchAllCachesImpl",
TRACE_ID_GLOBAL(trace_id),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
std::vector<CacheMatchResponse>* match_responses =
new std::vector<CacheMatchResponse>(cache_index_->num_entries());
base::RepeatingClosure barrier_closure = base::BarrierClosure(
cache_index_->num_entries(),
base::BindOnce(
&CacheStorage::MatchAllCachesDidMatchAll, weak_factory_.GetWeakPtr(),
base::WrapUnique(match_responses), trace_id, std::move(callback)));
size_t idx = 0;
for (const auto& cache_metadata : cache_index_->ordered_cache_metadata()) {
CacheStorageCacheHandle cache_handle = GetLoadedCache(cache_metadata.name);
DCHECK(cache_handle.value());
CacheStorageCache* cache_ptr = cache_handle.value();
cache_ptr->Match(
BackgroundFetchSettledFetch::CloneRequest(request),
match_options ? match_options->Clone() : nullptr, priority, trace_id,
base::BindOnce(&CacheStorage::MatchAllCachesDidMatch,
weak_factory_.GetWeakPtr(), std::move(cache_handle),
&match_responses->at(idx), barrier_closure, trace_id));
idx++;
}
}
void CacheStorage::MatchAllCachesDidMatch(
CacheStorageCacheHandle cache_handle,
CacheMatchResponse* out_match_response,
const base::RepeatingClosure& barrier_closure,
int64_t trace_id,
CacheStorageError error,
blink::mojom::FetchAPIResponsePtr response) {
TRACE_EVENT_WITH_FLOW0("CacheStorage", "CacheStorage::MatchAllCachesDidMatch",
TRACE_ID_GLOBAL(trace_id),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
out_match_response->error = error;
out_match_response->response = std::move(response);
barrier_closure.Run();
}
void CacheStorage::MatchAllCachesDidMatchAll(
std::unique_ptr<std::vector<CacheMatchResponse>> match_responses,
int64_t trace_id,
CacheStorageCache::ResponseCallback callback) {
TRACE_EVENT_WITH_FLOW0("CacheStorage",
"CacheStorage::MatchAllCachesDidMatchAll",
TRACE_ID_GLOBAL(trace_id),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
for (CacheMatchResponse& match_response : *match_responses) {
if (match_response.error == CacheStorageError::kErrorNotFound)
continue;
std::move(callback).Run(match_response.error,
std::move(match_response.response));
return;
}
std::move(callback).Run(CacheStorageError::kErrorNotFound, nullptr);
}
void CacheStorage::WriteToCacheImpl(const std::u16string& cache_name,
blink::mojom::FetchAPIRequestPtr request,
blink::mojom::FetchAPIResponsePtr response,
int64_t trace_id,
CacheStorage::ErrorCallback callback) {
TRACE_EVENT_WITH_FLOW2("CacheStorage", "CacheStorage::WriteToCacheImpl",
TRACE_ID_GLOBAL(trace_id),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
"cache_name", cache_name, "request",
CacheStorageTracedValue(request));
CacheStorageCacheHandle cache_handle = GetLoadedCache(cache_name);
if (!cache_handle.value()) {
std::move(callback).Run(CacheStorageError::kErrorCacheNameNotFound);
return;
}
CacheStorageCache* cache_ptr = cache_handle.value();
DCHECK(cache_ptr);
cache_ptr->Put(std::move(request), std::move(response), trace_id,
std::move(callback));
}
CacheStorageCacheHandle CacheStorage::GetLoadedCache(
const std::u16string& cache_name) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(initialized_);
auto map_iter = cache_map_.find(cache_name);
if (map_iter == cache_map_.end())
return CacheStorageCacheHandle();
CacheStorageCache* cache = map_iter->second.get();
if (!cache) {
const CacheStorageIndex::CacheMetadata* metadata =
cache_index_->GetMetadata(cache_name);
DCHECK(metadata);
std::unique_ptr<CacheStorageCache> new_cache = cache_loader_->CreateCache(
cache_name, metadata->size, metadata->padding);
CacheStorageCache* cache_ptr = new_cache.get();
map_iter->second = std::move(new_cache);
return cache_ptr->CreateHandle();
}
return cache->CreateHandle();
}
void CacheStorage::SizeRetrievedFromCache(CacheStorageCacheHandle cache_handle,
base::OnceClosure closure,
int64_t* accumulator,
int64_t size) {
auto* impl = CacheStorageCache::From(cache_handle);
if (doomed_caches_.find(impl) == doomed_caches_.end()) {
cache_index_->SetCacheSize(impl->cache_name(), impl->cache_size());
cache_index_->SetCachePadding(impl->cache_name(), impl->cache_padding());
}
*accumulator += (impl->cache_size() + impl->cache_padding());
std::move(closure).Run();
}
void CacheStorage::GetSizeThenCloseAllCachesImpl(SizeCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(initialized_);
std::unique_ptr<int64_t> accumulator(new int64_t(0));
int64_t* accumulator_ptr = accumulator.get();
base::RepeatingClosure barrier_closure = base::BarrierClosure(
cache_index_->num_entries() + doomed_caches_.size(),
base::BindOnce(&SizeRetrievedFromAllCaches, std::move(accumulator),
std::move(callback)));
for (const auto& cache_metadata : cache_index_->ordered_cache_metadata()) {
auto cache_handle = GetLoadedCache(cache_metadata.name);
CacheStorageCache* cache = CacheStorageCache::From(cache_handle);
cache->GetSizeThenClose(base::BindOnce(
&CacheStorage::SizeRetrievedFromCache, weak_factory_.GetWeakPtr(),
std::move(cache_handle), barrier_closure, accumulator_ptr));
}
for (const auto& cache_it : doomed_caches_) {
CacheStorageCache* cache = cache_it.first;
cache->GetSizeThenClose(base::BindOnce(
&CacheStorage::SizeRetrievedFromCache, weak_factory_.GetWeakPtr(),
cache->CreateHandle(), barrier_closure, accumulator_ptr));
}
}
void CacheStorage::SizeImpl(SizeCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(initialized_);
if (cache_index_->GetPaddedStorageSize() != kSizeUnknown) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback),
cache_index_->GetPaddedStorageSize()));
return;
}
std::unique_ptr<int64_t> accumulator(new int64_t(0));
int64_t* accumulator_ptr = accumulator.get();
base::RepeatingClosure barrier_closure = base::BarrierClosure(
cache_index_->num_entries(),
base::BindOnce(&SizeRetrievedFromAllCaches, std::move(accumulator),
std::move(callback)));
for (const auto& cache_metadata : cache_index_->ordered_cache_metadata()) {
if (cache_metadata.size != CacheStorage::kSizeUnknown &&
cache_metadata.padding != CacheStorage::kSizeUnknown) {
*accumulator_ptr += (cache_metadata.size + cache_metadata.padding);
barrier_closure.Run();
continue;
}
CacheStorageCacheHandle cache_handle = GetLoadedCache(cache_metadata.name);
CacheStorageCache* cache = CacheStorageCache::From(cache_handle);
cache->Size(base::BindOnce(
&CacheStorage::SizeRetrievedFromCache, weak_factory_.GetWeakPtr(),
std::move(cache_handle), barrier_closure, accumulator_ptr));
}
}
void CacheStorage::FlushIndexIfDirty() {
if (!index_write_pending())
return;
index_write_task_.Cancel();
cache_loader_->WriteIndex(*cache_index_, base::DoNothing());
}
#if BUILDFLAG(IS_ANDROID)
void CacheStorage::OnApplicationStateChange(
base::android::ApplicationState state) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (state == base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES) {
app_on_background_ = false;
} else if (state == base::android::APPLICATION_STATE_HAS_STOPPED_ACTIVITIES) {
app_on_background_ = true;
FlushIndexIfDirty();
}
}
#endif
} // namespace content