diff options
Diffstat (limited to 'Source/WebCore/Modules/indexeddb/IDBTransaction.cpp')
-rw-r--r-- | Source/WebCore/Modules/indexeddb/IDBTransaction.cpp | 1492 |
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) |