1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382
|
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_CONTEXT_IMPL_H_
#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_CONTEXT_IMPL_H_
#include <cstdint>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <vector>
#include "base/files/file_path.h"
#include "base/gtest_prod_util.h"
#include "base/memory/weak_ptr.h"
#include "base/threading/sequence_bound.h"
#include "components/services/storage/privileged/mojom/indexed_db_client_state_checker.mojom.h"
#include "components/services/storage/privileged/mojom/indexed_db_control.mojom.h"
#include "components/services/storage/privileged/mojom/indexed_db_control_test.mojom.h"
#include "components/services/storage/public/cpp/quota_client_callback_wrapper.h"
#include "components/services/storage/public/mojom/blob_storage_context.mojom.h"
#include "components/services/storage/public/mojom/file_system_access_context.mojom.h"
#include "components/services/storage/public/mojom/quota_client.mojom.h"
#include "components/services/storage/public/mojom/storage_policy_update.mojom.h"
#include "content/browser/indexed_db/instance/bucket_context.h"
#include "content/common/content_export.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/remote_set.h"
#include "net/base/schemeful_site.h"
#include "storage/browser/quota/quota_manager_proxy.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
namespace base {
class FilePath;
class SequencedTaskRunner;
} // namespace base
namespace storage {
struct BucketLocator;
class QuotaClientCallbackWrapper;
} // namespace storage
namespace content::indexed_db {
// This class manages all the active/open backing stores for IndexedDB, of which
// there is at most one per bucket. It also serves as the central liaison to
// other Chromium components such as the quota manager. It runs on its own
// thread, so almost all interaction is declared via a mojo interface.
class CONTENT_EXPORT IndexedDBContextImpl
: public storage::mojom::IndexedDBControl,
public storage::mojom::IndexedDBControlTest,
public storage::mojom::QuotaClient {
public:
// If `base_data_path` is empty, nothing will be saved to disk.
// This is *not* called on the IDBTaskRunner, unlike most other functions.
IndexedDBContextImpl(
const base::FilePath& base_data_path,
scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy,
mojo::PendingRemote<storage::mojom::BlobStorageContext>
blob_storage_context,
mojo::PendingRemote<storage::mojom::FileSystemAccessContext>
file_system_access_context,
scoped_refptr<base::SequencedTaskRunner> custom_task_runner);
~IndexedDBContextImpl() override;
// Called to initiate shutdown. This is *not* called on the IDBTaskRunner.
static void Shutdown(std::unique_ptr<IndexedDBContextImpl> context);
IndexedDBContextImpl(const IndexedDBContextImpl&) = delete;
IndexedDBContextImpl& operator=(const IndexedDBContextImpl&) = delete;
void BindControl(
mojo::PendingReceiver<storage::mojom::IndexedDBControl> control);
// mojom::IndexedDBControl implementation:
void BindIndexedDB(
const storage::BucketLocator& bucket_locator,
const storage::BucketClientInfo& client_info,
mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
client_state_checker_remote,
mojo::PendingReceiver<blink::mojom::IDBFactory> receiver) override;
void ForceClose(storage::BucketId bucket_id,
storage::mojom::ForceCloseReason reason,
base::OnceClosure callback) override;
void StartMetadataRecording(storage::BucketId bucket_id,
StartMetadataRecordingCallback callback) override;
void StopMetadataRecording(storage::BucketId bucket_id,
StopMetadataRecordingCallback callback) override;
void DownloadBucketData(storage::BucketId bucket_id,
DownloadBucketDataCallback callback) override;
void GetAllBucketsDetails(GetAllBucketsDetailsCallback callback) override;
void SetForceKeepSessionState() override;
void ApplyPolicyUpdates(std::vector<storage::mojom::StoragePolicyUpdatePtr>
policy_updates) override;
void BindTestInterfaceForTesting(
mojo::PendingReceiver<storage::mojom::IndexedDBControlTest> receiver)
override;
void AddObserver(
mojo::PendingRemote<storage::mojom::IndexedDBObserver> observer) override;
// mojom::IndexedDBControlTest implementation:
void GetBaseDataPathForTesting(
GetBaseDataPathForTestingCallback callback) override;
void GetFilePathForTesting(const storage::BucketLocator& bucket_locator,
GetFilePathForTestingCallback callback) override;
void ResetCachesForTesting(base::OnceClosure callback) override;
void GetPathForBlobForTesting(
const storage::BucketLocator& bucket_locator,
int64_t database_id,
int64_t blob_number,
GetPathForBlobForTestingCallback callback) override;
void FlushBackingStoreForTesting(const storage::BucketLocator& bucket_locator,
base::OnceClosure callback) override;
void GetUsageForTesting(GetUsageForTestingCallback) override;
void GetSchedulingPriorityForTesting(
GetSchedulingPriorityForTestingCallback callback) override;
void BindMockFailureSingletonForTesting(
mojo::PendingReceiver<storage::mojom::MockFailureInjector> receiver)
override;
void ForceInitializeFromFilesForTesting(
ForceInitializeFromFilesForTestingCallback callback) override;
// storage::mojom::QuotaClient implementation:
void GetBucketUsage(const storage::BucketLocator& bucket,
GetBucketUsageCallback callback) override;
void GetDefaultStorageKeys(GetDefaultStorageKeysCallback callback) override;
void DeleteBucketData(const storage::BucketLocator& bucket,
DeleteBucketDataCallback callback) override;
void PerformStorageCleanup(PerformStorageCleanupCallback callback) override;
// Exposed for testing.
bool BucketContextExists(storage::BucketId bucket_id);
// Exposed for testing.
const scoped_refptr<base::SequencedTaskRunner>& IDBTaskRunner() const {
return idb_task_runner_;
}
const base::FilePath GetFirstPartyDataPathForTesting() const;
base::SequenceBound<BucketContext>* GetBucketContextForTesting(
const storage::BucketId& id);
private:
friend class IndexedDBTest;
friend class FactoryTest;
FRIEND_TEST_ALL_PREFIXES(IndexedDBTest, BasicFactoryCreationAndTearDown);
FRIEND_TEST_ALL_PREFIXES(IndexedDBTest, TooLongOrigin);
void BindControlOnIDBSequence(
mojo::PendingReceiver<storage::mojom::IndexedDBControl> control);
void BindPipesOnIDBSequence(
mojo::PendingReceiver<storage::mojom::QuotaClient>
pending_quota_client_receiver,
mojo::PendingRemote<storage::mojom::BlobStorageContext>
pending_blob_storage_context,
mojo::PendingRemote<storage::mojom::FileSystemAccessContext>
pending_file_system_access_context);
// mojom::IndexedDBControl internal implementation:
void BindIndexedDBImpl(
const storage::BucketClientInfo& client_info,
mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
client_state_checker_remote,
mojo::PendingReceiver<blink::mojom::IDBFactory> receiver,
storage::QuotaErrorOr<storage::BucketInfo> bucket_info);
void ForceCloseImpl(
const storage::mojom::ForceCloseReason reason,
base::OnceClosure closure,
const std::optional<storage::BucketLocator>& bucket_locator);
// Always run immediately before destruction.
void ShutdownOnIDBSequence(base::TimeTicks start_time);
base::FilePath GetDataPath(
const storage::BucketLocator& bucket_locator) const;
const base::FilePath GetLegacyDataPath() const;
base::FilePath GetBlobStorePath(
const storage::BucketLocator& bucket_locator) const;
base::FilePath GetLevelDBPath(
const storage::BucketLocator& bucket_locator) const;
int64_t ReadUsageFromDisk(const storage::BucketLocator& bucket_locator,
bool write_in_progress) const;
void NotifyOfBucketModification(const storage::BucketLocator& bucket_locator);
base::Time GetBucketLastModified(
const storage::BucketLocator& bucket_locator);
// We need to initialize the buckets already stored to the disk, but this
// cannot be done in the constructor as it might block destruction.
void InitializeFromFilesIfNeeded(base::OnceClosure callback);
bool did_initialize_from_files_{false};
std::vector<base::OnceClosure> on_initialize_from_files_callbacks_;
using DidGetBucketLocatorCallback = base::OnceCallback<void(
const std::optional<storage::BucketLocator>& bucket_locator)>;
void GetOrCreateDefaultBucket(const blink::StorageKey& storage_key,
DidGetBucketLocatorCallback callback);
// Finds IDB files in their legacy location, which is currently used for
// default buckets in first party contexts. Non-default buckets and default
// buckets in third party contexts, when partitioning is enabled, are returned
// by `FindIndexedDBFiles`.
std::map<blink::StorageKey, base::FilePath> FindLegacyIndexedDBFiles() const;
// Reads IDB files from disk, looking in the directories where
// third-party-context IDB files are stored.
std::map<storage::BucketId, base::FilePath> FindIndexedDBFiles() const;
void DidForceCloseForDeleteBucketData(
const storage::BucketLocator& bucket_locator,
DeleteBucketDataCallback callback);
// Invoked after asynchronously retrieving buckets from the quota manager in
// service of `GetAllBucketsDetails()`.
void ContinueGetAllBucketsDetails(
GetAllBucketsDetailsCallback callback,
std::vector<storage::QuotaErrorOr<storage::BucketInfo>> bucket_infos);
// Calculates in-memory/incognito usage for usage reporting.
void GetInMemorySize(storage::BucketId bucket_id,
base::OnceCallback<void(int64_t)> on_got_size) const;
std::vector<storage::BucketId> GetOpenBucketIdsForTesting() const;
// Finishes filling in `info` with data relevant to idb-internals and passes
// the result back via `result`. The bucket is described by
// `info->bucket_locator`.
void FillInBucketMetadata(
storage::mojom::IdbBucketMetadataPtr info,
base::OnceCallback<void(storage::mojom::IdbBucketMetadataPtr)> result);
void EnsureBucketContext(const storage::BucketInfo& bucket,
const base::FilePath& data_directory);
void CompactBackingStoreForTesting(
const storage::BucketLocator& bucket_locator);
int64_t GetBucketDiskUsage(const storage::BucketLocator& bucket_locator);
// Returns all paths owned by this database, in arbitrary order.
std::vector<base::FilePath> GetStoragePaths(
const storage::BucketLocator& bucket_locator) const;
// Called when files for the given bucket have been written. `flushed` is set
// to true if the writes were flushed to disk already, as with a transaction
// that has strict durability.
void OnFilesWritten(const storage::BucketLocator& bucket_locator,
bool flushed);
void NotifyIndexedDBContentChanged(
const storage::BucketLocator& bucket_locator,
const std::u16string& database_name,
const std::u16string& object_store_name);
void DestroyBucketContext(storage::BucketLocator bucket_locator);
std::optional<storage::BucketLocator> LookUpBucket(
storage::BucketId bucket_id);
bool in_memory() const { return base_data_path_.empty(); }
const scoped_refptr<base::SequencedTaskRunner> idb_task_runner_;
// Bound and accessed on the `idb_task_runner_`.
mojo::Remote<storage::mojom::BlobStorageContext> blob_storage_context_;
mojo::Remote<storage::mojom::FileSystemAccessContext>
file_system_access_context_;
// If `base_data_path_` is empty then this is an incognito session and the
// backing store will be held in-memory rather than on-disk.
const base::FilePath base_data_path_;
// If true, nothing (not even session-only data) should be deleted on exit.
bool force_keep_session_state_ = false;
// Will be null in unit tests.
const scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy_;
// This set contains all buckets that have stored IDB data on disk. This is
// distinct from `bucket_contexts_`, which are only created for active IDB
// clients.
std::set<storage::BucketLocator> bucket_set_;
// This map is a cache of the size used by a given bucket. It's calculated by
// summing the system-reported sizes of all blob and LevelDB files. This cache
// is cleared after transactions that can change the size of the database
// (i.e. those that are not readonly), and re-populated lazily. There are
// three possible states for each bucket in this map:
//
// 1) Not present. This indicates that the `ReadUsageFromDisk()` should be
// called to calculate usage (and be stored in the map).
// 2) Present, with a non-negative value. This indicates that the cache is, as
// far as we know, valid and up to date for the given bucket. This state
// persists until the next writing transaction occurs.
// 3) Present, with a negative value. This indicates that the usage is not
// cached AND the last readwrite transaction did NOT flush changes to disk
// (i.e. durability was 'relaxed').
//
// On POSIX, the first and third states are treated equivalently. However, on
// Windows, `base::FileEnumerator` (and therefore
// `base::ComputeDirectorySize()`) will not report up-to-date sizes when a
// file is currently being written. When transactions are not set to
// "flush"/"sync" (terminology varies based on context), LevelDB will keep
// open its file handles. Therefore, on Windows, `ReadUsageFromDisk()` may
// not take into account recent writes, leading to situations where
// `navigator.storage.estimate()` will not report updates when interleaved
// with relaxed durability IDB transactions. The workaround for this is to
// open and close new file handles for all the files in the LevelDB data
// directory before calculating usage, as this updates the file system
// directory entry's metadata. See crbug.com/1489517 and
// https://devblogs.microsoft.com/oldnewthing/20111226-00/?p=8813
//
// TODO(crbug.com/40285925): use an abstract model for quota instead of real
// world bytes.
std::map<storage::BucketLocator, int64_t> bucket_size_map_;
// The set of sites whose storage should be cleared on shutdown. These are
// matched against the origin and top level site in each bucket's StorageKey.
std::set<url::Origin> origins_to_purge_on_shutdown_;
storage::QuotaClientCallbackWrapper quota_client_wrapper_{this};
mojo::Receiver<storage::mojom::QuotaClient> quota_client_receiver_;
mojo::ReceiverSet<storage::mojom::IndexedDBControl> control_receivers_;
mojo::ReceiverSet<storage::mojom::IndexedDBControlTest> test_receivers_;
mojo::RemoteSet<storage::mojom::IndexedDBObserver> observers_;
// For testing: when non-null, this receiver will be passed off to the next
// bucket context that's created.
mojo::PendingReceiver<storage::mojom::MockFailureInjector>
pending_failure_injector_;
std::map<storage::BucketId, base::SequenceBound<BucketContext>>
bucket_contexts_;
// For the most part, every bucket gets its own SequencedTaskRunner. But each
// "site", i.e. StorageKey's `top_level_site()`, has a cap on the number of
// task runners its buckets will be allotted, which is equal to the number of
// cores on the device. When creating a new BucketContext, it will get a
// unique task runner that runs on the threadpool unless `active_bucket_count`
// is over the number of cores, in which case the task runner will be shared
// with other buckets.
struct TaskRunnerLimiter {
TaskRunnerLimiter();
~TaskRunnerLimiter();
int active_bucket_count = 0;
scoped_refptr<base::SequencedTaskRunner> overflow_task_runner;
};
std::map<net::SchemefulSite, TaskRunnerLimiter> task_runner_limiters_;
// When true, run backing stores (and bucket contexts) on `idb_task_runner_`
// to simplify unit tests. This is set to true when the ctor param
// `custom_task_runner` is non null.
bool force_single_thread_ = false;
// If recording begins on a bucket ID that doesn't currently have a context,
// add it to a pending set and actually begin once the context is created.
std::set<storage::BucketId> pending_bucket_recording_;
// When `Shutdown()` was called, or null if it's not been called. Used for
// UMA.
base::TimeTicks shutdown_start_time_;
// weak_factory_->GetWeakPtr() may be used on any thread, but the resulting
// pointer must only be checked/used on idb_task_runner_.
base::WeakPtrFactory<IndexedDBContextImpl> weak_factory_{this};
};
} // namespace content::indexed_db
#endif // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_CONTEXT_IMPL_H_
|