diff options
Diffstat (limited to 'Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.cpp')
-rw-r--r-- | Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.cpp | 1908 |
1 files changed, 1908 insertions, 0 deletions
diff --git a/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.cpp b/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.cpp new file mode 100644 index 000000000..87863f9a3 --- /dev/null +++ b/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.cpp @@ -0,0 +1,1908 @@ +/* + * 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. + * + * 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" +#include "UniqueIDBDatabase.h" + +#if ENABLE(INDEXED_DATABASE) + +#include "IDBCursorInfo.h" +#include "IDBGetAllRecordsData.h" +#include "IDBGetAllResult.h" +#include "IDBGetRecordData.h" +#include "IDBIterateCursorData.h" +#include "IDBKeyRangeData.h" +#include "IDBResultData.h" +#include "IDBServer.h" +#include "IDBTransactionInfo.h" +#include "IDBValue.h" +#include "Logging.h" +#include "ScopeGuard.h" +#include "SerializedScriptValue.h" +#include "UniqueIDBDatabaseConnection.h" +#include <heap/HeapInlines.h> +#include <heap/StrongInlines.h> +#include <runtime/AuxiliaryBarrierInlines.h> +#include <runtime/StructureInlines.h> +#include <wtf/MainThread.h> +#include <wtf/NeverDestroyed.h> + +using namespace JSC; + +namespace WebCore { +namespace IDBServer { + +UniqueIDBDatabase::UniqueIDBDatabase(IDBServer& server, const IDBDatabaseIdentifier& identifier) + : m_server(server) + , m_identifier(identifier) + , m_operationAndTransactionTimer(*this, &UniqueIDBDatabase::operationAndTransactionTimerFired) +{ + LOG(IndexedDB, "UniqueIDBDatabase::UniqueIDBDatabase() (%p) %s", this, m_identifier.debugString().utf8().data()); +} + +UniqueIDBDatabase::~UniqueIDBDatabase() +{ + LOG(IndexedDB, "UniqueIDBDatabase::~UniqueIDBDatabase() (%p) %s", this, m_identifier.debugString().utf8().data()); + ASSERT(isMainThread()); + ASSERT(!hasAnyPendingCallbacks()); + ASSERT(!hasUnfinishedTransactions()); + ASSERT(m_pendingTransactions.isEmpty()); + ASSERT(m_openDatabaseConnections.isEmpty()); + ASSERT(m_clientClosePendingDatabaseConnections.isEmpty()); + ASSERT(m_serverClosePendingDatabaseConnections.isEmpty()); + ASSERT(!m_queuedTaskCount); +} + +const IDBDatabaseInfo& UniqueIDBDatabase::info() const +{ + RELEASE_ASSERT(m_databaseInfo); + return *m_databaseInfo; +} + +void UniqueIDBDatabase::openDatabaseConnection(IDBConnectionToClient& connection, const IDBRequestData& requestData) +{ + LOG(IndexedDB, "UniqueIDBDatabase::openDatabaseConnection"); + ASSERT(!m_hardClosedForUserDelete); + + m_pendingOpenDBRequests.add(ServerOpenDBRequest::create(connection, requestData)); + + // An open operation is already in progress, so we can't possibly handle this one yet. + if (m_isOpeningBackingStore) + return; + + handleDatabaseOperations(); +} + +bool UniqueIDBDatabase::hasAnyPendingCallbacks() const +{ + return !m_errorCallbacks.isEmpty() + || !m_keyDataCallbacks.isEmpty() + || !m_getResultCallbacks.isEmpty() + || !m_getAllResultsCallbacks.isEmpty() + || !m_countCallbacks.isEmpty(); +} + +bool UniqueIDBDatabase::isVersionChangeInProgress() +{ +#if !LOG_DISABLED + if (m_versionChangeTransaction) + ASSERT(m_versionChangeDatabaseConnection); +#endif + + return m_versionChangeDatabaseConnection; +} + +void UniqueIDBDatabase::performCurrentOpenOperation() +{ + LOG(IndexedDB, "(main) UniqueIDBDatabase::performCurrentOpenOperation (%p)", this); + + ASSERT(m_currentOpenDBRequest); + ASSERT(m_currentOpenDBRequest->isOpenRequest()); + + if (!m_databaseInfo) { + if (!m_isOpeningBackingStore) { + m_isOpeningBackingStore = true; + postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::openBackingStore, m_identifier)); + } + + return; + } + + // If we previously started a version change operation but were blocked by having open connections, + // we might now be unblocked. + if (m_versionChangeDatabaseConnection) { + if (!m_versionChangeTransaction && !hasAnyOpenConnections()) + startVersionChangeTransaction(); + return; + } + + // 3.3.1 Opening a database + // If requested version is undefined, then let requested version be 1 if db was created in the previous step, + // or the current version of db otherwise. + uint64_t requestedVersion = m_currentOpenDBRequest->requestData().requestedVersion(); + if (!requestedVersion) + requestedVersion = m_databaseInfo->version() ? m_databaseInfo->version() : 1; + + // 3.3.1 Opening a database + // If the database version higher than the requested version, abort these steps and return a VersionError. + if (requestedVersion < m_databaseInfo->version()) { + auto result = IDBResultData::error(m_currentOpenDBRequest->requestData().requestIdentifier(), IDBError(IDBDatabaseException::VersionError)); + m_currentOpenDBRequest->connection().didOpenDatabase(result); + m_currentOpenDBRequest = nullptr; + + return; + } + + if (!m_backingStoreOpenError.isNull()) { + auto result = IDBResultData::error(m_currentOpenDBRequest->requestData().requestIdentifier(), m_backingStoreOpenError); + m_currentOpenDBRequest->connection().didOpenDatabase(result); + m_currentOpenDBRequest = nullptr; + + return; + } + + Ref<UniqueIDBDatabaseConnection> connection = UniqueIDBDatabaseConnection::create(*this, *m_currentOpenDBRequest); + + if (requestedVersion == m_databaseInfo->version()) { + auto* rawConnection = &connection.get(); + addOpenDatabaseConnection(WTFMove(connection)); + + auto result = IDBResultData::openDatabaseSuccess(m_currentOpenDBRequest->requestData().requestIdentifier(), *rawConnection); + m_currentOpenDBRequest->connection().didOpenDatabase(result); + m_currentOpenDBRequest = nullptr; + + return; + } + + ASSERT(!m_versionChangeDatabaseConnection); + m_versionChangeDatabaseConnection = WTFMove(connection); + + // 3.3.7 "versionchange" transaction steps + // If there's no other open connections to this database, the version change process can begin immediately. + if (!hasAnyOpenConnections()) { + startVersionChangeTransaction(); + return; + } + + // Otherwise we have to notify all those open connections and wait for them to close. + maybeNotifyConnectionsOfVersionChange(); +} + +void UniqueIDBDatabase::performCurrentDeleteOperation() +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::performCurrentDeleteOperation - %s", m_identifier.debugString().utf8().data()); + + ASSERT(m_currentOpenDBRequest); + ASSERT(m_currentOpenDBRequest->isDeleteRequest()); + + if (m_deleteBackingStoreInProgress) + return; + + if (hasAnyOpenConnections()) { + maybeNotifyConnectionsOfVersionChange(); + return; + } + + if (hasUnfinishedTransactions()) + return; + + ASSERT(!hasAnyPendingCallbacks()); + ASSERT(m_pendingTransactions.isEmpty()); + ASSERT(m_openDatabaseConnections.isEmpty()); + + // It's possible to have multiple delete requests queued up in a row. + // In that scenario only the first request will actually have to delete the database. + // Subsequent requests can immediately notify their completion. + + if (!m_deleteBackingStoreInProgress) { + if (!m_databaseInfo && m_mostRecentDeletedDatabaseInfo) + didDeleteBackingStore(0); + else { + m_deleteBackingStoreInProgress = true; + postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::deleteBackingStore, m_identifier)); + } + } +} + +void UniqueIDBDatabase::deleteBackingStore(const IDBDatabaseIdentifier& identifier) +{ + ASSERT(!isMainThread()); + LOG(IndexedDB, "(db) UniqueIDBDatabase::deleteBackingStore"); + + uint64_t deletedVersion = 0; + + if (m_backingStore) { + m_backingStore->deleteBackingStore(); + m_backingStore = nullptr; + m_backingStoreSupportsSimultaneousTransactions = false; + m_backingStoreIsEphemeral = false; + } else { + auto backingStore = m_server.createBackingStore(identifier); + + IDBDatabaseInfo databaseInfo; + auto error = backingStore->getOrEstablishDatabaseInfo(databaseInfo); + if (!error.isNull()) + LOG_ERROR("Error getting database info from database %s that we are trying to delete", identifier.debugString().utf8().data()); + + deletedVersion = databaseInfo.version(); + backingStore->deleteBackingStore(); + } + + postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didDeleteBackingStore, deletedVersion)); +} + +void UniqueIDBDatabase::performUnconditionalDeleteBackingStore() +{ + ASSERT(!isMainThread()); + LOG(IndexedDB, "(db) UniqueIDBDatabase::performUnconditionalDeleteBackingStore"); + + if (!m_backingStore) + return; + + m_backingStore->deleteBackingStore(); + m_backingStore = nullptr; + m_backingStoreSupportsSimultaneousTransactions = false; + m_backingStoreIsEphemeral = false; +} + +void UniqueIDBDatabase::didDeleteBackingStore(uint64_t deletedVersion) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::didDeleteBackingStore"); + + ASSERT(!hasAnyPendingCallbacks()); + ASSERT(!hasUnfinishedTransactions()); + ASSERT(m_pendingTransactions.isEmpty()); + ASSERT(m_openDatabaseConnections.isEmpty()); + + // It's possible that the openDBRequest was cancelled from client-side after the delete was already dispatched to the backingstore. + // So it's okay if we don't have a currentOpenDBRequest, but if we do it has to be a deleteRequest. + ASSERT(!m_currentOpenDBRequest || m_currentOpenDBRequest->isDeleteRequest()); + + if (m_databaseInfo) + m_mostRecentDeletedDatabaseInfo = WTFMove(m_databaseInfo); + + // If this UniqueIDBDatabase was brought into existence for the purpose of deleting the file on disk, + // we won't have a m_mostRecentDeletedDatabaseInfo. In that case, we'll manufacture one using the + // passed in deletedVersion argument. + if (!m_mostRecentDeletedDatabaseInfo) + m_mostRecentDeletedDatabaseInfo = std::make_unique<IDBDatabaseInfo>(m_identifier.databaseName(), deletedVersion); + + if (m_currentOpenDBRequest) { + m_currentOpenDBRequest->notifyDidDeleteDatabase(*m_mostRecentDeletedDatabaseInfo); + m_currentOpenDBRequest = nullptr; + } + + m_deleteBackingStoreInProgress = false; + + if (m_clientClosePendingDatabaseConnections.isEmpty() && m_pendingOpenDBRequests.isEmpty()) { + m_server.closeUniqueIDBDatabase(*this); + return; + } + + invokeOperationAndTransactionTimer(); +} + +void UniqueIDBDatabase::didPerformUnconditionalDeleteBackingStore() +{ + // This function is a placeholder so the database thread can message back to the main thread. + ASSERT(m_hardClosedForUserDelete); +} + +void UniqueIDBDatabase::handleDatabaseOperations() +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::handleDatabaseOperations - There are %u pending", m_pendingOpenDBRequests.size()); + ASSERT(!m_hardClosedForUserDelete); + + if (m_deleteBackingStoreInProgress) + return; + + if (m_versionChangeDatabaseConnection || m_versionChangeTransaction || m_currentOpenDBRequest) { + // We can't start any new open-database operations right now, but we might be able to start handling a delete operation. + if (!m_currentOpenDBRequest && !m_pendingOpenDBRequests.isEmpty() && m_pendingOpenDBRequests.first()->isDeleteRequest()) + m_currentOpenDBRequest = m_pendingOpenDBRequests.takeFirst(); + + // Some operations (such as the first open operation after a delete) require multiple passes to completely handle + if (m_currentOpenDBRequest) + handleCurrentOperation(); + + return; + } + + if (m_pendingOpenDBRequests.isEmpty()) + return; + + m_currentOpenDBRequest = m_pendingOpenDBRequests.takeFirst(); + LOG(IndexedDB, "UniqueIDBDatabase::handleDatabaseOperations - Popped an operation, now there are %u pending", m_pendingOpenDBRequests.size()); + + handleCurrentOperation(); +} + +void UniqueIDBDatabase::handleCurrentOperation() +{ + LOG(IndexedDB, "(main) UniqueIDBDatabase::handleCurrentOperation"); + ASSERT(!m_hardClosedForUserDelete); + ASSERT(m_currentOpenDBRequest); + + RefPtr<UniqueIDBDatabase> protectedThis(this); + + if (m_currentOpenDBRequest->isOpenRequest()) + performCurrentOpenOperation(); + else if (m_currentOpenDBRequest->isDeleteRequest()) + performCurrentDeleteOperation(); + else + ASSERT_NOT_REACHED(); + + if (!m_currentOpenDBRequest) + invokeOperationAndTransactionTimer(); +} + +bool UniqueIDBDatabase::hasAnyOpenConnections() const +{ + return !m_openDatabaseConnections.isEmpty(); +} + +bool UniqueIDBDatabase::allConnectionsAreClosedOrClosing() const +{ + for (auto& connection : m_openDatabaseConnections) { + if (!connection->connectionIsClosing()) + return false; + } + + return true; +} + +static uint64_t generateUniqueCallbackIdentifier() +{ + ASSERT(isMainThread()); + static uint64_t currentID = 0; + return ++currentID; +} + +uint64_t UniqueIDBDatabase::storeCallbackOrFireError(ErrorCallback callback) +{ + if (m_hardClosedForUserDelete) { + callback(IDBError::userDeleteError()); + return 0; + } + + uint64_t identifier = generateUniqueCallbackIdentifier(); + ASSERT(!m_errorCallbacks.contains(identifier)); + m_errorCallbacks.add(identifier, callback); + return identifier; +} + +uint64_t UniqueIDBDatabase::storeCallbackOrFireError(KeyDataCallback callback) +{ + if (m_hardClosedForUserDelete) { + callback(IDBError::userDeleteError(), { }); + return 0; + } + + uint64_t identifier = generateUniqueCallbackIdentifier(); + ASSERT(!m_keyDataCallbacks.contains(identifier)); + m_keyDataCallbacks.add(identifier, callback); + return identifier; +} + +uint64_t UniqueIDBDatabase::storeCallbackOrFireError(GetResultCallback callback) +{ + if (m_hardClosedForUserDelete) { + callback(IDBError::userDeleteError(), { }); + return 0; + } + + uint64_t identifier = generateUniqueCallbackIdentifier(); + ASSERT(!m_getResultCallbacks.contains(identifier)); + m_getResultCallbacks.add(identifier, callback); + return identifier; +} + +uint64_t UniqueIDBDatabase::storeCallbackOrFireError(GetAllResultsCallback callback) +{ + if (m_hardClosedForUserDelete) { + callback(IDBError::userDeleteError(), { }); + return 0; + } + + uint64_t identifier = generateUniqueCallbackIdentifier(); + ASSERT(!m_getAllResultsCallbacks.contains(identifier)); + m_getAllResultsCallbacks.add(identifier, callback); + return identifier; +} + +uint64_t UniqueIDBDatabase::storeCallbackOrFireError(CountCallback callback) +{ + if (m_hardClosedForUserDelete) { + callback(IDBError::userDeleteError(), 0); + return 0; + } + + uint64_t identifier = generateUniqueCallbackIdentifier(); + ASSERT(!m_countCallbacks.contains(identifier)); + m_countCallbacks.add(identifier, callback); + return identifier; +} + +void UniqueIDBDatabase::handleDelete(IDBConnectionToClient& connection, const IDBRequestData& requestData) +{ + LOG(IndexedDB, "(main) UniqueIDBDatabase::handleDelete"); + ASSERT(!m_hardClosedForUserDelete); + + m_pendingOpenDBRequests.add(ServerOpenDBRequest::create(connection, requestData)); + handleDatabaseOperations(); +} + +void UniqueIDBDatabase::startVersionChangeTransaction() +{ + LOG(IndexedDB, "(main) UniqueIDBDatabase::startVersionChangeTransaction"); + + ASSERT(!m_versionChangeTransaction); + ASSERT(m_currentOpenDBRequest); + ASSERT(m_currentOpenDBRequest->isOpenRequest()); + ASSERT(m_versionChangeDatabaseConnection); + + auto operation = WTFMove(m_currentOpenDBRequest); + + uint64_t requestedVersion = operation->requestData().requestedVersion(); + if (!requestedVersion) + requestedVersion = m_databaseInfo->version() ? m_databaseInfo->version() : 1; + + addOpenDatabaseConnection(*m_versionChangeDatabaseConnection); + + m_versionChangeTransaction = &m_versionChangeDatabaseConnection->createVersionChangeTransaction(requestedVersion); + m_databaseInfo->setVersion(requestedVersion); + + m_inProgressTransactions.set(m_versionChangeTransaction->info().identifier(), m_versionChangeTransaction); + postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::beginTransactionInBackingStore, m_versionChangeTransaction->info())); + + auto result = IDBResultData::openDatabaseUpgradeNeeded(operation->requestData().requestIdentifier(), *m_versionChangeTransaction); + operation->connection().didOpenDatabase(result); +} + +void UniqueIDBDatabase::beginTransactionInBackingStore(const IDBTransactionInfo& info) +{ + LOG(IndexedDB, "(db) UniqueIDBDatabase::beginTransactionInBackingStore"); + m_backingStore->beginTransaction(info); +} + +void UniqueIDBDatabase::maybeNotifyConnectionsOfVersionChange() +{ + ASSERT(m_currentOpenDBRequest); + + if (m_currentOpenDBRequest->hasNotifiedConnectionsOfVersionChange()) + return; + + uint64_t newVersion = m_currentOpenDBRequest->isOpenRequest() ? m_currentOpenDBRequest->requestData().requestedVersion() : 0; + auto requestIdentifier = m_currentOpenDBRequest->requestData().requestIdentifier(); + + LOG(IndexedDB, "(main) UniqueIDBDatabase::notifyConnectionsOfVersionChange - %" PRIu64, newVersion); + + // 3.3.7 "versionchange" transaction steps + // Fire a versionchange event at each connection in m_openDatabaseConnections that is open. + // The event must not be fired on connections which has the closePending flag set. + HashSet<uint64_t> connectionIdentifiers; + for (auto connection : m_openDatabaseConnections) { + if (connection->closePending()) + continue; + + connection->fireVersionChangeEvent(requestIdentifier, newVersion); + connectionIdentifiers.add(connection->identifier()); + } + + if (!connectionIdentifiers.isEmpty()) + m_currentOpenDBRequest->notifiedConnectionsOfVersionChange(WTFMove(connectionIdentifiers)); + else + m_currentOpenDBRequest->maybeNotifyRequestBlocked(m_databaseInfo->version()); +} + +void UniqueIDBDatabase::notifyCurrentRequestConnectionClosedOrFiredVersionChangeEvent(uint64_t connectionIdentifier) +{ + LOG(IndexedDB, "UniqueIDBDatabase::notifyCurrentRequestConnectionClosedOrFiredVersionChangeEvent - %" PRIu64, connectionIdentifier); + + ASSERT(m_currentOpenDBRequest); + + m_currentOpenDBRequest->connectionClosedOrFiredVersionChangeEvent(connectionIdentifier); + + if (m_currentOpenDBRequest->hasConnectionsPendingVersionChangeEvent()) + return; + + if (!hasAnyOpenConnections() || allConnectionsAreClosedOrClosing()) { + invokeOperationAndTransactionTimer(); + return; + } + + // Since all open connections have fired their version change events but not all of them have closed, + // this request is officially blocked. + m_currentOpenDBRequest->maybeNotifyRequestBlocked(m_databaseInfo->version()); +} + +void UniqueIDBDatabase::didFireVersionChangeEvent(UniqueIDBDatabaseConnection& connection, const IDBResourceIdentifier& requestIdentifier) +{ + LOG(IndexedDB, "UniqueIDBDatabase::didFireVersionChangeEvent"); + + if (!m_currentOpenDBRequest) + return; + + ASSERT_UNUSED(requestIdentifier, m_currentOpenDBRequest->requestData().requestIdentifier() == requestIdentifier); + + notifyCurrentRequestConnectionClosedOrFiredVersionChangeEvent(connection.identifier()); +} + +void UniqueIDBDatabase::openDBRequestCancelled(const IDBResourceIdentifier& requestIdentifier) +{ + LOG(IndexedDB, "UniqueIDBDatabase::openDBRequestCancelled - %s", requestIdentifier.loggingString().utf8().data()); + + if (m_currentOpenDBRequest && m_currentOpenDBRequest->requestData().requestIdentifier() == requestIdentifier) + m_currentOpenDBRequest = nullptr; + + if (m_versionChangeDatabaseConnection && m_versionChangeDatabaseConnection->openRequestIdentifier() == requestIdentifier) { + ASSERT(!m_versionChangeTransaction || m_versionChangeTransaction->databaseConnection().openRequestIdentifier() == requestIdentifier); + ASSERT(!m_versionChangeTransaction || &m_versionChangeTransaction->databaseConnection() == m_versionChangeDatabaseConnection); + + connectionClosedFromClient(*m_versionChangeDatabaseConnection); + } + + for (auto& request : m_pendingOpenDBRequests) { + if (request->requestData().requestIdentifier() == requestIdentifier) { + m_pendingOpenDBRequests.remove(request); + return; + } + } +} + +void UniqueIDBDatabase::addOpenDatabaseConnection(Ref<UniqueIDBDatabaseConnection>&& connection) +{ + ASSERT(!m_openDatabaseConnections.contains(&connection.get())); + m_openDatabaseConnections.add(adoptRef(connection.leakRef())); +} + +void UniqueIDBDatabase::openBackingStore(const IDBDatabaseIdentifier& identifier) +{ + ASSERT(!isMainThread()); + LOG(IndexedDB, "(db) UniqueIDBDatabase::openBackingStore (%p)", this); + + ASSERT(!m_backingStore); + m_backingStore = m_server.createBackingStore(identifier); + m_backingStoreSupportsSimultaneousTransactions = m_backingStore->supportsSimultaneousTransactions(); + m_backingStoreIsEphemeral = m_backingStore->isEphemeral(); + + IDBDatabaseInfo databaseInfo; + auto error = m_backingStore->getOrEstablishDatabaseInfo(databaseInfo); + + postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didOpenBackingStore, databaseInfo, error)); +} + +void UniqueIDBDatabase::didOpenBackingStore(const IDBDatabaseInfo& info, const IDBError& error) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::didOpenBackingStore"); + + m_databaseInfo = std::make_unique<IDBDatabaseInfo>(info); + m_backingStoreOpenError = error; + + ASSERT(m_isOpeningBackingStore); + m_isOpeningBackingStore = false; + + handleDatabaseOperations(); +} + +void UniqueIDBDatabase::createObjectStore(UniqueIDBDatabaseTransaction& transaction, const IDBObjectStoreInfo& info, ErrorCallback callback) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::createObjectStore"); + + uint64_t callbackID = storeCallbackOrFireError(callback); + if (!callbackID) + return; + + postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performCreateObjectStore, callbackID, transaction.info().identifier(), info)); +} + +void UniqueIDBDatabase::performCreateObjectStore(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, const IDBObjectStoreInfo& info) +{ + ASSERT(!isMainThread()); + LOG(IndexedDB, "(db) UniqueIDBDatabase::performCreateObjectStore"); + + ASSERT(m_backingStore); + m_backingStore->createObjectStore(transactionIdentifier, info); + + IDBError error; + postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformCreateObjectStore, callbackIdentifier, error, info)); +} + +void UniqueIDBDatabase::didPerformCreateObjectStore(uint64_t callbackIdentifier, const IDBError& error, const IDBObjectStoreInfo& info) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformCreateObjectStore"); + + if (error.isNull()) + m_databaseInfo->addExistingObjectStore(info); + + performErrorCallback(callbackIdentifier, error); +} + +void UniqueIDBDatabase::deleteObjectStore(UniqueIDBDatabaseTransaction& transaction, const String& objectStoreName, ErrorCallback callback) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::deleteObjectStore"); + + uint64_t callbackID = storeCallbackOrFireError(callback); + if (!callbackID) + return; + + auto* info = m_databaseInfo->infoForExistingObjectStore(objectStoreName); + if (!info) { + performErrorCallback(callbackID, { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to delete non-existant object store") }); + return; + } + + postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performDeleteObjectStore, callbackID, transaction.info().identifier(), info->identifier())); +} + +void UniqueIDBDatabase::performDeleteObjectStore(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier) +{ + ASSERT(!isMainThread()); + LOG(IndexedDB, "(db) UniqueIDBDatabase::performDeleteObjectStore"); + + ASSERT(m_backingStore); + m_backingStore->deleteObjectStore(transactionIdentifier, objectStoreIdentifier); + + IDBError error; + postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformDeleteObjectStore, callbackIdentifier, error, objectStoreIdentifier)); +} + +void UniqueIDBDatabase::didPerformDeleteObjectStore(uint64_t callbackIdentifier, const IDBError& error, uint64_t objectStoreIdentifier) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformDeleteObjectStore"); + + if (error.isNull()) + m_databaseInfo->deleteObjectStore(objectStoreIdentifier); + + performErrorCallback(callbackIdentifier, error); +} + +void UniqueIDBDatabase::renameObjectStore(UniqueIDBDatabaseTransaction& transaction, uint64_t objectStoreIdentifier, const String& newName, ErrorCallback callback) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::renameObjectStore"); + + uint64_t callbackID = storeCallbackOrFireError(callback); + if (!callbackID) + return; + + auto* info = m_databaseInfo->infoForExistingObjectStore(objectStoreIdentifier); + if (!info) { + performErrorCallback(callbackID, { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to rename non-existant object store") }); + return; + } + + postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performRenameObjectStore, callbackID, transaction.info().identifier(), objectStoreIdentifier, newName)); +} + +void UniqueIDBDatabase::performRenameObjectStore(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const String& newName) +{ + ASSERT(!isMainThread()); + LOG(IndexedDB, "(db) UniqueIDBDatabase::performRenameObjectStore"); + + ASSERT(m_backingStore); + m_backingStore->renameObjectStore(transactionIdentifier, objectStoreIdentifier, newName); + + IDBError error; + postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformRenameObjectStore, callbackIdentifier, error, objectStoreIdentifier, newName)); +} + +void UniqueIDBDatabase::didPerformRenameObjectStore(uint64_t callbackIdentifier, const IDBError& error, uint64_t objectStoreIdentifier, const String& newName) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformRenameObjectStore"); + + if (error.isNull()) + m_databaseInfo->renameObjectStore(objectStoreIdentifier, newName); + + performErrorCallback(callbackIdentifier, error); +} + +void UniqueIDBDatabase::clearObjectStore(UniqueIDBDatabaseTransaction& transaction, uint64_t objectStoreIdentifier, ErrorCallback callback) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::clearObjectStore"); + + uint64_t callbackID = storeCallbackOrFireError(callback); + if (!callbackID) + return; + postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performClearObjectStore, callbackID, transaction.info().identifier(), objectStoreIdentifier)); +} + +void UniqueIDBDatabase::performClearObjectStore(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier) +{ + ASSERT(!isMainThread()); + LOG(IndexedDB, "(db) UniqueIDBDatabase::performClearObjectStore"); + + ASSERT(m_backingStore); + m_backingStore->clearObjectStore(transactionIdentifier, objectStoreIdentifier); + + IDBError error; + postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformClearObjectStore, callbackIdentifier, error)); +} + +void UniqueIDBDatabase::didPerformClearObjectStore(uint64_t callbackIdentifier, const IDBError& error) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformClearObjectStore"); + + performErrorCallback(callbackIdentifier, error); +} + +void UniqueIDBDatabase::createIndex(UniqueIDBDatabaseTransaction& transaction, const IDBIndexInfo& info, ErrorCallback callback) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::createIndex"); + + uint64_t callbackID = storeCallbackOrFireError(callback); + if (!callbackID) + return; + postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performCreateIndex, callbackID, transaction.info().identifier(), info)); +} + +void UniqueIDBDatabase::performCreateIndex(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, const IDBIndexInfo& info) +{ + ASSERT(!isMainThread()); + LOG(IndexedDB, "(db) UniqueIDBDatabase::performCreateIndex"); + + ASSERT(m_backingStore); + IDBError error = m_backingStore->createIndex(transactionIdentifier, info); + + postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformCreateIndex, callbackIdentifier, error, info)); +} + +void UniqueIDBDatabase::didPerformCreateIndex(uint64_t callbackIdentifier, const IDBError& error, const IDBIndexInfo& info) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformCreateIndex"); + + if (error.isNull()) { + ASSERT(m_databaseInfo); + auto* objectStoreInfo = m_databaseInfo->infoForExistingObjectStore(info.objectStoreIdentifier()); + ASSERT(objectStoreInfo); + objectStoreInfo->addExistingIndex(info); + } + + performErrorCallback(callbackIdentifier, error); +} + +void UniqueIDBDatabase::deleteIndex(UniqueIDBDatabaseTransaction& transaction, uint64_t objectStoreIdentifier, const String& indexName, ErrorCallback callback) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::deleteIndex"); + + uint64_t callbackID = storeCallbackOrFireError(callback); + if (!callbackID) + return; + + auto* objectStoreInfo = m_databaseInfo->infoForExistingObjectStore(objectStoreIdentifier); + if (!objectStoreInfo) { + performErrorCallback(callbackID, { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to delete index from non-existant object store") }); + return; + } + + auto* indexInfo = objectStoreInfo->infoForExistingIndex(indexName); + if (!indexInfo) { + performErrorCallback(callbackID, { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to delete non-existant index") }); + return; + } + + postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performDeleteIndex, callbackID, transaction.info().identifier(), objectStoreIdentifier, indexInfo->identifier())); +} + +void UniqueIDBDatabase::performDeleteIndex(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const uint64_t indexIdentifier) +{ + ASSERT(!isMainThread()); + LOG(IndexedDB, "(db) UniqueIDBDatabase::performDeleteIndex"); + + ASSERT(m_backingStore); + m_backingStore->deleteIndex(transactionIdentifier, objectStoreIdentifier, indexIdentifier); + + IDBError error; + postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformDeleteIndex, callbackIdentifier, error, objectStoreIdentifier, indexIdentifier)); +} + +void UniqueIDBDatabase::didPerformDeleteIndex(uint64_t callbackIdentifier, const IDBError& error, uint64_t objectStoreIdentifier, uint64_t indexIdentifier) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformDeleteIndex"); + + if (error.isNull()) { + auto* objectStoreInfo = m_databaseInfo->infoForExistingObjectStore(objectStoreIdentifier); + if (objectStoreInfo) + objectStoreInfo->deleteIndex(indexIdentifier); + } + + performErrorCallback(callbackIdentifier, error); +} + +void UniqueIDBDatabase::renameIndex(UniqueIDBDatabaseTransaction& transaction, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, const String& newName, ErrorCallback callback) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::renameIndex"); + + uint64_t callbackID = storeCallbackOrFireError(callback); + if (!callbackID) + return; + + auto* objectStoreInfo = m_databaseInfo->infoForExistingObjectStore(objectStoreIdentifier); + if (!objectStoreInfo) { + performErrorCallback(callbackID, { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to rename index in non-existant object store") }); + return; + } + + auto* indexInfo = objectStoreInfo->infoForExistingIndex(indexIdentifier); + if (!indexInfo) { + performErrorCallback(callbackID, { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to rename non-existant index") }); + return; + } + + postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performRenameIndex, callbackID, transaction.info().identifier(), objectStoreIdentifier, indexIdentifier, newName)); +} + +void UniqueIDBDatabase::performRenameIndex(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, const String& newName) +{ + ASSERT(!isMainThread()); + LOG(IndexedDB, "(db) UniqueIDBDatabase::performRenameIndex"); + + ASSERT(m_backingStore); + m_backingStore->renameIndex(transactionIdentifier, objectStoreIdentifier, indexIdentifier, newName); + + IDBError error; + postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformRenameIndex, callbackIdentifier, error, objectStoreIdentifier, indexIdentifier, newName)); +} + +void UniqueIDBDatabase::didPerformRenameIndex(uint64_t callbackIdentifier, const IDBError& error, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, const String& newName) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformRenameIndex"); + + if (error.isNull()) { + auto* objectStoreInfo = m_databaseInfo->infoForExistingObjectStore(objectStoreIdentifier); + ASSERT(objectStoreInfo); + if (objectStoreInfo) { + auto* indexInfo = objectStoreInfo->infoForExistingIndex(indexIdentifier); + ASSERT(indexInfo); + indexInfo->rename(newName); + } + } + + performErrorCallback(callbackIdentifier, error); +} + +void UniqueIDBDatabase::putOrAdd(const IDBRequestData& requestData, const IDBKeyData& keyData, const IDBValue& value, IndexedDB::ObjectStoreOverwriteMode overwriteMode, KeyDataCallback callback) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::putOrAdd"); + + uint64_t callbackID = storeCallbackOrFireError(callback); + if (!callbackID) + return; + postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performPutOrAdd, callbackID, requestData.transactionIdentifier(), requestData.objectStoreIdentifier(), keyData, value, overwriteMode)); +} + +VM& UniqueIDBDatabase::databaseThreadVM() +{ + ASSERT(!isMainThread()); + static VM* vm = &VM::create().leakRef(); + return *vm; +} + +ExecState& UniqueIDBDatabase::databaseThreadExecState() +{ + ASSERT(!isMainThread()); + + static NeverDestroyed<Strong<JSGlobalObject>> globalObject(databaseThreadVM(), JSGlobalObject::create(databaseThreadVM(), JSGlobalObject::createStructure(databaseThreadVM(), jsNull()))); + + RELEASE_ASSERT(globalObject.get()->globalExec()); + return *globalObject.get()->globalExec(); +} + +void UniqueIDBDatabase::performPutOrAdd(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const IDBKeyData& keyData, const IDBValue& originalRecordValue, IndexedDB::ObjectStoreOverwriteMode overwriteMode) +{ + ASSERT(!isMainThread()); + LOG(IndexedDB, "(db) UniqueIDBDatabase::performPutOrAdd"); + + ASSERT(m_backingStore); + ASSERT(objectStoreIdentifier); + + IDBKeyData usedKey; + IDBError error; + + auto* objectStoreInfo = m_backingStore->infoForObjectStore(objectStoreIdentifier); + if (!objectStoreInfo) { + error = IDBError(IDBDatabaseException::InvalidStateError, ASCIILiteral("Object store cannot be found in the backing store")); + postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformPutOrAdd, callbackIdentifier, error, usedKey)); + return; + } + + bool usedKeyIsGenerated = false; + ScopeGuard generatedKeyResetter; + if (objectStoreInfo->autoIncrement() && !keyData.isValid()) { + uint64_t keyNumber; + error = m_backingStore->generateKeyNumber(transactionIdentifier, objectStoreIdentifier, keyNumber); + if (!error.isNull()) { + postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformPutOrAdd, callbackIdentifier, error, usedKey)); + return; + } + + usedKey.setNumberValue(keyNumber); + usedKeyIsGenerated = true; + generatedKeyResetter.enable([this, transactionIdentifier, objectStoreIdentifier, keyNumber]() { + m_backingStore->revertGeneratedKeyNumber(transactionIdentifier, objectStoreIdentifier, keyNumber); + }); + } else + usedKey = keyData; + + if (overwriteMode == IndexedDB::ObjectStoreOverwriteMode::NoOverwrite) { + bool keyExists; + error = m_backingStore->keyExistsInObjectStore(transactionIdentifier, objectStoreIdentifier, usedKey, keyExists); + if (error.isNull() && keyExists) + error = IDBError(IDBDatabaseException::ConstraintError, ASCIILiteral("Key already exists in the object store")); + + if (!error.isNull()) { + postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformPutOrAdd, callbackIdentifier, error, usedKey)); + return; + } + } + + // 3.4.1.2 Object Store Storage Operation + // If ObjectStore has a key path and the key is autogenerated, then inject the key into the value + // using steps to assign a key to a value using a key path. + ThreadSafeDataBuffer injectedRecordValue; + if (usedKeyIsGenerated && objectStoreInfo->keyPath()) { + VM& vm = databaseThreadVM(); + JSLockHolder locker(vm); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto value = deserializeIDBValueToJSValue(databaseThreadExecState(), originalRecordValue.data()); + if (value.isUndefined()) { + postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformPutOrAdd, callbackIdentifier, IDBError(IDBDatabaseException::ConstraintError, ASCIILiteral("Unable to deserialize record value for record key injection")), usedKey)); + return; + } + + if (!injectIDBKeyIntoScriptValue(databaseThreadExecState(), usedKey, value, objectStoreInfo->keyPath().value())) { + postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformPutOrAdd, callbackIdentifier, IDBError(IDBDatabaseException::ConstraintError, ASCIILiteral("Unable to inject record key into record value")), usedKey)); + return; + } + + auto serializedValue = SerializedScriptValue::create(databaseThreadExecState(), value); + if (UNLIKELY(scope.exception())) { + postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformPutOrAdd, callbackIdentifier, IDBError(IDBDatabaseException::ConstraintError, ASCIILiteral("Unable to serialize record value after injecting record key")), usedKey)); + return; + } + + injectedRecordValue = ThreadSafeDataBuffer::copyVector(serializedValue->data()); + } + + // 3.4.1 Object Store Storage Operation + // ...If a record already exists in store ... + // then remove the record from store using the steps for deleting records from an object store... + // This is important because formally deleting it from from the object store also removes it from the appropriate indexes. + error = m_backingStore->deleteRange(transactionIdentifier, objectStoreIdentifier, usedKey); + if (!error.isNull()) { + postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformPutOrAdd, callbackIdentifier, error, usedKey)); + return; + } + + if (injectedRecordValue.data()) + error = m_backingStore->addRecord(transactionIdentifier, *objectStoreInfo, usedKey, { injectedRecordValue, originalRecordValue.blobURLs(), originalRecordValue.blobFilePaths() }); + else + error = m_backingStore->addRecord(transactionIdentifier, *objectStoreInfo, usedKey, originalRecordValue); + + if (!error.isNull()) { + postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformPutOrAdd, callbackIdentifier, error, usedKey)); + return; + } + + if (overwriteMode != IndexedDB::ObjectStoreOverwriteMode::OverwriteForCursor && objectStoreInfo->autoIncrement() && keyData.type() == IndexedDB::KeyType::Number) + error = m_backingStore->maybeUpdateKeyGeneratorNumber(transactionIdentifier, objectStoreIdentifier, keyData.number()); + + generatedKeyResetter.disable(); + postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformPutOrAdd, callbackIdentifier, error, usedKey)); +} + +void UniqueIDBDatabase::didPerformPutOrAdd(uint64_t callbackIdentifier, const IDBError& error, const IDBKeyData& resultKey) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformPutOrAdd"); + + performKeyDataCallback(callbackIdentifier, error, resultKey); +} + +void UniqueIDBDatabase::getRecord(const IDBRequestData& requestData, const IDBGetRecordData& getRecordData, GetResultCallback callback) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::getRecord"); + + uint64_t callbackID = storeCallbackOrFireError(callback); + if (!callbackID) + return; + + if (uint64_t indexIdentifier = requestData.indexIdentifier()) + postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performGetIndexRecord, callbackID, requestData.transactionIdentifier(), requestData.objectStoreIdentifier(), indexIdentifier, requestData.indexRecordType(), getRecordData.keyRangeData)); + else + postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performGetRecord, callbackID, requestData.transactionIdentifier(), requestData.objectStoreIdentifier(), getRecordData.keyRangeData, getRecordData.type)); +} + +void UniqueIDBDatabase::getAllRecords(const IDBRequestData& requestData, const IDBGetAllRecordsData& getAllRecordsData, GetAllResultsCallback callback) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::getAllRecords"); + + uint64_t callbackID = storeCallbackOrFireError(callback); + if (!callbackID) + return; + + postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performGetAllRecords, callbackID, requestData.transactionIdentifier(), getAllRecordsData)); +} + +void UniqueIDBDatabase::performGetRecord(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const IDBKeyRangeData& keyRangeData, IDBGetRecordDataType type) +{ + ASSERT(!isMainThread()); + LOG(IndexedDB, "(db) UniqueIDBDatabase::performGetRecord"); + + ASSERT(m_backingStore); + + IDBGetResult result; + IDBError error = m_backingStore->getRecord(transactionIdentifier, objectStoreIdentifier, keyRangeData, type, result); + + postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformGetRecord, callbackIdentifier, error, result)); +} + +void UniqueIDBDatabase::performGetIndexRecord(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, IndexedDB::IndexRecordType recordType, const IDBKeyRangeData& range) +{ + ASSERT(!isMainThread()); + LOG(IndexedDB, "(db) UniqueIDBDatabase::performGetIndexRecord"); + + ASSERT(m_backingStore); + + IDBGetResult result; + IDBError error = m_backingStore->getIndexRecord(transactionIdentifier, objectStoreIdentifier, indexIdentifier, recordType, range, result); + + postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformGetRecord, callbackIdentifier, error, result)); +} + +void UniqueIDBDatabase::didPerformGetRecord(uint64_t callbackIdentifier, const IDBError& error, const IDBGetResult& result) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformGetRecord"); + + performGetResultCallback(callbackIdentifier, error, result); +} + +void UniqueIDBDatabase::performGetAllRecords(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, const IDBGetAllRecordsData& getAllRecordsData) +{ + ASSERT(!isMainThread()); + LOG(IndexedDB, "(db) UniqueIDBDatabase::performGetAllRecords"); + + ASSERT(m_backingStore); + + IDBGetAllResult result; + IDBError error = m_backingStore->getAllRecords(transactionIdentifier, getAllRecordsData, result); + + postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformGetAllRecords, callbackIdentifier, error, WTFMove(result))); +} + +void UniqueIDBDatabase::didPerformGetAllRecords(uint64_t callbackIdentifier, const IDBError& error, const IDBGetAllResult& result) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformGetAllRecords"); + + performGetAllResultsCallback(callbackIdentifier, error, result); +} + +void UniqueIDBDatabase::getCount(const IDBRequestData& requestData, const IDBKeyRangeData& range, CountCallback callback) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::getCount"); + + uint64_t callbackID = storeCallbackOrFireError(callback); + if (!callbackID) + return; + postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performGetCount, callbackID, requestData.transactionIdentifier(), requestData.objectStoreIdentifier(), requestData.indexIdentifier(), range)); +} + +void UniqueIDBDatabase::performGetCount(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, const IDBKeyRangeData& keyRangeData) +{ + ASSERT(!isMainThread()); + LOG(IndexedDB, "(db) UniqueIDBDatabase::performGetCount"); + + ASSERT(m_backingStore); + ASSERT(objectStoreIdentifier); + + uint64_t count; + IDBError error = m_backingStore->getCount(transactionIdentifier, objectStoreIdentifier, indexIdentifier, keyRangeData, count); + + postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformGetCount, callbackIdentifier, error, count)); +} + +void UniqueIDBDatabase::didPerformGetCount(uint64_t callbackIdentifier, const IDBError& error, uint64_t count) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformGetCount"); + + performCountCallback(callbackIdentifier, error, count); +} + +void UniqueIDBDatabase::deleteRecord(const IDBRequestData& requestData, const IDBKeyRangeData& keyRangeData, ErrorCallback callback) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::deleteRecord"); + + uint64_t callbackID = storeCallbackOrFireError(callback); + if (!callbackID) + return; + postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performDeleteRecord, callbackID, requestData.transactionIdentifier(), requestData.objectStoreIdentifier(), keyRangeData)); +} + +void UniqueIDBDatabase::performDeleteRecord(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const IDBKeyRangeData& range) +{ + ASSERT(!isMainThread()); + LOG(IndexedDB, "(db) UniqueIDBDatabase::performDeleteRecord"); + + IDBError error = m_backingStore->deleteRange(transactionIdentifier, objectStoreIdentifier, range); + + postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformDeleteRecord, callbackIdentifier, error)); +} + +void UniqueIDBDatabase::didPerformDeleteRecord(uint64_t callbackIdentifier, const IDBError& error) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformDeleteRecord"); + + performErrorCallback(callbackIdentifier, error); +} + +void UniqueIDBDatabase::openCursor(const IDBRequestData& requestData, const IDBCursorInfo& info, GetResultCallback callback) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::openCursor"); + + uint64_t callbackID = storeCallbackOrFireError(callback); + if (!callbackID) + return; + postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performOpenCursor, callbackID, requestData.transactionIdentifier(), info)); +} + +void UniqueIDBDatabase::performOpenCursor(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, const IDBCursorInfo& info) +{ + ASSERT(!isMainThread()); + LOG(IndexedDB, "(db) UniqueIDBDatabase::performOpenCursor"); + + IDBGetResult result; + IDBError error = m_backingStore->openCursor(transactionIdentifier, info, result); + + postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformOpenCursor, callbackIdentifier, error, result)); +} + +void UniqueIDBDatabase::didPerformOpenCursor(uint64_t callbackIdentifier, const IDBError& error, const IDBGetResult& result) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformOpenCursor"); + + performGetResultCallback(callbackIdentifier, error, result); +} + +void UniqueIDBDatabase::iterateCursor(const IDBRequestData& requestData, const IDBIterateCursorData& data, GetResultCallback callback) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::iterateCursor"); + + uint64_t callbackID = storeCallbackOrFireError(callback); + if (!callbackID) + return; + postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performIterateCursor, callbackID, requestData.transactionIdentifier(), requestData.cursorIdentifier(), data)); +} + +void UniqueIDBDatabase::performIterateCursor(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, const IDBResourceIdentifier& cursorIdentifier, const IDBIterateCursorData& data) +{ + ASSERT(!isMainThread()); + LOG(IndexedDB, "(db) UniqueIDBDatabase::performIterateCursor"); + + IDBGetResult result; + IDBError error = m_backingStore->iterateCursor(transactionIdentifier, cursorIdentifier, data, result); + + if (error.isNull()) { + auto addResult = m_prefetchProtectors.add(cursorIdentifier, nullptr); + if (addResult.isNewEntry) { + addResult.iterator->value = this; + postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performPrefetchCursor, transactionIdentifier, cursorIdentifier)); + } + } + + postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformIterateCursor, callbackIdentifier, error, result)); +} + +void UniqueIDBDatabase::performPrefetchCursor(const IDBResourceIdentifier& transactionIdentifier, const IDBResourceIdentifier& cursorIdentifier) +{ + ASSERT(!isMainThread()); + ASSERT(m_prefetchProtectors.contains(cursorIdentifier)); + LOG(IndexedDB, "(db) UniqueIDBDatabase::performPrefetchCursor"); + + if (m_backingStore->prefetchCursor(transactionIdentifier, cursorIdentifier)) + postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performPrefetchCursor, transactionIdentifier, cursorIdentifier)); + else + postDatabaseTaskReply(WTF::Function<void ()>([prefetchProtector = m_prefetchProtectors.take(cursorIdentifier)]() { })); +} + +void UniqueIDBDatabase::didPerformIterateCursor(uint64_t callbackIdentifier, const IDBError& error, const IDBGetResult& result) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformIterateCursor"); + + performGetResultCallback(callbackIdentifier, error, result); +} + +bool UniqueIDBDatabase::prepareToFinishTransaction(UniqueIDBDatabaseTransaction& transaction) +{ + auto takenTransaction = m_inProgressTransactions.take(transaction.info().identifier()); + if (!takenTransaction) + return false; + + ASSERT(!m_finishingTransactions.contains(transaction.info().identifier())); + m_finishingTransactions.set(transaction.info().identifier(), WTFMove(takenTransaction)); + + return true; +} + +void UniqueIDBDatabase::commitTransaction(UniqueIDBDatabaseTransaction& transaction, ErrorCallback callback) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::commitTransaction - %s", transaction.info().identifier().loggingString().utf8().data()); + + ASSERT(&transaction.databaseConnection().database() == this); + + uint64_t callbackID = storeCallbackOrFireError(callback); + if (!callbackID) + return; + + if (!prepareToFinishTransaction(transaction)) { + if (!m_openDatabaseConnections.contains(&transaction.databaseConnection())) { + // This database connection is closing or has already closed, so there is no point in messaging back to it about the commit failing. + forgetErrorCallback(callbackID); + return; + } + + performErrorCallback(callbackID, { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to commit transaction that is already finishing") }); + return; + } + + postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performCommitTransaction, callbackID, transaction.info().identifier())); +} + +void UniqueIDBDatabase::performCommitTransaction(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier) +{ + ASSERT(!isMainThread()); + LOG(IndexedDB, "(db) UniqueIDBDatabase::performCommitTransaction - %s", transactionIdentifier.loggingString().utf8().data()); + + IDBError error = m_backingStore->commitTransaction(transactionIdentifier); + postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformCommitTransaction, callbackIdentifier, error, transactionIdentifier)); +} + +void UniqueIDBDatabase::didPerformCommitTransaction(uint64_t callbackIdentifier, const IDBError& error, const IDBResourceIdentifier& transactionIdentifier) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformCommitTransaction - %s", transactionIdentifier.loggingString().utf8().data()); + + performErrorCallback(callbackIdentifier, error); + + transactionCompleted(m_finishingTransactions.take(transactionIdentifier)); +} + +void UniqueIDBDatabase::abortTransaction(UniqueIDBDatabaseTransaction& transaction, ErrorCallback callback) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::abortTransaction - %s", transaction.info().identifier().loggingString().utf8().data()); + + ASSERT(&transaction.databaseConnection().database() == this); + + uint64_t callbackID = storeCallbackOrFireError(callback); + if (!callbackID) + return; + + if (!prepareToFinishTransaction(transaction)) { + if (!m_openDatabaseConnections.contains(&transaction.databaseConnection())) { + // This database connection is closing or has already closed, so there is no point in messaging back to it about the abort failing. + forgetErrorCallback(callbackID); + return; + } + + performErrorCallback(callbackID, { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to abort transaction that is already finishing") }); + return; + } + + postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performAbortTransaction, callbackID, transaction.info().identifier())); +} + +void UniqueIDBDatabase::didFinishHandlingVersionChange(UniqueIDBDatabaseConnection& connection, const IDBResourceIdentifier& transactionIdentifier) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::didFinishHandlingVersionChange"); + + ASSERT_UNUSED(transactionIdentifier, !m_versionChangeTransaction || m_versionChangeTransaction->info().identifier() == transactionIdentifier); + ASSERT_UNUSED(connection, !m_versionChangeDatabaseConnection || m_versionChangeDatabaseConnection.get() == &connection); + + m_versionChangeTransaction = nullptr; + m_versionChangeDatabaseConnection = nullptr; + + if (m_hardClosedForUserDelete) { + maybeFinishHardClose(); + return; + } + + invokeOperationAndTransactionTimer(); +} + +void UniqueIDBDatabase::performAbortTransaction(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier) +{ + ASSERT(!isMainThread()); + LOG(IndexedDB, "(db) UniqueIDBDatabase::performAbortTransaction - %s", transactionIdentifier.loggingString().utf8().data()); + + IDBError error = m_backingStore->abortTransaction(transactionIdentifier); + postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformAbortTransaction, callbackIdentifier, error, transactionIdentifier)); +} + +void UniqueIDBDatabase::didPerformAbortTransaction(uint64_t callbackIdentifier, const IDBError& error, const IDBResourceIdentifier& transactionIdentifier) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformAbortTransaction - %s", transactionIdentifier.loggingString().utf8().data()); + + auto transaction = m_finishingTransactions.take(transactionIdentifier); + ASSERT(transaction); + + if (m_versionChangeTransaction && m_versionChangeTransaction->info().identifier() == transactionIdentifier) { + ASSERT(m_versionChangeTransaction == transaction); + ASSERT(!m_versionChangeDatabaseConnection || &m_versionChangeTransaction->databaseConnection() == m_versionChangeDatabaseConnection); + ASSERT(m_versionChangeTransaction->originalDatabaseInfo()); + m_databaseInfo = std::make_unique<IDBDatabaseInfo>(*m_versionChangeTransaction->originalDatabaseInfo()); + } + + performErrorCallback(callbackIdentifier, error); + + transactionCompleted(WTFMove(transaction)); +} + +void UniqueIDBDatabase::transactionDestroyed(UniqueIDBDatabaseTransaction& transaction) +{ + if (m_versionChangeTransaction == &transaction) + m_versionChangeTransaction = nullptr; +} + +void UniqueIDBDatabase::connectionClosedFromClient(UniqueIDBDatabaseConnection& connection) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "(main) UniqueIDBDatabase::connectionClosedFromClient - %s (%" PRIu64 ")", connection.openRequestIdentifier().loggingString().utf8().data(), connection.identifier()); + + Ref<UniqueIDBDatabaseConnection> protectedConnection(connection); + m_openDatabaseConnections.remove(&connection); + + if (m_versionChangeDatabaseConnection == &connection) { + if (m_versionChangeTransaction) { + m_clientClosePendingDatabaseConnections.add(WTFMove(m_versionChangeDatabaseConnection)); + + auto transactionIdentifier = m_versionChangeTransaction->info().identifier(); + if (m_inProgressTransactions.contains(transactionIdentifier)) { + ASSERT(!m_finishingTransactions.contains(transactionIdentifier)); + connection.abortTransactionWithoutCallback(*m_versionChangeTransaction); + } + + return; + } + + m_versionChangeDatabaseConnection = nullptr; + } + + Deque<RefPtr<UniqueIDBDatabaseTransaction>> pendingTransactions; + while (!m_pendingTransactions.isEmpty()) { + auto transaction = m_pendingTransactions.takeFirst(); + if (&transaction->databaseConnection() != &connection) + pendingTransactions.append(WTFMove(transaction)); + } + + if (!pendingTransactions.isEmpty()) + m_pendingTransactions.swap(pendingTransactions); + + Deque<RefPtr<UniqueIDBDatabaseTransaction>> transactionsToAbort; + for (auto& transaction : m_inProgressTransactions.values()) { + if (&transaction->databaseConnection() == &connection) + transactionsToAbort.append(transaction); + } + + for (auto& transaction : transactionsToAbort) + transaction->abortWithoutCallback(); + + if (m_currentOpenDBRequest) + notifyCurrentRequestConnectionClosedOrFiredVersionChangeEvent(connection.identifier()); + + if (connection.hasNonFinishedTransactions()) { + m_clientClosePendingDatabaseConnections.add(WTFMove(protectedConnection)); + return; + } + + if (m_hardClosedForUserDelete) { + maybeFinishHardClose(); + return; + } + + // Now that a database connection has closed, previously blocked operations might be runnable. + invokeOperationAndTransactionTimer(); +} + +void UniqueIDBDatabase::connectionClosedFromServer(UniqueIDBDatabaseConnection& connection) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "UniqueIDBDatabase::connectionClosedFromServer - %s (%" PRIu64 ")", connection.openRequestIdentifier().loggingString().utf8().data(), connection.identifier()); + + if (m_clientClosePendingDatabaseConnections.contains(&connection)) { + ASSERT(!m_openDatabaseConnections.contains(&connection)); + ASSERT(!m_serverClosePendingDatabaseConnections.contains(&connection)); + return; + } + + Ref<UniqueIDBDatabaseConnection> protectedConnection(connection); + m_openDatabaseConnections.remove(&connection); + + connection.connectionToClient().didCloseFromServer(connection, IDBError::userDeleteError()); + + m_serverClosePendingDatabaseConnections.add(WTFMove(protectedConnection)); +} + +void UniqueIDBDatabase::confirmDidCloseFromServer(UniqueIDBDatabaseConnection& connection) +{ + ASSERT(isMainThread()); + LOG(IndexedDB, "UniqueIDBDatabase::confirmDidCloseFromServer - %s (%" PRIu64 ")", connection.openRequestIdentifier().loggingString().utf8().data(), connection.identifier()); + + ASSERT(m_serverClosePendingDatabaseConnections.contains(&connection)); + m_serverClosePendingDatabaseConnections.remove(&connection); +} + +void UniqueIDBDatabase::enqueueTransaction(Ref<UniqueIDBDatabaseTransaction>&& transaction) +{ + LOG(IndexedDB, "UniqueIDBDatabase::enqueueTransaction - %s", transaction->info().loggingString().utf8().data()); + ASSERT(!m_hardClosedForUserDelete); + + ASSERT(transaction->info().mode() != IDBTransactionMode::Versionchange); + + m_pendingTransactions.append(WTFMove(transaction)); + + invokeOperationAndTransactionTimer(); +} + +bool UniqueIDBDatabase::isCurrentlyInUse() const +{ + return !m_openDatabaseConnections.isEmpty() || !m_clientClosePendingDatabaseConnections.isEmpty() || !m_pendingOpenDBRequests.isEmpty() || m_currentOpenDBRequest || m_versionChangeDatabaseConnection || m_versionChangeTransaction || m_isOpeningBackingStore || m_deleteBackingStoreInProgress; +} + +bool UniqueIDBDatabase::hasUnfinishedTransactions() const +{ + return !m_inProgressTransactions.isEmpty() || !m_finishingTransactions.isEmpty(); +} + +void UniqueIDBDatabase::invokeOperationAndTransactionTimer() +{ + LOG(IndexedDB, "UniqueIDBDatabase::invokeOperationAndTransactionTimer()"); + ASSERT(!m_hardClosedForUserDelete); + + if (!m_operationAndTransactionTimer.isActive()) + m_operationAndTransactionTimer.startOneShot(0); +} + +void UniqueIDBDatabase::operationAndTransactionTimerFired() +{ + LOG(IndexedDB, "(main) UniqueIDBDatabase::operationAndTransactionTimerFired"); + ASSERT(!m_hardClosedForUserDelete); + + RefPtr<UniqueIDBDatabase> protectedThis(this); + + // This UniqueIDBDatabase might be no longer in use by any web page. + // Assuming it is not ephemeral, the server should now close it to free up resources. + if (!m_backingStoreIsEphemeral && !isCurrentlyInUse()) { + ASSERT(m_pendingTransactions.isEmpty()); + ASSERT(!hasUnfinishedTransactions()); + m_server.closeUniqueIDBDatabase(*this); + return; + } + + // The current operation might require multiple attempts to handle, so try to + // make further progress on it now. + if (m_currentOpenDBRequest) + handleCurrentOperation(); + + if (!m_currentOpenDBRequest) + handleDatabaseOperations(); + + bool hadDeferredTransactions = false; + auto transaction = takeNextRunnableTransaction(hadDeferredTransactions); + + if (transaction) { + m_inProgressTransactions.set(transaction->info().identifier(), transaction); + for (auto objectStore : transaction->objectStoreIdentifiers()) { + m_objectStoreTransactionCounts.add(objectStore); + if (!transaction->isReadOnly()) { + m_objectStoreWriteTransactions.add(objectStore); + ASSERT(m_objectStoreTransactionCounts.count(objectStore) == 1); + } + } + + activateTransactionInBackingStore(*transaction); + + // If no transactions were deferred, it's possible we can start another transaction right now. + if (!hadDeferredTransactions) + invokeOperationAndTransactionTimer(); + } +} + +void UniqueIDBDatabase::activateTransactionInBackingStore(UniqueIDBDatabaseTransaction& transaction) +{ + LOG(IndexedDB, "(main) UniqueIDBDatabase::activateTransactionInBackingStore"); + + RefPtr<UniqueIDBDatabase> protectedThis(this); + RefPtr<UniqueIDBDatabaseTransaction> refTransaction(&transaction); + + auto callback = [this, protectedThis, refTransaction](const IDBError& error) { + refTransaction->didActivateInBackingStore(error); + }; + + uint64_t callbackID = storeCallbackOrFireError(callback); + if (!callbackID) + return; + postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performActivateTransactionInBackingStore, callbackID, transaction.info())); +} + +void UniqueIDBDatabase::performActivateTransactionInBackingStore(uint64_t callbackIdentifier, const IDBTransactionInfo& info) +{ + LOG(IndexedDB, "(db) UniqueIDBDatabase::performActivateTransactionInBackingStore"); + + IDBError error = m_backingStore->beginTransaction(info); + postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformActivateTransactionInBackingStore, callbackIdentifier, error)); +} + +void UniqueIDBDatabase::didPerformActivateTransactionInBackingStore(uint64_t callbackIdentifier, const IDBError& error) +{ + LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformActivateTransactionInBackingStore"); + + invokeOperationAndTransactionTimer(); + + performErrorCallback(callbackIdentifier, error); +} + +template<typename T> bool scopesOverlap(const T& aScopes, const Vector<uint64_t>& bScopes) +{ + for (auto scope : bScopes) { + if (aScopes.contains(scope)) + return true; + } + + return false; +} + +RefPtr<UniqueIDBDatabaseTransaction> UniqueIDBDatabase::takeNextRunnableTransaction(bool& hadDeferredTransactions) +{ + hadDeferredTransactions = false; + + if (m_pendingTransactions.isEmpty()) + return nullptr; + + if (!m_backingStoreSupportsSimultaneousTransactions && hasUnfinishedTransactions()) { + LOG(IndexedDB, "UniqueIDBDatabase::takeNextRunnableTransaction - Backing store only supports 1 transaction, and we already have 1"); + return nullptr; + } + + Deque<RefPtr<UniqueIDBDatabaseTransaction>> deferredTransactions; + RefPtr<UniqueIDBDatabaseTransaction> currentTransaction; + + HashSet<uint64_t> deferredReadWriteScopes; + + while (!m_pendingTransactions.isEmpty()) { + currentTransaction = m_pendingTransactions.takeFirst(); + + switch (currentTransaction->info().mode()) { + case IDBTransactionMode::Readonly: { + bool hasOverlappingScopes = scopesOverlap(deferredReadWriteScopes, currentTransaction->objectStoreIdentifiers()); + hasOverlappingScopes |= scopesOverlap(m_objectStoreWriteTransactions, currentTransaction->objectStoreIdentifiers()); + + if (hasOverlappingScopes) + deferredTransactions.append(WTFMove(currentTransaction)); + + break; + } + case IDBTransactionMode::Readwrite: { + bool hasOverlappingScopes = scopesOverlap(m_objectStoreTransactionCounts, currentTransaction->objectStoreIdentifiers()); + hasOverlappingScopes |= scopesOverlap(deferredReadWriteScopes, currentTransaction->objectStoreIdentifiers()); + + if (hasOverlappingScopes) { + for (auto objectStore : currentTransaction->objectStoreIdentifiers()) + deferredReadWriteScopes.add(objectStore); + deferredTransactions.append(WTFMove(currentTransaction)); + } + + break; + } + case IDBTransactionMode::Versionchange: + // Version change transactions should never be scheduled in the traditional manner. + RELEASE_ASSERT_NOT_REACHED(); + } + + // If we didn't defer the currentTransaction above, it can be run now. + if (currentTransaction) + break; + } + + hadDeferredTransactions = !deferredTransactions.isEmpty(); + if (!hadDeferredTransactions) + return currentTransaction; + + // Prepend the deferred transactions back on the beginning of the deque for future scheduling passes. + while (!deferredTransactions.isEmpty()) + m_pendingTransactions.prepend(deferredTransactions.takeLast()); + + return currentTransaction; +} + +void UniqueIDBDatabase::transactionCompleted(RefPtr<UniqueIDBDatabaseTransaction>&& transaction) +{ + ASSERT(transaction); + ASSERT(!m_inProgressTransactions.contains(transaction->info().identifier())); + ASSERT(!m_finishingTransactions.contains(transaction->info().identifier())); + + for (auto objectStore : transaction->objectStoreIdentifiers()) { + if (!transaction->isReadOnly()) { + m_objectStoreWriteTransactions.remove(objectStore); + ASSERT(m_objectStoreTransactionCounts.count(objectStore) == 1); + } + m_objectStoreTransactionCounts.remove(objectStore); + } + + if (!transaction->databaseConnection().hasNonFinishedTransactions()) + m_clientClosePendingDatabaseConnections.remove(&transaction->databaseConnection()); + + if (m_versionChangeTransaction == transaction) + m_versionChangeTransaction = nullptr; + + // It's possible that this database had its backing store deleted but there were a few outstanding asynchronous operations. + // If this transaction completing was the last of those operations, we can finally delete this UniqueIDBDatabase. + if (m_clientClosePendingDatabaseConnections.isEmpty() && m_pendingOpenDBRequests.isEmpty() && !m_databaseInfo) { + m_server.closeUniqueIDBDatabase(*this); + return; + } + + // Previously blocked operations might be runnable. + if (!m_hardClosedForUserDelete) + invokeOperationAndTransactionTimer(); + else + maybeFinishHardClose(); +} + +void UniqueIDBDatabase::postDatabaseTask(CrossThreadTask&& task) +{ + m_databaseQueue.append([protectedThis = makeRef(*this), task = WTFMove(task)]() mutable { + task.performTask(); + }); + ++m_queuedTaskCount; + + m_server.postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::executeNextDatabaseTask)); +} + +void UniqueIDBDatabase::postDatabaseTaskReply(CrossThreadTask&& task) +{ + ASSERT(!isMainThread()); + + m_databaseReplyQueue.append([protectedThis = makeRef(*this), task = WTFMove(task)]() mutable { + task.performTask(); + }); + ++m_queuedTaskCount; + + m_server.postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::executeNextDatabaseTaskReply)); +} + +void UniqueIDBDatabase::executeNextDatabaseTask() +{ + ASSERT(!isMainThread()); + ASSERT(m_queuedTaskCount); + + auto task = m_databaseQueue.tryGetMessage(); + ASSERT(task); + + (*task)(); + --m_queuedTaskCount; + + // Release the task on the main thread in case it holds the last reference to this, + // as UniqueIDBDatabase objects must be deleted on the main thread. + callOnMainThread([task = WTFMove(task)] { + }); +} + +void UniqueIDBDatabase::executeNextDatabaseTaskReply() +{ + ASSERT(isMainThread()); + ASSERT(m_queuedTaskCount); + + auto task = m_databaseReplyQueue.tryGetMessage(); + ASSERT(task); + + (*task)(); + --m_queuedTaskCount; + + // If this database was force closed (e.g. for a user delete) and there are no more + // cleanup tasks left, delete this. + maybeFinishHardClose(); +} + +void UniqueIDBDatabase::maybeFinishHardClose() +{ + if (m_hardCloseProtector && isDoneWithHardClose()) { + callOnMainThread([this] { + ASSERT(isDoneWithHardClose()); + m_hardCloseProtector = nullptr; + }); + } +} + +bool UniqueIDBDatabase::isDoneWithHardClose() +{ + return !m_queuedTaskCount && m_clientClosePendingDatabaseConnections.isEmpty() && m_serverClosePendingDatabaseConnections.isEmpty(); +} + +static void errorOpenDBRequestForUserDelete(ServerOpenDBRequest& request) +{ + auto result = IDBResultData::error(request.requestData().requestIdentifier(), IDBError::userDeleteError()); + if (request.isOpenRequest()) + request.connection().didOpenDatabase(result); + else + request.connection().didDeleteDatabase(result); +} + +void UniqueIDBDatabase::immediateCloseForUserDelete() +{ + LOG(IndexedDB, "UniqueIDBDatabase::immediateCloseForUserDelete - Cancelling (%i, %i, %i, %i) callbacks", m_errorCallbacks.size(), m_keyDataCallbacks.size(), m_getResultCallbacks.size(), m_countCallbacks.size()); + + // Error out all transactions + Vector<IDBResourceIdentifier> inProgressIdentifiers; + copyKeysToVector(m_inProgressTransactions, inProgressIdentifiers); + for (auto& identifier : inProgressIdentifiers) + m_inProgressTransactions.get(identifier)->abortWithoutCallback(); + + ASSERT(m_inProgressTransactions.isEmpty()); + + m_pendingTransactions.clear(); + m_objectStoreTransactionCounts.clear(); + m_objectStoreWriteTransactions.clear(); + + // Error out all pending callbacks + Vector<uint64_t> callbackIdentifiers; + IDBError error = IDBError::userDeleteError(); + IDBKeyData keyData; + IDBGetResult getResult; + + copyKeysToVector(m_errorCallbacks, callbackIdentifiers); + for (auto identifier : callbackIdentifiers) + performErrorCallback(identifier, error); + + callbackIdentifiers.clear(); + copyKeysToVector(m_keyDataCallbacks, callbackIdentifiers); + for (auto identifier : callbackIdentifiers) + performKeyDataCallback(identifier, error, keyData); + + callbackIdentifiers.clear(); + copyKeysToVector(m_getResultCallbacks, callbackIdentifiers); + for (auto identifier : callbackIdentifiers) + performGetResultCallback(identifier, error, getResult); + + callbackIdentifiers.clear(); + copyKeysToVector(m_countCallbacks, callbackIdentifiers); + for (auto identifier : callbackIdentifiers) + performCountCallback(identifier, error, 0); + + // Error out all IDBOpenDBRequests + if (m_currentOpenDBRequest) { + errorOpenDBRequestForUserDelete(*m_currentOpenDBRequest); + m_currentOpenDBRequest = nullptr; + } + + for (auto& request : m_pendingOpenDBRequests) + errorOpenDBRequestForUserDelete(*request); + + m_pendingOpenDBRequests.clear(); + + // Close all open connections + ListHashSet<RefPtr<UniqueIDBDatabaseConnection>> openDatabaseConnections = m_openDatabaseConnections; + for (auto& connection : openDatabaseConnections) + connectionClosedFromServer(*connection); + + // Cancel the operation timer + m_operationAndTransactionTimer.stop(); + + // Set up the database to remain alive-but-inert until all of its background activity finishes and all + // database connections confirm that they have closed. + m_hardClosedForUserDelete = true; + m_hardCloseProtector = this; + + // Have the database unconditionally delete itself on the database task queue. + postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performUnconditionalDeleteBackingStore)); + + // Remove the database from the IDBServer's set of open databases. + // If there is no in-progress background thread activity for this database, it will be deleted here. + m_server.closeUniqueIDBDatabase(*this); +} + +void UniqueIDBDatabase::performErrorCallback(uint64_t callbackIdentifier, const IDBError& error) +{ + auto callback = m_errorCallbacks.take(callbackIdentifier); + ASSERT(callback || m_hardClosedForUserDelete); + if (callback) + callback(error); +} + +void UniqueIDBDatabase::performKeyDataCallback(uint64_t callbackIdentifier, const IDBError& error, const IDBKeyData& resultKey) +{ + auto callback = m_keyDataCallbacks.take(callbackIdentifier); + ASSERT(callback || m_hardClosedForUserDelete); + if (callback) + callback(error, resultKey); +} + +void UniqueIDBDatabase::performGetResultCallback(uint64_t callbackIdentifier, const IDBError& error, const IDBGetResult& resultData) +{ + auto callback = m_getResultCallbacks.take(callbackIdentifier); + ASSERT(callback || m_hardClosedForUserDelete); + if (callback) + callback(error, resultData); +} + +void UniqueIDBDatabase::performGetAllResultsCallback(uint64_t callbackIdentifier, const IDBError& error, const IDBGetAllResult& resultData) +{ + auto callback = m_getAllResultsCallbacks.take(callbackIdentifier); + ASSERT(callback || m_hardClosedForUserDelete); + if (callback) + callback(error, resultData); +} + +void UniqueIDBDatabase::performCountCallback(uint64_t callbackIdentifier, const IDBError& error, uint64_t count) +{ + auto callback = m_countCallbacks.take(callbackIdentifier); + ASSERT(callback || m_hardClosedForUserDelete); + if (callback) + callback(error, count); +} + +void UniqueIDBDatabase::forgetErrorCallback(uint64_t callbackIdentifier) +{ + ASSERT(m_errorCallbacks.contains(callbackIdentifier)); + m_errorCallbacks.remove(callbackIdentifier); +} + +} // namespace IDBServer +} // namespace WebCore + +#endif // ENABLE(INDEXED_DATABASE) |