| // Copyright (c) 2013 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_factory_impl.h" |
| |
| #include <stdint.h> |
| |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/command_line.h" |
| #include "base/feature_list.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/rand_util.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/timer/timer.h" |
| #include "content/browser/indexed_db/indexed_db_backing_store.h" |
| #include "content/browser/indexed_db/indexed_db_class_factory.h" |
| #include "content/browser/indexed_db/indexed_db_context_impl.h" |
| #include "content/browser/indexed_db/indexed_db_database_error.h" |
| #include "content/browser/indexed_db/indexed_db_leveldb_operations.h" |
| #include "content/browser/indexed_db/indexed_db_metadata_coding.h" |
| #include "content/browser/indexed_db/indexed_db_pre_close_task_queue.h" |
| #include "content/browser/indexed_db/indexed_db_tombstone_sweeper.h" |
| #include "content/browser/indexed_db/indexed_db_tracing.h" |
| #include "third_party/blink/public/platform/modules/indexeddb/web_idb_database_exception.h" |
| #include "third_party/leveldatabase/env_chromium.h" |
| |
| using base::ASCIIToUTF16; |
| using url::Origin; |
| |
| namespace content { |
| |
| namespace { |
| |
| using PreCloseTask = IndexedDBPreCloseTaskQueue::PreCloseTask; |
| |
| // Time after the last connection to a database is closed and when we destroy |
| // the backing store. |
| const int64_t kBackingStoreGracePeriodSeconds = 2; |
| // Total time we let pre-close tasks run. |
| const int64_t kPreCloseTasksMaxRunPeriodSeconds = 60; |
| // The number of iterations for every 'round' of the tombstone sweeper. |
| const int kTombstoneSweeperRoundIterations = 1000; |
| // The maximum total iterations for the tombstone sweeper. |
| const int kTombstoneSweeperMaxIterations = 10 * 1000 * 1000; |
| |
| constexpr const base::TimeDelta kMinEarliestOriginSweepFromNow = |
| base::TimeDelta::FromDays(1); |
| static_assert(kMinEarliestOriginSweepFromNow < |
| IndexedDBFactoryImpl::kMaxEarliestOriginSweepFromNow, |
| "Min < Max"); |
| |
| constexpr const base::TimeDelta kMinEarliestGlobalSweepFromNow = |
| base::TimeDelta::FromMinutes(10); |
| static_assert(kMinEarliestGlobalSweepFromNow < |
| IndexedDBFactoryImpl::kMaxEarliestGlobalSweepFromNow, |
| "Min < Max"); |
| |
| base::Time GenerateNextOriginSweepTime(base::Time now) { |
| uint64_t range = |
| IndexedDBFactoryImpl::kMaxEarliestOriginSweepFromNow.InMilliseconds() - |
| kMinEarliestOriginSweepFromNow.InMilliseconds(); |
| int64_t rand_millis = kMinEarliestOriginSweepFromNow.InMilliseconds() + |
| static_cast<int64_t>(base::RandGenerator(range)); |
| return now + base::TimeDelta::FromMilliseconds(rand_millis); |
| } |
| |
| base::Time GenerateNextGlobalSweepTime(base::Time now) { |
| uint64_t range = |
| IndexedDBFactoryImpl::kMaxEarliestGlobalSweepFromNow.InMilliseconds() - |
| kMinEarliestGlobalSweepFromNow.InMilliseconds(); |
| int64_t rand_millis = kMinEarliestGlobalSweepFromNow.InMilliseconds() + |
| static_cast<int64_t>(base::RandGenerator(range)); |
| return now + base::TimeDelta::FromMilliseconds(rand_millis); |
| } |
| |
| leveldb::Status GetDBSizeFromEnv(leveldb::Env* env, |
| const std::string& path, |
| int64_t* total_size_out) { |
| *total_size_out = 0; |
| // Root path should be /, but in MemEnv, a path name is not tailed with '/' |
| DCHECK_EQ(path.back(), '/'); |
| const std::string path_without_slash = path.substr(0, path.length() - 1); |
| |
| // This assumes that leveldb will not put a subdirectory into the directory |
| std::vector<std::string> file_names; |
| leveldb::Status s = env->GetChildren(path_without_slash, &file_names); |
| if (!s.ok()) |
| return s; |
| |
| for (std::string& file_name : file_names) { |
| file_name.insert(0, path); |
| uint64_t file_size; |
| s = env->GetFileSize(file_name, &file_size); |
| if (!s.ok()) |
| return s; |
| else |
| *total_size_out += static_cast<int64_t>(file_size); |
| } |
| return s; |
| } |
| |
| } // namespace |
| |
| const base::Feature kIDBTombstoneStatistics{"IDBTombstoneStatistics", |
| base::FEATURE_DISABLED_BY_DEFAULT}; |
| |
| const base::Feature kIDBTombstoneDeletion{"IDBTombstoneDeletion", |
| base::FEATURE_ENABLED_BY_DEFAULT}; |
| |
| constexpr const base::TimeDelta |
| IndexedDBFactoryImpl::kMaxEarliestGlobalSweepFromNow; |
| constexpr const base::TimeDelta |
| IndexedDBFactoryImpl::kMaxEarliestOriginSweepFromNow; |
| |
| IndexedDBFactoryImpl::IndexedDBFactoryImpl( |
| IndexedDBContextImpl* context, |
| indexed_db::LevelDBFactory* leveldb_factory, |
| base::Clock* clock) |
| : context_(context), |
| leveldb_factory_(leveldb_factory), |
| clock_(clock), |
| earliest_sweep_(GenerateNextGlobalSweepTime(clock_->Now())) {} |
| |
| IndexedDBFactoryImpl::~IndexedDBFactoryImpl() { |
| } |
| |
| void IndexedDBFactoryImpl::RemoveDatabaseFromMaps( |
| const IndexedDBDatabase::Identifier& identifier) { |
| auto it = database_map_.find(identifier); |
| DCHECK(it != database_map_.end()); |
| IndexedDBDatabase* database = it->second; |
| database_map_.erase(it); |
| |
| std::pair<OriginDBMap::iterator, OriginDBMap::iterator> range = |
| origin_dbs_.equal_range(database->identifier().first); |
| DCHECK(range.first != range.second); |
| for (auto it2 = range.first; it2 != range.second; ++it2) { |
| if (it2->second == database) { |
| origin_dbs_.erase(it2); |
| break; |
| } |
| } |
| } |
| |
| void IndexedDBFactoryImpl::ReleaseDatabase( |
| const IndexedDBDatabase::Identifier& identifier, |
| bool forced_close) { |
| DCHECK(!database_map_.find(identifier)->second->backing_store()); |
| |
| RemoveDatabaseFromMaps(identifier); |
| |
| // No grace period on a forced-close, as the initiator is |
| // assuming the backing store will be released once all |
| // connections are closed. |
| ReleaseBackingStore(identifier.first, forced_close); |
| } |
| |
| void IndexedDBFactoryImpl::ReleaseBackingStore(const Origin& origin, |
| bool immediate) { |
| if (!base::ContainsKey(backing_store_map_, origin)) |
| return; |
| if (immediate) { |
| auto it = backing_stores_with_active_blobs_.find(origin); |
| if (it != backing_stores_with_active_blobs_.end()) { |
| it->second->active_blob_registry()->ForceShutdown(); |
| backing_stores_with_active_blobs_.erase(it); |
| } |
| } |
| |
| // Only close if this is the last reference. |
| if (!HasLastBackingStoreReference(origin)) |
| return; |
| |
| // If this factory does hold the last reference to the backing store, it can |
| // be closed - but unless requested to close it immediately, keep it around |
| // for a short period so that a re-open is fast. |
| if (immediate) { |
| CloseBackingStore(origin); |
| return; |
| } |
| |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| kIDBCloseImmediatelySwitch)) { |
| MaybeCloseBackingStore(origin); |
| return; |
| } |
| |
| // Start a timer to close the backing store, unless something else opens it |
| // in the mean time. |
| DCHECK(!backing_store_map_[origin]->close_timer()->IsRunning()); |
| backing_store_map_[origin]->close_timer()->Start( |
| FROM_HERE, base::TimeDelta::FromSeconds(kBackingStoreGracePeriodSeconds), |
| base::BindOnce(&IndexedDBFactoryImpl::MaybeStartPreCloseTasks, this, |
| origin)); |
| } |
| |
| void IndexedDBFactoryImpl::MaybeStartPreCloseTasks(const Origin& origin) { |
| if (!base::ContainsKey(backing_store_map_, origin)) |
| return; |
| // Another reference may have been created since the maybe-close was posted, |
| // so it is necessary to check again. |
| if (!HasLastBackingStoreReference(origin)) |
| return; |
| |
| base::ScopedClosureRunner maybe_close_backing_store_runner( |
| base::BindOnce(&IndexedDBFactoryImpl::MaybeCloseBackingStore, |
| base::Unretained(this), origin)); |
| |
| base::Time now = clock_->Now(); |
| |
| // Check that the last sweep hasn't run too recently. |
| if (earliest_sweep_ > now) |
| return; |
| |
| bool tombstone_stats_enabled = |
| base::FeatureList::IsEnabled(kIDBTombstoneStatistics); |
| bool tombstone_deletion_enabled = |
| base::FeatureList::IsEnabled(kIDBTombstoneDeletion); |
| |
| // After this check, exactly one of the flags must be true. |
| if (tombstone_stats_enabled == tombstone_deletion_enabled) |
| return; |
| |
| scoped_refptr<IndexedDBBackingStore> store = backing_store_map_[origin]; |
| |
| base::Time origin_earliest_sweep; |
| leveldb::Status s = |
| indexed_db::GetEarliestSweepTime(store->db(), &origin_earliest_sweep); |
| // TODO(dmurph): Log this or report to UMA. |
| if (!s.ok()) |
| return; |
| |
| // This origin hasn't been swept too recently. |
| if (origin_earliest_sweep > now) |
| return; |
| |
| // A sweep will happen now, so reset the sweep timers. |
| earliest_sweep_ = GenerateNextGlobalSweepTime(now); |
| scoped_refptr<LevelDBTransaction> txn = |
| IndexedDBClassFactory::Get()->CreateLevelDBTransaction(store->db()); |
| indexed_db::SetEarliestSweepTime(txn.get(), GenerateNextOriginSweepTime(now)); |
| s = txn->Commit(); |
| |
| // TODO(dmurph): Log this or report to UMA. |
| if (!s.ok()) |
| return; |
| |
| std::list<std::unique_ptr<PreCloseTask>> tasks; |
| IndexedDBTombstoneSweeper::Mode mode = |
| tombstone_stats_enabled ? IndexedDBTombstoneSweeper::Mode::STATISTICS |
| : IndexedDBTombstoneSweeper::Mode::DELETION; |
| tasks.push_back(std::make_unique<IndexedDBTombstoneSweeper>( |
| mode, kTombstoneSweeperRoundIterations, kTombstoneSweeperMaxIterations, |
| store->db()->db())); |
| // TODO(dmurph): Add compaction task that compacts all indexes if we have |
| // more than X deletions. |
| |
| store->SetPreCloseTaskList(std::make_unique<IndexedDBPreCloseTaskQueue>( |
| std::move(tasks), maybe_close_backing_store_runner.Release(), |
| base::TimeDelta::FromSeconds(kPreCloseTasksMaxRunPeriodSeconds), |
| std::make_unique<base::OneShotTimer>())); |
| store->StartPreCloseTasks(); |
| } |
| |
| void IndexedDBFactoryImpl::MaybeCloseBackingStore(const Origin& origin) { |
| auto it = backing_store_map_.find(origin); |
| if (it == backing_store_map_.end()) |
| return; |
| |
| IndexedDBBackingStore* backing_store = it->second.get(); |
| backing_store->SetPreCloseTaskList(nullptr); |
| // Another reference may have opened since the maybe-close was posted, so it |
| // is necessary to check again. |
| if (HasLastBackingStoreReference(origin)) |
| CloseBackingStore(origin); |
| } |
| |
| void IndexedDBFactoryImpl::CloseBackingStore(const Origin& origin) { |
| auto it = backing_store_map_.find(origin); |
| if (it == backing_store_map_.end()) |
| return; |
| // Stop the timer and pre close tasks (if they are running) - this may happen |
| // if the timer was started and then a forced close occurs. |
| IndexedDBBackingStore* backing_store = it->second.get(); |
| backing_store->close_timer()->Stop(); |
| backing_store->SetPreCloseTaskList(nullptr); |
| |
| if (backing_store->IsBlobCleanupPending()) |
| backing_store->ForceRunBlobCleanup(); |
| |
| backing_store_map_.erase(it); |
| } |
| |
| bool IndexedDBFactoryImpl::HasLastBackingStoreReference( |
| const Origin& origin) const { |
| auto it = backing_store_map_.find(origin); |
| if (it == backing_store_map_.end()) |
| return false; |
| return it->second->HasOneRef(); |
| } |
| |
| leveldb::Status IndexedDBFactoryImpl::AbortTransactions(const Origin& origin) { |
| auto it = backing_store_map_.find(origin); |
| if (it == backing_store_map_.end()) { |
| return leveldb::Status::IOError( |
| "Internal error opening backing store for " |
| "indexedDB.abortTransactions."); |
| } |
| IndexedDBBackingStore* backing_store = it->second.get(); |
| |
| leveldb::Status get_names_status; |
| IndexedDBMetadataCoding metadata_coding; |
| std::vector<base::string16> db_names; |
| get_names_status = metadata_coding.ReadDatabaseNames( |
| backing_store->db(), backing_store->origin_identifier(), &db_names); |
| if (!get_names_status.ok()) { |
| return leveldb::Status::IOError( |
| "Internal error getting origin database names for " |
| "indexedDB.abortTransactions."); |
| } |
| |
| for (base::string16& name : db_names) { |
| const scoped_refptr<IndexedDBDatabase>& db = |
| database_map_[std::make_pair(origin, name)]; |
| db->AbortAllTransactionsForConnections(); |
| } |
| |
| return leveldb::Status::OK(); |
| } |
| |
| void IndexedDBFactoryImpl::ForceClose(const Origin& origin, |
| bool delete_in_memory_store) { |
| OriginDBs range = GetOpenDatabasesForOrigin(origin); |
| |
| while (range.first != range.second) { |
| IndexedDBDatabase* db = range.first->second; |
| ++range.first; |
| db->ForceClose(); |
| } |
| |
| auto it = backing_store_map_.find(origin); |
| if (it != backing_store_map_.end()) { |
| if (delete_in_memory_store) |
| in_memory_backing_stores_.erase(it->second); |
| |
| ReleaseBackingStore(origin, true /* immediate */); |
| } |
| } |
| |
| void IndexedDBFactoryImpl::ForceSchemaDowngrade(const Origin& origin) { |
| auto it = backing_store_map_.find(origin); |
| if (it == backing_store_map_.end()) |
| return; |
| |
| IndexedDBBackingStore* backing_store = it->second.get(); |
| leveldb::Status s = backing_store->RevertSchemaToV2(); |
| DLOG_IF(ERROR, !s.ok()) << "Unable to force downgrade: " << s.ToString(); |
| } |
| |
| V2SchemaCorruptionStatus IndexedDBFactoryImpl::HasV2SchemaCorruption( |
| const Origin& origin) { |
| auto it = backing_store_map_.find(origin); |
| if (it == backing_store_map_.end()) |
| return V2SchemaCorruptionStatus::kUnknown; |
| |
| IndexedDBBackingStore* backing_store = it->second.get(); |
| return backing_store->HasV2SchemaCorruption(); |
| } |
| |
| void IndexedDBFactoryImpl::ContextDestroyed() { |
| // Timers on backing stores hold a reference to this factory. When the |
| // context (which nominally owns this factory) is destroyed during thread |
| // termination the timers must be stopped so that this factory and the |
| // stores can be disposed of. |
| for (const auto& origin_backing_store_pair : backing_store_map_) { |
| origin_backing_store_pair.second->close_timer()->Stop(); |
| origin_backing_store_pair.second->SetPreCloseTaskList(nullptr); |
| } |
| backing_store_map_.clear(); |
| backing_stores_with_active_blobs_.clear(); |
| context_ = nullptr; |
| } |
| |
| void IndexedDBFactoryImpl::ReportOutstandingBlobs(const Origin& origin, |
| bool blobs_outstanding) { |
| if (!context_) |
| return; |
| if (blobs_outstanding) { |
| DCHECK(!backing_stores_with_active_blobs_.count(origin)); |
| auto it = backing_store_map_.find(origin); |
| if (it != backing_store_map_.end()) |
| backing_stores_with_active_blobs_.insert(*it); |
| else |
| DCHECK(false); |
| } else { |
| auto it = backing_stores_with_active_blobs_.find(origin); |
| if (it != backing_stores_with_active_blobs_.end()) { |
| backing_stores_with_active_blobs_.erase(it); |
| ReleaseBackingStore(origin, false /* immediate */); |
| } |
| } |
| } |
| |
| void IndexedDBFactoryImpl::GetDatabaseInfo( |
| scoped_refptr<IndexedDBCallbacks> callbacks, |
| const Origin& origin, |
| const base::FilePath& data_directory) { |
| IDB_TRACE("IndexedDBFactoryImpl::GetDatabaseInfo"); |
| // TODO(dmurph): Plumb data_loss back to script eventually? |
| IndexedDBDataLossInfo data_loss_info; |
| bool disk_full; |
| leveldb::Status s; |
| scoped_refptr<IndexedDBBackingStore> backing_store; |
| std::tie(backing_store, s, data_loss_info, disk_full) = |
| OpenBackingStore(origin, data_directory); |
| if (!backing_store.get()) { |
| IndexedDBDatabaseError error( |
| blink::kWebIDBDatabaseExceptionUnknownError, |
| ASCIIToUTF16("Internal error opening backing store for " |
| "indexedDB.databases().")); |
| callbacks->OnError(error); |
| if (s.IsCorruption()) |
| HandleBackingStoreCorruption(origin, error); |
| return; |
| } |
| |
| IndexedDBMetadataCoding metadata_coding; |
| std::vector<blink::mojom::IDBNameAndVersionPtr> names_and_versions; |
| s = metadata_coding.ReadDatabaseNamesAndVersions( |
| backing_store->db(), backing_store->origin_identifier(), |
| &names_and_versions); |
| if (!s.ok()) { |
| DLOG(ERROR) << "Internal error getting database info"; |
| IndexedDBDatabaseError error(blink::kWebIDBDatabaseExceptionUnknownError, |
| "Internal error opening backing store for " |
| "indexedDB.databases()."); |
| callbacks->OnError(error); |
| backing_store = nullptr; |
| if (s.IsCorruption()) |
| HandleBackingStoreCorruption(origin, error); |
| return; |
| } |
| callbacks->OnSuccess(std::move(names_and_versions)); |
| backing_store = nullptr; |
| ReleaseBackingStore(origin, false /* immediate */); |
| } |
| |
| void IndexedDBFactoryImpl::GetDatabaseNames( |
| scoped_refptr<IndexedDBCallbacks> callbacks, |
| const Origin& origin, |
| const base::FilePath& data_directory) { |
| IDB_TRACE("IndexedDBFactoryImpl::GetDatabaseNames"); |
| // TODO(dmurph): Plumb data_loss back to script eventually? |
| IndexedDBDataLossInfo data_loss_info; |
| bool disk_full; |
| leveldb::Status s; |
| scoped_refptr<IndexedDBBackingStore> backing_store; |
| std::tie(backing_store, s, data_loss_info, disk_full) = |
| OpenBackingStore(origin, data_directory); |
| if (!backing_store.get()) { |
| IndexedDBDatabaseError error( |
| blink::kWebIDBDatabaseExceptionUnknownError, |
| ASCIIToUTF16("Internal error opening backing store for " |
| "indexedDB.webkitGetDatabaseNames.")); |
| callbacks->OnError(error); |
| if (s.IsCorruption()) |
| HandleBackingStoreCorruption(origin, error); |
| return; |
| } |
| |
| IndexedDBMetadataCoding metadata_coding; |
| std::vector<base::string16> names; |
| s = metadata_coding.ReadDatabaseNames( |
| backing_store->db(), backing_store->origin_identifier(), &names); |
| if (!s.ok()) { |
| DLOG(ERROR) << "Internal error getting database names"; |
| IndexedDBDatabaseError error(blink::kWebIDBDatabaseExceptionUnknownError, |
| "Internal error opening backing store for " |
| "indexedDB.webkitGetDatabaseNames."); |
| callbacks->OnError(error); |
| backing_store = nullptr; |
| if (s.IsCorruption()) |
| HandleBackingStoreCorruption(origin, error); |
| return; |
| } |
| callbacks->OnSuccess(names); |
| backing_store = nullptr; |
| ReleaseBackingStore(origin, false /* immediate */); |
| } |
| |
| void IndexedDBFactoryImpl::DeleteDatabase( |
| const base::string16& name, |
| scoped_refptr<IndexedDBCallbacks> callbacks, |
| const Origin& origin, |
| const base::FilePath& data_directory, |
| bool force_close) { |
| IDB_TRACE("IndexedDBFactoryImpl::DeleteDatabase"); |
| IndexedDBDatabase::Identifier unique_identifier(origin, name); |
| auto it = database_map_.find(unique_identifier); |
| if (it != database_map_.end()) { |
| // If there are any connections to the database, directly delete the |
| // database. |
| it->second->DeleteDatabase(callbacks, force_close); |
| return; |
| } |
| |
| // TODO(dmurph): Plumb data_loss back to script eventually? |
| IndexedDBDataLossInfo data_loss_info; |
| bool disk_full; |
| leveldb::Status s; |
| scoped_refptr<IndexedDBBackingStore> backing_store; |
| std::tie(backing_store, s, data_loss_info, disk_full) = |
| OpenBackingStore(origin, data_directory); |
| if (!backing_store.get()) { |
| IndexedDBDatabaseError error( |
| blink::kWebIDBDatabaseExceptionUnknownError, |
| ASCIIToUTF16("Internal error opening backing store " |
| "for indexedDB.deleteDatabase.")); |
| callbacks->OnError(error); |
| if (s.IsCorruption()) { |
| HandleBackingStoreCorruption(origin, error); |
| } |
| return; |
| } |
| |
| IndexedDBMetadataCoding metadata_coding; |
| std::vector<base::string16> names; |
| s = metadata_coding.ReadDatabaseNames( |
| backing_store->db(), backing_store->origin_identifier(), &names); |
| if (!s.ok()) { |
| DLOG(ERROR) << "Internal error getting database names"; |
| IndexedDBDatabaseError error(blink::kWebIDBDatabaseExceptionUnknownError, |
| "Internal error opening backing store for " |
| "indexedDB.deleteDatabase."); |
| callbacks->OnError(error); |
| backing_store = nullptr; |
| if (s.IsCorruption()) |
| HandleBackingStoreCorruption(origin, error); |
| return; |
| } |
| if (!base::ContainsValue(names, name)) { |
| const int64_t version = 0; |
| callbacks->OnSuccess(version); |
| backing_store = nullptr; |
| ReleaseBackingStore(origin, false /* immediate */); |
| return; |
| } |
| |
| scoped_refptr<IndexedDBDatabase> database; |
| std::tie(database, s) = IndexedDBDatabase::Create( |
| name, backing_store.get(), this, |
| std::make_unique<IndexedDBMetadataCoding>(), unique_identifier, |
| backing_store->lock_manager()); |
| if (!database.get()) { |
| IndexedDBDatabaseError error( |
| blink::kWebIDBDatabaseExceptionUnknownError, |
| ASCIIToUTF16("Internal error creating database backend for " |
| "indexedDB.deleteDatabase.")); |
| callbacks->OnError(error); |
| if (s.IsCorruption()) { |
| backing_store = nullptr; |
| HandleBackingStoreCorruption(origin, error); |
| } |
| return; |
| } |
| |
| database_map_[unique_identifier] = database.get(); |
| origin_dbs_.insert(std::make_pair(origin, database.get())); |
| database->DeleteDatabase(callbacks, force_close); |
| RemoveDatabaseFromMaps(unique_identifier); |
| database = nullptr; |
| backing_store = nullptr; |
| ReleaseBackingStore(origin, false /* immediate */); |
| } |
| |
| void IndexedDBFactoryImpl::DatabaseDeleted( |
| const IndexedDBDatabase::Identifier& identifier) { |
| // NULL after ContextDestroyed() called, and in some unit tests. |
| if (!context_) |
| return; |
| context_->DatabaseDeleted(identifier.first); |
| } |
| |
| void IndexedDBFactoryImpl::BlobFilesCleaned(const url::Origin& origin) { |
| // NULL after ContextDestroyed() called, and in some unit tests. |
| if (!context_) |
| return; |
| context_->BlobFilesCleaned(origin); |
| } |
| |
| void IndexedDBFactoryImpl::AbortTransactionsAndCompactDatabase( |
| base::OnceCallback<void(leveldb::Status)> callback, |
| const Origin& origin) { |
| IDB_TRACE("IndexedDBFactoryImpl::AbortTransactionsAndCompactDatabase"); |
| auto it = backing_store_map_.find(origin); |
| if (it == backing_store_map_.end()) { |
| std::move(callback).Run(leveldb::Status::IOError( |
| "Internal error opening backing store for " |
| "indexedDB.abortTransactionsAndCompactDatabase.")); |
| return; |
| } |
| IndexedDBBackingStore* backing_store = it->second.get(); |
| leveldb::Status status = AbortTransactions(origin); |
| backing_store->Compact(); |
| std::move(callback).Run(status); |
| } |
| |
| void IndexedDBFactoryImpl::AbortTransactionsForDatabase( |
| base::OnceCallback<void(leveldb::Status)> callback, |
| const Origin& origin) { |
| IDB_TRACE("IndexedDBFactoryImpl::AbortTransactionsForDatabase"); |
| if (!base::ContainsKey(backing_store_map_, origin)) { |
| std::move(callback).Run( |
| leveldb::Status::IOError("Internal error opening backing store for " |
| "indexedDB.abortTransactionsForDatabase.")); |
| return; |
| } |
| std::move(callback).Run(AbortTransactions(origin)); |
| } |
| |
| void IndexedDBFactoryImpl::HandleBackingStoreFailure(const Origin& origin) { |
| // NULL after ContextDestroyed() called, and in some unit tests. |
| if (!context_) |
| return; |
| context_->ForceClose(origin, |
| IndexedDBContextImpl::FORCE_CLOSE_BACKING_STORE_FAILURE); |
| } |
| |
| void IndexedDBFactoryImpl::HandleBackingStoreCorruption( |
| const Origin& origin, |
| const IndexedDBDatabaseError& error) { |
| // Make a copy of origin as this is likely a reference to a member of a |
| // backing store which this function will be deleting. |
| Origin saved_origin(origin); |
| DCHECK(context_); |
| base::FilePath path_base = context_->data_path(); |
| |
| // The message may contain the database path, which may be considered |
| // sensitive data, and those strings are passed to the extension, so strip it. |
| std::string sanitized_message = base::UTF16ToUTF8(error.message()); |
| base::ReplaceSubstringsAfterOffset(&sanitized_message, 0u, |
| path_base.AsUTF8Unsafe(), "..."); |
| IndexedDBBackingStore::RecordCorruptionInfo(path_base, saved_origin, |
| sanitized_message); |
| HandleBackingStoreFailure(saved_origin); |
| // Note: DestroyBackingStore only deletes LevelDB files, leaving all others, |
| // so our corruption info file will remain. |
| const base::FilePath file_path = |
| path_base.Append(indexed_db::GetLevelDBFileName(saved_origin)); |
| leveldb::Status s = leveldb_factory_->DestroyLevelDB(file_path); |
| DLOG_IF(ERROR, !s.ok()) << "Unable to delete backing store: " << s.ToString(); |
| UMA_HISTOGRAM_ENUMERATION( |
| "WebCore.IndexedDB.DestroyCorruptBackingStoreStatus", |
| leveldb_env::GetLevelDBStatusUMAValue(s), |
| leveldb_env::LEVELDB_STATUS_MAX); |
| } |
| |
| bool IndexedDBFactoryImpl::IsDatabaseOpen(const Origin& origin, |
| const base::string16& name) const { |
| return base::ContainsKey(database_map_, |
| IndexedDBDatabase::Identifier(origin, name)); |
| } |
| |
| bool IndexedDBFactoryImpl::IsBackingStoreOpen(const Origin& origin) const { |
| return base::ContainsKey(backing_store_map_, origin); |
| } |
| |
| bool IndexedDBFactoryImpl::IsBackingStorePendingClose( |
| const Origin& origin) const { |
| auto it = backing_store_map_.find(origin); |
| if (it == backing_store_map_.end()) |
| return false; |
| return it->second->close_timer()->IsRunning() || |
| it->second->pre_close_task_queue(); |
| } |
| |
| std::tuple<scoped_refptr<IndexedDBBackingStore>, |
| leveldb::Status, |
| IndexedDBDataLossInfo, |
| bool /* disk_full */> |
| IndexedDBFactoryImpl::OpenBackingStore(const Origin& origin, |
| const base::FilePath& data_directory) { |
| auto it2 = backing_store_map_.find(origin); |
| scoped_refptr<IndexedDBBackingStore> backing_store; |
| if (it2 != backing_store_map_.end()) { |
| // Grab a refptr so the completion of the preclose task list doesn't close |
| // the backing store. |
| backing_store = it2->second; |
| backing_store->close_timer()->Stop(); |
| if (it2->second->pre_close_task_queue()) { |
| backing_store->pre_close_task_queue()->StopForNewConnection(); |
| backing_store->SetPreCloseTaskList(nullptr); |
| } |
| return {std::move(backing_store), leveldb::Status::OK(), |
| IndexedDBDataLossInfo(), false}; |
| } |
| |
| base::FilePath blob_path; |
| base::FilePath database_path; |
| leveldb::Status s; |
| if (!data_directory.empty()) { |
| // The database will be on-disk and not in-memory. |
| std::tie(database_path, blob_path, s) = |
| indexed_db::CreateDatabaseDirectories(data_directory, origin); |
| if (!s.ok()) |
| return {std::move(backing_store), s, IndexedDBDataLossInfo(), false}; |
| } |
| std::unique_ptr<LevelDBDatabase> database; |
| IndexedDBDataLossInfo data_loss_info; |
| bool disk_full; |
| std::tie(database, s, data_loss_info, disk_full) = |
| indexed_db::OpenAndVerifyLevelDBDatabase(origin, data_directory, |
| database_path, leveldb_factory_, |
| context_->TaskRunner()); |
| if (!s.ok()) |
| return {std::move(backing_store), s, data_loss_info, disk_full}; |
| |
| if (data_directory.empty()) { |
| backing_store = base::MakeRefCounted<IndexedDBBackingStore>( |
| nullptr, origin, base::FilePath(), std::move(database), |
| context_->TaskRunner()); |
| } else { |
| backing_store = CreateBackingStore(origin, blob_path, std::move(database), |
| context_->TaskRunner()); |
| } |
| bool first_open_since_startup = |
| backends_opened_since_startup_.insert(origin).second; |
| s = backing_store->Initialize( |
| /*cleanup_live_journal=*/!database_path.empty() && |
| first_open_since_startup); |
| if (!s.ok()) { |
| backing_store.reset(); |
| return {std::move(backing_store), s, data_loss_info, disk_full}; |
| } |
| // If an in-memory database, bind lifetime to this factory instance. |
| if (database_path.empty()) |
| in_memory_backing_stores_.insert(backing_store); |
| backing_store_map_[origin] = backing_store; |
| |
| // All backing stores associated with this factory should be of the same |
| // type. |
| DCHECK_EQ(!in_memory_backing_stores_.empty(), database_path.empty()); |
| DCHECK(backing_store); |
| return {std::move(backing_store), s, data_loss_info, disk_full}; |
| } |
| |
| scoped_refptr<IndexedDBBackingStore> IndexedDBFactoryImpl::CreateBackingStore( |
| const url::Origin& origin, |
| const base::FilePath& blob_path, |
| std::unique_ptr<LevelDBDatabase> db, |
| base::SequencedTaskRunner* task_runner) { |
| return base::MakeRefCounted<IndexedDBBackingStore>( |
| this, origin, blob_path, std::move(db), task_runner); |
| } |
| |
| void IndexedDBFactoryImpl::Open( |
| const base::string16& name, |
| std::unique_ptr<IndexedDBPendingConnection> connection, |
| const Origin& origin, |
| const base::FilePath& data_directory) { |
| IDB_TRACE("IndexedDBFactoryImpl::Open"); |
| IndexedDBDatabase::Identifier unique_identifier(origin, name); |
| auto it = database_map_.find(unique_identifier); |
| if (it != database_map_.end()) { |
| it->second->OpenConnection(std::move(connection)); |
| return; |
| } |
| leveldb::Status s; |
| scoped_refptr<IndexedDBBackingStore> backing_store; |
| bool disk_full; |
| std::tie(backing_store, s, connection->data_loss_info, disk_full) = |
| OpenBackingStore(origin, data_directory); |
| if (!backing_store) { |
| if (disk_full) { |
| connection->callbacks->OnError(IndexedDBDatabaseError( |
| blink::kWebIDBDatabaseExceptionQuotaError, |
| ASCIIToUTF16("Encountered full disk while opening " |
| "backing store for indexedDB.open."))); |
| return; |
| } |
| IndexedDBDatabaseError error( |
| blink::kWebIDBDatabaseExceptionUnknownError, |
| ASCIIToUTF16("Internal error opening backing store" |
| " for indexedDB.open.")); |
| connection->callbacks->OnError(error); |
| if (s.IsCorruption()) { |
| HandleBackingStoreCorruption(origin, error); |
| } |
| return; |
| } |
| |
| scoped_refptr<IndexedDBDatabase> database; |
| std::tie(database, s) = IndexedDBDatabase::Create( |
| name, backing_store.get(), this, |
| std::make_unique<IndexedDBMetadataCoding>(), unique_identifier, |
| backing_store->lock_manager()); |
| if (!database.get()) { |
| DLOG(ERROR) << "Unable to create the database"; |
| IndexedDBDatabaseError error(blink::kWebIDBDatabaseExceptionUnknownError, |
| ASCIIToUTF16("Internal error creating " |
| "database backend for " |
| "indexedDB.open.")); |
| connection->callbacks->OnError(error); |
| if (s.IsCorruption()) { |
| backing_store = nullptr; // Closes the LevelDB so that it can be deleted |
| HandleBackingStoreCorruption(origin, error); |
| } |
| return; |
| } |
| |
| database->OpenConnection(std::move(connection)); |
| if (database->ConnectionCount() > 0) { |
| database_map_[unique_identifier] = database.get(); |
| origin_dbs_.insert(std::make_pair(origin, database.get())); |
| } |
| } |
| |
| std::pair<IndexedDBFactoryImpl::OriginDBMapIterator, |
| IndexedDBFactoryImpl::OriginDBMapIterator> |
| IndexedDBFactoryImpl::GetOpenDatabasesForOrigin(const Origin& origin) const { |
| return origin_dbs_.equal_range(origin); |
| } |
| |
| size_t IndexedDBFactoryImpl::GetConnectionCount(const Origin& origin) const { |
| size_t count(0); |
| |
| OriginDBs range = GetOpenDatabasesForOrigin(origin); |
| for (auto it = range.first; it != range.second; ++it) |
| count += it->second->ConnectionCount(); |
| |
| return count; |
| } |
| |
| int64_t IndexedDBFactoryImpl::GetInMemoryDBSize(const Origin& origin) const { |
| auto it = backing_store_map_.find(origin); |
| // Origin won't be present in map if it has been deleted. |
| if (it == backing_store_map_.end()) |
| return 0; |
| |
| IndexedDBBackingStore* backing_store = it->second.get(); |
| int64_t level_db_size = 0; |
| leveldb::Status s = |
| GetDBSizeFromEnv(backing_store->db()->env(), "/", &level_db_size); |
| if (!s.ok()) |
| LOG(ERROR) << "Failed to GetDBSizeFromEnv: " << s.ToString(); |
| |
| return backing_store->GetInMemoryBlobSize() + level_db_size; |
| } |
| |
| base::Time IndexedDBFactoryImpl::GetLastModified( |
| const url::Origin& origin) const { |
| auto it = backing_store_map_.find(origin); |
| if (it == backing_store_map_.end()) |
| return base::Time(); |
| IndexedDBBackingStore* backing_store = it->second.get(); |
| return backing_store->db()->LastModified(); |
| } |
| |
| void IndexedDBFactoryImpl::NotifyIndexedDBContentChanged( |
| const url::Origin& origin, |
| const base::string16& database_name, |
| const base::string16& object_store_name) { |
| if (!context_) |
| return; |
| context_->NotifyIndexedDBContentChanged(origin, database_name, |
| object_store_name); |
| } |
| |
| } // namespace content |