blob: 4f78820424a78832a682d0f0804adc2fa7f0d4d5 [file] [log] [blame]
// Copyright 2019 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/transaction_impl.h"
#include <cstddef>
#include <string>
#include <utility>
#include <vector>
#include "base/metrics/histogram_functions.h"
#include "base/task/post_task.h"
#include "content/browser/indexed_db/indexed_db_callback_helpers.h"
#include "content/browser/indexed_db/indexed_db_connection.h"
#include "content/browser/indexed_db/indexed_db_context_impl.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_transaction.h"
#include "content/browser/indexed_db/indexed_db_value.h"
#include "storage/browser/blob/blob_storage_context.h"
#include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom-forward.h"
#include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom.h"
namespace content {
TransactionImpl::TransactionImpl(
base::WeakPtr<IndexedDBTransaction> transaction,
const url::Origin& origin,
base::WeakPtr<IndexedDBDispatcherHost> dispatcher_host,
scoped_refptr<base::SequencedTaskRunner> idb_runner)
: dispatcher_host_(dispatcher_host),
indexed_db_context_(dispatcher_host->context()),
transaction_(std::move(transaction)),
origin_(origin),
idb_runner_(std::move(idb_runner)) {
DCHECK(idb_runner_->RunsTasksInCurrentSequence());
DCHECK(dispatcher_host_);
DCHECK(transaction_);
}
TransactionImpl::~TransactionImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void TransactionImpl::CreateObjectStore(int64_t object_store_id,
const base::string16& name,
const blink::IndexedDBKeyPath& key_path,
bool auto_increment) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!transaction_)
return;
if (transaction_->mode() != blink::mojom::IDBTransactionMode::VersionChange) {
mojo::ReportBadMessage(
"CreateObjectStore must be called from a version change transaction.");
return;
}
IndexedDBConnection* connection = transaction_->connection();
if (!connection->IsConnected())
return;
transaction_->ScheduleTask(
blink::mojom::IDBTaskType::Preemptive,
BindWeakOperation(&IndexedDBDatabase::CreateObjectStoreOperation,
connection->database()->AsWeakPtr(), object_store_id,
name, key_path, auto_increment));
}
void TransactionImpl::DeleteObjectStore(int64_t object_store_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!transaction_)
return;
if (transaction_->mode() != blink::mojom::IDBTransactionMode::VersionChange) {
mojo::ReportBadMessage(
"DeleteObjectStore must be called from a version change transaction.");
return;
}
IndexedDBConnection* connection = transaction_->connection();
if (!connection->IsConnected())
return;
if (!connection->database()->IsObjectStoreIdInMetadata(object_store_id))
return;
transaction_->ScheduleTask(
BindWeakOperation(&IndexedDBDatabase::DeleteObjectStoreOperation,
connection->database()->AsWeakPtr(), object_store_id));
}
void TransactionImpl::Put(
int64_t object_store_id,
blink::mojom::IDBValuePtr input_value,
const blink::IndexedDBKey& key,
blink::mojom::IDBPutMode mode,
const std::vector<blink::IndexedDBIndexKeys>& index_keys,
blink::mojom::IDBTransaction::PutCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(dispatcher_host_);
std::vector<IndexedDBExternalObject> external_objects;
if (!input_value->external_objects.empty())
CreateExternalObjects(input_value, &external_objects);
if (!transaction_) {
IndexedDBDatabaseError error(blink::mojom::IDBException::kUnknownError,
"Unknown transaction.");
std::move(callback).Run(
blink::mojom::IDBTransactionPutResult::NewErrorResult(
blink::mojom::IDBError::New(error.code(), error.message())));
return;
}
IndexedDBConnection* connection = transaction_->connection();
if (!connection->IsConnected()) {
IndexedDBDatabaseError error(blink::mojom::IDBException::kUnknownError,
"Not connected.");
std::move(callback).Run(
blink::mojom::IDBTransactionPutResult::NewErrorResult(
blink::mojom::IDBError::New(error.code(), error.message())));
return;
}
uint64_t commit_size = input_value->bits.size() + key.size_estimate();
std::unique_ptr<IndexedDBDatabase::PutOperationParams> params(
std::make_unique<IndexedDBDatabase::PutOperationParams>());
IndexedDBValue& output_value = params->value;
// TODO(crbug.com/902498): Use mojom traits to map directly to
// std::string.
output_value.bits =
std::string(input_value->bits.begin(), input_value->bits.end());
// Release value->bits std::vector.
input_value->bits.clear();
swap(output_value.external_objects, external_objects);
blink::mojom::IDBTransaction::PutCallback aborting_callback =
CreateCallbackAbortOnDestruct<blink::mojom::IDBTransaction::PutCallback,
blink::mojom::IDBTransactionPutResultPtr>(
std::move(callback), transaction_->AsWeakPtr());
params->object_store_id = object_store_id;
params->key = std::make_unique<blink::IndexedDBKey>(key);
params->put_mode = mode;
params->callback = std::move(aborting_callback);
params->index_keys = index_keys;
// This is decremented in IndexedDBDatabase::PutOperation.
transaction_->set_in_flight_memory(transaction_->in_flight_memory() +
output_value.SizeEstimate());
transaction_->ScheduleTask(BindWeakOperation(
&IndexedDBDatabase::PutOperation, connection->database()->AsWeakPtr(),
std::move(params)));
// Size can't be big enough to overflow because it represents the
// actual bytes passed through IPC.
transaction_->set_size(transaction_->size() + commit_size);
}
void TransactionImpl::PutAll(int64_t object_store_id,
std::vector<blink::mojom::IDBPutParamsPtr> puts,
PutAllCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(dispatcher_host_);
if (!transaction_) {
IndexedDBDatabaseError error(blink::mojom::IDBException::kUnknownError,
"Unknown transaction.");
std::move(callback).Run(
blink::mojom::IDBTransactionPutAllResult::NewErrorResult(
blink::mojom::IDBError::New(error.code(), error.message())));
return;
}
std::vector<std::vector<IndexedDBExternalObject>> external_objects_per_put(
puts.size());
for (size_t i = 0; i < puts.size(); i++) {
if (!puts[i]->value->external_objects.empty())
CreateExternalObjects(puts[i]->value, &external_objects_per_put[i]);
}
IndexedDBConnection* connection = transaction_->connection();
if (!connection->IsConnected()) {
IndexedDBDatabaseError error(blink::mojom::IDBException::kUnknownError,
"Not connected.");
std::move(callback).Run(
blink::mojom::IDBTransactionPutAllResult::NewErrorResult(
blink::mojom::IDBError::New(error.code(), error.message())));
return;
}
base::CheckedNumeric<uint64_t> commit_size = 0;
base::CheckedNumeric<size_t> size_estimate = 0;
std::vector<std::unique_ptr<IndexedDBDatabase::PutAllOperationParams>>
put_params(puts.size());
for (size_t i = 0; i < puts.size(); i++) {
commit_size += puts[i]->value->bits.size();
commit_size += puts[i]->key.size_estimate();
put_params[i] =
std::make_unique<IndexedDBDatabase::PutAllOperationParams>();
// TODO(crbug.com/902498): Use mojom traits to map directly to
// std::string.
put_params[i]->value.bits =
std::string(puts[i]->value->bits.begin(), puts[i]->value->bits.end());
size_estimate += put_params[i]->value.SizeEstimate();
puts[i]->value->bits.clear();
put_params[i]->value.external_objects =
std::move(external_objects_per_put[i]);
put_params[i]->key = std::make_unique<blink::IndexedDBKey>(puts[i]->key);
put_params[i]->index_keys = std::move(puts[i]->index_keys);
}
blink::mojom::IDBTransaction::PutAllCallback aborting_callback =
CreateCallbackAbortOnDestruct<
blink::mojom::IDBTransaction::PutAllCallback,
blink::mojom::IDBTransactionPutAllResultPtr>(
std::move(callback), transaction_->AsWeakPtr());
// TODO(nums): Add checks to prevent overflow and underflow
// https://crbug.com/1116075
transaction_->set_in_flight_memory(
transaction_->in_flight_memory() +
base::checked_cast<int64_t>(size_estimate.ValueOrDie()));
transaction_->ScheduleTask(BindWeakOperation(
&IndexedDBDatabase::PutAllOperation, connection->database()->AsWeakPtr(),
object_store_id, std::move(put_params), std::move(aborting_callback)));
// Size can't be big enough to overflow because it represents the
// actual bytes passed through IPC.
transaction_->set_size(transaction_->size() + base::checked_cast<uint64_t>(
commit_size.ValueOrDie()));
}
void TransactionImpl::CreateExternalObjects(
blink::mojom::IDBValuePtr& value,
std::vector<IndexedDBExternalObject>* external_objects) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Should only be called if there are external objects to process.
CHECK(!value->external_objects.empty());
base::CheckedNumeric<uint64_t> total_blob_size = 0;
external_objects->resize(value->external_objects.size());
for (size_t i = 0; i < value->external_objects.size(); ++i) {
auto& object = value->external_objects[i];
switch (object->which()) {
case blink::mojom::IDBExternalObject::Tag::BLOB_OR_FILE: {
blink::mojom::IDBBlobInfoPtr& info = object->get_blob_or_file();
uint64_t size = info->size;
total_blob_size += size;
if (info->file) {
DCHECK_NE(info->size, IndexedDBExternalObject::kUnknownSize);
(*external_objects)[i] = IndexedDBExternalObject(
std::move(info->blob), info->uuid, info->file->name,
info->mime_type, info->file->last_modified, info->size);
} else {
(*external_objects)[i] = IndexedDBExternalObject(
std::move(info->blob), info->uuid, info->mime_type, info->size);
}
break;
}
case blink::mojom::IDBExternalObject::Tag::NATIVE_FILE_SYSTEM_TOKEN:
(*external_objects)[i] = IndexedDBExternalObject(
std::move(object->get_native_file_system_token()));
break;
}
}
}
void TransactionImpl::Commit(int64_t num_errors_handled) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!transaction_)
return;
IndexedDBConnection* connection = transaction_->connection();
if (!connection->IsConnected())
return;
transaction_->SetNumErrorsHandled(num_errors_handled);
// Always allow empty or delete-only transactions.
if (transaction_->size() == 0) {
connection->database()->Commit(transaction_.get());
return;
}
indexed_db_context_->quota_manager_proxy()->GetUsageAndQuota(
indexed_db_context_->IDBTaskRunner(), origin_,
blink::mojom::StorageType::kTemporary,
base::BindOnce(&TransactionImpl::OnGotUsageAndQuotaForCommit,
weak_factory_.GetWeakPtr()));
}
void TransactionImpl::OnGotUsageAndQuotaForCommit(
blink::mojom::QuotaStatusCode status,
int64_t usage,
int64_t quota) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!transaction_)
return;
// May have disconnected while quota check was pending.
IndexedDBConnection* connection = transaction_->connection();
if (!connection->IsConnected())
return;
if (status == blink::mojom::QuotaStatusCode::kOk &&
usage + transaction_->size() <= quota) {
connection->database()->Commit(transaction_.get());
} else {
connection->AbortTransactionAndTearDownOnError(
transaction_.get(),
IndexedDBDatabaseError(blink::mojom::IDBException::kQuotaError));
}
}
} // namespace content