| // 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 |