summaryrefslogtreecommitdiff
path: root/Source/WebCore/Modules/indexeddb/IDBTransaction.cpp
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
commit1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch)
tree46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/Modules/indexeddb/IDBTransaction.cpp
parent32761a6cee1d0dee366b885b7b9c777e67885688 (diff)
downloadWebKitGtk-tarball-master.tar.gz
Diffstat (limited to 'Source/WebCore/Modules/indexeddb/IDBTransaction.cpp')
-rw-r--r--Source/WebCore/Modules/indexeddb/IDBTransaction.cpp1492
1 files changed, 1233 insertions, 259 deletions
diff --git a/Source/WebCore/Modules/indexeddb/IDBTransaction.cpp b/Source/WebCore/Modules/indexeddb/IDBTransaction.cpp
index 5d2f3f875..1ee73d28d 100644
--- a/Source/WebCore/Modules/indexeddb/IDBTransaction.cpp
+++ b/Source/WebCore/Modules/indexeddb/IDBTransaction.cpp
@@ -1,26 +1,26 @@
/*
- * Copyright (C) 2010 Google Inc. All rights reserved.
+ * Copyright (C) 2015, 2016 Apple 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.
*
- * 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.
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. 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 INC. 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 "config.h"
@@ -28,404 +28,1378 @@
#if ENABLE(INDEXED_DATABASE)
-#include "EventException.h"
+#include "DOMError.h"
+#include "DOMStringList.h"
+#include "DOMWindow.h"
+#include "Event.h"
+#include "EventNames.h"
#include "EventQueue.h"
-#include "ExceptionCodePlaceholder.h"
+#include "IDBCursorWithValue.h"
#include "IDBDatabase.h"
#include "IDBDatabaseException.h"
+#include "IDBError.h"
#include "IDBEventDispatcher.h"
+#include "IDBGetRecordData.h"
#include "IDBIndex.h"
+#include "IDBIterateCursorData.h"
+#include "IDBKeyData.h"
+#include "IDBKeyRangeData.h"
#include "IDBObjectStore.h"
#include "IDBOpenDBRequest.h"
-#include "IDBPendingTransactionMonitor.h"
+#include "IDBRequest.h"
+#include "IDBResultData.h"
+#include "IDBValue.h"
+#include "JSDOMWindowBase.h"
#include "Logging.h"
-#include "ScriptCallStack.h"
#include "ScriptExecutionContext.h"
+#include "ScriptState.h"
+#include "SerializedScriptValue.h"
+#include "TransactionOperation.h"
+#include <wtf/NeverDestroyed.h>
+
+using namespace JSC;
namespace WebCore {
-PassRefPtr<IDBTransaction> IDBTransaction::create(ScriptExecutionContext* context, int64_t id, const Vector<String>& objectStoreNames, IndexedDB::TransactionMode mode, IDBDatabase* db)
+Ref<IDBTransaction> IDBTransaction::create(IDBDatabase& database, const IDBTransactionInfo& info)
{
- IDBOpenDBRequest* openDBRequest = 0;
- RefPtr<IDBTransaction> transaction(adoptRef(new IDBTransaction(context, id, objectStoreNames, mode, db, openDBRequest, IDBDatabaseMetadata())));
- transaction->suspendIfNeeded();
- return transaction.release();
+ return adoptRef(*new IDBTransaction(database, info, nullptr));
}
-PassRefPtr<IDBTransaction> IDBTransaction::create(ScriptExecutionContext* context, int64_t id, IDBDatabase* db, IDBOpenDBRequest* openDBRequest, const IDBDatabaseMetadata& previousMetadata)
+Ref<IDBTransaction> IDBTransaction::create(IDBDatabase& database, const IDBTransactionInfo& info, IDBOpenDBRequest& request)
{
- RefPtr<IDBTransaction> transaction(adoptRef(new IDBTransaction(context, id, Vector<String>(), IndexedDB::TransactionMode::VersionChange, db, openDBRequest, previousMetadata)));
- transaction->suspendIfNeeded();
- return transaction.release();
+ return adoptRef(*new IDBTransaction(database, info, &request));
}
-const AtomicString& IDBTransaction::modeReadOnly()
+IDBTransaction::IDBTransaction(IDBDatabase& database, const IDBTransactionInfo& info, IDBOpenDBRequest* request)
+ : IDBActiveDOMObject(database.scriptExecutionContext())
+ , m_database(database)
+ , m_info(info)
+ , m_pendingOperationTimer(*this, &IDBTransaction::pendingOperationTimerFired)
+ , m_completedOperationTimer(*this, &IDBTransaction::completedOperationTimerFired)
+ , m_openDBRequest(request)
+ , m_currentlyCompletingRequest(request)
+
{
- DEFINE_STATIC_LOCAL(AtomicString, readonly, ("readonly", AtomicString::ConstructFromLiteral));
- return readonly;
+ LOG(IndexedDB, "IDBTransaction::IDBTransaction - %s", m_info.loggingString().utf8().data());
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ if (m_info.mode() == IDBTransactionMode::Versionchange) {
+ ASSERT(m_openDBRequest);
+ m_openDBRequest->setVersionChangeTransaction(*this);
+ m_startedOnServer = true;
+ } else {
+ activate();
+
+ auto* context = scriptExecutionContext();
+ ASSERT(context);
+
+ RefPtr<IDBTransaction> self;
+ JSC::VM& vm = context->vm();
+ vm.whenIdle([self, this]() {
+ deactivate();
+ });
+
+ establishOnServer();
+ }
+
+ suspendIfNeeded();
}
-const AtomicString& IDBTransaction::modeReadWrite()
+IDBTransaction::~IDBTransaction()
{
- DEFINE_STATIC_LOCAL(AtomicString, readwrite, ("readwrite", AtomicString::ConstructFromLiteral));
- return readwrite;
+ ASSERT(currentThread() == m_database->originThreadID());
}
-const AtomicString& IDBTransaction::modeVersionChange()
+IDBClient::IDBConnectionProxy& IDBTransaction::connectionProxy()
{
- DEFINE_STATIC_LOCAL(AtomicString, versionchange, ("versionchange", AtomicString::ConstructFromLiteral));
- return versionchange;
+ return m_database->connectionProxy();
}
-const AtomicString& IDBTransaction::modeReadOnlyLegacy()
+Ref<DOMStringList> IDBTransaction::objectStoreNames() const
{
- DEFINE_STATIC_LOCAL(AtomicString, readonly, ("0", AtomicString::ConstructFromLiteral));
- return readonly;
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ const Vector<String> names = isVersionChange() ? m_database->info().objectStoreNames() : m_info.objectStores();
+
+ Ref<DOMStringList> objectStoreNames = DOMStringList::create();
+ for (auto& name : names)
+ objectStoreNames->append(name);
+
+ objectStoreNames->sort();
+ return objectStoreNames;
}
-const AtomicString& IDBTransaction::modeReadWriteLegacy()
+IDBDatabase* IDBTransaction::db()
{
- DEFINE_STATIC_LOCAL(AtomicString, readwrite, ("1", AtomicString::ConstructFromLiteral));
- return readwrite;
+ ASSERT(currentThread() == m_database->originThreadID());
+ return m_database.ptr();
}
+DOMError* IDBTransaction::error() const
+{
+ ASSERT(currentThread() == m_database->originThreadID());
+ return m_domError.get();
+}
-IDBTransaction::IDBTransaction(ScriptExecutionContext* context, int64_t id, const Vector<String>& objectStoreNames, IndexedDB::TransactionMode mode, IDBDatabase* db, IDBOpenDBRequest* openDBRequest, const IDBDatabaseMetadata& previousMetadata)
- : ActiveDOMObject(context)
- , m_id(id)
- , m_database(db)
- , m_objectStoreNames(objectStoreNames)
- , m_openDBRequest(openDBRequest)
- , m_mode(mode)
- , m_state(Active)
- , m_hasPendingActivity(true)
- , m_contextStopped(false)
- , m_previousMetadata(previousMetadata)
+ExceptionOr<Ref<IDBObjectStore>> IDBTransaction::objectStore(const String& objectStoreName)
{
- if (mode == IndexedDB::TransactionMode::VersionChange) {
- // Not active until the callback.
- m_state = Inactive;
+ LOG(IndexedDB, "IDBTransaction::objectStore");
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ if (!scriptExecutionContext())
+ return Exception { IDBDatabaseException::InvalidStateError };
+
+ if (isFinishedOrFinishing())
+ return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'objectStore' on 'IDBTransaction': The transaction finished.") };
+
+ Locker<Lock> locker(m_referencedObjectStoreLock);
+
+ auto iterator = m_referencedObjectStores.find(objectStoreName);
+ if (iterator != m_referencedObjectStores.end())
+ return Ref<IDBObjectStore> { *iterator->value };
+
+ bool found = false;
+ for (auto& objectStore : m_info.objectStores()) {
+ if (objectStore == objectStoreName) {
+ found = true;
+ break;
+ }
}
- // We pass a reference of this object before it can be adopted.
- relaxAdoptionRequirement();
- if (m_state == Active)
- IDBPendingTransactionMonitor::addNewTransaction(this);
- m_database->transactionCreated(this);
+ auto* info = m_database->info().infoForExistingObjectStore(objectStoreName);
+ if (!info)
+ return Exception { IDBDatabaseException::NotFoundError, ASCIILiteral("Failed to execute 'objectStore' on 'IDBTransaction': The specified object store was not found.") };
+
+ // Version change transactions are scoped to every object store in the database.
+ if (!info || (!found && !isVersionChange()))
+ return Exception { IDBDatabaseException::NotFoundError, ASCIILiteral("Failed to execute 'objectStore' on 'IDBTransaction': The specified object store was not found.") };
+
+ auto objectStore = std::make_unique<IDBObjectStore>(*scriptExecutionContext(), *info, *this);
+ auto* rawObjectStore = objectStore.get();
+ m_referencedObjectStores.set(objectStoreName, WTFMove(objectStore));
+
+ return Ref<IDBObjectStore>(*rawObjectStore);
}
-IDBTransaction::~IDBTransaction()
+
+void IDBTransaction::abortDueToFailedRequest(DOMError& error)
{
- ASSERT(m_state == Finished || m_contextStopped);
- ASSERT(m_requestList.isEmpty() || m_contextStopped);
+ LOG(IndexedDB, "IDBTransaction::abortDueToFailedRequest");
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ if (isFinishedOrFinishing())
+ return;
+
+ m_domError = &error;
+ internalAbort();
+}
+
+void IDBTransaction::transitionedToFinishing(IndexedDB::TransactionState state)
+{
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ ASSERT(!isFinishedOrFinishing());
+ m_state = state;
+ ASSERT(isFinishedOrFinishing());
}
-const String& IDBTransaction::mode() const
+ExceptionOr<void> IDBTransaction::abort()
{
- return modeToString(m_mode);
+ LOG(IndexedDB, "IDBTransaction::abort");
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ if (isFinishedOrFinishing())
+ return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'abort' on 'IDBTransaction': The transaction is inactive or finished.") };
+
+ internalAbort();
+
+ return { };
}
-void IDBTransaction::setError(PassRefPtr<DOMError> error, const String& errorMessage)
+void IDBTransaction::internalAbort()
{
- ASSERT(m_state != Finished);
- ASSERT(error);
+ LOG(IndexedDB, "IDBTransaction::internalAbort");
+ ASSERT(currentThread() == m_database->originThreadID());
+ ASSERT(!isFinishedOrFinishing());
+
+ m_database->willAbortTransaction(*this);
+
+ if (isVersionChange()) {
+ Locker<Lock> locker(m_referencedObjectStoreLock);
+
+ auto& info = m_database->info();
+ Vector<uint64_t> identifiersToRemove;
+ for (auto& iterator : m_deletedObjectStores) {
+ if (info.infoForExistingObjectStore(iterator.key)) {
+ auto name = iterator.value->info().name();
+ m_referencedObjectStores.set(name, WTFMove(iterator.value));
+ identifiersToRemove.append(iterator.key);
+ }
+ }
+
+ for (auto identifier : identifiersToRemove)
+ m_deletedObjectStores.remove(identifier);
- // The first error to be set is the true cause of the
- // transaction abort.
- if (!m_error) {
- m_error = error;
- m_errorMessage = errorMessage;
+ for (auto& objectStore : m_referencedObjectStores.values())
+ objectStore->rollbackForVersionChangeAbort();
}
+
+ transitionedToFinishing(IndexedDB::TransactionState::Aborting);
+
+ m_abortQueue.swap(m_pendingTransactionOperationQueue);
+
+ LOG(IndexedDBOperations, "IDB abort-on-server operation: Transaction %s", info().identifier().loggingString().utf8().data());
+ scheduleOperation(IDBClient::createTransactionOperation(*this, nullptr, &IDBTransaction::abortOnServerAndCancelRequests));
}
-PassRefPtr<IDBObjectStore> IDBTransaction::objectStore(const String& name, ExceptionCode& ec)
+void IDBTransaction::abortInProgressOperations(const IDBError& error)
{
- if (m_state == Finished) {
- ec = IDBDatabaseException::InvalidStateError;
- return 0;
+ LOG(IndexedDB, "IDBTransaction::abortInProgressOperations");
+
+ Vector<RefPtr<IDBClient::TransactionOperation>> inProgressAbortVector;
+ inProgressAbortVector.reserveInitialCapacity(m_transactionOperationsInProgressQueue.size());
+ while (!m_transactionOperationsInProgressQueue.isEmpty())
+ inProgressAbortVector.uncheckedAppend(m_transactionOperationsInProgressQueue.takeFirst());
+
+ for (auto& operation : inProgressAbortVector) {
+ m_transactionOperationsInProgressQueue.append(operation.get());
+ m_currentlyCompletingRequest = nullptr;
+ operation->doComplete(IDBResultData::error(operation->identifier(), error));
}
- IDBObjectStoreMap::iterator it = m_objectStoreMap.find(name);
- if (it != m_objectStoreMap.end())
- return it->value;
+ Vector<RefPtr<IDBClient::TransactionOperation>> completedOnServerAbortVector;
+ completedOnServerAbortVector.reserveInitialCapacity(m_completedOnServerQueue.size());
+ while (!m_completedOnServerQueue.isEmpty())
+ completedOnServerAbortVector.uncheckedAppend(m_completedOnServerQueue.takeFirst().first);
- if (!isVersionChange() && !m_objectStoreNames.contains(name)) {
- ec = IDBDatabaseException::NotFoundError;
- return 0;
+ for (auto& operation : completedOnServerAbortVector) {
+ m_currentlyCompletingRequest = nullptr;
+ operation->doComplete(IDBResultData::error(operation->identifier(), error));
}
- int64_t objectStoreId = m_database->findObjectStoreId(name);
- if (objectStoreId == IDBObjectStoreMetadata::InvalidId) {
- ASSERT(isVersionChange());
- ec = IDBDatabaseException::NotFoundError;
- return 0;
+ connectionProxy().forgetActiveOperations(inProgressAbortVector);
+}
+
+void IDBTransaction::abortOnServerAndCancelRequests(IDBClient::TransactionOperation& operation)
+{
+ LOG(IndexedDB, "IDBTransaction::abortOnServerAndCancelRequests");
+ ASSERT(currentThread() == m_database->originThreadID());
+ ASSERT(m_pendingTransactionOperationQueue.isEmpty());
+
+ m_database->connectionProxy().abortTransaction(*this);
+
+ ASSERT(m_transactionOperationMap.contains(operation.identifier()));
+ ASSERT(m_transactionOperationsInProgressQueue.last() == &operation);
+ m_transactionOperationMap.remove(operation.identifier());
+ m_transactionOperationsInProgressQueue.removeLast();
+
+ m_currentlyCompletingRequest = nullptr;
+
+ IDBError error(IDBDatabaseException::AbortError);
+
+ abortInProgressOperations(error);
+
+ for (auto& operation : m_abortQueue) {
+ m_currentlyCompletingRequest = nullptr;
+ m_transactionOperationsInProgressQueue.append(operation.get());
+ operation->doComplete(IDBResultData::error(operation->identifier(), error));
}
- const IDBDatabaseMetadata& metadata = m_database->metadata();
+ // Since we're aborting, it should be impossible to have queued any further operations.
+ ASSERT(m_pendingTransactionOperationQueue.isEmpty());
+}
- RefPtr<IDBObjectStore> objectStore = IDBObjectStore::create(metadata.objectStores.get(objectStoreId), this);
- objectStoreCreated(name, objectStore);
- return objectStore.release();
+const char* IDBTransaction::activeDOMObjectName() const
+{
+ ASSERT(currentThread() == m_database->originThreadID());
+ return "IDBTransaction";
}
-void IDBTransaction::objectStoreCreated(const String& name, PassRefPtr<IDBObjectStore> prpObjectStore)
+bool IDBTransaction::canSuspendForDocumentSuspension() const
{
- ASSERT(m_state != Finished);
- RefPtr<IDBObjectStore> objectStore = prpObjectStore;
- m_objectStoreMap.set(name, objectStore);
- if (isVersionChange())
- m_objectStoreCleanupMap.set(objectStore, objectStore->metadata());
+ ASSERT(currentThread() == m_database->originThreadID());
+ return false;
}
-void IDBTransaction::objectStoreDeleted(const String& name)
+bool IDBTransaction::hasPendingActivity() const
{
- ASSERT(m_state != Finished);
- ASSERT(isVersionChange());
- IDBObjectStoreMap::iterator it = m_objectStoreMap.find(name);
- if (it != m_objectStoreMap.end()) {
- RefPtr<IDBObjectStore> objectStore = it->value;
- m_objectStoreMap.remove(name);
- objectStore->markDeleted();
- m_objectStoreCleanupMap.set(objectStore, objectStore->metadata());
- m_deletedObjectStores.add(objectStore);
- }
+ ASSERT(currentThread() == m_database->originThreadID() || mayBeGCThread());
+ return !m_contextStopped && m_state != IndexedDB::TransactionState::Finished;
}
-void IDBTransaction::setActive(bool active)
+void IDBTransaction::stop()
{
- ASSERT_WITH_MESSAGE(m_state != Finished, "A finished transaction tried to setActive(%s)", active ? "true" : "false");
- if (m_state == Finishing)
+ LOG(IndexedDB, "IDBTransaction::stop - %s", m_info.loggingString().utf8().data());
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ // IDBDatabase::stop() calls IDBTransaction::stop() for each of its active transactions.
+ // Since the order of calling ActiveDOMObject::stop() is random, we might already have been stopped.
+ if (m_contextStopped)
return;
- ASSERT(active != (m_state == Active));
- m_state = active ? Active : Inactive;
- if (!active && m_requestList.isEmpty())
- backendDB()->commit(m_id);
+ removeAllEventListeners();
+
+ m_contextStopped = true;
+
+ if (isFinishedOrFinishing())
+ return;
+
+ internalAbort();
+}
+
+bool IDBTransaction::isActive() const
+{
+ ASSERT(currentThread() == m_database->originThreadID());
+ return m_state == IndexedDB::TransactionState::Active;
}
-void IDBTransaction::abort(ExceptionCode& ec)
+bool IDBTransaction::isFinishedOrFinishing() const
{
- if (m_state == Finishing || m_state == Finished) {
- ec = IDBDatabaseException::InvalidStateError;
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ return m_state == IndexedDB::TransactionState::Committing
+ || m_state == IndexedDB::TransactionState::Aborting
+ || m_state == IndexedDB::TransactionState::Finished;
+}
+
+void IDBTransaction::addRequest(IDBRequest& request)
+{
+ ASSERT(currentThread() == m_database->originThreadID());
+ m_openRequests.add(&request);
+}
+
+void IDBTransaction::removeRequest(IDBRequest& request)
+{
+ ASSERT(currentThread() == m_database->originThreadID());
+ ASSERT(m_openRequests.contains(&request));
+ m_openRequests.remove(&request);
+}
+
+void IDBTransaction::scheduleOperation(RefPtr<IDBClient::TransactionOperation>&& operation)
+{
+ ASSERT(!m_transactionOperationMap.contains(operation->identifier()));
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ m_pendingTransactionOperationQueue.append(operation);
+ m_transactionOperationMap.set(operation->identifier(), WTFMove(operation));
+
+ schedulePendingOperationTimer();
+}
+
+void IDBTransaction::schedulePendingOperationTimer()
+{
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ if (!m_pendingOperationTimer.isActive())
+ m_pendingOperationTimer.startOneShot(0);
+}
+
+void IDBTransaction::pendingOperationTimerFired()
+{
+ LOG(IndexedDB, "IDBTransaction::pendingOperationTimerFired (%p)", this);
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ if (!m_startedOnServer)
+ return;
+
+ // If the last in-progress operation we've sent to the server is not an IDBRequest operation,
+ // then we have to wait until it completes before sending any more.
+ if (!m_transactionOperationsInProgressQueue.isEmpty() && !m_transactionOperationsInProgressQueue.last()->nextRequestCanGoToServer())
return;
- }
- m_state = Finishing;
+ // We want to batch operations together without spinning the runloop for performance,
+ // but don't want to affect responsiveness of the main thread.
+ // This number is a good compromise in ad-hoc testing.
+ static const size_t operationBatchLimit = 128;
+
+ for (size_t iterations = 0; !m_pendingTransactionOperationQueue.isEmpty() && iterations < operationBatchLimit; ++iterations) {
+ auto operation = m_pendingTransactionOperationQueue.takeFirst();
+ m_transactionOperationsInProgressQueue.append(operation.get());
+ operation->perform();
+
+ if (!operation->nextRequestCanGoToServer())
+ break;
- while (!m_requestList.isEmpty()) {
- RefPtr<IDBRequest> request = *m_requestList.begin();
- m_requestList.remove(request);
- request->abort();
}
- RefPtr<IDBTransaction> selfRef = this;
- backendDB()->abort(m_id);
+ if (!m_transactionOperationMap.isEmpty() || !m_openRequests.isEmpty())
+ return;
+
+ if (!isFinishedOrFinishing())
+ commit();
}
-IDBTransaction::OpenCursorNotifier::OpenCursorNotifier(PassRefPtr<IDBTransaction> transaction, IDBCursor* cursor)
- : m_transaction(transaction),
- m_cursor(cursor)
+void IDBTransaction::operationCompletedOnServer(const IDBResultData& data, IDBClient::TransactionOperation& operation)
{
- m_transaction->registerOpenCursor(m_cursor);
+ ASSERT(currentThread() == m_database->originThreadID());
+ ASSERT(currentThread() == operation.originThreadID());
+
+ m_completedOnServerQueue.append({ &operation, data });
+ scheduleCompletedOperationTimer();
}
-IDBTransaction::OpenCursorNotifier::~OpenCursorNotifier()
+void IDBTransaction::scheduleCompletedOperationTimer()
{
- if (m_cursor)
- m_transaction->unregisterOpenCursor(m_cursor);
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ if (!m_completedOperationTimer.isActive())
+ m_completedOperationTimer.startOneShot(0);
}
-void IDBTransaction::OpenCursorNotifier::cursorFinished()
+void IDBTransaction::completedOperationTimerFired()
{
- if (m_cursor) {
- m_transaction->unregisterOpenCursor(m_cursor);
- m_cursor = 0;
- m_transaction.clear();
- }
+ LOG(IndexedDB, "IDBTransaction::completedOperationTimerFired (%p)", this);
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ if (m_completedOnServerQueue.isEmpty() || m_currentlyCompletingRequest)
+ return;
+
+ auto iterator = m_completedOnServerQueue.takeFirst();
+ iterator.first->doComplete(iterator.second);
+
+ if (!m_completedOnServerQueue.isEmpty() && !m_currentlyCompletingRequest)
+ scheduleCompletedOperationTimer();
+}
+
+void IDBTransaction::completeNoncursorRequest(IDBRequest& request, const IDBResultData& result)
+{
+ ASSERT(!m_currentlyCompletingRequest);
+
+ request.completeRequestAndDispatchEvent(result);
+
+ m_currentlyCompletingRequest = &request;
}
-void IDBTransaction::registerOpenCursor(IDBCursor* cursor)
+void IDBTransaction::completeCursorRequest(IDBRequest& request, const IDBResultData& result)
{
- m_openCursors.add(cursor);
+ ASSERT(!m_currentlyCompletingRequest);
+
+ request.didOpenOrIterateCursor(result);
+
+ m_currentlyCompletingRequest = &request;
}
-void IDBTransaction::unregisterOpenCursor(IDBCursor* cursor)
+void IDBTransaction::finishedDispatchEventForRequest(IDBRequest& request)
{
- m_openCursors.remove(cursor);
+ if (isFinishedOrFinishing())
+ return;
+
+ ASSERT_UNUSED(request, !m_currentlyCompletingRequest || m_currentlyCompletingRequest == &request);
+
+ m_currentlyCompletingRequest = nullptr;
+ scheduleCompletedOperationTimer();
}
-void IDBTransaction::closeOpenCursors()
+void IDBTransaction::commit()
{
- HashSet<IDBCursor*> cursors;
- cursors.swap(m_openCursors);
- for (HashSet<IDBCursor*>::iterator i = cursors.begin(); i != cursors.end(); ++i)
- (*i)->close();
+ LOG(IndexedDB, "IDBTransaction::commit");
+ ASSERT(currentThread() == m_database->originThreadID());
+ ASSERT(!isFinishedOrFinishing());
+
+ transitionedToFinishing(IndexedDB::TransactionState::Committing);
+ m_database->willCommitTransaction(*this);
+
+ LOG(IndexedDBOperations, "IDB commit operation: Transaction %s", info().identifier().loggingString().utf8().data());
+ scheduleOperation(IDBClient::createTransactionOperation(*this, nullptr, &IDBTransaction::commitOnServer));
}
-void IDBTransaction::registerRequest(IDBRequest* request)
+void IDBTransaction::commitOnServer(IDBClient::TransactionOperation& operation)
{
- ASSERT(request);
- ASSERT(m_state == Active);
- m_requestList.add(request);
+ LOG(IndexedDB, "IDBTransaction::commitOnServer");
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ m_database->connectionProxy().commitTransaction(*this);
+
+ ASSERT(!m_transactionOperationsInProgressQueue.isEmpty());
+ ASSERT(m_transactionOperationsInProgressQueue.last() == &operation);
+ m_transactionOperationsInProgressQueue.removeLast();
+
+ ASSERT(m_transactionOperationMap.contains(operation.identifier()));
+ m_transactionOperationMap.remove(operation.identifier());
}
-void IDBTransaction::unregisterRequest(IDBRequest* request)
+void IDBTransaction::finishAbortOrCommit()
{
- ASSERT(request);
- // If we aborted the request, it will already have been removed.
- m_requestList.remove(request);
+ ASSERT(m_state != IndexedDB::TransactionState::Finished);
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ m_state = IndexedDB::TransactionState::Finished;
}
-void IDBTransaction::onAbort(PassRefPtr<IDBDatabaseError> prpError)
+void IDBTransaction::didStart(const IDBError& error)
{
- LOG(StorageAPI, "IDBTransaction::onAbort");
- RefPtr<IDBDatabaseError> error = prpError;
- ASSERT(m_state != Finished);
+ LOG(IndexedDB, "IDBTransaction::didStart");
+ ASSERT(currentThread() == m_database->originThreadID());
- if (m_state != Finishing) {
- ASSERT(error.get());
- setError(DOMError::create(error->name()), error->message());
+ m_database->didStartTransaction(*this);
- // Abort was not triggered by front-end, so outstanding requests must
- // be aborted now.
- while (!m_requestList.isEmpty()) {
- RefPtr<IDBRequest> request = *m_requestList.begin();
- m_requestList.remove(request);
- request->abort();
- }
- m_state = Finishing;
+ m_startedOnServer = true;
+
+ // It's possible the transaction failed to start on the server.
+ // That equates to an abort.
+ if (!error.isNull()) {
+ didAbort(error);
+ return;
}
+ schedulePendingOperationTimer();
+}
+
+void IDBTransaction::notifyDidAbort(const IDBError& error)
+{
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ m_database->didAbortTransaction(*this);
+ m_idbError = error;
+ fireOnAbort();
+
if (isVersionChange()) {
- for (IDBObjectStoreMetadataMap::iterator it = m_objectStoreCleanupMap.begin(); it != m_objectStoreCleanupMap.end(); ++it)
- it->key->setMetadata(it->value);
- m_database->setMetadata(m_previousMetadata);
- m_database->close();
+ ASSERT(m_openDBRequest);
+ m_openDBRequest->fireErrorAfterVersionChangeCompletion();
}
- m_objectStoreCleanupMap.clear();
- closeOpenCursors();
+}
- // Enqueue events before notifying database, as database may close which enqueues more events and order matters.
- enqueueEvent(Event::create(eventNames().abortEvent, true, false));
- m_database->transactionFinished(this);
+void IDBTransaction::didAbort(const IDBError& error)
+{
+ LOG(IndexedDB, "IDBTransaction::didAbort");
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ if (m_state == IndexedDB::TransactionState::Finished)
+ return;
+
+ notifyDidAbort(error);
+
+ finishAbortOrCommit();
}
-void IDBTransaction::onComplete()
+void IDBTransaction::didCommit(const IDBError& error)
{
- LOG(StorageAPI, "IDBTransaction::onComplete");
- ASSERT(m_state != Finished);
- m_state = Finishing;
- m_objectStoreCleanupMap.clear();
- closeOpenCursors();
+ LOG(IndexedDB, "IDBTransaction::didCommit");
+ ASSERT(currentThread() == m_database->originThreadID());
+ ASSERT(m_state == IndexedDB::TransactionState::Committing);
+
+ if (error.isNull()) {
+ m_database->didCommitTransaction(*this);
+ fireOnComplete();
+ } else {
+ m_database->willAbortTransaction(*this);
+ notifyDidAbort(error);
+ }
- // Enqueue events before notifying database, as database may close which enqueues more events and order matters.
+ finishAbortOrCommit();
+}
+
+void IDBTransaction::fireOnComplete()
+{
+ LOG(IndexedDB, "IDBTransaction::fireOnComplete");
+ ASSERT(currentThread() == m_database->originThreadID());
enqueueEvent(Event::create(eventNames().completeEvent, false, false));
- m_database->transactionFinished(this);
}
-bool IDBTransaction::hasPendingActivity() const
+void IDBTransaction::fireOnAbort()
{
- // 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 m_hasPendingActivity && !m_contextStopped;
+ LOG(IndexedDB, "IDBTransaction::fireOnAbort");
+ ASSERT(currentThread() == m_database->originThreadID());
+ enqueueEvent(Event::create(eventNames().abortEvent, true, false));
}
-IndexedDB::TransactionMode IDBTransaction::stringToMode(const String& modeString, ExceptionCode& ec)
+void IDBTransaction::enqueueEvent(Ref<Event>&& event)
{
- if (modeString.isNull()
- || modeString == IDBTransaction::modeReadOnly())
- return IndexedDB::TransactionMode::ReadOnly;
- if (modeString == IDBTransaction::modeReadWrite())
- return IndexedDB::TransactionMode::ReadWrite;
+ ASSERT(m_state != IndexedDB::TransactionState::Finished);
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ if (!scriptExecutionContext() || m_contextStopped)
+ return;
- ec = TypeError;
- return IndexedDB::TransactionMode::ReadOnly;
+ event->setTarget(this);
+ scriptExecutionContext()->eventQueue().enqueueEvent(WTFMove(event));
}
-const AtomicString& IDBTransaction::modeToString(IndexedDB::TransactionMode mode)
+bool IDBTransaction::dispatchEvent(Event& event)
{
- switch (mode) {
- case IndexedDB::TransactionMode::ReadOnly:
- return IDBTransaction::modeReadOnly();
- break;
+ LOG(IndexedDB, "IDBTransaction::dispatchEvent");
- case IndexedDB::TransactionMode::ReadWrite:
- return IDBTransaction::modeReadWrite();
- break;
+ ASSERT(currentThread() == m_database->originThreadID());
+ ASSERT(scriptExecutionContext());
+ ASSERT(!m_contextStopped);
+ ASSERT(event.target() == this);
+ ASSERT(event.type() == eventNames().completeEvent || event.type() == eventNames().abortEvent);
- case IndexedDB::TransactionMode::VersionChange:
- return IDBTransaction::modeVersionChange();
- break;
+ Vector<RefPtr<EventTarget>> targets;
+ targets.append(this);
+ targets.append(db());
+
+ bool result = IDBEventDispatcher::dispatch(event, targets);
+
+ if (isVersionChange()) {
+ ASSERT(m_openDBRequest);
+ m_openDBRequest->versionChangeTransactionDidFinish();
+
+ if (event.type() == eventNames().completeEvent) {
+ if (m_database->isClosingOrClosed())
+ m_openDBRequest->fireErrorAfterVersionChangeCompletion();
+ else
+ m_openDBRequest->fireSuccessAfterVersionChangeCommit();
+ }
+
+ m_openDBRequest = nullptr;
}
- ASSERT_NOT_REACHED();
- return IDBTransaction::modeReadOnly();
+ return result;
}
-bool IDBTransaction::dispatchEvent(PassRefPtr<Event> event)
+Ref<IDBObjectStore> IDBTransaction::createObjectStore(const IDBObjectStoreInfo& info)
{
- LOG(StorageAPI, "IDBTransaction::dispatchEvent");
- ASSERT(m_state != Finished);
- ASSERT(m_hasPendingActivity);
+ LOG(IndexedDB, "IDBTransaction::createObjectStore");
+ ASSERT(isVersionChange());
ASSERT(scriptExecutionContext());
- ASSERT(event->target() == this);
- m_state = Finished;
+ ASSERT(currentThread() == m_database->originThreadID());
- // Break reference cycles.
- for (IDBObjectStoreMap::iterator it = m_objectStoreMap.begin(); it != m_objectStoreMap.end(); ++it)
- it->value->transactionFinished();
- m_objectStoreMap.clear();
- for (IDBObjectStoreSet::iterator it = m_deletedObjectStores.begin(); it != m_deletedObjectStores.end(); ++it)
- (*it)->transactionFinished();
- m_deletedObjectStores.clear();
+ Locker<Lock> locker(m_referencedObjectStoreLock);
- Vector<RefPtr<EventTarget>> targets;
- targets.append(this);
- targets.append(db());
+ auto objectStore = std::make_unique<IDBObjectStore>(*scriptExecutionContext(), info, *this);
+ auto* rawObjectStore = objectStore.get();
+ m_referencedObjectStores.set(info.name(), WTFMove(objectStore));
+
+ LOG(IndexedDBOperations, "IDB create object store operation: %s", info.condensedLoggingString().utf8().data());
+ scheduleOperation(IDBClient::createTransactionOperation(*this, &IDBTransaction::didCreateObjectStoreOnServer, &IDBTransaction::createObjectStoreOnServer, info));
+
+ return *rawObjectStore;
+}
+
+void IDBTransaction::createObjectStoreOnServer(IDBClient::TransactionOperation& operation, const IDBObjectStoreInfo& info)
+{
+ LOG(IndexedDB, "IDBTransaction::createObjectStoreOnServer");
+ ASSERT(currentThread() == m_database->originThreadID());
+ ASSERT(isVersionChange());
+
+ m_database->connectionProxy().createObjectStore(operation, info);
+}
+
+void IDBTransaction::didCreateObjectStoreOnServer(const IDBResultData& resultData)
+{
+ LOG(IndexedDB, "IDBTransaction::didCreateObjectStoreOnServer");
+ ASSERT(currentThread() == m_database->originThreadID());
+ ASSERT_UNUSED(resultData, resultData.type() == IDBResultType::CreateObjectStoreSuccess || resultData.type() == IDBResultType::Error);
+}
+
+void IDBTransaction::renameObjectStore(IDBObjectStore& objectStore, const String& newName)
+{
+ LOG(IndexedDB, "IDBTransaction::renameObjectStore");
+
+ Locker<Lock> locker(m_referencedObjectStoreLock);
+
+ ASSERT(isVersionChange());
+ ASSERT(scriptExecutionContext());
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ ASSERT(m_referencedObjectStores.contains(objectStore.info().name()));
+ ASSERT(!m_referencedObjectStores.contains(newName));
+ ASSERT(m_referencedObjectStores.get(objectStore.info().name()) == &objectStore);
- // FIXME: When we allow custom event dispatching, this will probably need to change.
- ASSERT(event->type() == eventNames().completeEvent || event->type() == eventNames().abortEvent);
- bool returnValue = IDBEventDispatcher::dispatch(event.get(), targets);
- // FIXME: Try to construct a test where |this| outlives openDBRequest and we
- // get a crash.
- if (m_openDBRequest) {
- ASSERT(isVersionChange());
- m_openDBRequest->transactionDidFinishAndDispatch();
+ uint64_t objectStoreIdentifier = objectStore.info().identifier();
+
+ LOG(IndexedDBOperations, "IDB rename object store operation: %s to %s", objectStore.info().condensedLoggingString().utf8().data(), newName.utf8().data());
+ scheduleOperation(IDBClient::createTransactionOperation(*this, &IDBTransaction::didRenameObjectStoreOnServer, &IDBTransaction::renameObjectStoreOnServer, objectStoreIdentifier, newName));
+
+ m_referencedObjectStores.set(newName, m_referencedObjectStores.take(objectStore.info().name()));
+}
+
+void IDBTransaction::renameObjectStoreOnServer(IDBClient::TransactionOperation& operation, const uint64_t& objectStoreIdentifier, const String& newName)
+{
+ LOG(IndexedDB, "IDBTransaction::renameObjectStoreOnServer");
+ ASSERT(currentThread() == m_database->originThreadID());
+ ASSERT(isVersionChange());
+
+ m_database->connectionProxy().renameObjectStore(operation, objectStoreIdentifier, newName);
+}
+
+void IDBTransaction::didRenameObjectStoreOnServer(const IDBResultData& resultData)
+{
+ LOG(IndexedDB, "IDBTransaction::didRenameObjectStoreOnServer");
+ ASSERT(currentThread() == m_database->originThreadID());
+ ASSERT_UNUSED(resultData, resultData.type() == IDBResultType::RenameObjectStoreSuccess || resultData.type() == IDBResultType::Error);
+}
+
+std::unique_ptr<IDBIndex> IDBTransaction::createIndex(IDBObjectStore& objectStore, const IDBIndexInfo& info)
+{
+ LOG(IndexedDB, "IDBTransaction::createIndex");
+ ASSERT(isVersionChange());
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ if (!scriptExecutionContext())
+ return nullptr;
+
+ LOG(IndexedDBOperations, "IDB create index operation: %s under object store %s", info.condensedLoggingString().utf8().data(), objectStore.info().condensedLoggingString().utf8().data());
+ scheduleOperation(IDBClient::createTransactionOperation(*this, &IDBTransaction::didCreateIndexOnServer, &IDBTransaction::createIndexOnServer, info));
+
+ return std::make_unique<IDBIndex>(*scriptExecutionContext(), info, objectStore);
+}
+
+void IDBTransaction::createIndexOnServer(IDBClient::TransactionOperation& operation, const IDBIndexInfo& info)
+{
+ LOG(IndexedDB, "IDBTransaction::createIndexOnServer");
+ ASSERT(currentThread() == m_database->originThreadID());
+ ASSERT(isVersionChange());
+
+ m_database->connectionProxy().createIndex(operation, info);
+}
+
+void IDBTransaction::didCreateIndexOnServer(const IDBResultData& resultData)
+{
+ LOG(IndexedDB, "IDBTransaction::didCreateIndexOnServer");
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ if (resultData.type() == IDBResultType::CreateIndexSuccess)
+ return;
+
+ ASSERT(resultData.type() == IDBResultType::Error);
+
+ // This operation might have failed because the transaction is already aborting.
+ if (m_state == IndexedDB::TransactionState::Aborting)
+ return;
+
+ // Otherwise, failure to create an index forced abortion of the transaction.
+ abortDueToFailedRequest(DOMError::create(IDBDatabaseException::getErrorName(resultData.error().code()), resultData.error().message()));
+}
+
+void IDBTransaction::renameIndex(IDBIndex& index, const String& newName)
+{
+ LOG(IndexedDB, "IDBTransaction::renameIndex");
+ Locker<Lock> locker(m_referencedObjectStoreLock);
+
+ ASSERT(isVersionChange());
+ ASSERT(scriptExecutionContext());
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ ASSERT(m_referencedObjectStores.contains(index.objectStore().info().name()));
+ ASSERT(m_referencedObjectStores.get(index.objectStore().info().name()) == &index.objectStore());
+
+ index.objectStore().renameReferencedIndex(index, newName);
+
+ uint64_t objectStoreIdentifier = index.objectStore().info().identifier();
+ uint64_t indexIdentifier = index.info().identifier();
+
+ LOG(IndexedDBOperations, "IDB rename index operation: %s to %s under object store %" PRIu64, index.info().condensedLoggingString().utf8().data(), newName.utf8().data(), index.info().objectStoreIdentifier());
+ scheduleOperation(IDBClient::createTransactionOperation(*this, &IDBTransaction::didRenameIndexOnServer, &IDBTransaction::renameIndexOnServer, objectStoreIdentifier, indexIdentifier, newName));
+}
+
+void IDBTransaction::renameIndexOnServer(IDBClient::TransactionOperation& operation, const uint64_t& objectStoreIdentifier, const uint64_t& indexIdentifier, const String& newName)
+{
+ LOG(IndexedDB, "IDBTransaction::renameIndexOnServer");
+ ASSERT(currentThread() == m_database->originThreadID());
+ ASSERT(isVersionChange());
+
+ m_database->connectionProxy().renameIndex(operation, objectStoreIdentifier, indexIdentifier, newName);
+}
+
+void IDBTransaction::didRenameIndexOnServer(const IDBResultData& resultData)
+{
+ LOG(IndexedDB, "IDBTransaction::didRenameIndexOnServer");
+ ASSERT(currentThread() == m_database->originThreadID());
+ ASSERT_UNUSED(resultData, resultData.type() == IDBResultType::RenameIndexSuccess || resultData.type() == IDBResultType::Error);
+}
+
+Ref<IDBRequest> IDBTransaction::requestOpenCursor(ExecState& state, IDBObjectStore& objectStore, const IDBCursorInfo& info)
+{
+ LOG(IndexedDB, "IDBTransaction::requestOpenCursor");
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ if (info.cursorType() == IndexedDB::CursorType::KeyOnly)
+ return doRequestOpenCursor(state, IDBCursor::create(*this, objectStore, info));
+
+ return doRequestOpenCursor(state, IDBCursorWithValue::create(*this, objectStore, info));
+}
+
+Ref<IDBRequest> IDBTransaction::requestOpenCursor(ExecState& state, IDBIndex& index, const IDBCursorInfo& info)
+{
+ LOG(IndexedDB, "IDBTransaction::requestOpenCursor");
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ if (info.cursorType() == IndexedDB::CursorType::KeyOnly)
+ return doRequestOpenCursor(state, IDBCursor::create(*this, index, info));
+
+ return doRequestOpenCursor(state, IDBCursorWithValue::create(*this, index, info));
+}
+
+Ref<IDBRequest> IDBTransaction::doRequestOpenCursor(ExecState& state, Ref<IDBCursor>&& cursor)
+{
+ ASSERT(isActive());
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ ASSERT_UNUSED(state, scriptExecutionContext() == scriptExecutionContextFromExecState(&state));
+
+ auto request = IDBRequest::create(*scriptExecutionContext(), cursor.get(), *this);
+ addRequest(request.get());
+
+ LOG(IndexedDBOperations, "IDB open cursor operation: %s", cursor->info().loggingString().utf8().data());
+ scheduleOperation(IDBClient::createTransactionOperation(*this, request.get(), &IDBTransaction::didOpenCursorOnServer, &IDBTransaction::openCursorOnServer, cursor->info()));
+
+ return request;
+}
+
+void IDBTransaction::openCursorOnServer(IDBClient::TransactionOperation& operation, const IDBCursorInfo& info)
+{
+ LOG(IndexedDB, "IDBTransaction::openCursorOnServer");
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ m_database->connectionProxy().openCursor(operation, info);
+}
+
+void IDBTransaction::didOpenCursorOnServer(IDBRequest& request, const IDBResultData& resultData)
+{
+ LOG(IndexedDB, "IDBTransaction::didOpenCursorOnServer");
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ completeCursorRequest(request, resultData);
+}
+
+void IDBTransaction::iterateCursor(IDBCursor& cursor, const IDBIterateCursorData& data)
+{
+ LOG(IndexedDB, "IDBTransaction::iterateCursor");
+ ASSERT(isActive());
+ ASSERT(cursor.request());
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ addRequest(*cursor.request());
+
+ LOG(IndexedDBOperations, "IDB iterate cursor operation: %s %s", cursor.info().loggingString().utf8().data(), data.loggingString().utf8().data());
+ scheduleOperation(IDBClient::createTransactionOperation(*this, *cursor.request(), &IDBTransaction::didIterateCursorOnServer, &IDBTransaction::iterateCursorOnServer, data));
+}
+
+// FIXME: changes here
+void IDBTransaction::iterateCursorOnServer(IDBClient::TransactionOperation& operation, const IDBIterateCursorData& data)
+{
+ LOG(IndexedDB, "IDBTransaction::iterateCursorOnServer");
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ m_database->connectionProxy().iterateCursor(operation, data);
+}
+
+void IDBTransaction::didIterateCursorOnServer(IDBRequest& request, const IDBResultData& resultData)
+{
+ LOG(IndexedDB, "IDBTransaction::didIterateCursorOnServer");
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ completeCursorRequest(request, resultData);
+}
+
+Ref<IDBRequest> IDBTransaction::requestGetAllObjectStoreRecords(JSC::ExecState& state, IDBObjectStore& objectStore, const IDBKeyRangeData& keyRangeData, IndexedDB::GetAllType getAllType, std::optional<uint32_t> count)
+{
+ LOG(IndexedDB, "IDBTransaction::requestGetAllObjectStoreRecords");
+ ASSERT(isActive());
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ ASSERT_UNUSED(state, scriptExecutionContext() == scriptExecutionContextFromExecState(&state));
+
+ auto request = IDBRequest::create(*scriptExecutionContext(), objectStore, *this);
+ addRequest(request.get());
+
+ IDBGetAllRecordsData getAllRecordsData { keyRangeData, getAllType, count, objectStore.info().identifier(), 0 };
+
+ LOG(IndexedDBOperations, "IDB get all object store records operation: %s", getAllRecordsData.loggingString().utf8().data());
+ scheduleOperation(IDBClient::createTransactionOperation(*this, request.get(), &IDBTransaction::didGetAllRecordsOnServer, &IDBTransaction::getAllRecordsOnServer, getAllRecordsData));
+
+ return request;
+}
+
+Ref<IDBRequest> IDBTransaction::requestGetAllIndexRecords(JSC::ExecState& state, IDBIndex& index, const IDBKeyRangeData& keyRangeData, IndexedDB::GetAllType getAllType, std::optional<uint32_t> count)
+{
+ LOG(IndexedDB, "IDBTransaction::requestGetAllIndexRecords");
+ ASSERT(isActive());
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ ASSERT_UNUSED(state, scriptExecutionContext() == scriptExecutionContextFromExecState(&state));
+
+ auto request = IDBRequest::create(*scriptExecutionContext(), index, *this);
+ addRequest(request.get());
+
+ IDBGetAllRecordsData getAllRecordsData { keyRangeData, getAllType, count, index.objectStore().info().identifier(), index.info().identifier() };
+
+ LOG(IndexedDBOperations, "IDB get all index records operation: %s", getAllRecordsData.loggingString().utf8().data());
+ scheduleOperation(IDBClient::createTransactionOperation(*this, request.get(), &IDBTransaction::didGetAllRecordsOnServer, &IDBTransaction::getAllRecordsOnServer, getAllRecordsData));
+
+ return request;
+}
+
+void IDBTransaction::getAllRecordsOnServer(IDBClient::TransactionOperation& operation, const IDBGetAllRecordsData& getAllRecordsData)
+{
+ LOG(IndexedDB, "IDBTransaction::getAllRecordsOnServer");
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ m_database->connectionProxy().getAllRecords(operation, getAllRecordsData);
+}
+
+void IDBTransaction::didGetAllRecordsOnServer(IDBRequest& request, const IDBResultData& resultData)
+{
+ LOG(IndexedDB, "IDBTransaction::didGetAllRecordsOnServer");
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ if (resultData.type() == IDBResultType::Error) {
+ completeNoncursorRequest(request, resultData);
+ return;
+ }
+
+ ASSERT(resultData.type() == IDBResultType::GetAllRecordsSuccess);
+
+ auto& getAllResult = resultData.getAllResult();
+ switch (getAllResult.type()) {
+ case IndexedDB::GetAllType::Keys:
+ request.setResult(getAllResult.keys());
+ break;
+ case IndexedDB::GetAllType::Values:
+ request.setResult(getAllResult.values());
+ break;
}
- m_hasPendingActivity = false;
- return returnValue;
+
+ completeNoncursorRequest(request, resultData);
}
-bool IDBTransaction::canSuspend() const
+Ref<IDBRequest> IDBTransaction::requestGetRecord(ExecState& state, IDBObjectStore& objectStore, const IDBGetRecordData& getRecordData)
{
- // FIXME: Technically we can suspend before the first request is schedule
- // and after the complete/abort event is enqueued.
- return m_state == Finished;
+ LOG(IndexedDB, "IDBTransaction::requestGetRecord");
+ ASSERT(isActive());
+ ASSERT(!getRecordData.keyRangeData.isNull);
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ ASSERT_UNUSED(state, scriptExecutionContext() == scriptExecutionContextFromExecState(&state));
+
+ IndexedDB::ObjectStoreRecordType type = getRecordData.type == IDBGetRecordDataType::KeyAndValue ? IndexedDB::ObjectStoreRecordType::ValueOnly : IndexedDB::ObjectStoreRecordType::KeyOnly;
+
+ auto request = IDBRequest::createObjectStoreGet(*scriptExecutionContext(), objectStore, type, *this);
+ addRequest(request.get());
+
+ LOG(IndexedDBOperations, "IDB get record operation: %s %s", objectStore.info().condensedLoggingString().utf8().data(), getRecordData.loggingString().utf8().data());
+ scheduleOperation(IDBClient::createTransactionOperation(*this, request.get(), &IDBTransaction::didGetRecordOnServer, &IDBTransaction::getRecordOnServer, getRecordData));
+
+ return request;
}
-void IDBTransaction::stop()
+Ref<IDBRequest> IDBTransaction::requestGetValue(ExecState& state, IDBIndex& index, const IDBKeyRangeData& range)
{
- m_contextStopped = true;
+ LOG(IndexedDB, "IDBTransaction::requestGetValue");
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ return requestIndexRecord(state, index, IndexedDB::IndexRecordType::Value, range);
+}
+
+Ref<IDBRequest> IDBTransaction::requestGetKey(ExecState& state, IDBIndex& index, const IDBKeyRangeData& range)
+{
+ LOG(IndexedDB, "IDBTransaction::requestGetValue");
+ ASSERT(currentThread() == m_database->originThreadID());
- abort(IGNORE_EXCEPTION);
+ return requestIndexRecord(state, index, IndexedDB::IndexRecordType::Key, range);
}
-void IDBTransaction::enqueueEvent(PassRefPtr<Event> event)
+Ref<IDBRequest> IDBTransaction::requestIndexRecord(ExecState& state, IDBIndex& index, IndexedDB::IndexRecordType type, const IDBKeyRangeData& range)
{
- ASSERT_WITH_MESSAGE(m_state != Finished, "A finished transaction tried to enqueue an event of type %s.", event->type().string().utf8().data());
- if (m_contextStopped || !scriptExecutionContext())
+ LOG(IndexedDB, "IDBTransaction::requestGetValue");
+ ASSERT(isActive());
+ ASSERT(!range.isNull);
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ ASSERT_UNUSED(state, scriptExecutionContext() == scriptExecutionContextFromExecState(&state));
+
+ auto request = IDBRequest::createIndexGet(*scriptExecutionContext(), index, type, *this);
+ addRequest(request.get());
+
+ IDBGetRecordData getRecordData = { range, IDBGetRecordDataType::KeyAndValue };
+
+ LOG(IndexedDBOperations, "IDB get index record operation: %s %s", index.info().condensedLoggingString().utf8().data(), getRecordData.loggingString().utf8().data());
+ scheduleOperation(IDBClient::createTransactionOperation(*this, request.get(), &IDBTransaction::didGetRecordOnServer, &IDBTransaction::getRecordOnServer, getRecordData));
+
+ return request;
+}
+
+void IDBTransaction::getRecordOnServer(IDBClient::TransactionOperation& operation, const IDBGetRecordData& getRecordData)
+{
+ LOG(IndexedDB, "IDBTransaction::getRecordOnServer");
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ m_database->connectionProxy().getRecord(operation, getRecordData);
+}
+
+void IDBTransaction::didGetRecordOnServer(IDBRequest& request, const IDBResultData& resultData)
+{
+ LOG(IndexedDB, "IDBTransaction::didGetRecordOnServer");
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ if (resultData.type() == IDBResultType::Error) {
+ completeNoncursorRequest(request, resultData);
return;
+ }
- event->setTarget(this);
- scriptExecutionContext()->eventQueue().enqueueEvent(event);
+ ASSERT(resultData.type() == IDBResultType::GetRecordSuccess);
+
+ bool useResultKey = request.sourceIndexIdentifier() && request.requestedIndexRecordType() == IndexedDB::IndexRecordType::Key;
+ if (!useResultKey)
+ useResultKey = request.requestedObjectStoreRecordType() == IndexedDB::ObjectStoreRecordType::KeyOnly;
+
+ const IDBGetResult& result = resultData.getResult();
+
+ if (useResultKey) {
+ if (!result.keyData().isNull())
+ request.setResult(result.keyData());
+ else
+ request.setResultToUndefined();
+ } else {
+ if (resultData.getResult().value().data().data())
+ request.setResultToStructuredClone(resultData.getResult().value());
+ else
+ request.setResultToUndefined();
+ }
+
+ completeNoncursorRequest(request, resultData);
+}
+
+Ref<IDBRequest> IDBTransaction::requestCount(ExecState& state, IDBObjectStore& objectStore, const IDBKeyRangeData& range)
+{
+ LOG(IndexedDB, "IDBTransaction::requestCount (IDBObjectStore)");
+ ASSERT(isActive());
+ ASSERT(!range.isNull);
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ ASSERT_UNUSED(state, scriptExecutionContext() == scriptExecutionContextFromExecState(&state));
+
+ auto request = IDBRequest::create(*scriptExecutionContext(), objectStore, *this);
+ addRequest(request.get());
+
+ LOG(IndexedDBOperations, "IDB object store count operation: %s, range %s", objectStore.info().condensedLoggingString().utf8().data(), range.loggingString().utf8().data());
+ scheduleOperation(IDBClient::createTransactionOperation(*this, request.get(), &IDBTransaction::didGetCountOnServer, &IDBTransaction::getCountOnServer, range));
+
+ return request;
+}
+
+Ref<IDBRequest> IDBTransaction::requestCount(ExecState& state, IDBIndex& index, const IDBKeyRangeData& range)
+{
+ LOG(IndexedDB, "IDBTransaction::requestCount (IDBIndex)");
+ ASSERT(isActive());
+ ASSERT(!range.isNull);
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ ASSERT_UNUSED(state, scriptExecutionContext() == scriptExecutionContextFromExecState(&state));
+
+ auto request = IDBRequest::create(*scriptExecutionContext(), index, *this);
+ addRequest(request.get());
+
+ LOG(IndexedDBOperations, "IDB index count operation: %s, range %s", index.info().condensedLoggingString().utf8().data(), range.loggingString().utf8().data());
+ scheduleOperation(IDBClient::createTransactionOperation(*this, request.get(), &IDBTransaction::didGetCountOnServer, &IDBTransaction::getCountOnServer, range));
+
+ return request;
+}
+
+void IDBTransaction::getCountOnServer(IDBClient::TransactionOperation& operation, const IDBKeyRangeData& keyRange)
+{
+ LOG(IndexedDB, "IDBTransaction::getCountOnServer");
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ m_database->connectionProxy().getCount(operation, keyRange);
+}
+
+void IDBTransaction::didGetCountOnServer(IDBRequest& request, const IDBResultData& resultData)
+{
+ LOG(IndexedDB, "IDBTransaction::didGetCountOnServer");
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ request.setResult(resultData.resultInteger());
+ completeNoncursorRequest(request, resultData);
}
-IDBDatabaseBackend* IDBTransaction::backendDB() const
+Ref<IDBRequest> IDBTransaction::requestDeleteRecord(ExecState& state, IDBObjectStore& objectStore, const IDBKeyRangeData& range)
{
- return db()->backend();
+ LOG(IndexedDB, "IDBTransaction::requestDeleteRecord");
+ ASSERT(isActive());
+ ASSERT(!range.isNull);
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ ASSERT_UNUSED(state, scriptExecutionContext() == scriptExecutionContextFromExecState(&state));
+
+ auto request = IDBRequest::create(*scriptExecutionContext(), objectStore, *this);
+ addRequest(request.get());
+
+ LOG(IndexedDBOperations, "IDB delete record operation: %s, range %s", objectStore.info().condensedLoggingString().utf8().data(), range.loggingString().utf8().data());
+ scheduleOperation(IDBClient::createTransactionOperation(*this, request.get(), &IDBTransaction::didDeleteRecordOnServer, &IDBTransaction::deleteRecordOnServer, range));
+ return request;
+}
+
+void IDBTransaction::deleteRecordOnServer(IDBClient::TransactionOperation& operation, const IDBKeyRangeData& keyRange)
+{
+ LOG(IndexedDB, "IDBTransaction::deleteRecordOnServer");
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ m_database->connectionProxy().deleteRecord(operation, keyRange);
+}
+
+void IDBTransaction::didDeleteRecordOnServer(IDBRequest& request, const IDBResultData& resultData)
+{
+ LOG(IndexedDB, "IDBTransaction::didDeleteRecordOnServer");
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ request.setResultToUndefined();
+ completeNoncursorRequest(request, resultData);
+}
+
+Ref<IDBRequest> IDBTransaction::requestClearObjectStore(ExecState& state, IDBObjectStore& objectStore)
+{
+ LOG(IndexedDB, "IDBTransaction::requestClearObjectStore");
+ ASSERT(isActive());
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ ASSERT_UNUSED(state, scriptExecutionContext() == scriptExecutionContextFromExecState(&state));
+
+ auto request = IDBRequest::create(*scriptExecutionContext(), objectStore, *this);
+ addRequest(request.get());
+
+ uint64_t objectStoreIdentifier = objectStore.info().identifier();
+
+ LOG(IndexedDBOperations, "IDB clear object store operation: %s", objectStore.info().condensedLoggingString().utf8().data());
+ scheduleOperation(IDBClient::createTransactionOperation(*this, request.get(), &IDBTransaction::didClearObjectStoreOnServer, &IDBTransaction::clearObjectStoreOnServer, objectStoreIdentifier));
+
+ return request;
+}
+
+void IDBTransaction::clearObjectStoreOnServer(IDBClient::TransactionOperation& operation, const uint64_t& objectStoreIdentifier)
+{
+ LOG(IndexedDB, "IDBTransaction::clearObjectStoreOnServer");
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ m_database->connectionProxy().clearObjectStore(operation, objectStoreIdentifier);
+}
+
+void IDBTransaction::didClearObjectStoreOnServer(IDBRequest& request, const IDBResultData& resultData)
+{
+ LOG(IndexedDB, "IDBTransaction::didClearObjectStoreOnServer");
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ request.setResultToUndefined();
+ completeNoncursorRequest(request, resultData);
+}
+
+Ref<IDBRequest> IDBTransaction::requestPutOrAdd(ExecState& state, IDBObjectStore& objectStore, IDBKey* key, SerializedScriptValue& value, IndexedDB::ObjectStoreOverwriteMode overwriteMode)
+{
+ LOG(IndexedDB, "IDBTransaction::requestPutOrAdd");
+ ASSERT(isActive());
+ ASSERT(!isReadOnly());
+ ASSERT(objectStore.info().autoIncrement() || key);
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ ASSERT_UNUSED(state, scriptExecutionContext() == scriptExecutionContextFromExecState(&state));
+
+ auto request = IDBRequest::create(*scriptExecutionContext(), objectStore, *this);
+ addRequest(request.get());
+
+ LOG(IndexedDBOperations, "IDB putOrAdd operation: %s key: %s", objectStore.info().condensedLoggingString().utf8().data(), key ? key->loggingString().utf8().data() : "<null key>");
+ scheduleOperation(IDBClient::createTransactionOperation(*this, request.get(), &IDBTransaction::didPutOrAddOnServer, &IDBTransaction::putOrAddOnServer, key, &value, overwriteMode));
+
+ return request;
+}
+
+void IDBTransaction::putOrAddOnServer(IDBClient::TransactionOperation& operation, RefPtr<IDBKey> key, RefPtr<SerializedScriptValue> value, const IndexedDB::ObjectStoreOverwriteMode& overwriteMode)
+{
+ LOG(IndexedDB, "IDBTransaction::putOrAddOnServer");
+ ASSERT(currentThread() == originThreadID());
+ ASSERT(!isReadOnly());
+ ASSERT(value);
+
+ if (!value->hasBlobURLs()) {
+ m_database->connectionProxy().putOrAdd(operation, key.get(), *value, overwriteMode);
+ return;
+ }
+
+ // Due to current limitations on our ability to post tasks back to a worker thread,
+ // workers currently write blobs to disk synchronously.
+ // FIXME: https://bugs.webkit.org/show_bug.cgi?id=157958 - Make this asynchronous after refactoring allows it.
+ if (!isMainThread()) {
+ auto idbValue = value->writeBlobsToDiskForIndexedDBSynchronously();
+ if (idbValue.data().data())
+ m_database->connectionProxy().putOrAdd(operation, key.get(), idbValue, overwriteMode);
+ else {
+ // If the IDBValue doesn't have any data, then something went wrong writing the blobs to disk.
+ // In that case, we cannot successfully store this record, so we callback with an error.
+ RefPtr<IDBClient::TransactionOperation> protectedOperation(&operation);
+ auto result = IDBResultData::error(operation.identifier(), { IDBDatabaseException::UnknownError, ASCIILiteral("Error preparing Blob/File data to be stored in object store") });
+ scriptExecutionContext()->postTask([protectedOperation = WTFMove(protectedOperation), result = WTFMove(result)](ScriptExecutionContext&) {
+ protectedOperation->doComplete(result);
+ });
+ }
+ return;
+ }
+
+ // Since this request won't actually go to the server until the blob writes are complete,
+ // stop future requests from going to the server ahead of it.
+ operation.setNextRequestCanGoToServer(false);
+
+ value->writeBlobsToDiskForIndexedDB([protectedThis = makeRef(*this), this, protectedOperation = Ref<IDBClient::TransactionOperation>(operation), keyData = IDBKeyData(key.get()).isolatedCopy(), overwriteMode](const IDBValue& idbValue) mutable {
+ ASSERT(currentThread() == originThreadID());
+ ASSERT(isMainThread());
+ if (idbValue.data().data()) {
+ m_database->connectionProxy().putOrAdd(protectedOperation.get(), WTFMove(keyData), idbValue, overwriteMode);
+ return;
+ }
+
+ // If the IDBValue doesn't have any data, then something went wrong writing the blobs to disk.
+ // In that case, we cannot successfully store this record, so we callback with an error.
+ auto result = IDBResultData::error(protectedOperation->identifier(), { IDBDatabaseException::UnknownError, ASCIILiteral("Error preparing Blob/File data to be stored in object store") });
+ callOnMainThread([protectedThis = WTFMove(protectedThis), protectedOperation = WTFMove(protectedOperation), result = WTFMove(result)]() mutable {
+ protectedOperation->doComplete(result);
+ });
+ });
+}
+
+void IDBTransaction::didPutOrAddOnServer(IDBRequest& request, const IDBResultData& resultData)
+{
+ LOG(IndexedDB, "IDBTransaction::didPutOrAddOnServer");
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ if (auto* result = resultData.resultKey())
+ request.setResult(*result);
+ else
+ request.setResultToUndefined();
+ completeNoncursorRequest(request, resultData);
+}
+
+void IDBTransaction::deleteObjectStore(const String& objectStoreName)
+{
+ LOG(IndexedDB, "IDBTransaction::deleteObjectStore");
+ ASSERT(currentThread() == m_database->originThreadID());
+ ASSERT(isVersionChange());
+
+ Locker<Lock> locker(m_referencedObjectStoreLock);
+
+ if (auto objectStore = m_referencedObjectStores.take(objectStoreName)) {
+ objectStore->markAsDeleted();
+ auto identifier = objectStore->info().identifier();
+ m_deletedObjectStores.set(identifier, WTFMove(objectStore));
+ }
+
+ LOG(IndexedDBOperations, "IDB delete object store operation: %s", objectStoreName.utf8().data());
+ scheduleOperation(IDBClient::createTransactionOperation(*this, &IDBTransaction::didDeleteObjectStoreOnServer, &IDBTransaction::deleteObjectStoreOnServer, objectStoreName));
+}
+
+void IDBTransaction::deleteObjectStoreOnServer(IDBClient::TransactionOperation& operation, const String& objectStoreName)
+{
+ LOG(IndexedDB, "IDBTransaction::deleteObjectStoreOnServer");
+ ASSERT(isVersionChange());
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ m_database->connectionProxy().deleteObjectStore(operation, objectStoreName);
}
+void IDBTransaction::didDeleteObjectStoreOnServer(const IDBResultData& resultData)
+{
+ LOG(IndexedDB, "IDBTransaction::didDeleteObjectStoreOnServer");
+ ASSERT(currentThread() == m_database->originThreadID());
+ ASSERT_UNUSED(resultData, resultData.type() == IDBResultType::DeleteObjectStoreSuccess || resultData.type() == IDBResultType::Error);
}
+void IDBTransaction::deleteIndex(uint64_t objectStoreIdentifier, const String& indexName)
+{
+ LOG(IndexedDB, "IDBTransaction::deleteIndex");
+ ASSERT(currentThread() == m_database->originThreadID());
+ ASSERT(isVersionChange());
+
+ LOG(IndexedDBOperations, "IDB delete index operation: %s (%" PRIu64 ")", indexName.utf8().data(), objectStoreIdentifier);
+ scheduleOperation(IDBClient::createTransactionOperation(*this, &IDBTransaction::didDeleteIndexOnServer, &IDBTransaction::deleteIndexOnServer, objectStoreIdentifier, indexName));
+}
+
+void IDBTransaction::deleteIndexOnServer(IDBClient::TransactionOperation& operation, const uint64_t& objectStoreIdentifier, const String& indexName)
+{
+ LOG(IndexedDB, "IDBTransaction::deleteIndexOnServer");
+ ASSERT(isVersionChange());
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ m_database->connectionProxy().deleteIndex(operation, objectStoreIdentifier, indexName);
+}
+
+void IDBTransaction::didDeleteIndexOnServer(const IDBResultData& resultData)
+{
+ LOG(IndexedDB, "IDBTransaction::didDeleteIndexOnServer");
+ ASSERT(currentThread() == m_database->originThreadID());
+ ASSERT_UNUSED(resultData, resultData.type() == IDBResultType::DeleteIndexSuccess || resultData.type() == IDBResultType::Error);
+}
+
+void IDBTransaction::operationCompletedOnClient(IDBClient::TransactionOperation& operation)
+{
+ LOG(IndexedDB, "IDBTransaction::operationCompletedOnClient");
+
+ ASSERT(currentThread() == m_database->originThreadID());
+ ASSERT(currentThread() == operation.originThreadID());
+ ASSERT(m_transactionOperationMap.get(operation.identifier()) == &operation);
+ ASSERT(m_transactionOperationsInProgressQueue.first() == &operation);
+
+ m_transactionOperationMap.remove(operation.identifier());
+ m_transactionOperationsInProgressQueue.removeFirst();
+
+ schedulePendingOperationTimer();
+}
+
+void IDBTransaction::establishOnServer()
+{
+ LOG(IndexedDB, "IDBTransaction::establishOnServer");
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ m_database->connectionProxy().establishTransaction(*this);
+}
+
+void IDBTransaction::activate()
+{
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ if (isFinishedOrFinishing())
+ return;
+
+ m_state = IndexedDB::TransactionState::Active;
+}
+
+void IDBTransaction::deactivate()
+{
+ ASSERT(currentThread() == m_database->originThreadID());
+
+ if (m_state == IndexedDB::TransactionState::Active)
+ m_state = IndexedDB::TransactionState::Inactive;
+
+ schedulePendingOperationTimer();
+}
+
+void IDBTransaction::connectionClosedFromServer(const IDBError& error)
+{
+ LOG(IndexedDB, "IDBTransaction::connectionClosedFromServer - %s", error.message().utf8().data());
+
+ m_state = IndexedDB::TransactionState::Aborting;
+
+ abortInProgressOperations(error);
+
+ Vector<RefPtr<IDBClient::TransactionOperation>> operations;
+ copyValuesToVector(m_transactionOperationMap, operations);
+
+ for (auto& operation : operations) {
+ m_currentlyCompletingRequest = nullptr;
+ m_transactionOperationsInProgressQueue.append(operation.get());
+ ASSERT(m_transactionOperationsInProgressQueue.first() == operation.get());
+ operation->doComplete(IDBResultData::error(operation->identifier(), error));
+ }
+
+ connectionProxy().forgetActiveOperations(operations);
+
+ m_pendingTransactionOperationQueue.clear();
+ m_abortQueue.clear();
+ m_transactionOperationMap.clear();
+
+ m_idbError = error;
+ m_domError = error.toDOMError();
+ fireOnAbort();
+}
+
+void IDBTransaction::visitReferencedObjectStores(JSC::SlotVisitor& visitor) const
+{
+ Locker<Lock> locker(m_referencedObjectStoreLock);
+ for (auto& objectStore : m_referencedObjectStores.values())
+ visitor.addOpaqueRoot(objectStore.get());
+ for (auto& objectStore : m_deletedObjectStores.values())
+ visitor.addOpaqueRoot(objectStore.get());
+}
+
+} // namespace WebCore
+
#endif // ENABLE(INDEXED_DATABASE)