| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // 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 <algorithm> |
| #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/scoped_temp_dir.h" |
| #include "base/metrics/histogram_functions.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; |
| } |
| |
| // This may be called after the IndexedDBContext is destroyed. |
| const std::map<blink::StorageKey, base::FilePath> |
| DefaultBucketFilePerFirstPartyStorageKey( |
| const base::FilePath& first_party_path) { |
| // TODO(jsbell): DCHECK that this is running on an IndexedDB sequence, |
| // if a global handle to it is ever available. |
| std::map<blink::StorageKey, base::FilePath> storage_key_to_file_path; |
| if (first_party_path.empty()) |
| return storage_key_to_file_path; |
| base::FileEnumerator file_enumerator(first_party_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; |
| } |
| |
| // This may be called after the IndexedDBContext is destroyed. |
| const std::map<storage::BucketId, base::FilePath> |
| DefaultBucketFilePerThirdPartyBucketId(const base::FilePath& third_party_path) { |
| // TODO(jsbell): DCHECK that this is running on an IndexedDB sequence, |
| // if a global handle to it is ever available. |
| std::map<storage::BucketId, base::FilePath> bucket_id_to_file_path; |
| if (third_party_path.empty()) |
| return bucket_id_to_file_path; |
| 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 |
| |
| // 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) { |
| GetOrCreateDefaultBucket( |
| storage_key, |
| base::BindOnce(&IndexedDBContextImpl::BindIndexedDBImpl, |
| weak_factory_.GetWeakPtr(), std::move(receiver))); |
| } |
| |
| void IndexedDBContextImpl::BindIndexedDBImpl( |
| mojo::PendingReceiver<blink::mojom::IDBFactory> receiver, |
| const absl::optional<storage::BucketLocator>& bucket_locator) { |
| 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()); |
| storage_key_to_bucket_locator_[bucket_locator.storage_key] = bucket_locator; |
| DoDeleteBucketData(bucket_locator, std::move(callback)); |
| } |
| |
| void IndexedDBContextImpl::DoDeleteBucketData( |
| const storage::BucketLocator& bucket_locator, |
| base::OnceCallback<void(bool)> callback) { |
| // TODO(estade): handle non-default buckets. |
| if (!bucket_locator.is_default) |
| return; |
| |
| ForceClose(bucket_locator, |
| storage::mojom::ForceCloseReason::FORCE_CLOSE_DELETE_ORIGIN, |
| base::DoNothing()); |
| // InitializeFromFilesIfNeeded might not have finished, so we need to check |
| // if there's a file in the directory and not exit early if so. |
| const auto& storage_key_to_file_path = |
| DefaultBucketFilePerFirstPartyStorageKey(GetFirstPartyDataPath()); |
| const auto& bucket_id_to_file_path = |
| DefaultBucketFilePerThirdPartyBucketId(GetThirdPartyDataPath()); |
| if (!HasBucket(bucket_locator) && |
| storage_key_to_file_path.find(bucket_locator.storage_key) == |
| storage_key_to_file_path.end() && |
| bucket_id_to_file_path.find(bucket_locator.id) == |
| bucket_id_to_file_path.end()) { |
| std::move(callback).Run(true); |
| return; |
| } |
| |
| if (is_incognito()) { |
| bucket_set_.erase(bucket_locator); |
| bucket_size_map_.erase(bucket_locator); |
| storage_key_to_bucket_locator_.erase(bucket_locator.storage_key); |
| std::move(callback).Run(true); |
| return; |
| } |
| |
| base::FilePath idb_directory = GetLevelDBPath(bucket_locator); |
| EnsureDiskUsageCacheInitialized(bucket_locator); |
| |
| leveldb::Status s = |
| IndexedDBClassFactory::Get()->leveldb_factory().DestroyLevelDB( |
| idb_directory); |
| 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); |
| storage_key_to_bucket_locator_.erase(bucket_locator.storage_key); |
| } |
| std::move(callback).Run(success); |
| } |
| |
| void IndexedDBContextImpl::ForceClose(const blink::StorageKey& storage_key, |
| storage::mojom::ForceCloseReason reason, |
| base::OnceClosure closure) { |
| GetOrCreateDefaultBucket( |
| storage_key, |
| base::BindOnce(&IndexedDBContextImpl::ForceCloseImpl, |
| weak_factory_.GetWeakPtr(), reason, std::move(closure))); |
| } |
| |
| void IndexedDBContextImpl::ForceClose( |
| const storage::BucketLocator& bucket_locator, |
| storage::mojom::ForceCloseReason reason, |
| base::OnceClosure closure) { |
| DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); |
| storage_key_to_bucket_locator_[bucket_locator.storage_key] = bucket_locator; |
| ForceCloseImpl(reason, std::move(closure), bucket_locator); |
| } |
| |
| void IndexedDBContextImpl::ForceCloseImpl( |
| const storage::mojom::ForceCloseReason reason, |
| base::OnceClosure closure, |
| const absl::optional<storage::BucketLocator>& bucket_locator) { |
| base::UmaHistogramEnumeration("WebCore.IndexedDB.Context.ForceCloseReason", |
| reason); |
| if (!bucket_locator || !HasBucket(*bucket_locator)) { |
| 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. |
| auto bucket_locator_copy = *bucket_locator; |
| indexeddb_factory_->ForceClose( |
| bucket_locator_copy, |
| reason == storage::mojom::ForceCloseReason::FORCE_CLOSE_DELETE_ORIGIN); |
| DCHECK_EQ(0UL, GetConnectionCountSync(bucket_locator_copy)); |
| std::move(closure).Run(); |
| } |
| |
| void IndexedDBContextImpl::GetConnectionCount( |
| const blink::StorageKey& storage_key, |
| GetConnectionCountCallback callback) { |
| GetOrCreateDefaultBucket( |
| storage_key, |
| base::BindOnce(&IndexedDBContextImpl::GetConnectionCountImpl, |
| weak_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void IndexedDBContextImpl::GetConnectionCount( |
| const storage::BucketLocator& bucket_locator, |
| GetConnectionCountCallback callback) { |
| DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); |
| storage_key_to_bucket_locator_[bucket_locator.storage_key] = bucket_locator; |
| GetConnectionCountImpl(std::move(callback), bucket_locator); |
| } |
| |
| void IndexedDBContextImpl::GetConnectionCountImpl( |
| GetConnectionCountCallback callback, |
| const absl::optional<storage::BucketLocator>& bucket_locator) { |
| size_t count = 0; |
| if (bucket_locator) { |
| count = GetConnectionCountSync(*bucket_locator); |
| } |
| std::move(callback).Run(count); |
| } |
| |
| size_t IndexedDBContextImpl::GetConnectionCountSync( |
| const storage::BucketLocator& bucket_locator) { |
| size_t count = 0; |
| if (HasBucket(bucket_locator) && indexeddb_factory_.get()) { |
| count = indexeddb_factory_->GetConnectionCount(bucket_locator); |
| } |
| return count; |
| } |
| |
| void IndexedDBContextImpl::DownloadBucketData( |
| const blink::StorageKey& storage_key, |
| DownloadBucketDataCallback callback) { |
| GetOrCreateDefaultBucket( |
| storage_key, |
| base::BindOnce(&IndexedDBContextImpl::DownloadBucketDataImpl, |
| weak_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void IndexedDBContextImpl::DownloadBucketData( |
| const storage::BucketLocator& bucket_locator, |
| DownloadBucketDataCallback callback) { |
| DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); |
| storage_key_to_bucket_locator_[bucket_locator.storage_key] = bucket_locator; |
| return DownloadBucketDataImpl(std::move(callback), bucket_locator); |
| } |
| |
| void IndexedDBContextImpl::DownloadBucketDataImpl( |
| DownloadBucketDataCallback callback, |
| const absl::optional<storage::BucketLocator>& bucket_locator) { |
| bool success = false; |
| |
| // Make sure the database hasn't been deleted. |
| if (!bucket_locator || !HasBucket(*bucket_locator)) { |
| std::move(callback).Run(success, base::FilePath(), base::FilePath()); |
| return; |
| } |
| |
| ForceClose(*bucket_locator, |
| 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 info(base::Value::Type::DICTIONARY); |
| // TODO(https://crbug.com/1199077): Serialize storage key directly |
| // once supported by OriginDetails. |
| info.SetStringKey("url", bucket_locator.storage_key.origin().Serialize()); |
| info.SetDoubleKey("size", |
| static_cast<double>(GetBucketDiskUsage(bucket_locator))); |
| info.SetDoubleKey("last_modified", |
| GetBucketLastModified(bucket_locator).ToJsTime()); |
| |
| base::Value paths(base::Value::Type::LIST); |
| if (!is_incognito()) { |
| for (const base::FilePath& path : GetStoragePaths(bucket_locator)) |
| paths.Append(path.AsUTF8Unsafe()); |
| } else { |
| paths.Append("N/A"); |
| } |
| info.SetKey("paths", std::move(paths)); |
| info.SetDoubleKey("connection_count", |
| GetConnectionCountSync(bucket_locator)); |
| |
| // 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 database_list(base::Value::Type::LIST); |
| |
| for (IndexedDBDatabase* db : databases) { |
| base::Value db_info(base::Value::Type::DICTIONARY); |
| |
| db_info.SetStringKey("name", db->name()); |
| db_info.SetDoubleKey("connection_count", db->ConnectionCount()); |
| db_info.SetDoubleKey("active_open_delete", db->ActiveOpenDeleteCount()); |
| db_info.SetDoubleKey("pending_open_delete", db->PendingOpenDeleteCount()); |
| |
| base::Value transaction_list(base::Value::Type::LIST); |
| |
| for (IndexedDBConnection* connection : db->connections()) { |
| for (const auto& transaction_id_pair : connection->transactions()) { |
| const auto* transaction = transaction_id_pair.second.get(); |
| base::Value transaction_info(base::Value::Type::DICTIONARY); |
| |
| switch (transaction->mode()) { |
| case blink::mojom::IDBTransactionMode::ReadOnly: |
| transaction_info.SetStringKey("mode", "readonly"); |
| break; |
| case blink::mojom::IDBTransactionMode::ReadWrite: |
| transaction_info.SetStringKey("mode", "readwrite"); |
| break; |
| case blink::mojom::IDBTransactionMode::VersionChange: |
| transaction_info.SetStringKey("mode", "versionchange"); |
| break; |
| } |
| |
| switch (transaction->state()) { |
| case IndexedDBTransaction::CREATED: |
| transaction_info.SetStringKey("status", "blocked"); |
| break; |
| case IndexedDBTransaction::STARTED: |
| if (transaction->diagnostics().tasks_scheduled > 0) |
| transaction_info.SetStringKey("status", "running"); |
| else |
| transaction_info.SetStringKey("status", "started"); |
| break; |
| case IndexedDBTransaction::COMMITTING: |
| transaction_info.SetStringKey("status", "committing"); |
| break; |
| case IndexedDBTransaction::FINISHED: |
| transaction_info.SetStringKey("status", "finished"); |
| break; |
| } |
| |
| transaction_info.SetDoubleKey("tid", transaction->id()); |
| transaction_info.SetDoubleKey( |
| "age", |
| (base::Time::Now() - transaction->diagnostics().creation_time) |
| .InMillisecondsF()); |
| transaction_info.SetDoubleKey( |
| "runtime", |
| (base::Time::Now() - transaction->diagnostics().start_time) |
| .InMillisecondsF()); |
| transaction_info.SetDoubleKey( |
| "tasks_scheduled", transaction->diagnostics().tasks_scheduled); |
| transaction_info.SetDoubleKey( |
| "tasks_completed", transaction->diagnostics().tasks_completed); |
| |
| base::Value scope(base::Value::Type::LIST); |
| 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.SetKey("scope", std::move(scope)); |
| transaction_list.Append(std::move(transaction_info)); |
| } |
| } |
| db_info.SetKey("transactions", std::move(transaction_list)); |
| |
| database_list.Append(std::move(db_info)); |
| } |
| info.SetKey("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(GetFirstPartyDataPath()); |
| } |
| |
| 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() || !HasBucket(bucket_locator)) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| if (indexeddb_factory_.get()) { |
| indexeddb_factory_->ForceSchemaDowngrade(bucket_locator); |
| std::move(callback).Run(true); |
| return; |
| } |
| ForceClose( |
| bucket_locator, |
| 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() || !HasBucket(bucket_locator)) { |
| 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, 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()); |
| } |
| |
| bool IndexedDBContextImpl::HasBucket( |
| const storage::BucketLocator& bucket_locator) { |
| DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); |
| return bucket_set_.find(bucket_locator) != bucket_set_.end(); |
| } |
| |
| 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 (!HasBucket(bucket_locator)) |
| return 0; |
| EnsureDiskUsageCacheInitialized(bucket_locator); |
| return bucket_size_map_[bucket_locator]; |
| } |
| |
| base::Time IndexedDBContextImpl::GetBucketLastModified( |
| const storage::BucketLocator& bucket_locator) { |
| DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); |
| if (!HasBucket(bucket_locator)) |
| 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 { |
| // TODO(crbug.com/1315371): Allow custom bucket names. |
| if (bucket_locator.storage_key.IsFirstPartyContext()) { |
| // First-party idb files, for legacy reasons, are stored at: |
| // {{storage_partition_path}}/IndexedDB/ |
| // TODO(crbug.com/1315371): Migrate all first party buckets to the new path. |
| return GetFirstPartyDataPath(); |
| } else { |
| // Third-party idb files are stored at: |
| // {{storage_partition_path}}/WebStorage/ |
| // TODO(crbug.com/1315371): Use QuotaManagerProxy::GetBucketPath. |
| return GetThirdPartyDataPath(); |
| } |
| } |
| |
| const base::FilePath IndexedDBContextImpl::GetFirstPartyDataPath() const { |
| return base_data_path_.empty() |
| ? base_data_path_ |
| : base_data_path_.Append(storage::kIndexedDbDirectory); |
| } |
| |
| const base::FilePath IndexedDBContextImpl::GetFirstPartyDataPathForTesting() |
| const { |
| return GetFirstPartyDataPath(); |
| } |
| |
| const base::FilePath IndexedDBContextImpl::GetThirdPartyDataPath() const { |
| return base_data_path_.empty() |
| ? base_data_path_ |
| : base_data_path_.Append(storage::kWebStorageDirectory); |
| } |
| |
| void IndexedDBContextImpl::FactoryOpened( |
| const storage::BucketLocator& bucket_locator) { |
| DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); |
| const auto& it = |
| storage_key_to_bucket_locator_.find(bucket_locator.storage_key); |
| if (it == storage_key_to_bucket_locator_.end()) { |
| storage_key_to_bucket_locator_[bucket_locator.storage_key] = bucket_locator; |
| } |
| 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()); |
| const auto& it = |
| storage_key_to_bucket_locator_.find(bucket_locator.storage_key); |
| if (it == storage_key_to_bucket_locator_.end()) { |
| storage_key_to_bucket_locator_[bucket_locator.storage_key] = bucket_locator; |
| } |
| 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) == 0) |
| QueryDiskAndUpdateQuotaUsage(bucket_locator); |
| } |
| |
| void IndexedDBContextImpl::TransactionComplete( |
| const storage::BucketLocator& bucket_locator) { |
| DCHECK(!indexeddb_factory_.get() || |
| indexeddb_factory_->GetConnectionCount(bucket_locator) > 0); |
| QueryDiskAndUpdateQuotaUsage(bucket_locator); |
| } |
| |
| void IndexedDBContextImpl::DatabaseDeleted( |
| const storage::BucketLocator& bucket_locator) { |
| const auto& it = |
| storage_key_to_bucket_locator_.find(bucket_locator.storage_key); |
| if (it == storage_key_to_bucket_locator_.end()) { |
| storage_key_to_bucket_locator_[bucket_locator.storage_key] = 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 = |
| DefaultBucketFilePerFirstPartyStorageKey(GetFirstPartyDataPath()); |
| const auto& bucket_id_to_file_path = |
| DefaultBucketFilePerThirdPartyBucketId(GetThirdPartyDataPath()); |
| 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& first_party_it = |
| storage_key_to_file_path.find(bucket_locator.storage_key); |
| const auto& third_party_it = bucket_id_to_file_path.find(bucket_locator.id); |
| // If the bucket exists on the file system, it is in only one of the two |
| // possible locations depending on if the key is first or third party. |
| if (first_party_it != storage_key_to_file_path.end()) { |
| DCHECK(third_party_it == bucket_id_to_file_path.end()); |
| DCHECK(bucket_locator.storage_key.IsFirstPartyContext()); |
| path = first_party_it->second; |
| } else if (third_party_it != bucket_id_to_file_path.end()) { |
| DCHECK(first_party_it == storage_key_to_file_path.end()); |
| DCHECK(bucket_locator.storage_key.IsThirdPartyContext()); |
| path = third_party_it->second; |
| } |
| if (!path.empty()) { |
| factory->ForceClose(bucket_locator, 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 = |
| DefaultBucketFilePerFirstPartyStorageKey(GetFirstPartyDataPath()); |
| const auto& bucket_id_to_file_path = |
| DefaultBucketFilePerThirdPartyBucketId(GetThirdPartyDataPath()); |
| 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))); |
| |
| for (auto& iter : storage_key_to_file_path) { |
| GetOrCreateDefaultBucket( |
| iter.first, |
| base::BindOnce( |
| [](Barrier barrier, |
| const absl::optional<storage::BucketLocator>& bucket_locator) { |
| barrier.Run(bucket_locator); |
| }, |
| barrier)); |
| } |
| |
| for (auto& iter : bucket_id_to_file_path) { |
| quota_manager_proxy_->GetBucketById( |
| iter.first, idb_task_runner_, |
| base::BindOnce( |
| [](base::WeakPtr<IndexedDBContextImpl> context, Barrier barrier, |
| storage::QuotaErrorOr<storage::BucketInfo> result) { |
| if (result.ok()) { |
| const auto& bucket_locator = result->ToBucketLocator(); |
| if (context) { |
| DCHECK(bucket_locator.is_default); |
| context->storage_key_to_bucket_locator_[bucket_locator |
| .storage_key] = |
| bucket_locator; |
| } |
| barrier.Run(bucket_locator); |
| } else { |
| barrier.Run(absl::nullopt); |
| } |
| }, |
| weak_factory_.GetWeakPtr(), barrier)); |
| } |
| } |
| |
| void IndexedDBContextImpl::ForceInitializeFromFilesForTesting( |
| ForceInitializeFromFilesForTestingCallback callback) { |
| did_initialize_from_files_ = false; |
| InitializeFromFilesIfNeeded(std::move(callback)); |
| } |
| |
| void IndexedDBContextImpl::GetOrCreateDefaultBucket( |
| const blink::StorageKey& storage_key, |
| DidGetBucketLocatorCallback callback) { |
| DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); |
| const auto& bucket_locator = storage_key_to_bucket_locator_.find(storage_key); |
| if (bucket_locator == storage_key_to_bucket_locator_.end()) { |
| quota_manager_proxy_->UpdateOrCreateBucket( |
| storage::BucketInitParams::ForDefaultBucket(storage_key), |
| idb_task_runner_, |
| base::BindOnce( |
| [](base::WeakPtr<IndexedDBContextImpl> context, |
| DidGetBucketLocatorCallback inner_callback, |
| storage::QuotaErrorOr<storage::BucketInfo> result) { |
| if (result.ok()) { |
| const auto& bucket_locator = result->ToBucketLocator(); |
| if (context) { |
| context->storage_key_to_bucket_locator_[bucket_locator |
| .storage_key] = |
| bucket_locator; |
| } |
| std::move(inner_callback).Run(bucket_locator); |
| } else { |
| std::move(inner_callback).Run(absl::nullopt); |
| } |
| }, |
| weak_factory_.GetWeakPtr(), std::move(callback))); |
| } else { |
| std::move(callback).Run(bucket_locator->second); |
| } |
| } |
| |
| } // namespace content |