| /* | 
 |  * Copyright (C) 2010 Google Inc. All rights reserved. | 
 |  * | 
 |  * Redistribution and use in source and binary forms, with or without | 
 |  * modification, are permitted provided that the following conditions | 
 |  * are met: | 
 |  * | 
 |  * 1.  Redistributions of source code must retain the above copyright | 
 |  *     notice, this list of conditions and the following disclaimer. | 
 |  * 2.  Redistributions in binary form must reproduce the above copyright | 
 |  *     notice, this list of conditions and the following disclaimer in the | 
 |  *     documentation and/or other materials provided with the distribution. | 
 |  * | 
 |  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY | 
 |  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | 
 |  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | 
 |  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY | 
 |  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | 
 |  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | 
 |  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | 
 |  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 
 |  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | 
 |  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
 |  */ | 
 |  | 
 | #include "third_party/blink/renderer/modules/indexeddb/idb_transaction.h" | 
 |  | 
 | #include <memory> | 
 | #include <utility> | 
 |  | 
 | #include "base/auto_reset.h" | 
 | #include "base/format_macros.h" | 
 | #include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom-blink.h" | 
 | #include "third_party/blink/renderer/bindings/core/v8/active_script_wrappable_creation_key.h" | 
 | #include "third_party/blink/renderer/bindings/modules/v8/v8_idb_transaction_durability.h" | 
 | #include "third_party/blink/renderer/core/dom/dom_exception.h" | 
 | #include "third_party/blink/renderer/core/execution_context/agent.h" | 
 | #include "third_party/blink/renderer/core/execution_context/execution_context.h" | 
 | #include "third_party/blink/renderer/modules/indexed_db_names.h" | 
 | #include "third_party/blink/renderer/modules/indexeddb/idb_cursor.h" | 
 | #include "third_party/blink/renderer/modules/indexeddb/idb_database.h" | 
 | #include "third_party/blink/renderer/modules/indexeddb/idb_event_dispatcher.h" | 
 | #include "third_party/blink/renderer/modules/indexeddb/idb_index.h" | 
 | #include "third_party/blink/renderer/modules/indexeddb/idb_object_store.h" | 
 | #include "third_party/blink/renderer/modules/indexeddb/idb_open_db_request.h" | 
 | #include "third_party/blink/renderer/modules/indexeddb/idb_request_queue_item.h" | 
 | #include "third_party/blink/renderer/platform/bindings/exception_state.h" | 
 | #include "third_party/blink/renderer/platform/bindings/script_state.h" | 
 | #include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h" | 
 | #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" | 
 | #include "third_party/blink/renderer/platform/scheduler/public/event_loop.h" | 
 | #include "third_party/blink/renderer/platform/wtf/functional.h" | 
 |  | 
 | namespace blink { | 
 |  | 
 | IDBTransaction* IDBTransaction::CreateNonVersionChange( | 
 |     ScriptState* script_state, | 
 |     TransactionMojoRemote remote, | 
 |     int64_t id, | 
 |     const HashSet<String>& scope, | 
 |     mojom::blink::IDBTransactionMode mode, | 
 |     mojom::blink::IDBTransactionDurability durability, | 
 |     IDBDatabase* db) { | 
 |   DCHECK_NE(mode, mojom::blink::IDBTransactionMode::VersionChange); | 
 |   DCHECK(!scope.empty()) << "Non-version transactions should operate on a " | 
 |                             "well-defined set of stores"; | 
 |  | 
 |   return MakeGarbageCollected<IDBTransaction>(script_state, std::move(remote), | 
 |                                               id, scope, mode, durability, db); | 
 | } | 
 |  | 
 | IDBTransaction* IDBTransaction::CreateVersionChange( | 
 |     ExecutionContext* execution_context, | 
 |     TransactionMojoRemote remote, | 
 |     int64_t id, | 
 |     IDBDatabase* db, | 
 |     IDBOpenDBRequest* open_db_request, | 
 |     const IDBDatabaseMetadata& old_metadata) { | 
 |   return MakeGarbageCollected<IDBTransaction>(execution_context, | 
 |                                               std::move(remote), id, db, | 
 |                                               open_db_request, old_metadata); | 
 | } | 
 |  | 
 | IDBTransaction::IDBTransaction( | 
 |     ScriptState* script_state, | 
 |     TransactionMojoRemote remote, | 
 |     int64_t id, | 
 |     const HashSet<String>& scope, | 
 |     mojom::blink::IDBTransactionMode mode, | 
 |     mojom::blink::IDBTransactionDurability durability, | 
 |     IDBDatabase* db) | 
 |     : ActiveScriptWrappable<IDBTransaction>({}), | 
 |       ExecutionContextLifecycleObserver(ExecutionContext::From(script_state)), | 
 |       remote_(std::move(remote)), | 
 |       id_(id), | 
 |       database_(db), | 
 |       mode_(mode), | 
 |       durability_(durability), | 
 |       scope_(scope), | 
 |       state_(kActive) { | 
 |   DCHECK(database_); | 
 |   DCHECK(!scope_.empty()) << "Non-versionchange transactions must operate " | 
 |                              "on a well-defined set of stores"; | 
 |   DCHECK(mode_ == mojom::blink::IDBTransactionMode::ReadOnly || | 
 |          mode_ == mojom::blink::IDBTransactionMode::ReadWrite) | 
 |       << "Invalid transaction mode"; | 
 |  | 
 |   ExecutionContext::From(script_state) | 
 |       ->GetAgent() | 
 |       ->event_loop() | 
 |       ->EnqueueEndOfMicrotaskCheckpointTask( | 
 |           BindOnce(&IDBTransaction::SetActive, WrapPersistent(this), false)); | 
 |  | 
 |   database_->TransactionCreated(this); | 
 | } | 
 |  | 
 | IDBTransaction::IDBTransaction(ExecutionContext* execution_context, | 
 |                                TransactionMojoRemote remote, | 
 |                                int64_t id, | 
 |                                IDBDatabase* db, | 
 |                                IDBOpenDBRequest* open_db_request, | 
 |                                const IDBDatabaseMetadata& old_metadata) | 
 |     : ActiveScriptWrappable<IDBTransaction>({}), | 
 |       ExecutionContextLifecycleObserver(execution_context), | 
 |       remote_(std::move(remote)), | 
 |       id_(id), | 
 |       database_(db), | 
 |       open_db_request_(open_db_request), | 
 |       mode_(mojom::blink::IDBTransactionMode::VersionChange), | 
 |       durability_(mojom::blink::IDBTransactionDurability::Default), | 
 |       state_(kInactive), | 
 |       old_database_metadata_(old_metadata) { | 
 |   DCHECK(database_); | 
 |   DCHECK(open_db_request_); | 
 |   DCHECK(scope_.empty()); | 
 |  | 
 |   database_->TransactionCreated(this); | 
 | } | 
 |  | 
 | IDBTransaction::~IDBTransaction() { | 
 |   // Note: IDBTransaction is a ExecutionContextLifecycleObserver (rather than | 
 |   // ContextClient) only in order to be able call upon GetExecutionContext() | 
 |   // during this destructor. | 
 |   DCHECK(state_ == kFinished || !GetExecutionContext()); | 
 |   DCHECK(request_list_.empty() || !GetExecutionContext()); | 
 | } | 
 |  | 
 | void IDBTransaction::Trace(Visitor* visitor) const { | 
 |   visitor->Trace(remote_); | 
 |   visitor->Trace(database_); | 
 |   visitor->Trace(open_db_request_); | 
 |   visitor->Trace(error_); | 
 |   visitor->Trace(request_list_); | 
 |   visitor->Trace(object_store_map_); | 
 |   visitor->Trace(old_store_metadata_); | 
 |   visitor->Trace(deleted_indexes_); | 
 |   EventTarget::Trace(visitor); | 
 |   ExecutionContextLifecycleObserver::Trace(visitor); | 
 | } | 
 |  | 
 | void IDBTransaction::SetError(DOMException* error) { | 
 |   DCHECK_NE(state_, kFinished); | 
 |   DCHECK(error); | 
 |  | 
 |   // The first error to be set is the true cause of the | 
 |   // transaction abort. | 
 |   if (!error_) | 
 |     error_ = error; | 
 | } | 
 |  | 
 | IDBObjectStore* IDBTransaction::objectStore(const String& name, | 
 |                                             ExceptionState& exception_state) { | 
 |   if (IsFinished() || IsFinishing()) { | 
 |     exception_state.ThrowDOMException( | 
 |         DOMExceptionCode::kInvalidStateError, | 
 |         IDBDatabase::kTransactionFinishedErrorMessage); | 
 |     return nullptr; | 
 |   } | 
 |  | 
 |   IDBObjectStoreMap::iterator it = object_store_map_.find(name); | 
 |   if (it != object_store_map_.end()) | 
 |     return it->value.Get(); | 
 |  | 
 |   if (!IsVersionChange() && !scope_.Contains(name)) { | 
 |     exception_state.ThrowDOMException( | 
 |         DOMExceptionCode::kNotFoundError, | 
 |         IDBDatabase::kNoSuchObjectStoreErrorMessage); | 
 |     return nullptr; | 
 |   } | 
 |  | 
 |   int64_t object_store_id = database_->FindObjectStoreId(name); | 
 |   if (object_store_id == IDBObjectStoreMetadata::kInvalidId) { | 
 |     DCHECK(IsVersionChange()); | 
 |     exception_state.ThrowDOMException( | 
 |         DOMExceptionCode::kNotFoundError, | 
 |         IDBDatabase::kNoSuchObjectStoreErrorMessage); | 
 |     return nullptr; | 
 |   } | 
 |  | 
 |   DCHECK(database_->Metadata().object_stores.Contains(object_store_id)); | 
 |   scoped_refptr<IDBObjectStoreMetadata> object_store_metadata = | 
 |       database_->Metadata().object_stores.at(object_store_id); | 
 |   DCHECK(object_store_metadata.get()); | 
 |  | 
 |   auto* object_store = MakeGarbageCollected<IDBObjectStore>( | 
 |       std::move(object_store_metadata), this); | 
 |   DCHECK(!object_store_map_.Contains(name)); | 
 |   object_store_map_.Set(name, object_store); | 
 |  | 
 |   if (IsVersionChange()) { | 
 |     DCHECK(!object_store->IsNewlyCreated()) | 
 |         << "Object store IDs are not assigned sequentially"; | 
 |     scoped_refptr<IDBObjectStoreMetadata> backup_metadata = | 
 |         object_store->Metadata().CreateCopy(); | 
 |     old_store_metadata_.Set(object_store, std::move(backup_metadata)); | 
 |   } | 
 |   return object_store; | 
 | } | 
 |  | 
 | void IDBTransaction::ObjectStoreCreated(const String& name, | 
 |                                         IDBObjectStore* object_store) { | 
 |   DCHECK_NE(state_, kFinished) | 
 |       << "A finished transaction created an object store"; | 
 |   DCHECK_EQ(mode_, mojom::blink::IDBTransactionMode::VersionChange) | 
 |       << "A non-versionchange transaction created an object store"; | 
 |   DCHECK(!object_store_map_.Contains(name)) | 
 |       << "An object store was created with the name of an existing store"; | 
 |   DCHECK(object_store->IsNewlyCreated()) | 
 |       << "Object store IDs are not assigned sequentially"; | 
 |   object_store_map_.Set(name, object_store); | 
 | } | 
 |  | 
 | void IDBTransaction::ObjectStoreDeleted(const int64_t object_store_id, | 
 |                                         const String& name) { | 
 |   DCHECK_NE(state_, kFinished) | 
 |       << "A finished transaction deleted an object store"; | 
 |   DCHECK_EQ(mode_, mojom::blink::IDBTransactionMode::VersionChange) | 
 |       << "A non-versionchange transaction deleted an object store"; | 
 |   IDBObjectStoreMap::iterator it = object_store_map_.find(name); | 
 |   if (it == object_store_map_.end()) { | 
 |     // No IDBObjectStore instance was created for the deleted store in this | 
 |     // transaction. This happens if IDBDatabase.deleteObjectStore() is called | 
 |     // with the name of a store that wasn't instantated. We need to be able to | 
 |     // revert the metadata change if the transaction aborts, in order to return | 
 |     // correct values from IDB{Database, Transaction}.objectStoreNames. | 
 |     DCHECK(database_->Metadata().object_stores.Contains(object_store_id)); | 
 |     scoped_refptr<IDBObjectStoreMetadata> metadata = | 
 |         database_->Metadata().object_stores.at(object_store_id); | 
 |     DCHECK(metadata.get()); | 
 |     DCHECK_EQ(metadata->name, name); | 
 |     deleted_object_stores_.push_back(std::move(metadata)); | 
 |   } else { | 
 |     IDBObjectStore* object_store = it->value; | 
 |     object_store_map_.erase(name); | 
 |     object_store->MarkDeleted(); | 
 |     if (object_store->Id() > old_database_metadata_.max_object_store_id) { | 
 |       // The store was created and deleted in this transaction, so it will | 
 |       // not be restored even if the transaction aborts. We have just | 
 |       // removed our last reference to it. | 
 |       DCHECK(!old_store_metadata_.Contains(object_store)); | 
 |       object_store->ClearIndexCache(); | 
 |     } else { | 
 |       // The store was created before this transaction, and we created an | 
 |       // IDBObjectStore instance for it. When that happened, we must have | 
 |       // snapshotted the store's metadata as well. | 
 |       DCHECK(old_store_metadata_.Contains(object_store)); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void IDBTransaction::ObjectStoreRenamed(const String& old_name, | 
 |                                         const String& new_name) { | 
 |   DCHECK_NE(state_, kFinished) | 
 |       << "A finished transaction renamed an object store"; | 
 |   DCHECK_EQ(mode_, mojom::blink::IDBTransactionMode::VersionChange) | 
 |       << "A non-versionchange transaction renamed an object store"; | 
 |  | 
 |   DCHECK(!object_store_map_.Contains(new_name)); | 
 |   DCHECK(object_store_map_.Contains(old_name)) | 
 |       << "The object store had to be accessed in order to be renamed."; | 
 |   object_store_map_.Set(new_name, object_store_map_.Take(old_name)); | 
 | } | 
 |  | 
 | void IDBTransaction::IndexDeleted(IDBIndex* index) { | 
 |   DCHECK(index); | 
 |   DCHECK(!index->IsDeleted()) << "IndexDeleted called twice for the same index"; | 
 |  | 
 |   IDBObjectStore* object_store = index->objectStore(); | 
 |   DCHECK_EQ(object_store->transaction(), this); | 
 |   DCHECK(object_store_map_.Contains(object_store->name())) | 
 |       << "An index was deleted without accessing its object store"; | 
 |  | 
 |   const auto& object_store_iterator = old_store_metadata_.find(object_store); | 
 |   if (object_store_iterator == old_store_metadata_.end()) { | 
 |     // The index's object store was created in this transaction, so this | 
 |     // index was also created (and deleted) in this transaction, and will | 
 |     // not be restored if the transaction aborts. | 
 |     // | 
 |     // Subtle proof for the first sentence above: Deleting an index requires | 
 |     // calling deleteIndex() on the store's IDBObjectStore instance. | 
 |     // Whenever we create an IDBObjectStore instance for a previously | 
 |     // created store, we snapshot the store's metadata. So, deleting an | 
 |     // index of an "old" store can only be done after the store's metadata | 
 |     // is snapshotted. | 
 |     return; | 
 |   } | 
 |  | 
 |   const IDBObjectStoreMetadata* old_store_metadata = | 
 |       object_store_iterator->value.get(); | 
 |   DCHECK(old_store_metadata); | 
 |   if (!old_store_metadata->indexes.Contains(index->Id())) { | 
 |     // The index's object store was created before this transaction, but the | 
 |     // index was created (and deleted) in this transaction, so it will not | 
 |     // be restored if the transaction aborts. | 
 |     return; | 
 |   } | 
 |  | 
 |   deleted_indexes_.push_back(index); | 
 | } | 
 |  | 
 | void IDBTransaction::SetActive(bool new_is_active) { | 
 |   DCHECK_NE(state_, kFinished) | 
 |       << "A finished transaction tried to SetActive(" << new_is_active << ")"; | 
 |   if (IsFinishing()) | 
 |     return; | 
 |  | 
 |   DCHECK_NE(new_is_active, (state_ == kActive)); | 
 |   state_ = new_is_active ? kActive : kInactive; | 
 |  | 
 |   if (!new_is_active && request_list_.empty()) { | 
 |     state_ = kCommitting; | 
 |     remote_->Commit(num_errors_handled_); | 
 |   } | 
 | } | 
 |  | 
 | void IDBTransaction::SetActiveDuringSerialization(bool new_is_active) { | 
 |   if (new_is_active) { | 
 |     DCHECK_EQ(state_, kInactive) | 
 |         << "Incorrect state restore during Structured Serialization"; | 
 |     state_ = kActive; | 
 |   } else { | 
 |     DCHECK_EQ(state_, kActive) | 
 |         << "Structured serialization attempted while transaction is inactive"; | 
 |     state_ = kInactive; | 
 |   } | 
 | } | 
 |  | 
 | void IDBTransaction::abort(ExceptionState& exception_state) { | 
 |   if (IsFinishing() || IsFinished()) { | 
 |     exception_state.ThrowDOMException( | 
 |         DOMExceptionCode::kInvalidStateError, | 
 |         IDBDatabase::kTransactionFinishedErrorMessage); | 
 |     return; | 
 |   } | 
 |   StartAborting(nullptr); | 
 | } | 
 |  | 
 | void IDBTransaction::commit(ExceptionState& exception_state) { | 
 |   if (IsFinishing() || IsFinished()) { | 
 |     exception_state.ThrowDOMException( | 
 |         DOMExceptionCode::kInvalidStateError, | 
 |         IDBDatabase::kTransactionFinishedErrorMessage); | 
 |     return; | 
 |   } | 
 |  | 
 |   if (state_ == kInactive) { | 
 |     exception_state.ThrowDOMException( | 
 |         DOMExceptionCode::kInvalidStateError, | 
 |         IDBDatabase::kTransactionInactiveErrorMessage); | 
 |     return; | 
 |   } | 
 |  | 
 |   if (!GetExecutionContext()) | 
 |     return; | 
 |  | 
 |   state_ = kCommitting; | 
 |   remote_->Commit(num_errors_handled_); | 
 | } | 
 |  | 
 | void IDBTransaction::RegisterRequest(IDBRequest* request) { | 
 |   DCHECK(request); | 
 |   DCHECK(!request_list_.Contains(request)); | 
 |   DCHECK_EQ(state_, kActive); | 
 |   request_list_.insert(request); | 
 | } | 
 |  | 
 | void IDBTransaction::UnregisterRequest(IDBRequest* request) { | 
 |   DCHECK(request); | 
 | #if DCHECK_IS_ON() | 
 |   // Make sure that no pending IDBRequest gets left behind in the result queue. | 
 |   DCHECK(!request->QueueItem() || request->QueueItem()->IsReady()); | 
 | #endif | 
 |  | 
 |   // If we aborted the request, it will already have been removed. | 
 |   request_list_.erase(request); | 
 | } | 
 |  | 
 | void IDBTransaction::EnqueueResult( | 
 |     std::unique_ptr<IDBRequestQueueItem> result) { | 
 |   result_queue_.push_back(std::move(result)); | 
 |   // StartLoading() may complete post-processing synchronously, so the result | 
 |   // needs to be in the queue before StartLoading() is called. | 
 |   result_queue_.back()->StartLoading(); | 
 | } | 
 |  | 
 | void IDBTransaction::OnResultReady() { | 
 |   // Re-entrancy can occur when sending a result causes the transaction to | 
 |   // abort, which cancels loading on other pending results. | 
 |   if (handling_ready_) { | 
 |     return; | 
 |   } | 
 |   base::AutoReset reset(&handling_ready_, true); | 
 |  | 
 |   while (!result_queue_.empty() && result_queue_.front()->IsReady()) { | 
 |     result_queue_.TakeFirst()->SendResult(); | 
 |   } | 
 | } | 
 |  | 
 | void IDBTransaction::OnAbort(DOMException* error) { | 
 |   TRACE_EVENT1("IndexedDB", "IDBTransaction::onAbort", "txn.id", id_); | 
 |   if (!GetExecutionContext()) { | 
 |     Finished(); | 
 |     return; | 
 |   } | 
 |  | 
 |   DCHECK_NE(state_, kFinished); | 
 |   if (state_ != kAborting) { | 
 |     // Abort was not triggered by front-end. | 
 |     StartAborting(error, /*from_frontend=*/false); | 
 |   } | 
 |  | 
 |   if (IsVersionChange()) | 
 |     database_->close(); | 
 |  | 
 |   // Step 6 of https://w3c.github.io/IndexedDB/#abort-a-transaction | 
 |   // requires that these steps are asynchronous: | 
 |   // | 
 |   //   Queue a task to run these steps: | 
 |   //     1. If transaction is an upgrade transaction, then set transaction’s | 
 |   //     connection's associated database's upgrade transaction to null. | 
 |   //     2. [...] | 
 |   // | 
 |   // However, `OnAbort` is a result of a round trip through the browser, so it | 
 |   // was already queued and we don't have to re-enqueue. | 
 |  | 
 |   // First set the database/connection's upgrade transaction to null. | 
 |   database_->TransactionWillFinish(this); | 
 |   // Then fire the abort event. (This will also set the request's transaction to | 
 |   // null after dispatching.) | 
 |   DispatchEvent(*Event::CreateBubble(event_type_names::kAbort)); | 
 |   // Now do final cleanup. | 
 |   Finished(); | 
 | } | 
 |  | 
 | void IDBTransaction::OnComplete() { | 
 |   TRACE_EVENT1("IndexedDB", "IDBTransaction::onComplete", "txn.id", id_); | 
 |   if (!GetExecutionContext()) { | 
 |     Finished(); | 
 |     return; | 
 |   } | 
 |  | 
 |   DCHECK_EQ(state_, kCommitting); | 
 |  | 
 |   // See comments in `OnAbort()` on importance of ordering. | 
 |   database_->TransactionWillFinish(this); | 
 |   DispatchEvent(*Event::Create(event_type_names::kComplete)); | 
 |   Finished(); | 
 | } | 
 |  | 
 | void IDBTransaction::StartAborting(DOMException* error, bool from_frontend) { | 
 |   // Backend aborts must always come with an error. | 
 |   DCHECK(error || from_frontend); | 
 |  | 
 |   if (error) { | 
 |     SetError(error); | 
 |   } | 
 |   if (IsFinished() || IsFinishing()) { | 
 |     return; | 
 |   } | 
 |  | 
 |   state_ = kAborting; | 
 |  | 
 |   if (!GetExecutionContext()) { | 
 |     return; | 
 |   } | 
 |  | 
 |   // As per the spec, the first step in aborting a transaction is to mark object | 
 |   // stores and indexes as deleted. The (two-step) process of aborting | 
 |   // outstanding requests is later (the 5th step). | 
 |   // https://w3c.github.io/IndexedDB/#abort-a-transaction | 
 |   RevertDatabaseMetadata(); | 
 |   // Step 5 of the algorithm requires this step to be queued rather than | 
 |   // executed synchronously, but if the abort was initiated by the backend (e.g. | 
 |   // due to a constraint error), we're already asynchronous. | 
 |   AbortOutstandingRequests(/*queue_tasks=*/from_frontend); | 
 |  | 
 |   if (from_frontend && database_->IsConnectionOpen()) { | 
 |     database_->Abort(id_); | 
 |   } | 
 | } | 
 |  | 
 | void IDBTransaction::CreateObjectStore(int64_t object_store_id, | 
 |                                        const String& name, | 
 |                                        const IDBKeyPath& key_path, | 
 |                                        bool auto_increment) { | 
 |   if (remote_.is_connected()) { | 
 |     remote_->CreateObjectStore(object_store_id, name, key_path, auto_increment); | 
 |   } | 
 | } | 
 |  | 
 | void IDBTransaction::DeleteObjectStore(int64_t object_store_id) { | 
 |   if (remote_.is_connected()) { | 
 |     remote_->DeleteObjectStore(object_store_id); | 
 |   } | 
 | } | 
 |  | 
 | void IDBTransaction::Put(int64_t object_store_id, | 
 |                          std::unique_ptr<IDBValue> value, | 
 |                          std::unique_ptr<IDBKey> primary_key, | 
 |                          mojom::blink::IDBPutMode put_mode, | 
 |                          Vector<IDBIndexKeys> index_keys, | 
 |                          mojom::blink::IDBTransaction::PutCallback callback) { | 
 |   if (!remote_.is_connected()) { | 
 |     std::move(callback).Run( | 
 |         mojom::blink::IDBTransactionPutResult::NewErrorResult( | 
 |             mojom::blink::IDBError::New( | 
 |                 mojom::blink::IDBException::kUnknownError, | 
 |                 "Unknown transaction"))); | 
 |     return; | 
 |   } | 
 |  | 
 |   IDBCursor::ResetCursorPrefetchCaches(id_, nullptr); | 
 |  | 
 |   size_t index_keys_size = 0; | 
 |   for (const auto& index_key : index_keys) { | 
 |     index_keys_size++;  // Account for index_key.first (int64_t). | 
 |     for (const auto& key : index_key.keys) { | 
 |       // Because all size estimates are based on RAM usage, it is impossible to | 
 |       // overflow index_keys_size. | 
 |       index_keys_size += key->SizeEstimate(); | 
 |     } | 
 |   } | 
 |  | 
 |   size_t estimated_size = | 
 |       value->Data().size() + primary_key->SizeEstimate() + index_keys_size; | 
 |  | 
 |   const size_t max_put_value_size = max_put_value_size_override_.value_or( | 
 |       mojom::blink::kIDBMaxMessageSize - mojom::blink::kIDBMaxMessageOverhead); | 
 |   if (estimated_size >= max_put_value_size) { | 
 |     std::move(callback).Run( | 
 |         mojom::blink::IDBTransactionPutResult::NewErrorResult( | 
 |             mojom::blink::IDBError::New( | 
 |                 mojom::blink::IDBException::kUnknownError, | 
 |                 String::Format("The serialized keys and/or value are too large" | 
 |                                " (size=%" PRIuS " bytes, max=%" PRIuS | 
 |                                " bytes).", | 
 |                                estimated_size, max_put_value_size)))); | 
 |     return; | 
 |   } | 
 |  | 
 |   remote_->Put(object_store_id, std::move(value), std::move(primary_key), | 
 |                put_mode, std::move(index_keys), std::move(callback)); | 
 | } | 
 |  | 
 | void IDBTransaction::SetIndexKeys(int64_t object_store_id, | 
 |                                   std::unique_ptr<IDBKey> primary_key, | 
 |                                   IDBIndexKeys index_keys) { | 
 |   remote_->SetIndexKeys(object_store_id, std::move(primary_key), | 
 |                         std::move(index_keys)); | 
 | } | 
 |  | 
 | void IDBTransaction::SetIndexReady(int64_t object_store_id) { | 
 |   remote_->SetIndexKeysDone(); | 
 | } | 
 |  | 
 | void IDBTransaction::FlushForTesting() { | 
 |   remote_.FlushForTesting(); | 
 | } | 
 |  | 
 | bool IDBTransaction::HasPendingActivity() const { | 
 |   // FIXME: In an ideal world, we should return true as long as anyone has a or | 
 |   // can get a handle to us or any child request object and any of those have | 
 |   // event listeners. This is  in order to handle user generated events | 
 |   // properly. | 
 |   return has_pending_activity_ && GetExecutionContext(); | 
 | } | 
 |  | 
 | mojom::blink::IDBTransactionMode IDBTransaction::EnumToMode( | 
 |     V8IDBTransactionMode::Enum mode) { | 
 |   switch (mode) { | 
 |     case V8IDBTransactionMode::Enum::kReadonly: | 
 |       return mojom::blink::IDBTransactionMode::ReadOnly; | 
 |     case V8IDBTransactionMode::Enum::kReadwrite: | 
 |       return mojom::blink::IDBTransactionMode::ReadWrite; | 
 |     case V8IDBTransactionMode::Enum::kVersionchange: | 
 |       return mojom::blink::IDBTransactionMode::VersionChange; | 
 |   } | 
 | } | 
 |  | 
 | V8IDBTransactionMode IDBTransaction::mode() const { | 
 |   switch (mode_) { | 
 |     case mojom::blink::IDBTransactionMode::ReadOnly: | 
 |       return V8IDBTransactionMode(V8IDBTransactionMode::Enum::kReadonly); | 
 |  | 
 |     case mojom::blink::IDBTransactionMode::ReadWrite: | 
 |       return V8IDBTransactionMode(V8IDBTransactionMode::Enum::kReadwrite); | 
 |  | 
 |     case mojom::blink::IDBTransactionMode::VersionChange: | 
 |       return V8IDBTransactionMode(V8IDBTransactionMode::Enum::kVersionchange); | 
 |   } | 
 | } | 
 |  | 
 | V8IDBTransactionDurability IDBTransaction::durability() const { | 
 |   switch (durability_) { | 
 |     case mojom::blink::IDBTransactionDurability::Default: | 
 |       return V8IDBTransactionDurability( | 
 |           V8IDBTransactionDurability::Enum::kDefault); | 
 |  | 
 |     case mojom::blink::IDBTransactionDurability::Strict: | 
 |       return V8IDBTransactionDurability( | 
 |           V8IDBTransactionDurability::Enum::kStrict); | 
 |  | 
 |     case mojom::blink::IDBTransactionDurability::Relaxed: | 
 |       return V8IDBTransactionDurability( | 
 |           V8IDBTransactionDurability::Enum::kRelaxed); | 
 |   } | 
 |  | 
 |   NOTREACHED(); | 
 | } | 
 |  | 
 | DOMStringList* IDBTransaction::objectStoreNames() const { | 
 |   if (IsVersionChange()) | 
 |     return database_->objectStoreNames(); | 
 |  | 
 |   auto* object_store_names = MakeGarbageCollected<DOMStringList>(); | 
 |   for (const String& object_store_name : scope_) | 
 |     object_store_names->Append(object_store_name); | 
 |   object_store_names->Sort(); | 
 |   return object_store_names; | 
 | } | 
 |  | 
 | const AtomicString& IDBTransaction::InterfaceName() const { | 
 |   return event_target_names::kIDBTransaction; | 
 | } | 
 |  | 
 | ExecutionContext* IDBTransaction::GetExecutionContext() const { | 
 |   return ExecutionContextLifecycleObserver::GetExecutionContext(); | 
 | } | 
 |  | 
 | const char* IDBTransaction::InactiveErrorMessage() const { | 
 |   switch (state_) { | 
 |     case kActive: | 
 |       // Callers should check !IsActive() before calling. | 
 |       NOTREACHED(); | 
 |     case kInactive: | 
 |       return IDBDatabase::kTransactionInactiveErrorMessage; | 
 |     case kCommitting: | 
 |     case kAborting: | 
 |     case kFinished: | 
 |       return IDBDatabase::kTransactionFinishedErrorMessage; | 
 |   } | 
 |   NOTREACHED(); | 
 | } | 
 |  | 
 | DispatchEventResult IDBTransaction::DispatchEventInternal(Event& event) { | 
 |   TRACE_EVENT1("IndexedDB", "IDBTransaction::dispatchEvent", "txn.id", id_); | 
 |  | 
 |   event.SetTarget(this); | 
 |  | 
 |   // Per spec: "A transaction's get the parent algorithm returns the | 
 |   // transaction’s connection." | 
 |   HeapVector<Member<EventTarget>> targets; | 
 |   targets.push_back(this); | 
 |   targets.push_back(db()); | 
 |  | 
 |   // If this event originated from script, it should have no side effects. | 
 |   if (!event.isTrusted()) | 
 |     return IDBEventDispatcher::Dispatch(event, targets); | 
 |   DCHECK(event.type() == event_type_names::kComplete || | 
 |          event.type() == event_type_names::kAbort); | 
 |  | 
 |   if (!GetExecutionContext()) { | 
 |     state_ = kFinished; | 
 |     return DispatchEventResult::kCanceledBeforeDispatch; | 
 |   } | 
 |   DCHECK_NE(state_, kFinished); | 
 |   DCHECK(has_pending_activity_); | 
 |   DCHECK(GetExecutionContext()); | 
 |   DCHECK_EQ(event.RawTarget(), this); | 
 |   state_ = kFinished; | 
 |  | 
 |   DispatchEventResult dispatch_result = | 
 |       IDBEventDispatcher::Dispatch(event, targets); | 
 |   // FIXME: Try to construct a test where |this| outlives openDBRequest and we | 
 |   // get a crash. | 
 |   if (open_db_request_) { | 
 |     DCHECK(IsVersionChange()); | 
 |     open_db_request_->TransactionDidFinishAndDispatch(); | 
 |   } | 
 |   has_pending_activity_ = false; | 
 |   return dispatch_result; | 
 | } | 
 |  | 
 | void IDBTransaction::AbortOutstandingRequests(bool queue_tasks) { | 
 |   decltype(request_list_) request_list; | 
 |   request_list.Swap(request_list_); | 
 |   for (IDBRequest* request : request_list) { | 
 |     request->Abort(queue_tasks); | 
 |   } | 
 | } | 
 |  | 
 | void IDBTransaction::RevertDatabaseMetadata() { | 
 |   DCHECK_NE(state_, kActive); | 
 |   if (!IsVersionChange()) | 
 |     return; | 
 |  | 
 |   // Mark stores created by this transaction as deleted. | 
 |   for (auto& object_store : object_store_map_.Values()) { | 
 |     const int64_t object_store_id = object_store->Id(); | 
 |     if (!object_store->IsNewlyCreated()) { | 
 |       DCHECK(old_store_metadata_.Contains(object_store)); | 
 |       continue; | 
 |     } | 
 |  | 
 |     DCHECK(!old_store_metadata_.Contains(object_store)); | 
 |     database_->RevertObjectStoreCreation(object_store_id); | 
 |     object_store->MarkDeleted(); | 
 |   } | 
 |  | 
 |   for (auto& it : old_store_metadata_) { | 
 |     IDBObjectStore* object_store = it.key; | 
 |     scoped_refptr<IDBObjectStoreMetadata> old_metadata = it.value; | 
 |  | 
 |     database_->RevertObjectStoreMetadata(old_metadata); | 
 |     object_store->RevertMetadata(old_metadata); | 
 |   } | 
 |   for (auto& index : deleted_indexes_) | 
 |     index->objectStore()->RevertDeletedIndexMetadata(*index); | 
 |   for (auto& old_medata : deleted_object_stores_) | 
 |     database_->RevertObjectStoreMetadata(std::move(old_medata)); | 
 |  | 
 |   // We only need to revert the database's own metadata because we have reverted | 
 |   // the metadata for the database's object stores above. | 
 |   database_->SetDatabaseMetadata(old_database_metadata_); | 
 | } | 
 |  | 
 | void IDBTransaction::Finished() { | 
 | #if DCHECK_IS_ON() | 
 |   DCHECK(!finish_called_); | 
 |   finish_called_ = true; | 
 | #endif  // DCHECK_IS_ON() | 
 |  | 
 |   database_->TransactionFinished(this); | 
 |  | 
 |   // Remove references to the IDBObjectStore and IDBIndex instances held by | 
 |   // this transaction, so Oilpan can garbage-collect the instances that aren't | 
 |   // used by JavaScript. | 
 |  | 
 |   for (auto& it : object_store_map_) { | 
 |     IDBObjectStore* object_store = it.value; | 
 |     if (!IsVersionChange() || object_store->IsNewlyCreated()) { | 
 |       DCHECK(!old_store_metadata_.Contains(object_store)); | 
 |       object_store->ClearIndexCache(); | 
 |     } else { | 
 |       // We'll call ClearIndexCache() on this store in the loop below. | 
 |       DCHECK(old_store_metadata_.Contains(object_store)); | 
 |     } | 
 |   } | 
 |   object_store_map_.clear(); | 
 |  | 
 |   for (auto& it : old_store_metadata_) { | 
 |     IDBObjectStore* object_store = it.key; | 
 |     object_store->ClearIndexCache(); | 
 |   } | 
 |   old_store_metadata_.clear(); | 
 |  | 
 |   deleted_indexes_.clear(); | 
 |   deleted_object_stores_.clear(); | 
 | } | 
 |  | 
 | }  // namespace blink |