|  | // 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. | 
|  |  | 
|  | #include "content/browser/indexed_db/indexed_db_context_impl.h" | 
|  |  | 
|  | #include <memory> | 
|  | #include <string> | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/barrier_callback.h" | 
|  | #include "base/bind.h" | 
|  | #include "base/callback_helpers.h" | 
|  | #include "base/check_op.h" | 
|  | #include "base/command_line.h" | 
|  | #include "base/files/file_enumerator.h" | 
|  | #include "base/files/file_path.h" | 
|  | #include "base/files/file_util.h" | 
|  | #include "base/files/scoped_temp_dir.h" | 
|  | #include "base/metrics/histogram_functions.h" | 
|  | #include "base/ranges/algorithm.h" | 
|  | #include "base/strings/string_util.h" | 
|  | #include "base/task/sequenced_task_runner.h" | 
|  | #include "base/task/thread_pool.h" | 
|  | #include "base/threading/thread_restrictions.h" | 
|  | #include "base/time/default_clock.h" | 
|  | #include "base/time/time.h" | 
|  | #include "base/trace_event/base_tracing.h" | 
|  | #include "base/values.h" | 
|  | #include "components/services/storage/filesystem_proxy_factory.h" | 
|  | #include "components/services/storage/indexed_db/leveldb/leveldb_factory.h" | 
|  | #include "components/services/storage/indexed_db/scopes/varint_coding.h" | 
|  | #include "components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_database.h" | 
|  | #include "components/services/storage/public/cpp/buckets/bucket_info.h" | 
|  | #include "components/services/storage/public/cpp/buckets/bucket_locator.h" | 
|  | #include "components/services/storage/public/cpp/buckets/constants.h" | 
|  | #include "components/services/storage/public/cpp/constants.h" | 
|  | #include "components/services/storage/public/cpp/quota_client_callback_wrapper.h" | 
|  | #include "components/services/storage/public/cpp/quota_error_or.h" | 
|  | #include "components/services/storage/public/mojom/quota_client.mojom.h" | 
|  | #include "components/services/storage/public/mojom/storage_usage_info.mojom.h" | 
|  | #include "content/browser/indexed_db/indexed_db_bucket_state.h" | 
|  | #include "content/browser/indexed_db/indexed_db_bucket_state_handle.h" | 
|  | #include "content/browser/indexed_db/indexed_db_class_factory.h" | 
|  | #include "content/browser/indexed_db/indexed_db_connection.h" | 
|  | #include "content/browser/indexed_db/indexed_db_database.h" | 
|  | #include "content/browser/indexed_db/indexed_db_dispatcher_host.h" | 
|  | #include "content/browser/indexed_db/indexed_db_factory_impl.h" | 
|  | #include "content/browser/indexed_db/indexed_db_leveldb_operations.h" | 
|  | #include "content/browser/indexed_db/indexed_db_quota_client.h" | 
|  | #include "content/browser/indexed_db/indexed_db_transaction.h" | 
|  | #include "content/browser/indexed_db/mock_browsertest_indexed_db_class_factory.h" | 
|  | #include "mojo/public/cpp/bindings/pending_remote.h" | 
|  | #include "mojo/public/cpp/bindings/self_owned_receiver.h" | 
|  | #include "storage/browser/database/database_util.h" | 
|  | #include "storage/browser/quota/quota_client_type.h" | 
|  | #include "storage/common/database/database_identifier.h" | 
|  | #include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom.h" | 
|  | #include "third_party/blink/public/mojom/quota/quota_types.mojom-shared.h" | 
|  | #include "third_party/blink/public/mojom/quota/quota_types.mojom.h" | 
|  | #include "third_party/zlib/google/zip.h" | 
|  | #include "url/origin.h" | 
|  |  | 
|  | using base::DictionaryValue; | 
|  | using base::ListValue; | 
|  | using storage::DatabaseUtil; | 
|  |  | 
|  | namespace content { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | static MockBrowserTestIndexedDBClassFactory* GetTestClassFactory() { | 
|  | static ::base::LazyInstance<MockBrowserTestIndexedDBClassFactory>::Leaky | 
|  | s_factory = LAZY_INSTANCE_INITIALIZER; | 
|  | return s_factory.Pointer(); | 
|  | } | 
|  |  | 
|  | static IndexedDBClassFactory* GetTestIDBClassFactory() { | 
|  | return GetTestClassFactory(); | 
|  | } | 
|  |  | 
|  | bool IsAllowedPath(const std::vector<base::FilePath>& allowed_paths, | 
|  | const base::FilePath& candidate_path) { | 
|  | for (const base::FilePath& allowed_path : allowed_paths) { | 
|  | if (candidate_path == allowed_path || allowed_path.IsParent(candidate_path)) | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | // static | 
|  | void IndexedDBContextImpl::ReleaseOnIDBSequence( | 
|  | scoped_refptr<IndexedDBContextImpl>&& context) { | 
|  | if (!context->IDBTaskRunner()->RunsTasksInCurrentSequence()) { | 
|  | IndexedDBContextImpl* context_ptr = context.get(); | 
|  | context_ptr->IDBTaskRunner()->ReleaseSoon(FROM_HERE, std::move(context)); | 
|  | } | 
|  | } | 
|  |  | 
|  | IndexedDBContextImpl::IndexedDBContextImpl( | 
|  | const base::FilePath& base_data_path, | 
|  | scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy, | 
|  | base::Clock* clock, | 
|  | mojo::PendingRemote<storage::mojom::BlobStorageContext> | 
|  | blob_storage_context, | 
|  | mojo::PendingRemote<storage::mojom::FileSystemAccessContext> | 
|  | file_system_access_context, | 
|  | scoped_refptr<base::SequencedTaskRunner> io_task_runner, | 
|  | scoped_refptr<base::SequencedTaskRunner> custom_task_runner) | 
|  | : idb_task_runner_( | 
|  | custom_task_runner | 
|  | ? custom_task_runner | 
|  | : (base::ThreadPool::CreateSequencedTaskRunner( | 
|  | {base::MayBlock(), base::WithBaseSyncPrimitives(), | 
|  | base::TaskPriority::USER_VISIBLE, | 
|  | // BLOCK_SHUTDOWN to support clearing session-only storage. | 
|  | base::TaskShutdownBehavior::BLOCK_SHUTDOWN}))), | 
|  | dispatcher_host_(this, std::move(io_task_runner)), | 
|  | base_data_path_(base_data_path.empty() ? base::FilePath() | 
|  | : base_data_path), | 
|  | force_keep_session_state_(false), | 
|  | quota_manager_proxy_(std::move(quota_manager_proxy)), | 
|  | clock_(clock), | 
|  | quota_client_(std::make_unique<IndexedDBQuotaClient>(*this)), | 
|  | quota_client_wrapper_( | 
|  | std::make_unique<storage::QuotaClientCallbackWrapper>( | 
|  | quota_client_.get())), | 
|  | quota_client_receiver_(quota_client_wrapper_.get()), | 
|  | filesystem_proxy_(storage::CreateFilesystemProxy()) { | 
|  | TRACE_EVENT0("IndexedDB", "init"); | 
|  |  | 
|  | // QuotaManagerProxy::RegisterClient() must be called during construction | 
|  | // until crbug.com/1182630 is fixed. | 
|  | mojo::PendingRemote<storage::mojom::QuotaClient> quota_client_remote; | 
|  | mojo::PendingReceiver<storage::mojom::QuotaClient> quota_client_receiver = | 
|  | quota_client_remote.InitWithNewPipeAndPassReceiver(); | 
|  | quota_manager_proxy_->RegisterClient( | 
|  | std::move(quota_client_remote), | 
|  | storage::QuotaClientType::kIndexedDatabase, | 
|  | {blink::mojom::StorageType::kTemporary}); | 
|  | IDBTaskRunner()->PostTask( | 
|  | FROM_HERE, base::BindOnce(&IndexedDBContextImpl::BindPipesOnIDBSequence, | 
|  | weak_factory_.GetWeakPtr(), | 
|  | std::move(quota_client_receiver), | 
|  | std::move(blob_storage_context), | 
|  | std::move(file_system_access_context))); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::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) { | 
|  | DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); | 
|  | if (pending_quota_client_receiver) { | 
|  | quota_client_receiver_.Bind(std::move(pending_quota_client_receiver)); | 
|  | } | 
|  | if (pending_blob_storage_context) { | 
|  | blob_storage_context_.Bind(std::move(pending_blob_storage_context)); | 
|  | } | 
|  | if (pending_file_system_access_context) { | 
|  | file_system_access_context_.Bind( | 
|  | std::move(pending_file_system_access_context)); | 
|  | } | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::Bind( | 
|  | mojo::PendingReceiver<storage::mojom::IndexedDBControl> control) { | 
|  | // We cannot run this in the constructor it needs to be async, but the async | 
|  | // tasks might not finish before the destructor runs. | 
|  | InitializeFromFilesIfNeeded(base::DoNothing()); | 
|  | receivers_.Add(this, std::move(control)); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::BindIndexedDB( | 
|  | const blink::StorageKey& storage_key, | 
|  | mojo::PendingReceiver<blink::mojom::IDBFactory> receiver) { | 
|  | quota_manager_proxy()->UpdateOrCreateBucket( | 
|  | storage::BucketInitParams::ForDefaultBucket(storage_key), | 
|  | idb_task_runner_, | 
|  | base::BindOnce(&IndexedDBContextImpl::BindIndexedDBImpl, | 
|  | weak_factory_.GetWeakPtr(), std::move(receiver))); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::BindIndexedDBForBucket( | 
|  | const storage::BucketLocator& bucket_locator, | 
|  | mojo::PendingReceiver<blink::mojom::IDBFactory> receiver) { | 
|  | // Query the database to make sure the bucket still exists. | 
|  | quota_manager_proxy()->GetBucketById( | 
|  | bucket_locator.id, idb_task_runner_, | 
|  | base::BindOnce(&IndexedDBContextImpl::BindIndexedDBImpl, | 
|  | weak_factory_.GetWeakPtr(), std::move(receiver))); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::BindIndexedDBImpl( | 
|  | mojo::PendingReceiver<blink::mojom::IDBFactory> receiver, | 
|  | storage::QuotaErrorOr<storage::BucketInfo> bucket_info) { | 
|  | absl::optional<storage::BucketLocator> bucket_locator; | 
|  | if (bucket_info.ok()) | 
|  | bucket_locator = bucket_info->ToBucketLocator(); | 
|  | dispatcher_host_.AddReceiver(bucket_locator, std::move(receiver)); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::GetUsage(GetUsageCallback usage_callback) { | 
|  | InitializeFromFilesIfNeeded( | 
|  | base::BindOnce(&IndexedDBContextImpl::GetUsageImpl, | 
|  | weak_factory_.GetWeakPtr(), std::move(usage_callback))); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::GetUsageImpl(GetUsageCallback usage_callback) { | 
|  | // TODO(https://crbug.com/1199077): Pass the real StorageKey when | 
|  | // StorageUsageInfo is converted. | 
|  | std::map<url::Origin, storage::mojom::StorageUsageInfoPtr> usage_map; | 
|  | for (const auto& bucket_locator : GetAllBuckets()) { | 
|  | const auto& origin = bucket_locator.storage_key.origin(); | 
|  | if (usage_map.find(origin) != usage_map.end()) { | 
|  | usage_map[origin]->total_size_bytes += GetBucketDiskUsage(bucket_locator); | 
|  | const auto& last_modified = GetBucketLastModified(bucket_locator); | 
|  | if (usage_map[origin]->last_modified < last_modified) { | 
|  | usage_map[origin]->last_modified = last_modified; | 
|  | } | 
|  | } else { | 
|  | usage_map[origin] = storage::mojom::StorageUsageInfo::New( | 
|  | origin, GetBucketDiskUsage(bucket_locator), | 
|  | GetBucketLastModified(bucket_locator)); | 
|  | } | 
|  | } | 
|  | std::vector<storage::mojom::StorageUsageInfoPtr> result; | 
|  | for (const auto& it : usage_map) { | 
|  | result.emplace_back(it.second->Clone()); | 
|  | } | 
|  | std::move(usage_callback).Run(std::move(result)); | 
|  | } | 
|  |  | 
|  | // Note - this is being kept async (instead of having a 'sync' version) to allow | 
|  | // ForceClose to become asynchronous.  This is required for | 
|  | // https://crbug.com/965142. | 
|  | void IndexedDBContextImpl::DeleteForStorageKey( | 
|  | const blink::StorageKey& storage_key, | 
|  | DeleteForStorageKeyCallback callback) { | 
|  | quota_manager_proxy_->GetBucketsForStorageKey( | 
|  | storage_key, blink::mojom::StorageType::kTemporary, | 
|  | /*delete_expired=*/false, IDBTaskRunner(), | 
|  | base::BindOnce(&IndexedDBContextImpl::OnGotBucketsForDeletion, | 
|  | weak_factory_.GetWeakPtr(), std::move(callback))); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::OnGotBucketsForDeletion( | 
|  | base::OnceCallback<void(bool)> callback, | 
|  | storage::QuotaErrorOr<std::set<storage::BucketInfo>> buckets) { | 
|  | if (!buckets.ok() || buckets.value().empty()) { | 
|  | std::move(callback).Run(buckets.ok()); | 
|  | return; | 
|  | } | 
|  |  | 
|  | auto barrier = base::BarrierCallback<bool>( | 
|  | buckets->size(), | 
|  | base::BindOnce( | 
|  | [](base::OnceCallback<void(bool)> final_callback, | 
|  | const std::vector<bool>& successes) { | 
|  | std::move(final_callback) | 
|  | .Run(base::ranges::all_of( | 
|  | successes, [](bool success) { return success; })); | 
|  | }, | 
|  | std::move(callback))); | 
|  |  | 
|  | for (const auto& bucket : buckets.value()) { | 
|  | DoDeleteBucketData(bucket.ToBucketLocator(), barrier); | 
|  | } | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::DeleteBucketData( | 
|  | const storage::BucketLocator& bucket_locator, | 
|  | base::OnceCallback<void(bool)> callback) { | 
|  | DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); | 
|  | DoDeleteBucketData(bucket_locator, std::move(callback)); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::DoDeleteBucketData( | 
|  | const storage::BucketLocator& bucket_locator, | 
|  | base::OnceCallback<void(bool)> callback) { | 
|  | ForceClose(bucket_locator.id, | 
|  | storage::mojom::ForceCloseReason::FORCE_CLOSE_DELETE_ORIGIN, | 
|  | base::DoNothing()); | 
|  | if (is_incognito()) { | 
|  | bucket_set_.erase(bucket_locator); | 
|  | bucket_size_map_.erase(bucket_locator); | 
|  | std::move(callback).Run(true); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!base::DirectoryExists(GetDataPath(bucket_locator))) { | 
|  | std::move(callback).Run(true); | 
|  | return; | 
|  | } | 
|  |  | 
|  | base::FilePath idb_file_path = GetLevelDBPath(bucket_locator); | 
|  | EnsureDiskUsageCacheInitialized(bucket_locator); | 
|  |  | 
|  | leveldb::Status s = | 
|  | IndexedDBClassFactory::Get()->leveldb_factory().DestroyLevelDB( | 
|  | idb_file_path); | 
|  | bool success = s.ok(); | 
|  | if (success) { | 
|  | success = filesystem_proxy_->DeletePathRecursively( | 
|  | GetBlobStorePath(bucket_locator)); | 
|  | } | 
|  |  | 
|  | QueryDiskAndUpdateQuotaUsage(bucket_locator); | 
|  | if (success) { | 
|  | bucket_set_.erase(bucket_locator); | 
|  | bucket_size_map_.erase(bucket_locator); | 
|  | } | 
|  | std::move(callback).Run(success); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::ForceClose(storage::BucketId bucket_id, | 
|  | storage::mojom::ForceCloseReason reason, | 
|  | base::OnceClosure closure) { | 
|  | base::UmaHistogramEnumeration("WebCore.IndexedDB.Context.ForceCloseReason", | 
|  | reason); | 
|  | if (!LookUpBucket(bucket_id)) { | 
|  | std::move(closure).Run(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!indexeddb_factory_.get()) { | 
|  | std::move(closure).Run(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Make a copy of storage_key, as the ref might go away here during the close. | 
|  | indexeddb_factory_->ForceClose( | 
|  | bucket_id, | 
|  | reason == storage::mojom::ForceCloseReason::FORCE_CLOSE_DELETE_ORIGIN); | 
|  | DCHECK_EQ(0UL, GetConnectionCountSync(bucket_id)); | 
|  | std::move(closure).Run(); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::GetConnectionCount( | 
|  | storage::BucketId bucket_id, | 
|  | GetConnectionCountCallback callback) { | 
|  | GetConnectionCountImpl(std::move(callback), bucket_id); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::GetConnectionCountImpl( | 
|  | GetConnectionCountCallback callback, | 
|  | storage::BucketId bucket_id) { | 
|  | std::move(callback).Run(GetConnectionCountSync(bucket_id)); | 
|  | } | 
|  |  | 
|  | size_t IndexedDBContextImpl::GetConnectionCountSync( | 
|  | storage::BucketId bucket_id) { | 
|  | size_t count = 0; | 
|  | if (LookUpBucket(bucket_id) && indexeddb_factory_.get()) { | 
|  | count = indexeddb_factory_->GetConnectionCount(bucket_id); | 
|  | } | 
|  | return count; | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::DownloadBucketData( | 
|  | storage::BucketId bucket_id, | 
|  | DownloadBucketDataCallback callback) { | 
|  | bool success = false; | 
|  |  | 
|  | auto bucket_locator = LookUpBucket(bucket_id); | 
|  | // Make sure the database hasn't been deleted. | 
|  | if (!bucket_locator) { | 
|  | std::move(callback).Run(success, base::FilePath(), base::FilePath()); | 
|  | return; | 
|  | } | 
|  |  | 
|  | ForceClose(bucket_id, | 
|  | storage::mojom::ForceCloseReason::FORCE_CLOSE_INTERNALS_PAGE, | 
|  | base::DoNothing()); | 
|  |  | 
|  | base::ScopedTempDir temp_dir; | 
|  | if (!temp_dir.CreateUniqueTempDir()) { | 
|  | std::move(callback).Run(success, base::FilePath(), base::FilePath()); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // This will need to get cleaned up after the download has completed. | 
|  | base::FilePath temp_path = temp_dir.Take(); | 
|  |  | 
|  | std::string storage_key_id = | 
|  | storage::GetIdentifierFromOrigin(bucket_locator->storage_key.origin()); | 
|  | base::FilePath zip_path = temp_path.AppendASCII(storage_key_id) | 
|  | .AddExtension(FILE_PATH_LITERAL("zip")); | 
|  |  | 
|  | std::vector<base::FilePath> paths = GetStoragePaths(*bucket_locator); | 
|  | zip::ZipWithFilterCallback(GetDataPath(*bucket_locator), zip_path, | 
|  | base::BindRepeating(IsAllowedPath, paths)); | 
|  |  | 
|  | success = true; | 
|  | std::move(callback).Run(success, temp_path, zip_path); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::GetAllBucketsDetails( | 
|  | GetAllBucketsDetailsCallback callback) { | 
|  | DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); | 
|  | std::vector<storage::BucketLocator> bucket_locators = GetAllBuckets(); | 
|  |  | 
|  | std::sort(bucket_locators.begin(), bucket_locators.end()); | 
|  |  | 
|  | base::Value::List list; | 
|  | for (const auto& bucket_locator : bucket_locators) { | 
|  | base::Value::Dict info; | 
|  | // TODO(https://crbug.com/1199077): Serialize storage key directly | 
|  | // once supported by OriginDetails. | 
|  | // TODO(estade): replace this Value dict with mojom. | 
|  | info.Set("url", bucket_locator.storage_key.origin().Serialize()); | 
|  | info.Set("bucket_id", | 
|  | static_cast<double>(bucket_locator.id.GetUnsafeValue())); | 
|  | info.Set("size", static_cast<double>(GetBucketDiskUsage(bucket_locator))); | 
|  | info.Set("last_modified", GetBucketLastModified(bucket_locator).ToJsTime()); | 
|  |  | 
|  | base::Value::List paths; | 
|  | if (!is_incognito()) { | 
|  | for (const base::FilePath& path : GetStoragePaths(bucket_locator)) | 
|  | paths.Append(path.AsUTF8Unsafe()); | 
|  | } else { | 
|  | paths.Append("N/A"); | 
|  | } | 
|  | info.Set("paths", std::move(paths)); | 
|  | info.Set("connection_count", | 
|  | static_cast<double>(GetConnectionCountSync(bucket_locator.id))); | 
|  |  | 
|  | // This ends up being O(NlogN), where N = number of open databases. We | 
|  | // iterate over all open databases to extract just those in the | 
|  | // bucket_locator, and we're iterating over all bucket_locators in the outer | 
|  | // loop. | 
|  |  | 
|  | if (!indexeddb_factory_.get()) { | 
|  | list.Append(std::move(info)); | 
|  | continue; | 
|  | } | 
|  | std::vector<IndexedDBDatabase*> databases = | 
|  | indexeddb_factory_->GetOpenDatabasesForBucket(bucket_locator); | 
|  | // TODO(jsbell): Sort by name? | 
|  | base::Value::List database_list; | 
|  |  | 
|  | for (IndexedDBDatabase* db : databases) { | 
|  | base::Value::Dict db_info; | 
|  |  | 
|  | db_info.Set("name", db->name()); | 
|  | db_info.Set("connection_count", | 
|  | static_cast<double>(db->ConnectionCount())); | 
|  | db_info.Set("active_open_delete", | 
|  | static_cast<double>(db->ActiveOpenDeleteCount())); | 
|  | db_info.Set("pending_open_delete", | 
|  | static_cast<double>(db->PendingOpenDeleteCount())); | 
|  |  | 
|  | base::Value::List transaction_list; | 
|  |  | 
|  | for (IndexedDBConnection* connection : db->connections()) { | 
|  | for (const auto& transaction_id_pair : connection->transactions()) { | 
|  | const auto* transaction = transaction_id_pair.second.get(); | 
|  | base::Value::Dict transaction_info; | 
|  |  | 
|  | switch (transaction->mode()) { | 
|  | case blink::mojom::IDBTransactionMode::ReadOnly: | 
|  | transaction_info.Set("mode", "readonly"); | 
|  | break; | 
|  | case blink::mojom::IDBTransactionMode::ReadWrite: | 
|  | transaction_info.Set("mode", "readwrite"); | 
|  | break; | 
|  | case blink::mojom::IDBTransactionMode::VersionChange: | 
|  | transaction_info.Set("mode", "versionchange"); | 
|  | break; | 
|  | } | 
|  |  | 
|  | switch (transaction->state()) { | 
|  | case IndexedDBTransaction::CREATED: | 
|  | transaction_info.Set("status", "blocked"); | 
|  | break; | 
|  | case IndexedDBTransaction::STARTED: | 
|  | if (transaction->diagnostics().tasks_scheduled > 0) | 
|  | transaction_info.Set("status", "running"); | 
|  | else | 
|  | transaction_info.Set("status", "started"); | 
|  | break; | 
|  | case IndexedDBTransaction::COMMITTING: | 
|  | transaction_info.Set("status", "committing"); | 
|  | break; | 
|  | case IndexedDBTransaction::FINISHED: | 
|  | transaction_info.Set("status", "finished"); | 
|  | break; | 
|  | } | 
|  |  | 
|  | transaction_info.Set("tid", static_cast<double>(transaction->id())); | 
|  | transaction_info.Set("age", (base::Time::Now() - | 
|  | transaction->diagnostics().creation_time) | 
|  | .InMillisecondsF()); | 
|  | transaction_info.Set( | 
|  | "runtime", | 
|  | (base::Time::Now() - transaction->diagnostics().start_time) | 
|  | .InMillisecondsF()); | 
|  | transaction_info.Set("tasks_scheduled", | 
|  | transaction->diagnostics().tasks_scheduled); | 
|  | transaction_info.Set("tasks_completed", | 
|  | transaction->diagnostics().tasks_completed); | 
|  |  | 
|  | base::Value::List scope; | 
|  | for (const auto& id : transaction->scope()) { | 
|  | auto stores_it = db->metadata().object_stores.find(id); | 
|  | if (stores_it != db->metadata().object_stores.end()) | 
|  | scope.Append(stores_it->second.name); | 
|  | } | 
|  |  | 
|  | transaction_info.Set("scope", std::move(scope)); | 
|  | transaction_list.Append(std::move(transaction_info)); | 
|  | } | 
|  | } | 
|  | db_info.Set("transactions", std::move(transaction_list)); | 
|  |  | 
|  | database_list.Append(std::move(db_info)); | 
|  | } | 
|  | info.Set("databases", std::move(database_list)); | 
|  | list.Append(std::move(info)); | 
|  | } | 
|  |  | 
|  | std::move(callback).Run(is_incognito(), std::move(list)); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::SetForceKeepSessionState() { | 
|  | IDBTaskRunner()->PostTask( | 
|  | FROM_HERE, base::BindOnce( | 
|  | [](base::WeakPtr<IndexedDBContextImpl> context) { | 
|  | if (context) | 
|  | context->force_keep_session_state_ = true; | 
|  | }, | 
|  | weak_factory_.GetWeakPtr())); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::ApplyPolicyUpdates( | 
|  | std::vector<storage::mojom::StoragePolicyUpdatePtr> policy_updates) { | 
|  | DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); | 
|  | for (const auto& update : policy_updates) { | 
|  | if (!update->purge_on_shutdown) { | 
|  | sites_to_purge_on_shutdown_.erase(net::SchemefulSite(update->origin)); | 
|  | } else { | 
|  | sites_to_purge_on_shutdown_.insert(net::SchemefulSite(update->origin)); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::BindTestInterface( | 
|  | mojo::PendingReceiver<storage::mojom::IndexedDBControlTest> receiver) { | 
|  | DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); | 
|  | test_receivers_.Add(this, std::move(receiver)); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::AddObserver( | 
|  | mojo::PendingRemote<storage::mojom::IndexedDBObserver> observer) { | 
|  | IDBTaskRunner()->PostTask( | 
|  | FROM_HERE, | 
|  | base::BindOnce( | 
|  | [](base::WeakPtr<IndexedDBContextImpl> context, | 
|  | mojo::PendingRemote<storage::mojom::IndexedDBObserver> observer) { | 
|  | if (context) | 
|  | context->observers_.Add(std::move(observer)); | 
|  | }, | 
|  | weak_factory_.GetWeakPtr(), std::move(observer))); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::GetBaseDataPathForTesting( | 
|  | GetBaseDataPathForTestingCallback callback) { | 
|  | std::move(callback).Run(GetLegacyDataPath()); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::GetFilePathForTesting( | 
|  | const storage::BucketLocator& bucket_locator, | 
|  | GetFilePathForTestingCallback callback) { | 
|  | std::move(callback).Run(GetLevelDBPath(bucket_locator)); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::ResetCachesForTesting(base::OnceClosure callback) { | 
|  | bucket_set_.clear(); | 
|  | bucket_size_map_.clear(); | 
|  | std::move(callback).Run(); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::ForceSchemaDowngradeForTesting( | 
|  | const storage::BucketLocator& bucket_locator, | 
|  | ForceSchemaDowngradeForTestingCallback callback) { | 
|  | DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); | 
|  |  | 
|  | if (is_incognito() || !LookUpBucket(bucket_locator.id)) { | 
|  | std::move(callback).Run(false); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (indexeddb_factory_.get()) { | 
|  | indexeddb_factory_->ForceSchemaDowngrade(bucket_locator); | 
|  | std::move(callback).Run(true); | 
|  | return; | 
|  | } | 
|  | ForceClose( | 
|  | bucket_locator.id, | 
|  | storage::mojom::ForceCloseReason::FORCE_SCHEMA_DOWNGRADE_INTERNALS_PAGE, | 
|  | base::DoNothing()); | 
|  | std::move(callback).Run(false); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::HasV2SchemaCorruptionForTesting( | 
|  | const storage::BucketLocator& bucket_locator, | 
|  | HasV2SchemaCorruptionForTestingCallback callback) { | 
|  | DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); | 
|  |  | 
|  | if (is_incognito() || !LookUpBucket(bucket_locator.id)) { | 
|  | std::move(callback).Run( | 
|  | storage::mojom::V2SchemaCorruptionStatus::CORRUPTION_UNKNOWN); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (indexeddb_factory_.get()) { | 
|  | std::move(callback).Run( | 
|  | static_cast<storage::mojom::V2SchemaCorruptionStatus>( | 
|  | indexeddb_factory_->HasV2SchemaCorruption(bucket_locator))); | 
|  | return; | 
|  | } | 
|  | return std::move(callback).Run( | 
|  | storage::mojom::V2SchemaCorruptionStatus::CORRUPTION_UNKNOWN); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::WriteToIndexedDBForTesting( | 
|  | const storage::BucketLocator& bucket_locator, | 
|  | const std::string& key, | 
|  | const std::string& value, | 
|  | base::OnceClosure callback) { | 
|  | IndexedDBBucketStateHandle handle; | 
|  | leveldb::Status s; | 
|  | std::tie(handle, s, std::ignore, std::ignore, std::ignore) = | 
|  | GetIDBFactory()->GetOrOpenBucketFactory(bucket_locator, | 
|  | GetDataPath(bucket_locator), | 
|  | /*create_if_missing=*/true); | 
|  | CHECK(s.ok()) << s.ToString(); | 
|  | CHECK(handle.IsHeld()); | 
|  |  | 
|  | TransactionalLevelDBDatabase* db = | 
|  | handle.bucket_state()->backing_store()->db(); | 
|  | std::string value_copy = value; | 
|  | s = db->Put(key, &value_copy); | 
|  | CHECK(s.ok()) << s.ToString(); | 
|  | handle.Release(); | 
|  | GetIDBFactory()->ForceClose(bucket_locator.id, true); | 
|  | std::move(callback).Run(); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::GetBlobCountForTesting( | 
|  | const storage::BucketLocator& bucket_locator, | 
|  | GetBlobCountForTestingCallback callback) { | 
|  | std::move(callback).Run(GetBucketBlobFileCount(bucket_locator)); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::GetNextBlobNumberForTesting( | 
|  | const storage::BucketLocator& bucket_locator, | 
|  | int64_t database_id, | 
|  | GetNextBlobNumberForTestingCallback callback) { | 
|  | IndexedDBBucketStateHandle handle; | 
|  | leveldb::Status s; | 
|  | std::tie(handle, s, std::ignore, std::ignore, std::ignore) = | 
|  | GetIDBFactory()->GetOrOpenBucketFactory(bucket_locator, | 
|  | GetDataPath(bucket_locator), | 
|  | /*create_if_missing=*/true); | 
|  | CHECK(s.ok()) << s.ToString(); | 
|  | CHECK(handle.IsHeld()); | 
|  |  | 
|  | TransactionalLevelDBDatabase* db = | 
|  | handle.bucket_state()->backing_store()->db(); | 
|  |  | 
|  | const std::string key_gen_key = DatabaseMetaDataKey::Encode( | 
|  | database_id, DatabaseMetaDataKey::BLOB_KEY_GENERATOR_CURRENT_NUMBER); | 
|  | std::string data; | 
|  | bool found = false; | 
|  | bool ok = db->Get(key_gen_key, &data, &found).ok(); | 
|  | CHECK(found); | 
|  | CHECK(ok); | 
|  | base::StringPiece slice(data); | 
|  | int64_t number; | 
|  | CHECK(DecodeVarInt(&slice, &number)); | 
|  | CHECK(DatabaseMetaDataKey::IsValidBlobNumber(number)); | 
|  |  | 
|  | std::move(callback).Run(number); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::GetPathForBlobForTesting( | 
|  | const storage::BucketLocator& bucket_locator, | 
|  | int64_t database_id, | 
|  | int64_t blob_number, | 
|  | GetPathForBlobForTestingCallback callback) { | 
|  | IndexedDBBucketStateHandle handle; | 
|  | leveldb::Status s; | 
|  | std::tie(handle, s, std::ignore, std::ignore, std::ignore) = | 
|  | GetIDBFactory()->GetOrOpenBucketFactory(bucket_locator, | 
|  | GetDataPath(bucket_locator), | 
|  | /*create_if_missing=*/true); | 
|  | CHECK(s.ok()) << s.ToString(); | 
|  | CHECK(handle.IsHeld()); | 
|  |  | 
|  | IndexedDBBackingStore* backing_store = handle.bucket_state()->backing_store(); | 
|  | base::FilePath path = | 
|  | backing_store->GetBlobFileName(database_id, blob_number); | 
|  | std::move(callback).Run(path); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::CompactBackingStoreForTesting( | 
|  | const storage::BucketLocator& bucket_locator, | 
|  | base::OnceClosure callback) { | 
|  | IndexedDBFactoryImpl* factory = GetIDBFactory(); | 
|  |  | 
|  | std::vector<IndexedDBDatabase*> databases = | 
|  | factory->GetOpenDatabasesForBucket(bucket_locator); | 
|  |  | 
|  | if (!databases.empty()) { | 
|  | // Compact the first db's backing store since all the db's are in the same | 
|  | // backing store. | 
|  | IndexedDBDatabase* db = databases[0]; | 
|  | IndexedDBBackingStore* backing_store = db->backing_store(); | 
|  | backing_store->Compact(); | 
|  | } | 
|  | std::move(callback).Run(); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::BindMockFailureSingletonForTesting( | 
|  | mojo::PendingReceiver<storage::mojom::MockFailureInjector> receiver) { | 
|  | // Lazily instantiate the GetTestClassFactory. | 
|  | if (!mock_failure_injector_.has_value()) | 
|  | mock_failure_injector_.emplace(GetTestClassFactory()); | 
|  |  | 
|  | // TODO(enne): this should really not be a static setter. | 
|  | CHECK(!mock_failure_injector_->is_bound()); | 
|  | GetTestClassFactory()->Reset(); | 
|  | IndexedDBClassFactory::SetIndexedDBClassFactoryGetter(GetTestIDBClassFactory); | 
|  |  | 
|  | mock_failure_injector_->Bind(std::move(receiver)); | 
|  | mock_failure_injector_->set_disconnect_handler(base::BindOnce([]() { | 
|  | IndexedDBClassFactory::SetIndexedDBClassFactoryGetter(nullptr); | 
|  | })); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::GetDatabaseKeysForTesting( | 
|  | GetDatabaseKeysForTestingCallback callback) { | 
|  | std::move(callback).Run(SchemaVersionKey::Encode(), DataVersionKey::Encode()); | 
|  | } | 
|  |  | 
|  | IndexedDBFactoryImpl* IndexedDBContextImpl::GetIDBFactory() { | 
|  | DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); | 
|  | if (!indexeddb_factory_.get()) { | 
|  | indexeddb_factory_ = std::make_unique<IndexedDBFactoryImpl>( | 
|  | this, IndexedDBClassFactory::Get(), clock_); | 
|  | } | 
|  | return indexeddb_factory_.get(); | 
|  | } | 
|  |  | 
|  | std::vector<storage::BucketLocator> IndexedDBContextImpl::GetAllBuckets() { | 
|  | DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); | 
|  | return std::vector<storage::BucketLocator>(bucket_set_.begin(), | 
|  | bucket_set_.end()); | 
|  | } | 
|  |  | 
|  | absl::optional<storage::BucketLocator> IndexedDBContextImpl::LookUpBucket( | 
|  | storage::BucketId bucket_id) { | 
|  | DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); | 
|  | auto bucket_locator = | 
|  | base::ranges::find(bucket_set_, bucket_id, &storage::BucketLocator::id); | 
|  | if (bucket_locator == bucket_set_.end()) | 
|  | return absl::nullopt; | 
|  |  | 
|  | return *bucket_locator; | 
|  | } | 
|  |  | 
|  | int IndexedDBContextImpl::GetBucketBlobFileCount( | 
|  | const storage::BucketLocator& bucket_locator) { | 
|  | DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); | 
|  | int count = 0; | 
|  | base::FileEnumerator file_enumerator(GetBlobStorePath(bucket_locator), true, | 
|  | base::FileEnumerator::FILES); | 
|  | for (base::FilePath file_path = file_enumerator.Next(); !file_path.empty(); | 
|  | file_path = file_enumerator.Next()) { | 
|  | count++; | 
|  | } | 
|  | return count; | 
|  | } | 
|  |  | 
|  | int64_t IndexedDBContextImpl::GetBucketDiskUsage( | 
|  | const storage::BucketLocator& bucket_locator) { | 
|  | DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); | 
|  | if (!LookUpBucket(bucket_locator.id)) | 
|  | return 0; | 
|  | EnsureDiskUsageCacheInitialized(bucket_locator); | 
|  | return bucket_size_map_[bucket_locator]; | 
|  | } | 
|  |  | 
|  | base::Time IndexedDBContextImpl::GetBucketLastModified( | 
|  | const storage::BucketLocator& bucket_locator) { | 
|  | DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); | 
|  | if (!LookUpBucket(bucket_locator.id)) | 
|  | return base::Time(); | 
|  |  | 
|  | if (is_incognito()) { | 
|  | if (!indexeddb_factory_) | 
|  | return base::Time(); | 
|  | return indexeddb_factory_->GetLastModified(bucket_locator); | 
|  | } | 
|  |  | 
|  | base::FilePath idb_directory = GetLevelDBPath(bucket_locator); | 
|  | absl::optional<base::File::Info> info = | 
|  | filesystem_proxy_->GetFileInfo(idb_directory); | 
|  | if (!info.has_value()) | 
|  | return base::Time(); | 
|  | return info->last_modified; | 
|  | } | 
|  |  | 
|  | std::vector<base::FilePath> IndexedDBContextImpl::GetStoragePaths( | 
|  | const storage::BucketLocator& bucket_locator) const { | 
|  | std::vector<base::FilePath> paths = {GetLevelDBPath(bucket_locator), | 
|  | GetBlobStorePath(bucket_locator)}; | 
|  | return paths; | 
|  | } | 
|  |  | 
|  | const base::FilePath IndexedDBContextImpl::GetDataPath( | 
|  | const storage::BucketLocator& bucket_locator) const { | 
|  | if (indexed_db::ShouldUseLegacyFilePath(bucket_locator)) { | 
|  | // First-party idb files for the default, for legacy reasons, are stored at: | 
|  | // {{storage_partition_path}}/IndexedDB/ | 
|  | // TODO(crbug.com/1315371): Migrate all first party buckets to the new path. | 
|  | return GetLegacyDataPath(); | 
|  | } | 
|  |  | 
|  | // Third-party idb files are stored at: | 
|  | // {{storage_partition_path}}/WebStorage/{{bucket_id}}/IndexedDB/ | 
|  | return quota_manager_proxy()->GetClientBucketPath( | 
|  | bucket_locator, storage::QuotaClientType::kIndexedDatabase); | 
|  | } | 
|  |  | 
|  | const base::FilePath IndexedDBContextImpl::GetLegacyDataPath() const { | 
|  | return base_data_path_.empty() | 
|  | ? base_data_path_ | 
|  | : base_data_path_.Append(storage::kIndexedDbDirectory); | 
|  | } | 
|  |  | 
|  | const base::FilePath IndexedDBContextImpl::GetFirstPartyDataPathForTesting() | 
|  | const { | 
|  | return GetLegacyDataPath(); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::FactoryOpened( | 
|  | const storage::BucketLocator& bucket_locator) { | 
|  | DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); | 
|  | if (bucket_set_.insert(bucket_locator).second) { | 
|  | // A newly created db, notify the quota system. | 
|  | QueryDiskAndUpdateQuotaUsage(bucket_locator); | 
|  | } else { | 
|  | EnsureDiskUsageCacheInitialized(bucket_locator); | 
|  | } | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::ConnectionOpened( | 
|  | const storage::BucketLocator& bucket_locator, | 
|  | IndexedDBConnection* connection) { | 
|  | DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); | 
|  | quota_manager_proxy()->NotifyBucketAccessed(bucket_locator.id, | 
|  | base::Time::Now()); | 
|  | if (bucket_set_.insert(bucket_locator).second) { | 
|  | // A newly created db, notify the quota system. | 
|  | QueryDiskAndUpdateQuotaUsage(bucket_locator); | 
|  | } else { | 
|  | EnsureDiskUsageCacheInitialized(bucket_locator); | 
|  | } | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::ConnectionClosed( | 
|  | const storage::BucketLocator& bucket_locator, | 
|  | IndexedDBConnection* connection) { | 
|  | DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); | 
|  | quota_manager_proxy()->NotifyBucketAccessed(bucket_locator.id, | 
|  | base::Time::Now()); | 
|  | if (indexeddb_factory_.get() && | 
|  | indexeddb_factory_->GetConnectionCount(bucket_locator.id) == 0) | 
|  | QueryDiskAndUpdateQuotaUsage(bucket_locator); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::TransactionComplete( | 
|  | const storage::BucketLocator& bucket_locator) { | 
|  | DCHECK(!indexeddb_factory_.get() || | 
|  | indexeddb_factory_->GetConnectionCount(bucket_locator.id) > 0); | 
|  | QueryDiskAndUpdateQuotaUsage(bucket_locator); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::DatabaseDeleted( | 
|  | const storage::BucketLocator& bucket_locator) { | 
|  | bucket_set_.insert(bucket_locator); | 
|  | QueryDiskAndUpdateQuotaUsage(bucket_locator); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::BlobFilesCleaned( | 
|  | const storage::BucketLocator& bucket_locator) { | 
|  | QueryDiskAndUpdateQuotaUsage(bucket_locator); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::NotifyIndexedDBListChanged( | 
|  | const storage::BucketLocator& bucket_locator) { | 
|  | for (auto& observer : observers_) { | 
|  | observer->OnIndexedDBListChanged(bucket_locator); | 
|  | } | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::NotifyIndexedDBContentChanged( | 
|  | const storage::BucketLocator& bucket_locator, | 
|  | const std::u16string& database_name, | 
|  | const std::u16string& object_store_name) { | 
|  | for (auto& observer : observers_) { | 
|  | observer->OnIndexedDBContentChanged(bucket_locator, database_name, | 
|  | object_store_name); | 
|  | } | 
|  | } | 
|  |  | 
|  | IndexedDBContextImpl::~IndexedDBContextImpl() { | 
|  | DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); | 
|  | if (indexeddb_factory_.get()) | 
|  | indexeddb_factory_->ContextDestroyed(); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::ShutdownOnIDBSequence() { | 
|  | DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); | 
|  |  | 
|  | if (force_keep_session_state_) | 
|  | return; | 
|  |  | 
|  | // Clear session-only databases. | 
|  | if (sites_to_purge_on_shutdown_.empty()) | 
|  | return; | 
|  |  | 
|  | IndexedDBFactoryImpl* factory = GetIDBFactory(); | 
|  | const auto& storage_key_to_file_path = FindLegacyIndexedDBFiles(); | 
|  | const auto& bucket_id_to_file_path = FindIndexedDBFiles(); | 
|  | for (const auto& bucket_locator : bucket_set_) { | 
|  | const auto& origin_it = sites_to_purge_on_shutdown_.find( | 
|  | net::SchemefulSite(bucket_locator.storage_key.origin())); | 
|  | const auto& top_site_it = sites_to_purge_on_shutdown_.find( | 
|  | bucket_locator.storage_key.top_level_site()); | 
|  | if (origin_it == sites_to_purge_on_shutdown_.end() && | 
|  | top_site_it == sites_to_purge_on_shutdown_.end()) { | 
|  | // No match for a site we want to clear in this bucket locator. | 
|  | continue; | 
|  | } | 
|  | base::FilePath path; | 
|  | const auto& legacy_it = | 
|  | storage_key_to_file_path.find(bucket_locator.storage_key); | 
|  | const auto& bucket_path_it = bucket_id_to_file_path.find(bucket_locator.id); | 
|  | // If the bucket exists on the file system, it is in one of two possible | 
|  | // locations. | 
|  | if (legacy_it != storage_key_to_file_path.end()) { | 
|  | DCHECK(bucket_path_it == bucket_id_to_file_path.end()); | 
|  | DCHECK(bucket_locator.storage_key.IsFirstPartyContext()); | 
|  | DCHECK(bucket_locator.is_default); | 
|  | path = legacy_it->second; | 
|  | } else if (bucket_path_it != bucket_id_to_file_path.end()) { | 
|  | DCHECK(legacy_it == storage_key_to_file_path.end()); | 
|  | path = bucket_path_it->second; | 
|  | } | 
|  | if (!path.empty()) { | 
|  | factory->ForceClose(bucket_locator.id, false); | 
|  | filesystem_proxy_->DeletePathRecursively(path); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::Shutdown() { | 
|  | // Important: This function is NOT called on the IDB Task Runner. All variable | 
|  | // access must be thread-safe. | 
|  | if (is_incognito()) | 
|  | return; | 
|  |  | 
|  | IDBTaskRunner()->PostTask( | 
|  | FROM_HERE, | 
|  | base::BindOnce( | 
|  | &IndexedDBContextImpl::InitializeFromFilesIfNeeded, | 
|  | weak_factory_.GetWeakPtr(), | 
|  | base::BindOnce(&IndexedDBContextImpl::ShutdownOnIDBSequence, | 
|  | base::WrapRefCounted(this)))); | 
|  | } | 
|  |  | 
|  | base::FilePath IndexedDBContextImpl::GetBlobStorePath( | 
|  | const storage::BucketLocator& bucket_locator) const { | 
|  | DCHECK(!is_incognito()); | 
|  | return GetDataPath(bucket_locator) | 
|  | .Append(indexed_db::GetBlobStoreFileName(bucket_locator)); | 
|  | } | 
|  |  | 
|  | base::FilePath IndexedDBContextImpl::GetLevelDBPath( | 
|  | const storage::BucketLocator& bucket_locator) const { | 
|  | DCHECK(!is_incognito()); | 
|  | return GetDataPath(bucket_locator) | 
|  | .Append(indexed_db::GetLevelDBFileName(bucket_locator)); | 
|  | } | 
|  |  | 
|  | base::FilePath IndexedDBContextImpl::GetLevelDBPathForTesting( | 
|  | const storage::BucketLocator& bucket_locator) const { | 
|  | return GetLevelDBPath(bucket_locator); | 
|  | } | 
|  |  | 
|  | int64_t IndexedDBContextImpl::ReadUsageFromDisk( | 
|  | const storage::BucketLocator& bucket_locator) const { | 
|  | if (is_incognito()) { | 
|  | if (!indexeddb_factory_) | 
|  | return 0; | 
|  | return indexeddb_factory_->GetInMemoryDBSize(bucket_locator); | 
|  | } | 
|  |  | 
|  | int64_t total_size = 0; | 
|  | for (const base::FilePath& path : GetStoragePaths(bucket_locator)) | 
|  | total_size += filesystem_proxy_->ComputeDirectorySize(path); | 
|  | return total_size; | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::EnsureDiskUsageCacheInitialized( | 
|  | const storage::BucketLocator& bucket_locator) { | 
|  | if (bucket_size_map_.find(bucket_locator) == bucket_size_map_.end()) | 
|  | bucket_size_map_[bucket_locator] = ReadUsageFromDisk(bucket_locator); | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::QueryDiskAndUpdateQuotaUsage( | 
|  | const storage::BucketLocator& bucket_locator) { | 
|  | int64_t former_disk_usage = bucket_size_map_[bucket_locator]; | 
|  | int64_t current_disk_usage = ReadUsageFromDisk(bucket_locator); | 
|  | int64_t difference = current_disk_usage - former_disk_usage; | 
|  | if (difference) { | 
|  | bucket_size_map_[bucket_locator] = current_disk_usage; | 
|  | quota_manager_proxy()->NotifyBucketModified( | 
|  | storage::QuotaClientType::kIndexedDatabase, bucket_locator.id, | 
|  | difference, base::Time::Now(), base::SequencedTaskRunnerHandle::Get(), | 
|  | base::DoNothing()); | 
|  | NotifyIndexedDBListChanged(bucket_locator); | 
|  | } | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::InitializeFromFilesIfNeeded( | 
|  | base::OnceClosure callback) { | 
|  | DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); | 
|  | if (did_initialize_from_files_) { | 
|  | std::move(callback).Run(); | 
|  | return; | 
|  | } | 
|  | const auto& storage_key_to_file_path = FindLegacyIndexedDBFiles(); | 
|  | const auto& bucket_id_to_file_path = FindIndexedDBFiles(); | 
|  | if (storage_key_to_file_path.empty() && bucket_id_to_file_path.empty()) { | 
|  | did_initialize_from_files_ = true; | 
|  | std::move(callback).Run(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | using Barrier = | 
|  | base::RepeatingCallback<void(absl::optional<storage::BucketLocator>)>; | 
|  | Barrier barrier = | 
|  | base::BarrierCallback<absl::optional<storage::BucketLocator>>( | 
|  | storage_key_to_file_path.size() + bucket_id_to_file_path.size(), | 
|  | base::BindOnce( | 
|  | [](base::WeakPtr<IndexedDBContextImpl> context, | 
|  | base::OnceClosure inner_callback, | 
|  | const std::vector<absl::optional<storage::BucketLocator>>& | 
|  | bucket_locators) { | 
|  | DCHECK(context); | 
|  | for (auto& locator : bucket_locators) { | 
|  | if (locator) | 
|  | context->bucket_set_.insert(*locator); | 
|  | } | 
|  | context->did_initialize_from_files_ = true; | 
|  | std::move(inner_callback).Run(); | 
|  | }, | 
|  | weak_factory_.GetWeakPtr(), std::move(callback))); | 
|  |  | 
|  | auto on_lookup_done = base::BindRepeating( | 
|  | [](Barrier barrier, | 
|  | storage::QuotaErrorOr<storage::BucketInfo> bucket_info) { | 
|  | if (bucket_info.ok()) | 
|  | barrier.Run(bucket_info->ToBucketLocator()); | 
|  | else | 
|  | barrier.Run(absl::nullopt); | 
|  | }, | 
|  | barrier); | 
|  |  | 
|  | for (auto& iter : storage_key_to_file_path) { | 
|  | quota_manager_proxy()->UpdateOrCreateBucket( | 
|  | storage::BucketInitParams::ForDefaultBucket(iter.first), | 
|  | idb_task_runner_, on_lookup_done); | 
|  | } | 
|  |  | 
|  | for (auto& iter : bucket_id_to_file_path) { | 
|  | quota_manager_proxy()->GetBucketById(iter.first, idb_task_runner_, | 
|  | on_lookup_done); | 
|  | } | 
|  | } | 
|  |  | 
|  | void IndexedDBContextImpl::ForceInitializeFromFilesForTesting( | 
|  | ForceInitializeFromFilesForTestingCallback callback) { | 
|  | did_initialize_from_files_ = false; | 
|  | InitializeFromFilesIfNeeded(std::move(callback)); | 
|  | } | 
|  |  | 
|  | const std::map<blink::StorageKey, base::FilePath> | 
|  | IndexedDBContextImpl::FindLegacyIndexedDBFiles() { | 
|  | DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); | 
|  |  | 
|  | base::FilePath data_path = GetLegacyDataPath(); | 
|  | if (data_path.empty()) | 
|  | return {}; | 
|  | std::map<blink::StorageKey, base::FilePath> storage_key_to_file_path; | 
|  | base::FileEnumerator file_enumerator(data_path, /*recursive=*/false, | 
|  | base::FileEnumerator::DIRECTORIES); | 
|  | for (base::FilePath file_path = file_enumerator.Next(); !file_path.empty(); | 
|  | file_path = file_enumerator.Next()) { | 
|  | if (file_path.Extension() == indexed_db::kLevelDBExtension && | 
|  | file_path.RemoveExtension().Extension() == | 
|  | indexed_db::kIndexedDBExtension) { | 
|  | std::string storage_key_id = file_path.BaseName() | 
|  | .RemoveExtension() | 
|  | .RemoveExtension() | 
|  | .MaybeAsASCII(); | 
|  | storage_key_to_file_path[blink::StorageKey( | 
|  | storage::GetOriginFromIdentifier(storage_key_id))] = file_path; | 
|  | } | 
|  | } | 
|  | return storage_key_to_file_path; | 
|  | } | 
|  |  | 
|  | const std::map<storage::BucketId, base::FilePath> | 
|  | IndexedDBContextImpl::FindIndexedDBFiles() { | 
|  | DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); | 
|  |  | 
|  | std::map<storage::BucketId, base::FilePath> bucket_id_to_file_path; | 
|  | if (base_data_path_.empty()) | 
|  | return bucket_id_to_file_path; | 
|  |  | 
|  | base::FilePath third_party_path = | 
|  | base_data_path_.Append(storage::kWebStorageDirectory); | 
|  | base::FileEnumerator file_enumerator(third_party_path, /*recursive=*/true, | 
|  | base::FileEnumerator::DIRECTORIES); | 
|  | for (base::FilePath file_path = file_enumerator.Next(); !file_path.empty(); | 
|  | file_path = file_enumerator.Next()) { | 
|  | if (file_path.BaseName().Extension() == indexed_db::kLevelDBExtension && | 
|  | file_path.BaseName().RemoveExtension().value() == | 
|  | indexed_db::kIndexedDBFile && | 
|  | file_path.DirName().BaseName().value() == | 
|  | storage::kIndexedDbDirectory) { | 
|  | int64_t raw_bucket_id = 0; | 
|  | bool success = base::StringToInt64( | 
|  | file_path.DirName().DirName().BaseName().value(), &raw_bucket_id); | 
|  | if (success && raw_bucket_id > 0) { | 
|  | bucket_id_to_file_path[storage::BucketId::FromUnsafeValue( | 
|  | raw_bucket_id)] = file_path; | 
|  | } | 
|  | } | 
|  | } | 
|  | return bucket_id_to_file_path; | 
|  | } | 
|  |  | 
|  | }  // namespace content |