910e62b5创建于 1月15日历史提交
/*
 * 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 "base/not_fatal_until.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) {
  CHECK(request, base::NotFatalUntil::M145);
  CHECK_EQ(state_, kActive, base::NotFatalUntil::M145);
  auto add_result = request_list_.insert(request);
  CHECK(add_result.is_new_entry, base::NotFatalUntil::M145);
}

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