blob: e6da2c6af869e2d09bd30995f1961788a2027fa2 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/network/shared_dictionary/shared_dictionary_manager.h"
#include <algorithm>
#include <memory>
#include <ranges>
#include "base/feature_list.h"
#include "base/location.h"
#include "base/memory/ref_counted.h"
#include "base/trace_event/typed_macros.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "net/base/load_flags.h"
#include "net/shared_dictionary/shared_dictionary.h"
#include "services/network/public/cpp/features.h"
#include "services/network/shared_dictionary/shared_dictionary_manager_in_memory.h"
#include "services/network/shared_dictionary/shared_dictionary_manager_on_disk.h"
#include "services/network/shared_dictionary/shared_dictionary_storage.h"
namespace network {
namespace {
// SharedDictionaryManager keeps 10 instances in cache until there is memory
// pressure.
constexpr size_t kCachedStorageMaxSize = 10;
} // namespace
// static
std::unique_ptr<SharedDictionaryManager>
SharedDictionaryManager::CreateInMemory(uint64_t cache_max_size,
uint64_t cache_max_count) {
return std::make_unique<SharedDictionaryManagerInMemory>(cache_max_size,
cache_max_count);
}
class SharedDictionaryManager::PreloadedDictionaries
: public mojom::PreloadedSharedDictionaryInfoHandle {
public:
PreloadedDictionaries(
mojo::PendingReceiver<mojom::PreloadedSharedDictionaryInfoHandle>
preload_handle,
SharedDictionaryManager* manager)
: receiver_(this, std::move(preload_handle)), manager_(manager) {
receiver_.set_disconnect_handler(base::BindOnce(
&PreloadedDictionaries::OnDisconnected, base::Unretained(this)));
}
~PreloadedDictionaries() override = default;
PreloadedDictionaries(const PreloadedDictionaries&) = delete;
PreloadedDictionaries& operator=(const PreloadedDictionaries&) = delete;
void MaybeAddPreload(const GURL& url, mojom::RequestDestination destination) {
url::Origin frame_origin = url::Origin::Create(url);
if (frame_origin.opaque()) {
return;
}
net::SharedDictionaryIsolationKey isolation_key(frame_origin,
net::SchemefulSite(url));
scoped_refptr<SharedDictionaryStorage> storage =
manager_->GetStorage(isolation_key);
storages_.insert(storage);
storage->GetDictionary(url, destination,
base::BindOnce(&PreloadedDictionaries::OnDictionary,
weak_factory_.GetWeakPtr()));
}
bool Contains(const net::SharedDictionary& other) {
return std::ranges::any_of(
dictionaries_,
[&other](const scoped_refptr<net::SharedDictionary>& dict) {
return dict.get() == &other;
});
}
private:
void OnDisconnected() { manager_->DeletePreloadedDictionaries(this); }
void OnDictionary(scoped_refptr<net::SharedDictionary> dictionary) {
if (dictionary && !Contains(*dictionary)) {
dictionaries_.insert(std::move(dictionary));
}
}
std::set<scoped_refptr<SharedDictionaryStorage>> storages_;
std::set<scoped_refptr<net::SharedDictionary>> dictionaries_;
mojo::Receiver<mojom::PreloadedSharedDictionaryInfoHandle> receiver_;
raw_ptr<SharedDictionaryManager> manager_;
base::WeakPtrFactory<PreloadedDictionaries> weak_factory_{this};
};
// static
std::unique_ptr<SharedDictionaryManager> SharedDictionaryManager::CreateOnDisk(
const base::FilePath& database_path,
const base::FilePath& cache_directory_path,
uint64_t cache_max_size,
uint64_t cache_max_count,
#if BUILDFLAG(IS_ANDROID)
disk_cache::ApplicationStatusListenerGetter app_status_listener_getter,
#endif // BUILDFLAG(IS_ANDROID)
scoped_refptr<disk_cache::BackendFileOperationsFactory>
file_operations_factory) {
return std::make_unique<SharedDictionaryManagerOnDisk>(
database_path, cache_directory_path, cache_max_size, cache_max_count,
#if BUILDFLAG(IS_ANDROID)
app_status_listener_getter,
#endif // BUILDFLAG(IS_ANDROID)
std::move(file_operations_factory));
}
SharedDictionaryManager::SharedDictionaryManager()
: cached_storages_(kCachedStorageMaxSize) {
memory_pressure_listener_registration_ =
std::make_unique<base::AsyncMemoryPressureListenerRegistration>(
FROM_HERE, base::MemoryPressureListenerTag::kSharedDictionaryManager,
this);
}
SharedDictionaryManager::~SharedDictionaryManager() = default;
scoped_refptr<SharedDictionaryStorage> SharedDictionaryManager::GetStorage(
const net::SharedDictionaryIsolationKey& isolation_key) {
TRACE_EVENT("loading", "SharedDictionaryManager::GetStorage");
auto cached_storages_it = cached_storages_.Get(isolation_key);
if (cached_storages_it != cached_storages_.end()) {
return cached_storages_it->second;
}
auto it = storages_.find(isolation_key);
if (it != storages_.end()) {
DCHECK(it->second);
return it->second.get();
}
SharedDictionaryStorageEvictionReason previous_eviction_reason =
SharedDictionaryStorageEvictionReason::kNotEvicted;
if (auto evicted_it = previously_evicted_keys_.find(isolation_key);
evicted_it != previously_evicted_keys_.end()) {
previous_eviction_reason = evicted_it->second;
previously_evicted_keys_.erase(evicted_it);
}
scoped_refptr<SharedDictionaryStorage> storage =
CreateStorage(isolation_key, previous_eviction_reason);
CHECK(storage);
storages_.emplace(isolation_key, storage.get());
if (memory_pressure_level_ == base::MEMORY_PRESSURE_LEVEL_NONE) {
if (cached_storages_.size() >= cached_storages_.max_size()) {
// The cache is full. The last element will be evicted.
previously_evicted_keys_[cached_storages_.rbegin()->first] =
SharedDictionaryStorageEvictionReason::kCacheFull;
}
cached_storages_.Put(isolation_key, storage);
}
return storage;
}
void SharedDictionaryManager::OnStorageDeleted(
const net::SharedDictionaryIsolationKey& isolation_key) {
size_t removed_count = storages_.erase(isolation_key);
DCHECK_EQ(1U, removed_count);
}
base::WeakPtr<SharedDictionaryManager> SharedDictionaryManager::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
void SharedDictionaryManager::OnMemoryPressure(
base::MemoryPressureLevel level) {
memory_pressure_level_ = level;
if (memory_pressure_level_ != base::MEMORY_PRESSURE_LEVEL_NONE) {
SharedDictionaryStorageEvictionReason eviction_reason =
(memory_pressure_level_ == base::MEMORY_PRESSURE_LEVEL_CRITICAL)
? SharedDictionaryStorageEvictionReason::kMemoryPressureCritical
: SharedDictionaryStorageEvictionReason::kMemoryPressureModerate;
for (const auto& it : cached_storages_) {
previously_evicted_keys_[it.first] = eviction_reason;
}
cached_storages_.Clear();
preloaded_dictionaries_set_.clear();
}
HandleMemoryPressure(level);
}
size_t SharedDictionaryManager::GetStorageCountForTesting() {
return storages_.size();
}
net::SharedDictionaryGetter
SharedDictionaryManager::MaybeCreateSharedDictionaryGetter(
int request_load_flags,
mojom::RequestDestination request_destination) {
if (!(request_load_flags & net::LOAD_CAN_USE_SHARED_DICTIONARY)) {
return net::SharedDictionaryGetter();
}
return base::BindRepeating(
[](base::WeakPtr<SharedDictionaryManager> manager,
mojom::RequestDestination request_destination,
const std::optional<net::SharedDictionaryIsolationKey>& isolation_key,
const GURL& request_url) -> scoped_refptr<net::SharedDictionary> {
return manager ? manager->GetDictionaryImpl(request_destination,
isolation_key, request_url)
: nullptr;
},
GetWeakPtr(), request_destination);
}
scoped_refptr<net::SharedDictionary> SharedDictionaryManager::GetDictionaryImpl(
mojom::RequestDestination request_destination,
const std::optional<net::SharedDictionaryIsolationKey>& isolation_key,
const GURL& request_url) {
if (!isolation_key) {
return nullptr;
}
scoped_refptr<net::SharedDictionary> dict =
GetStorage(*isolation_key)
->GetDictionarySync(request_url, request_destination);
// Disable preloaded dictionary usage if the PreloadedDictionaryConditionalUse
// feature is enabled and its binary is not yet loaded.
if (dict &&
base::FeatureList::IsEnabled(
features::kPreloadedDictionaryConditionalUse) &&
std::ranges::any_of(preloaded_dictionaries_set_,
[&dict](const auto& preloaded_dict) {
return preloaded_dict->Contains(*dict);
}) &&
dict->ReadAll(base::BindOnce([](int) {})) != net::OK) {
return nullptr;
}
return dict;
}
void SharedDictionaryManager::PreloadSharedDictionaryInfoForDocument(
const std::vector<GURL>& urls,
mojo::PendingReceiver<mojom::PreloadedSharedDictionaryInfoHandle>
preload_handle) {
if (memory_pressure_level_ != base::MEMORY_PRESSURE_LEVEL_NONE) {
return;
}
auto preloaded_dictionaries =
std::make_unique<PreloadedDictionaries>(std::move(preload_handle), this);
for (const GURL& url : urls) {
preloaded_dictionaries->MaybeAddPreload(
url, mojom::RequestDestination::kDocument);
}
preloaded_dictionaries_set_.insert(std::move(preloaded_dictionaries));
}
void SharedDictionaryManager::DeletePreloadedDictionaries(
PreloadedDictionaries* preloaded_dictionaries) {
auto it = preloaded_dictionaries_set_.find(preloaded_dictionaries);
CHECK(it != preloaded_dictionaries_set_.end());
preloaded_dictionaries_set_.erase(it);
}
bool SharedDictionaryManager::HasPreloadedSharedDictionaryInfo() const {
return !preloaded_dictionaries_set_.empty();
}
} // namespace network