blob: 52047647c9848744847e9f87241522ae8ef79940 [file] [log] [blame]
// Copyright 2013 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/instance/database.h"
#include <math.h>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/containers/flat_set.h"
#include "base/debug/dump_without_crashing.h"
#include "base/functional/bind.h"
#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/trace_event/trace_event.h"
#include "base/types/expected_macros.h"
#include "base/unguessable_token.h"
#include "components/services/storage/indexed_db/locks/partitioned_lock_id.h"
#include "components/services/storage/indexed_db/locks/partitioned_lock_manager.h"
#include "components/services/storage/privileged/mojom/indexed_db_client_state_checker.mojom.h"
#include "components/services/storage/privileged/mojom/indexed_db_internals_types.mojom.h"
#include "content/browser/indexed_db/indexed_db_external_object.h"
#include "content/browser/indexed_db/indexed_db_value.h"
#include "content/browser/indexed_db/instance/backing_store.h"
#include "content/browser/indexed_db/instance/bucket_context.h"
#include "content/browser/indexed_db/instance/bucket_context_handle.h"
#include "content/browser/indexed_db/instance/callback_helpers.h"
#include "content/browser/indexed_db/instance/connection.h"
#include "content/browser/indexed_db/instance/cursor.h"
#include "content/browser/indexed_db/instance/database_callbacks.h"
#include "content/browser/indexed_db/instance/factory_client.h"
#include "content/browser/indexed_db/instance/index_writer.h"
#include "content/browser/indexed_db/instance/pending_connection.h"
#include "content/browser/indexed_db/instance/transaction.h"
#include "content/browser/indexed_db/status.h"
#include "ipc/constants.mojom.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "third_party/blink/public/common/indexeddb/indexeddb_key_path.h"
#include "third_party/blink/public/common/indexeddb/indexeddb_key_range.h"
#include "third_party/blink/public/common/indexeddb/indexeddb_metadata.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom.h"
#include "third_party/leveldatabase/env_chromium.h"
using blink::IndexedDBDatabaseMetadata;
using blink::IndexedDBIndexKeys;
using blink::IndexedDBIndexMetadata;
using blink::IndexedDBKey;
using blink::IndexedDBKeyPath;
using blink::IndexedDBKeyRange;
using blink::IndexedDBObjectStoreMetadata;
namespace content::indexed_db {
namespace {
// `backing_store_db` can be null only if `mode` is VersionChange.
std::vector<PartitionedLockManager::PartitionedLockRequest>
BuildLockRequestsForLevelDb(const std::u16string& database_name,
const BackingStore::Database* backing_store_db,
blink::mojom::IDBTransactionMode mode,
const std::set<int64_t>& scope) {
// NB: LevelDB lock IDs are potentially persisted to disk - see
// `LevelDBPartitionedLock`.
const constexpr int kDatabaseLockPartition = 0;
PartitionedLockId database_lock_id{kDatabaseLockPartition,
base::UTF16ToUTF8(database_name)};
if (mode == blink::mojom::IDBTransactionMode::VersionChange) {
return {{std::move(database_lock_id),
PartitionedLockManager::LockType::kExclusive}};
}
CHECK(backing_store_db);
std::vector<PartitionedLockManager::PartitionedLockRequest> lock_requests;
lock_requests.reserve(1 + scope.size());
lock_requests.emplace_back(std::move(database_lock_id),
PartitionedLockManager::LockType::kShared);
const constexpr int kObjectStoreLockPartition = 1;
const auto object_store_lock_type =
mode == blink::mojom::IDBTransactionMode::ReadOnly
? PartitionedLockManager::LockType::kShared
: PartitionedLockManager::LockType::kExclusive;
for (int64_t object_store_id : scope) {
lock_requests.emplace_back(
PartitionedLockId{
kObjectStoreLockPartition,
backing_store_db->GetObjectStoreLockIdKey(object_store_id)},
object_store_lock_type);
}
return lock_requests;
}
std::vector<PartitionedLockManager::PartitionedLockRequest>
BuildLockRequestsForSqlite(uint32_t database_id,
blink::mojom::IDBTransactionMode mode,
const std::set<int64_t>& scope) {
// TODO(crbug.com/427608926): Refactor `PartitionedLockId` to not need `key`
// to be a string.
const constexpr int kMetadataLockPartition = 0;
PartitionedLockId metadata_lock_id{kMetadataLockPartition,
base::StringPrintf("%u", database_id)};
if (mode == blink::mojom::IDBTransactionMode::VersionChange) {
return {{std::move(metadata_lock_id),
PartitionedLockManager::LockType::kExclusive}};
}
std::vector<PartitionedLockManager::PartitionedLockRequest> lock_requests{
{std::move(metadata_lock_id), PartitionedLockManager::LockType::kShared}};
if (mode == blink::mojom::IDBTransactionMode::ReadWrite) {
const constexpr int kWriteOperationsLockPartition = 1;
lock_requests.emplace_back(
PartitionedLockId{kWriteOperationsLockPartition,
base::StringPrintf("%u", database_id)},
PartitionedLockManager::LockType::kExclusive);
}
lock_requests.reserve(lock_requests.size() + scope.size());
const constexpr int kObjectStoreLockPartition = 2;
const auto object_store_lock_type =
mode == blink::mojom::IDBTransactionMode::ReadOnly
? PartitionedLockManager::LockType::kShared
: PartitionedLockManager::LockType::kExclusive;
for (int64_t object_store_id : scope) {
lock_requests.emplace_back(
PartitionedLockId{
kObjectStoreLockPartition,
base::StringPrintf("%u|%lld", database_id, object_store_id)},
object_store_lock_type);
}
return lock_requests;
}
// Values returned to the IDB client may contain a primary key value generated
// by IDB. This is optional and only done when using a key generator. This key
// value cannot (at least easily) be amended to the object being written to the
// database, so they are kept separately, and sent back with the original data
// so that the render process can amend the returned object.
blink::mojom::IDBReturnValuePtr ConvertValueToReturnValue(
Transaction& transaction,
IndexedDBValue value,
blink::IndexedDBKey primary_key,
blink::IndexedDBKeyPath key_path) {
auto mojo_value = blink::mojom::IDBReturnValue::New();
if (primary_key.IsValid()) {
mojo_value->primary_key = std::move(primary_key);
mojo_value->key_path = std::move(key_path);
}
mojo_value->value = transaction.BuildMojoValue(std::move(value));
return mojo_value;
}
// Returns an `IDBReturnValuePtr` created from the cursor's current position.
blink::mojom::IDBReturnValuePtr ExtractReturnValueFromCursorValue(
Transaction& transaction,
const IndexedDBObjectStoreMetadata& object_store_metadata,
BackingStore::Cursor& cursor) {
IndexedDBValue value(std::move(cursor.GetValue()));
const bool is_generated_key = !value.empty() &&
object_store_metadata.auto_increment &&
!object_store_metadata.key_path.IsNull();
blink::IndexedDBKey primary_key;
blink::IndexedDBKeyPath key_path;
if (is_generated_key) {
primary_key = cursor.GetPrimaryKey().Clone();
key_path = object_store_metadata.key_path;
}
return ConvertValueToReturnValue(transaction, std::move(value),
std::move(primary_key), std::move(key_path));
}
blink::mojom::IDBErrorPtr CreateIDBErrorPtr(blink::mojom::IDBException code,
const std::string& message,
Transaction* transaction) {
transaction->IncrementNumErrorsSent();
return blink::mojom::IDBError::New(code, base::UTF8ToUTF16(message));
}
} // namespace
Database::OpenCursorOperationParams::OpenCursorOperationParams() = default;
Database::OpenCursorOperationParams::~OpenCursorOperationParams() = default;
Database::Database(uint32_t id_for_locks,
const std::u16string& name,
BucketContext& bucket_context)
: id_for_locks_(id_for_locks),
name_(name),
bucket_context_(bucket_context),
connection_coordinator_(this, bucket_context) {}
Database::~Database() = default;
BackingStore* Database::backing_store() {
return bucket_context_->backing_store();
}
PartitionedLockManager& Database::lock_manager() {
return bucket_context_->lock_manager();
}
int64_t Database::version() const {
return backing_store_db_ ? metadata().version
: blink::IndexedDBDatabaseMetadata::NO_VERSION;
}
bool Database::IsInitialized() const {
return backing_store_db_ != nullptr;
}
StatusOr<int64_t> Database::DeleteDatabase(std::vector<PartitionedLock> locks,
base::OnceClosure on_complete) {
if (!backing_store_db_) {
return blink::IndexedDBDatabaseMetadata::DEFAULT_VERSION;
}
const int64_t old_version = version();
Status s = backing_store_db_->DeleteDatabase(std::move(locks),
std::move(on_complete));
backing_store_db_.reset();
if (!s.ok()) {
return base::unexpected(s);
}
return old_version;
}
std::vector<PartitionedLockManager::PartitionedLockRequest>
Database::BuildLockRequestsForTransaction(
blink::mojom::IDBTransactionMode mode,
const std::set<int64_t>& scope) const {
return bucket_context_->ShouldUseSqlite()
? BuildLockRequestsForSqlite(id_for_locks_, mode, scope)
: BuildLockRequestsForLevelDb(name_, backing_store_db_.get(), mode,
scope);
}
bool Database::OnlyHasOneClient() const {
if (connections_.empty()) {
return true;
}
const base::UnguessableToken& token = (*connections_.begin())->client_token();
return std::all_of(connections_.begin(), connections_.end(),
[&token](Connection* connection) {
return connection->client_token() == token;
});
}
void Database::RequireBlockingTransactionClientsToBeActive(
Transaction* current_transaction,
std::vector<PartitionedLockManager::PartitionedLockRequest>&
lock_requests) {
if (OnlyHasOneClient()) {
return;
}
std::vector<PartitionedLockId> blocked_lock_ids =
lock_manager().GetUnacquirableLocks(lock_requests);
if (blocked_lock_ids.empty()) {
return;
}
for (Connection* connection : connections_) {
if (connection->client_token() ==
current_transaction->connection()->client_token()) {
continue;
}
// If any of the connection's transactions is holding one of the blocked
// lock IDs, require that client to be active.
if (connection->IsHoldingLocks(blocked_lock_ids)) {
connection->DisallowInactiveClient(
storage::mojom::DisallowInactiveClientReason::
kTransactionIsAcquiringLocks,
base::DoNothing());
}
}
}
void Database::RegisterAndScheduleTransaction(Transaction* transaction) {
TRACE_EVENT1("IndexedDB", "Database::RegisterAndScheduleTransaction",
"txn.id", transaction->id());
// Locks for version change transactions are covered by `ConnectionRequest`.
DCHECK_NE(transaction->mode(),
blink::mojom::IDBTransactionMode::VersionChange);
std::vector<PartitionedLockManager::PartitionedLockRequest> lock_requests =
BuildLockRequestsForTransaction(transaction->mode(),
transaction->scope());
RequireBlockingTransactionClientsToBeActive(transaction, lock_requests);
lock_manager().AcquireLocks(
std::move(lock_requests), *transaction->mutable_locks_receiver(),
base::BindOnce(&Transaction::Start, transaction->AsWeakPtr()),
base::BindRepeating(&Connection::HasHigherPriorityThan,
transaction->mutable_locks_receiver()));
}
Status Database::RunTasks() {
// First execute any pending tasks in the connection coordinator.
ConnectionCoordinator::ExecuteTaskResult task_state;
Status status;
do {
std::tie(task_state, status) =
connection_coordinator_.ExecuteTask(!connections_.empty());
} while (task_state == ConnectionCoordinator::ExecuteTaskResult::kMoreTasks);
if (task_state == ConnectionCoordinator::ExecuteTaskResult::kError) {
CHECK(!status.ok());
return status;
}
bool transactions_removed = true;
// Finally, execute transactions that have tasks & remove those that are
// complete.
while (transactions_removed) {
transactions_removed = false;
Transaction* finished_upgrade_transaction = nullptr;
bool upgrade_transaction_commmitted = false;
for (Connection* connection : connections_) {
std::vector<int64_t> txns_to_remove;
for (const auto& id_txn_pair : connection->transactions()) {
Transaction* txn = id_txn_pair.second.get();
// Determine if the transaction's task queue should be processed.
switch (txn->state()) {
case Transaction::FINISHED:
if (txn->mode() ==
blink::mojom::IDBTransactionMode::VersionChange) {
finished_upgrade_transaction = txn;
upgrade_transaction_commmitted = !txn->aborted();
}
txns_to_remove.push_back(id_txn_pair.first);
continue;
case Transaction::CREATED:
continue;
case Transaction::STARTED:
case Transaction::COMMITTING:
break;
}
// Process the queue for transactions that are STARTED or COMMITTING.
// Add transactions that can be removed to a queue.
StatusOr<Transaction::RunTasksResult> task_result = txn->RunTasks();
if (!task_result.has_value()) {
return task_result.error();
}
switch (task_result.value()) {
case Transaction::RunTasksResult::kCommitted:
case Transaction::RunTasksResult::kAborted:
if (txn->mode() ==
blink::mojom::IDBTransactionMode::VersionChange) {
DCHECK(!finished_upgrade_transaction);
finished_upgrade_transaction = txn;
upgrade_transaction_commmitted = !txn->aborted();
}
txns_to_remove.push_back(txn->id());
break;
case Transaction::RunTasksResult::kNotFinished:
continue;
}
}
// Do the removals.
for (int64_t id : txns_to_remove) {
connection->RemoveTransaction(id);
transactions_removed = true;
}
if (finished_upgrade_transaction) {
connection_coordinator_.OnUpgradeTransactionFinished(
upgrade_transaction_commmitted);
}
}
}
return Status::OK();
}
Status Database::ForceCloseAndRunTasks(const std::string& message) {
if (!bucket_context_->ShouldUseSqlite()) {
DCHECK(!force_closing_);
} else if (force_closing_) {
// Re-entrancy can validly occur if there's an error in the code below,
// e.g. in `CloseAndReportForceClose`.
return Status::OK();
}
force_closing_ = true;
for (Connection* connection : connections_) {
connection->CloseAndReportForceClose(message);
}
connections_.clear();
IDB_RETURN_IF_ERROR(connection_coordinator_.PruneTasksForForceClose(message));
connection_coordinator_.OnNoConnections();
// Execute any pending tasks in the connection coordinator.
ConnectionCoordinator::ExecuteTaskResult task_state;
Status status;
do {
std::tie(task_state, status) = connection_coordinator_.ExecuteTask(false);
DCHECK(task_state !=
ConnectionCoordinator::ExecuteTaskResult::kPendingAsyncWork)
<< "There are no more connections, so all tasks should be able to "
"complete synchronously.";
} while (task_state != ConnectionCoordinator::ExecuteTaskResult::kDone &&
task_state != ConnectionCoordinator::ExecuteTaskResult::kError);
DCHECK(connections_.empty());
bucket_context_->QueueRunTasks();
return status;
}
void Database::ScheduleOpenConnection(
std::unique_ptr<PendingConnection> connection) {
connection_coordinator_.ScheduleOpenConnection(std::move(connection));
}
void Database::ScheduleDeleteDatabase(
std::unique_ptr<FactoryClient> factory_client,
base::OnceClosure on_deletion_complete) {
connection_coordinator_.ScheduleDeleteDatabase(
std::move(factory_client), std::move(on_deletion_complete));
}
Status Database::VersionChangeOperation(int64_t version,
Transaction* transaction) {
TRACE_EVENT1("IndexedDB", "Database::VersionChangeOperation", "txn.id",
transaction->id());
int64_t old_version = metadata().version;
DCHECK_GT(version, old_version);
IDB_RETURN_IF_ERROR(
transaction->BackingStoreTransaction()->SetDatabaseVersion(version));
connection_coordinator_.BindVersionChangeTransactionReceiver();
connection_coordinator_.OnUpgradeTransactionStarted(old_version);
return Status::OK();
}
Status Database::GetOperation(int64_t object_store_id,
int64_t index_id,
IndexedDBKeyRange key_range,
CursorType cursor_type,
blink::mojom::IDBDatabase::GetCallback callback,
Transaction* transaction) {
TRACE_EVENT1("IndexedDB", "Database::GetOperation", "txn.id",
transaction->id());
if (!IsObjectStoreIdAndMaybeIndexIdInMetadata(object_store_id, index_id)) {
std::move(callback).Run(blink::mojom::IDBDatabaseGetResult::NewErrorResult(
CreateIDBErrorPtr(blink::mojom::IDBException::kUnknownError,
"Bad request", transaction)));
return Status::InvalidArgument("Invalid object_store_id and/or index_id.");
}
const IndexedDBObjectStoreMetadata& object_store_metadata =
GetObjectStoreMetadata(object_store_id);
IndexedDBKey key;
if (key_range.IsOnlyKey()) {
key = std::move(key_range).TakeOnlyKey();
} else {
StatusOr<std::unique_ptr<BackingStore::Cursor>> backing_store_cursor;
if (index_id == IndexedDBIndexMetadata::kInvalidId) {
// ObjectStore Retrieval Operation
if (cursor_type == CursorType::kKeyOnly) {
backing_store_cursor =
transaction->BackingStoreTransaction()->OpenObjectStoreKeyCursor(
object_store_id, key_range,
blink::mojom::IDBCursorDirection::Next);
} else {
backing_store_cursor =
transaction->BackingStoreTransaction()->OpenObjectStoreCursor(
object_store_id, key_range,
blink::mojom::IDBCursorDirection::Next);
}
} else if (cursor_type == CursorType::kKeyOnly) {
// Index Value Retrieval Operation
backing_store_cursor =
transaction->BackingStoreTransaction()->OpenIndexKeyCursor(
object_store_id, index_id, key_range,
blink::mojom::IDBCursorDirection::Next);
} else {
// Index Referenced Value Retrieval Operation
backing_store_cursor =
transaction->BackingStoreTransaction()->OpenIndexCursor(
object_store_id, index_id, key_range,
blink::mojom::IDBCursorDirection::Next);
}
if (!backing_store_cursor.has_value()) {
std::move(callback).Run(
blink::mojom::IDBDatabaseGetResult::NewErrorResult(CreateIDBErrorPtr(
blink::mojom::IDBException::kUnknownError,
"Corruption detected, unable to continue", transaction)));
return backing_store_cursor.error();
}
if (!*backing_store_cursor) {
// This means we've run out of data.
std::move(callback).Run(
blink::mojom::IDBDatabaseGetResult::NewEmpty(true));
return Status::OK();
}
key = std::move(**backing_store_cursor).TakeKey();
}
if (index_id == IndexedDBIndexMetadata::kInvalidId) {
// Object Store Retrieval Operation
ASSIGN_OR_RETURN(
IndexedDBValue value,
transaction->BackingStoreTransaction()->GetRecord(object_store_id, key),
[&callback, transaction](const Status& status) {
std::move(callback).Run(
blink::mojom::IDBDatabaseGetResult::NewErrorResult(
CreateIDBErrorPtr(blink::mojom::IDBException::kUnknownError,
"Unknown error", transaction)));
return status;
});
if (value.empty()) {
std::move(callback).Run(
blink::mojom::IDBDatabaseGetResult::NewEmpty(true));
return Status::OK();
}
if (cursor_type == CursorType::kKeyOnly) {
std::move(callback).Run(
blink::mojom::IDBDatabaseGetResult::NewKey(std::move(key)));
return Status::OK();
}
blink::IndexedDBKey primary_key;
blink::IndexedDBKeyPath key_path;
if (object_store_metadata.auto_increment &&
!object_store_metadata.key_path.IsNull()) {
primary_key = std::move(key);
key_path = object_store_metadata.key_path;
}
blink::mojom::IDBReturnValuePtr mojo_value =
ConvertValueToReturnValue(*transaction, std::move(value),
std::move(primary_key), std::move(key_path));
std::move(callback).Run(
blink::mojom::IDBDatabaseGetResult::NewValue(std::move(mojo_value)));
return Status::OK();
}
// From here we are dealing only with indexes.
ASSIGN_OR_RETURN(
IndexedDBKey primary_key,
transaction->BackingStoreTransaction()->GetFirstPrimaryKeyForIndexKey(
object_store_id, index_id, key),
[&callback, transaction](const Status& status) {
std::move(callback).Run(
blink::mojom::IDBDatabaseGetResult::NewErrorResult(
CreateIDBErrorPtr(blink::mojom::IDBException::kUnknownError,
"Unknown error", transaction)));
return status;
});
if (!primary_key.IsValid()) {
std::move(callback).Run(blink::mojom::IDBDatabaseGetResult::NewEmpty(true));
return Status::OK();
}
if (cursor_type == CursorType::kKeyOnly) {
// Index Value Retrieval Operation
std::move(callback).Run(
blink::mojom::IDBDatabaseGetResult::NewKey(std::move(primary_key)));
return Status::OK();
}
// Index Referenced Value Retrieval Operation
ASSIGN_OR_RETURN(
IndexedDBValue value,
transaction->BackingStoreTransaction()->GetRecord(object_store_id,
primary_key),
[&callback, transaction](const Status& status) {
std::move(callback).Run(
blink::mojom::IDBDatabaseGetResult::NewErrorResult(
CreateIDBErrorPtr(blink::mojom::IDBException::kUnknownError,
"Unknown error", transaction)));
return status;
});
if (value.empty()) {
std::move(callback).Run(blink::mojom::IDBDatabaseGetResult::NewEmpty(true));
return Status::OK();
}
blink::IndexedDBKey primary_key_return;
blink::IndexedDBKeyPath key_path_return;
if (object_store_metadata.auto_increment &&
!object_store_metadata.key_path.IsNull()) {
primary_key_return = std::move(primary_key);
key_path_return = object_store_metadata.key_path;
}
blink::mojom::IDBReturnValuePtr mojo_value = ConvertValueToReturnValue(
*transaction, std::move(value), std::move(primary_key_return),
std::move(key_path_return));
std::move(callback).Run(
blink::mojom::IDBDatabaseGetResult::NewValue(std::move(mojo_value)));
return Status::OK();
}
Transaction::Operation Database::CreateGetAllOperation(
int64_t object_store_id,
int64_t index_id,
blink::IndexedDBKeyRange key_range,
blink::mojom::IDBGetAllResultType result_type,
int64_t max_count,
blink::mojom::IDBCursorDirection direction,
blink::mojom::IDBDatabase::GetAllCallback callback,
Transaction* transaction) {
return BindWeakOperation(&Database::GetAllOperation, AsWeakPtr(),
object_store_id, index_id, std::move(key_range),
result_type, max_count, direction,
std::make_unique<GetAllResultSinkWrapper>(
transaction->AsWeakPtr(), std::move(callback)));
}
static_assert(sizeof(size_t) >= sizeof(int32_t),
"Size of size_t is less than size of int32");
static_assert(blink::mojom::kIDBMaxMessageOverhead <= INT32_MAX,
"kIDBMaxMessageOverhead is more than INT32_MAX");
Database::GetAllResultSinkWrapper::GetAllResultSinkWrapper(
base::WeakPtr<Transaction> transaction,
blink::mojom::IDBDatabase::GetAllCallback callback)
: transaction_(transaction), callback_(std::move(callback)) {}
Database::GetAllResultSinkWrapper::~GetAllResultSinkWrapper() {
if (!callback_) {
return;
}
if (transaction_) {
transaction_->IncrementNumErrorsSent();
// If we're reaching this line because the Connection has been
// disconnected from its remote, then `result_sink_` won't have been
// successfully associated, and invoking any methods on it will CHECK.
// See crbug.com/346955148.
// TODO(crbug.com/347047640): remove this workaround when 347047640 is
// fixed.
if (!transaction_->connection()->is_shutting_down()) {
DatabaseError error(blink::mojom::IDBException::kIgnorableAbortError,
"Backend aborted error");
Get()->OnError(
blink::mojom::IDBError::New(error.code(), error.message()));
}
} else {
// Make sure `callback_` is invoked because the Mojo client is waiting for a
// response.
Get();
}
}
mojo::AssociatedRemote<blink::mojom::IDBDatabaseGetAllResultSink>&
Database::GetAllResultSinkWrapper::Get() {
if (!result_sink_) {
mojo::PendingAssociatedReceiver<blink::mojom::IDBDatabaseGetAllResultSink>
pending_receiver;
if (use_dedicated_receiver_for_testing_) {
pending_receiver = result_sink_.BindNewEndpointAndPassDedicatedReceiver();
} else {
pending_receiver = result_sink_.BindNewEndpointAndPassReceiver();
}
std::move(callback_).Run(std::move(pending_receiver));
}
return result_sink_;
}
Status Database::GetAllOperation(
int64_t object_store_id,
int64_t index_id,
IndexedDBKeyRange key_range,
blink::mojom::IDBGetAllResultType result_type,
int64_t max_count,
blink::mojom::IDBCursorDirection direction,
std::unique_ptr<GetAllResultSinkWrapper> result_sink,
Transaction* transaction) {
TRACE_EVENT1("IndexedDB", "Database::GetAllOperation", "txn.id",
transaction->id());
if (!IsObjectStoreIdAndMaybeIndexIdInMetadata(object_store_id, index_id)) {
result_sink->Get()->OnError(CreateIDBErrorPtr(
blink::mojom::IDBException::kUnknownError, "Bad request", transaction));
return Status::InvalidArgument("Invalid object_store_id.");
}
DCHECK_GT(max_count, 0);
const IndexedDBObjectStoreMetadata& object_store_metadata =
GetObjectStoreMetadata(object_store_id);
StatusOr<std::unique_ptr<BackingStore::Cursor>> cursor;
if (result_type == blink::mojom::IDBGetAllResultType::Keys) {
// Retrieving keys
if (index_id == IndexedDBIndexMetadata::kInvalidId) {
// Object Store: Key Retrieval Operation
cursor = transaction->BackingStoreTransaction()->OpenObjectStoreKeyCursor(
object_store_id, key_range, direction);
} else {
// Index Value: (Primary Key) Retrieval Operation
cursor = transaction->BackingStoreTransaction()->OpenIndexKeyCursor(
object_store_id, index_id, key_range, direction);
}
} else {
// Retrieving values
if (index_id == IndexedDBIndexMetadata::kInvalidId) {
// Object Store: Value Retrieval Operation
cursor = transaction->BackingStoreTransaction()->OpenObjectStoreCursor(
object_store_id, key_range, direction);
} else {
// Object Store: Referenced Value Retrieval Operation
cursor = transaction->BackingStoreTransaction()->OpenIndexCursor(
object_store_id, index_id, key_range, direction);
}
}
if (!cursor.has_value()) {
DLOG(ERROR) << "Unable to open cursor operation: "
<< cursor.error().ToString();
result_sink->Get()->OnError(CreateIDBErrorPtr(
blink::mojom::IDBException::kUnknownError,
"Corruption detected, unable to continue", transaction));
return cursor.error();
}
std::vector<blink::mojom::IDBRecordPtr> found_records;
auto send_records = [&](bool done) {
result_sink->Get()->ReceiveResults(std::move(found_records), done);
found_records.clear();
};
// No records found.
if (!*cursor) {
send_records(/*done=*/true);
return Status::OK();
}
bool did_first_seek = false;
// Max idbvalue size before blob wrapping is 64k, so make an assumption
// that max key/value size is 128kb tops, to fit under 128mb mojo limit.
// This value is just a heuristic and is an attempt to make sure that
// GetAll fits under the message limit size.
static_assert(
blink::mojom::kIDBMaxMessageSize >
blink::mojom::kIDBGetAllChunkSize * blink::mojom::kIDBWrapThreshold,
"Chunk heuristic too large");
const size_t max_values_before_sending = blink::mojom::kIDBGetAllChunkSize;
int64_t num_found_items = 0;
while (num_found_items++ < max_count) {
StatusOr<bool> cursor_valid = true;
if (did_first_seek) {
cursor_valid = (*cursor)->Continue();
} else {
// Cursor creation performs the first seek, returning a nullptr cursor
// when invalid.
did_first_seek = true;
}
if (!cursor_valid.has_value()) {
result_sink->Get()->OnError(
CreateIDBErrorPtr(blink::mojom::IDBException::kUnknownError,
"Seek failure, unable to continue", transaction));
return cursor_valid.error();
}
if (!cursor_valid.value()) {
break;
}
blink::mojom::IDBRecordPtr return_record;
if (result_type == blink::mojom::IDBGetAllResultType::Keys) {
return_record =
blink::mojom::IDBRecord::New((*cursor)->GetPrimaryKey().Clone(),
/*value=*/nullptr,
/*index_key=*/std::nullopt);
} else if (result_type == blink::mojom::IDBGetAllResultType::Values) {
blink::mojom::IDBReturnValuePtr return_value =
ExtractReturnValueFromCursorValue(*transaction, object_store_metadata,
**cursor);
return_record = blink::mojom::IDBRecord::New(
/*primary_key=*/std::nullopt, std::move(return_value),
/*index_key=*/std::nullopt);
} else if (result_type == blink::mojom::IDBGetAllResultType::Records) {
// Construct the record, which includes the primary key, value and index
// key.
blink::mojom::IDBReturnValuePtr return_value =
ExtractReturnValueFromCursorValue(*transaction, object_store_metadata,
**cursor);
std::optional<IndexedDBKey> index_key;
if (index_id != IndexedDBIndexMetadata::kInvalidId) {
// The index key only exists for `IDBIndex::getAllRecords()`.
index_key = (*cursor)->GetKey().Clone();
}
return_record = blink::mojom::IDBRecord::New(
(*cursor)->GetPrimaryKey().Clone(), std::move(return_value),
std::move(index_key));
} else {
NOTREACHED();
}
found_records.emplace_back(std::move(return_record));
// Periodically stream records if we have too many.
if (found_records.size() >= max_values_before_sending) {
send_records(/*done=*/false);
}
}
send_records(/*done=*/true);
return Status::OK();
}
Status Database::OpenCursorOperation(
std::unique_ptr<OpenCursorOperationParams> params,
const storage::BucketLocator& bucket_locator,
Transaction* transaction) {
TRACE_EVENT1("IndexedDB", "Database::OpenCursorOperation", "txn.id",
transaction->id());
if (!IsObjectStoreIdAndMaybeIndexIdInMetadata(params->object_store_id,
params->index_id)) {
return Status::InvalidArgument("Invalid object_store_id and/or index_id.");
}
// The frontend has begun indexing, so this pauses the transaction
// until the indexing is complete. This can't happen any earlier
// because we don't want to switch to early mode in case multiple
// indexes are being created in a row, with Put()'s in between.
if (params->task_type == blink::mojom::IDBTaskType::Preemptive) {
transaction->AddPreemptiveEvent();
}
StatusOr<std::unique_ptr<BackingStore::Cursor>> backing_store_cursor;
if (params->index_id == IndexedDBIndexMetadata::kInvalidId) {
if (params->cursor_type == CursorType::kKeyOnly) {
DCHECK_EQ(params->task_type, blink::mojom::IDBTaskType::Normal);
backing_store_cursor =
transaction->BackingStoreTransaction()->OpenObjectStoreKeyCursor(
params->object_store_id, params->key_range, params->direction);
} else {
backing_store_cursor =
transaction->BackingStoreTransaction()->OpenObjectStoreCursor(
params->object_store_id, params->key_range, params->direction);
}
} else {
DCHECK_EQ(params->task_type, blink::mojom::IDBTaskType::Normal);
if (params->cursor_type == CursorType::kKeyOnly) {
backing_store_cursor =
transaction->BackingStoreTransaction()->OpenIndexKeyCursor(
params->object_store_id, params->index_id, params->key_range,
params->direction);
} else {
backing_store_cursor =
transaction->BackingStoreTransaction()->OpenIndexCursor(
params->object_store_id, params->index_id, params->key_range,
params->direction);
}
}
if (!backing_store_cursor.has_value()) {
DLOG(ERROR) << "Unable to open cursor operation: "
<< backing_store_cursor.error().ToString();
return backing_store_cursor.error();
}
if (!*backing_store_cursor) {
// Occurs when we've reached the end of cursor's data.
std::move(params->callback)
.Run(blink::mojom::IDBDatabaseOpenCursorResult::NewEmpty(true));
return Status::OK();
}
mojo::PendingAssociatedRemote<blink::mojom::IDBCursor> pending_remote;
Cursor* cursor = Cursor::CreateAndBind(
std::move(*backing_store_cursor), params->cursor_type, params->task_type,
transaction->AsWeakPtr(), pending_remote);
transaction->RegisterOpenCursor(cursor);
blink::mojom::IDBValuePtr mojo_value;
if (cursor->Value()) {
mojo_value = transaction->BuildMojoValue(std::move(*cursor->Value()));
}
std::move(params->callback)
.Run(blink::mojom::IDBDatabaseOpenCursorResult::NewValue(
blink::mojom::IDBDatabaseOpenCursorValue::New(
std::move(pending_remote), cursor->key().Clone(),
cursor->primary_key().Clone(), std::move(mojo_value))));
return Status::OK();
}
Status Database::CountOperation(
int64_t object_store_id,
int64_t index_id,
IndexedDBKeyRange key_range,
blink::mojom::IDBDatabase::CountCallback callback,
Transaction* transaction) {
TRACE_EVENT1("IndexedDB", "Database::CountOperation", "txn.id",
transaction->id());
if (!IsObjectStoreIdAndMaybeIndexIdInMetadata(object_store_id, index_id)) {
return Status::InvalidArgument("Invalid object_store_id and/or index_id.");
}
uint32_t count = -1;
if (index_id == IndexedDBIndexMetadata::kInvalidId) {
ASSIGN_OR_RETURN(
count, transaction->BackingStoreTransaction()->GetObjectStoreKeyCount(
object_store_id, std::move(key_range)));
} else {
ASSIGN_OR_RETURN(count,
transaction->BackingStoreTransaction()->GetIndexKeyCount(
object_store_id, index_id, std::move(key_range)));
}
std::move(callback).Run(/*success=*/true, count);
return Status::OK();
}
Status Database::DeleteRangeOperation(
int64_t object_store_id,
IndexedDBKeyRange key_range,
blink::mojom::IDBDatabase::DeleteRangeCallback success_callback,
Transaction* transaction) {
TRACE_EVENT1("IndexedDB", "Database::DeleteRangeOperation", "txn.id",
transaction->id());
Status s;
if (IsObjectStoreIdInMetadata(object_store_id)) {
s = transaction->BackingStoreTransaction()->DeleteRange(object_store_id,
key_range);
} else {
s = Status::InvalidArgument("Invalid object_store_id.");
}
if (s.ok()) {
const IndexedDBObjectStoreMetadata& object_store_metadata =
GetObjectStoreMetadata(object_store_id);
bucket_context_->delegate().on_content_changed.Run(
metadata().name, object_store_metadata.name);
}
std::move(success_callback).Run(s.ok());
return s;
}
Status Database::GetKeyGeneratorCurrentNumberOperation(
int64_t object_store_id,
blink::mojom::IDBDatabase::GetKeyGeneratorCurrentNumberCallback callback,
Transaction* transaction) {
if (!IsObjectStoreIdInMetadata(object_store_id)) {
std::move(callback).Run(
-1, CreateIDBErrorPtr(blink::mojom::IDBException::kDataError,
"Object store id not valid.", transaction));
return Status::InvalidArgument("Invalid object_store_id.");
}
ASSIGN_OR_RETURN(
int64_t current_number,
transaction->BackingStoreTransaction()->GetKeyGeneratorCurrentNumber(
object_store_id),
[&callback, transaction](const Status& status) {
std::move(callback).Run(
-1, CreateIDBErrorPtr(
blink::mojom::IDBException::kDataError,
"Failed to get the current number of key generator.",
transaction));
return status;
});
std::move(callback).Run(current_number, nullptr);
return Status::OK();
}
Status Database::ClearOperation(
int64_t object_store_id,
blink::mojom::IDBDatabase::ClearCallback success_callback,
Transaction* transaction) {
TRACE_EVENT1("IndexedDB", "Database::ClearOperation", "txn.id",
transaction->id());
Status s = Status::InvalidArgument("Invalid object_store_id.");
if (IsObjectStoreIdInMetadata(object_store_id)) {
s = transaction->BackingStoreTransaction()->ClearObjectStore(
object_store_id);
}
if (s.ok()) {
const IndexedDBObjectStoreMetadata& object_store_metadata =
GetObjectStoreMetadata(object_store_id);
bucket_context_->delegate().on_content_changed.Run(
name_, object_store_metadata.name);
}
std::move(success_callback).Run(s.ok());
return s;
}
bool Database::IsObjectStoreIdInMetadata(int64_t object_store_id) const {
if (!base::Contains(metadata().object_stores, object_store_id)) {
DLOG(ERROR) << "Invalid object_store_id";
return false;
}
return true;
}
bool Database::IsObjectStoreIdAndMaybeIndexIdInMetadata(
int64_t object_store_id,
int64_t index_id) const {
if (!IsObjectStoreIdInMetadata(object_store_id)) {
return false;
}
const IndexedDBObjectStoreMetadata& object_store_metadata =
GetObjectStoreMetadata(object_store_id);
if (index_id != IndexedDBIndexMetadata::kInvalidId &&
!base::Contains(object_store_metadata.indexes, index_id)) {
DLOG(ERROR) << "Invalid index_id";
return false;
}
return true;
}
storage::mojom::IdbDatabaseMetadataPtr Database::GetIdbInternalsMetadata()
const {
storage::mojom::IdbDatabaseMetadataPtr info =
storage::mojom::IdbDatabaseMetadata::New();
info->name = name();
info->connection_count = ConnectionCount();
info->active_open_delete = ActiveOpenDeleteCount();
info->pending_open_delete = PendingOpenDeleteCount();
for (const Connection* connection : connections()) {
for (const auto& [_, transaction] : connection->transactions()) {
info->transactions.push_back(transaction->GetIdbInternalsMetadata());
}
}
return info;
}
void Database::NotifyOfIdbInternalsRelevantChange() {
// This metadata is included in the context metadata, so call up the chain.
bucket_context_->NotifyOfIdbInternalsRelevantChange();
}
// kIDBMaxMessageSize is defined based on the original
// IPC::mojom::kChannelMaximumMessageSize value. We use kIDBMaxMessageSize to
// limit the size of arguments we pass into our Mojo calls. We want to ensure
// this value is always no bigger than the current kMaximumMessageSize value
// which also ensures it is always no bigger than the current Mojo message
// size limit.
static_assert(
blink::mojom::kIDBMaxMessageSize <= IPC::mojom::kChannelMaximumMessageSize,
"kIDBMaxMessageSize is bigger than IPC::mojom::kChannelMaximumMessageSize");
void Database::CallUpgradeTransactionStartedForTesting(int64_t old_version) {
connection_coordinator_.OnUpgradeTransactionStarted(old_version);
}
Status Database::OpenInternal() {
auto result = backing_store()->CreateOrOpenDatabase(name_);
if (result.has_value()) {
backing_store_db_ = std::move(result.value());
return Status::OK();
}
return result.error();
}
std::unique_ptr<Connection> Database::CreateConnection(
std::unique_ptr<DatabaseCallbacks> database_callbacks,
mojo::Remote<storage::mojom::IndexedDBClientStateChecker>
client_state_checker,
base::UnguessableToken client_token,
int scheduling_priority) {
auto connection = std::make_unique<Connection>(
*bucket_context_, weak_factory_.GetWeakPtr(),
base::BindRepeating(&Database::VersionChangeIgnored,
weak_factory_.GetWeakPtr()),
base::BindOnce(&Database::ConnectionClosed, weak_factory_.GetWeakPtr()),
std::move(database_callbacks), std::move(client_state_checker),
client_token, scheduling_priority);
connections_.insert(connection.get());
bucket_context_->OnConnectionPriorityUpdated();
return connection;
}
void Database::VersionChangeIgnored() {
connection_coordinator_.OnVersionChangeIgnored();
}
bool Database::HasNoConnections() const {
return force_closing_ || connections().empty();
}
void Database::SendVersionChangeToAllConnections(int64_t old_version,
int64_t new_version) {
if (force_closing_) {
return;
}
for (auto* connection : connections()) {
// Before invoking this method, the `ConnectionCoordinator` had
// set the request state to `kPendingNoConnections`. Now the request will
// be blocked until all the existing connections to this database is
// closed. There are three possible ways for the connection to be closed:
// 1. If the client is already pending close, then the `VersionChange`
// event will be ignored and the open request will be deemed blocked until
// the pending close completes.
// 2. If the client is active, the `VersionChange` event will be enqueued
// and the registered event listener will be fired asynchronously. The
// event listener should be responsible for actively closing the IndexedDB
// connection. The document won't be eligible for BFCache before the
// connection is closed if it receives the `versionchange` event.
// 3. While the above two cases rely on the `VersionChange` event to be
// delivered to the renderer process, the third case happens purely from
// the IndexedDB/browser context. If the client is inactive, the
// `VersionChange` event will not be delivered, instead, a mojo call is
// sent to the browser process to disallow the activation of the inactive
// client, which will close the connection as part of the destruction. No
// matter which path it follows, the `SendVersionChangeToAllConnections`
// method is executed asynchronously.
connection->DisallowInactiveClient(
storage::mojom::DisallowInactiveClientReason::kVersionChangeEvent,
base::BindOnce(
[](base::WeakPtr<Connection> connection, int64_t old_version,
int64_t new_version, bool was_client_active) {
if (connection && connection->IsConnected() &&
was_client_active) {
connection->callbacks()->OnVersionChange(old_version,
new_version);
}
},
connection->GetWeakPtr(), old_version, new_version));
}
}
void Database::ConnectionClosed(Connection* connection) {
TRACE_EVENT0("IndexedDB", "Database::ConnectionClosed");
// Ignore connection closes during force close to prevent re-entry.
if (force_closing_) {
return;
}
connections_.erase(connection);
bucket_context_->OnConnectionPriorityUpdated();
connection_coordinator_.OnConnectionClosed(connection);
if (connections_.empty()) {
connection_coordinator_.OnNoConnections();
}
if (CanBeDestroyed()) {
bucket_context_->QueueRunTasks();
}
}
bool Database::CanBeDestroyed() {
return !connection_coordinator_.HasTasks() && connections_.empty();
}
const IndexedDBObjectStoreMetadata& Database::GetObjectStoreMetadata(
int64_t object_store_id) const {
auto object_store_it = metadata().object_stores.find(object_store_id);
DCHECK(object_store_it != metadata().object_stores.end());
return object_store_it->second;
}
} // namespace content::indexed_db