summaryrefslogtreecommitdiff
path: root/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.cpp
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
commit1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch)
tree46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.cpp
parent32761a6cee1d0dee366b885b7b9c777e67885688 (diff)
downloadWebKitGtk-tarball-master.tar.gz
Diffstat (limited to 'Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.cpp')
-rw-r--r--Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.cpp1908
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)