/*
 * Copyright (C) 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 "IDBConnectionProxy.h"

#include "IDBActiveDOMObjectInlines.h"
#include "IDBCursorInfo.h"
#include "IDBDatabase.h"
#include "IDBDatabaseNameAndVersion.h"
#include "IDBDatabaseNameAndVersionRequest.h"
#include "IDBGetRecordData.h"
#include "IDBIterateCursorData.h"
#include "IDBKeyRangeData.h"
#include "IDBOpenDBRequest.h"
#include "IDBOpenRequestData.h"
#include "IDBRequestData.h"
#include "IDBResultData.h"
#include "ScriptExecutionContext.h"
#include "SecurityOrigin.h"
#include "TransactionOperation.h"
#include <wtf/MainThread.h>
#include <wtf/TZoneMallocInlines.h>

namespace WebCore {
namespace IDBClient {

WTF_MAKE_TZONE_ALLOCATED_IMPL(IDBConnectionProxy);

IDBConnectionProxy::IDBConnectionProxy(IDBConnectionToServer& connection, PAL::SessionID sessionID)
    : m_connectionToServer(connection)
    , m_serverConnectionIdentifier(connection.identifier())
    , m_sessionID(sessionID)
{
    ASSERT(isMainThread());
}

void IDBConnectionProxy::ref()
{
    m_connectionToServer->ref();
}

void IDBConnectionProxy::deref()
{
    m_connectionToServer->deref();
}

PAL::SessionID IDBConnectionProxy::sessionID() const
{
    return m_sessionID;
}

Ref<IDBOpenDBRequest> IDBConnectionProxy::openDatabase(ScriptExecutionContext& context, const IDBDatabaseIdentifier& databaseIdentifier, uint64_t version)
{
    Ref request = [&] {
        Locker locker { m_openDBRequestMapLock };

        Ref request = IDBOpenDBRequest::createOpenRequest(context, *this, databaseIdentifier, version);
        ASSERT(!m_openDBRequestMap.contains(request->resourceIdentifier()));
        m_openDBRequestMap.set(request->resourceIdentifier(), request);
        return request;
    }();

    callConnectionOnMainThread(&IDBConnectionToServer::openDatabase, IDBOpenRequestData(*this, request));

    return request;
}

Ref<IDBOpenDBRequest> IDBConnectionProxy::deleteDatabase(ScriptExecutionContext& context, const IDBDatabaseIdentifier& databaseIdentifier)
{
    Ref request = [&] {
        Locker locker { m_openDBRequestMapLock };

        Ref request = IDBOpenDBRequest::createDeleteRequest(context, *this, databaseIdentifier);
        ASSERT(!m_openDBRequestMap.contains(request->resourceIdentifier()));
        m_openDBRequestMap.set(request->resourceIdentifier(), request);
        return request;
    }();

    callConnectionOnMainThread(&IDBConnectionToServer::deleteDatabase, IDBOpenRequestData(*this, request));

    return request;
}

void IDBConnectionProxy::didOpenDatabase(const IDBResultData& resultData)
{
    completeOpenDBRequest(resultData);
}

void IDBConnectionProxy::didDeleteDatabase(const IDBResultData& resultData)
{
    completeOpenDBRequest(resultData);
}

void IDBConnectionProxy::completeOpenDBRequest(const IDBResultData& resultData)
{
    ASSERT(isMainThread());

    RefPtr<IDBOpenDBRequest> request;
    {
        Locker locker { m_openDBRequestMapLock };
        request = m_openDBRequestMap.take(resultData.requestIdentifier());
    }

    if (!request)
        return;

    if (request->isContextSuspended()) {
        switch (resultData.type()) {
        case IDBResultType::OpenDatabaseUpgradeNeeded: {
            abortOpenAndUpgradeNeeded(resultData.databaseConnectionIdentifier(), resultData.transactionInfo().identifier());
            auto result = IDBResultData::error(resultData.requestIdentifier(), IDBError { ExceptionCode::UnknownError, "Version change transaction on cached page is aborted to unblock other connections"_s });
            request->performCallbackOnOriginThread(*request, &IDBOpenDBRequest::requestCompleted, result);
            return;
        }
        default:
            break;
        }
    }

    request->performCallbackOnOriginThread(*request, &IDBOpenDBRequest::requestCompleted, resultData);
}

void IDBConnectionProxy::createObjectStore(TransactionOperation& operation, const IDBObjectStoreInfo& info)
{
    const IDBRequestData requestData { operation };
    saveOperation(operation);

    callConnectionOnMainThread(&IDBConnectionToServer::createObjectStore, requestData, info);
}

void IDBConnectionProxy::renameObjectStore(TransactionOperation& operation, IDBObjectStoreIdentifier objectStoreIdentifier, const String& newName)
{
    const IDBRequestData requestData { operation };
    saveOperation(operation);

    callConnectionOnMainThread(&IDBConnectionToServer::renameObjectStore, requestData, objectStoreIdentifier, newName);
}

void IDBConnectionProxy::renameIndex(TransactionOperation& operation, IDBObjectStoreIdentifier objectStoreIdentifier, IDBIndexIdentifier indexIdentifier, const String& newName)
{
    const IDBRequestData requestData { operation };
    saveOperation(operation);

    callConnectionOnMainThread(&IDBConnectionToServer::renameIndex, requestData, objectStoreIdentifier, indexIdentifier, newName);
}

void IDBConnectionProxy::deleteObjectStore(TransactionOperation& operation, const String& objectStoreName)
{
    const IDBRequestData requestData { operation };
    saveOperation(operation);

    callConnectionOnMainThread(&IDBConnectionToServer::deleteObjectStore, requestData, objectStoreName);
}

void IDBConnectionProxy::clearObjectStore(TransactionOperation& operation, IDBObjectStoreIdentifier objectStoreIdentifier)
{
    const IDBRequestData requestData { operation };
    saveOperation(operation);

    callConnectionOnMainThread(&IDBConnectionToServer::clearObjectStore, requestData, objectStoreIdentifier);
}

void IDBConnectionProxy::createIndex(TransactionOperation& operation, const IDBIndexInfo& info)
{
    const IDBRequestData requestData { operation };
    saveOperation(operation);

    callConnectionOnMainThread(&IDBConnectionToServer::createIndex, requestData, info);
}

void IDBConnectionProxy::deleteIndex(TransactionOperation& operation, IDBObjectStoreIdentifier objectStoreIdentifier, const String& indexName)
{
    const IDBRequestData requestData { operation };
    saveOperation(operation);

    callConnectionOnMainThread(&IDBConnectionToServer::deleteIndex, requestData, WTF::move(objectStoreIdentifier), indexName);
}

void IDBConnectionProxy::putOrAdd(TransactionOperation& operation, IDBKeyData&& keyData, const IDBValue& value, const IndexIDToIndexKeyMap& indexKeys, const IndexedDB::ObjectStoreOverwriteMode mode)
{
    const IDBRequestData requestData { operation };
    saveOperation(operation);

    callConnectionOnMainThread(&IDBConnectionToServer::putOrAdd, requestData, keyData, value, indexKeys, mode);
}

void IDBConnectionProxy::getRecord(TransactionOperation& operation, const IDBGetRecordData& getRecordData)
{
    const IDBRequestData requestData { operation };
    saveOperation(operation);

    callConnectionOnMainThread(&IDBConnectionToServer::getRecord, requestData, getRecordData);
}

void IDBConnectionProxy::getAllRecords(TransactionOperation& operation, const IDBGetAllRecordsData& getAllRecordsData)
{
    const IDBRequestData requestData { operation };
    saveOperation(operation);

    callConnectionOnMainThread(&IDBConnectionToServer::getAllRecords, requestData, getAllRecordsData);
}

void IDBConnectionProxy::getCount(TransactionOperation& operation, const IDBKeyRangeData& keyRange)
{
    const IDBRequestData requestData { operation };
    saveOperation(operation);

    callConnectionOnMainThread(&IDBConnectionToServer::getCount, requestData, keyRange);
}

void IDBConnectionProxy::deleteRecord(TransactionOperation& operation, const IDBKeyRangeData& keyRange)
{
    const IDBRequestData requestData { operation };
    saveOperation(operation);

    callConnectionOnMainThread(&IDBConnectionToServer::deleteRecord, requestData, keyRange);
}

void IDBConnectionProxy::openCursor(TransactionOperation& operation, const IDBCursorInfo& info)
{
    const IDBRequestData requestData { operation };
    saveOperation(operation);

    callConnectionOnMainThread(&IDBConnectionToServer::openCursor, requestData, info);
}

void IDBConnectionProxy::iterateCursor(TransactionOperation& operation, const IDBIterateCursorData& data)
{
    const IDBRequestData requestData { operation };
    if (data.option != IndexedDB::CursorIterateOption::DoNotReply)
        saveOperation(operation);

    callConnectionOnMainThread(&IDBConnectionToServer::iterateCursor, requestData, data);
}

void IDBConnectionProxy::saveOperation(TransactionOperation& operation)
{
    Locker locker { m_transactionOperationLock };

    ASSERT(!m_activeOperations.contains(operation.identifier()));
    m_activeOperations.set(operation.identifier(), operation);
}

void IDBConnectionProxy::completeOperation(const IDBResultData& resultData)
{
    RefPtr<TransactionOperation> operation;
    {
        Locker locker { m_transactionOperationLock };
        operation = m_activeOperations.take(resultData.requestIdentifier());
    }

    if (!operation)
        return;

    operation->transitionToComplete(resultData, WTF::move(operation));
}

void IDBConnectionProxy::abortOpenAndUpgradeNeeded(IDBDatabaseConnectionIdentifier databaseConnectionIdentifier, const std::optional<IDBResourceIdentifier>& transactionIdentifier)
{
    callConnectionOnMainThread(&IDBConnectionToServer::abortOpenAndUpgradeNeeded, databaseConnectionIdentifier, transactionIdentifier);
}

std::pair<RefPtr<IDBDatabase>, std::optional<ScriptExecutionContextIdentifier>> IDBConnectionProxy::databaseFromConnectionIdentifier(IDBDatabaseConnectionIdentifier connectionIdentifier)
{
    Locker locker { m_databaseConnectionMapLock };
    auto it = m_databaseConnectionMap.find(connectionIdentifier);
    if (it == m_databaseConnectionMap.end())
        return { };

    RefPtr database = it->value.database.get();
    if (!database) {
        m_databaseConnectionMap.remove(it);
        return { };
    }

    return { database, it->value.contextIdentifier };
}

void IDBConnectionProxy::fireVersionChangeEvent(IDBDatabaseConnectionIdentifier databaseConnectionIdentifier, const IDBResourceIdentifier& requestIdentifier, uint64_t requestedVersion)
{
    auto [database, contextIdentifier] = databaseFromConnectionIdentifier(databaseConnectionIdentifier);
    if (!database)
        return;

    ASSERT(contextIdentifier);
    if (database->isContextSuspended()) {
        didFireVersionChangeEvent(databaseConnectionIdentifier, requestIdentifier, IndexedDB::ConnectionClosedOnBehalfOfServer::Yes);
        ScriptExecutionContext::ensureOnContextThreadForCrossThreadTask(*contextIdentifier, createCrossThreadTask(*database,  &IDBDatabase::connectionToServerLost, IDBError { ExceptionCode::UnknownError, "Connection on cached page closed to unblock other connections"_s }));
        return;
    }

    ScriptExecutionContext::ensureOnContextThreadForCrossThreadTask(*contextIdentifier, createCrossThreadTask(*database,  &IDBDatabase::fireVersionChangeEvent, requestIdentifier, requestedVersion));
}

void IDBConnectionProxy::didFireVersionChangeEvent(IDBDatabaseConnectionIdentifier databaseConnectionIdentifier, const IDBResourceIdentifier& requestIdentifier, IndexedDB::ConnectionClosedOnBehalfOfServer connectionClosed)
{
    callConnectionOnMainThread(&IDBConnectionToServer::didFireVersionChangeEvent, databaseConnectionIdentifier, requestIdentifier, connectionClosed);
}

void IDBConnectionProxy::generateIndexKeyForRecord(const IDBResourceIdentifier& requestIdentifier, const IDBIndexInfo& indexInfo, const std::optional<IDBKeyPath>& keyPath, const IDBKeyData& key, const IDBValue& value, std::optional<int64_t> recordID)
{
    Locker locker { m_transactionOperationLock };
    RefPtr operation = m_activeOperations.get(requestIdentifier);
    if (!operation)
        return;

    auto contextIdentifier = operation->scriptExecutionContextIdentifier();
    if (!contextIdentifier)
        return;

    ScriptExecutionContext::ensureOnContextThreadForCrossThreadTask(*contextIdentifier, createCrossThreadTask(operation->transaction(),  &IDBTransaction::generateIndexKeyForRecord, requestIdentifier, indexInfo, keyPath, key, value, recordID));
}

void IDBConnectionProxy::didGenerateIndexKeyForRecord(const IDBResourceIdentifier& transactionIdentifier, const IDBResourceIdentifier& requestIdentifier, const IDBIndexInfo& indexInfo, const IDBKeyData& key, const IndexKey& indexKey, std::optional<int64_t> recordID)
{
    callConnectionOnMainThread(&IDBConnectionToServer::didGenerateIndexKeyForRecord, transactionIdentifier, requestIdentifier, indexInfo, key, indexKey, recordID);
}

void IDBConnectionProxy::notifyOpenDBRequestBlocked(const IDBResourceIdentifier& requestIdentifier, uint64_t oldVersion, uint64_t newVersion)
{
    ASSERT(isMainThread());

    RefPtr<IDBOpenDBRequest> request;
    {
        Locker locker { m_openDBRequestMapLock };
        request = m_openDBRequestMap.get(requestIdentifier);
    }

    if (!request)
        return;

    request->performCallbackOnOriginThread(*request, &IDBOpenDBRequest::requestBlocked, oldVersion, newVersion);
}

void IDBConnectionProxy::openDBRequestCancelled(const IDBOpenRequestData& requestData)
{
    callConnectionOnMainThread(&IDBConnectionToServer::openDBRequestCancelled, requestData);
}

void IDBConnectionProxy::establishTransaction(IDBTransaction& transaction)
{
    {
        Locker locker { m_transactionMapLock };
        ASSERT(!hasRecordOfTransaction(transaction));
        m_pendingTransactions.set(transaction.info().identifier(), transaction);
    }

    callConnectionOnMainThread(&IDBConnectionToServer::establishTransaction, transaction.database().databaseConnectionIdentifier(), transaction.info());
}

void IDBConnectionProxy::didStartTransaction(const IDBResourceIdentifier& transactionIdentifier, const IDBError& error)
{
    RefPtr<IDBTransaction> transaction;
    {
        Locker locker { m_transactionMapLock };
        transaction = m_pendingTransactions.take(transactionIdentifier);
    }

    if (!transaction)
        return;

    transaction->performCallbackOnOriginThread(*transaction, &IDBTransaction::didStart, error);
}

void IDBConnectionProxy::commitTransaction(IDBTransaction& transaction, uint64_t handledRequestResultsCount)
{
    {
        Locker locker { m_transactionMapLock };
        ASSERT(!m_committingTransactions.contains(transaction.info().identifier()));
        m_committingTransactions.set(transaction.info().identifier(), transaction);
    }

    callConnectionOnMainThread(&IDBConnectionToServer::commitTransaction, transaction.info().identifier(), handledRequestResultsCount);
}

void IDBConnectionProxy::didCommitTransaction(const IDBResourceIdentifier& transactionIdentifier, const IDBError& error)
{
    RefPtr<IDBTransaction> transaction;
    {
        Locker locker { m_transactionMapLock };
        transaction = m_committingTransactions.take(transactionIdentifier);
    }

    if (!transaction)
        return;

    transaction->performCallbackOnOriginThread(*transaction, &IDBTransaction::didCommit, error);
}

void IDBConnectionProxy::abortTransaction(IDBTransaction& transaction)
{
    {
        Locker locker { m_transactionMapLock };
        ASSERT(!m_abortingTransactions.contains(transaction.info().identifier()));
        m_abortingTransactions.set(transaction.info().identifier(), transaction);
    }

    callConnectionOnMainThread(&IDBConnectionToServer::abortTransaction, transaction.info().identifier());
}

void IDBConnectionProxy::didAbortTransaction(const IDBResourceIdentifier& transactionIdentifier, const IDBError& error)
{
    RefPtr<IDBTransaction> transaction;
    {
        Locker locker { m_transactionMapLock };
        transaction = m_abortingTransactions.take(transactionIdentifier);
    }

    if (!transaction)
        return;

    transaction->performCallbackOnOriginThread(*transaction, &IDBTransaction::didAbort, error);
}

bool IDBConnectionProxy::hasRecordOfTransaction(const IDBTransaction& transaction) const
{
    ASSERT(m_transactionMapLock.isLocked());

    auto identifier = transaction.info().identifier();
    return m_pendingTransactions.contains(identifier) || m_committingTransactions.contains(identifier) || m_abortingTransactions.contains(identifier);
}

void IDBConnectionProxy::didFinishHandlingVersionChangeTransaction(IDBDatabaseConnectionIdentifier databaseConnectionIdentifier, IDBTransaction& transaction)
{
    callConnectionOnMainThread(&IDBConnectionToServer::didFinishHandlingVersionChangeTransaction, databaseConnectionIdentifier, transaction.info().identifier());
}

void IDBConnectionProxy::databaseConnectionPendingClose(IDBDatabase& database)
{
    callConnectionOnMainThread(&IDBConnectionToServer::databaseConnectionPendingClose, database.databaseConnectionIdentifier());
}

void IDBConnectionProxy::databaseConnectionClosed(IDBDatabase& database)
{
    callConnectionOnMainThread(&IDBConnectionToServer::databaseConnectionClosed, database.databaseConnectionIdentifier());
}

void IDBConnectionProxy::didCloseFromServer(IDBDatabaseConnectionIdentifier databaseConnectionIdentifier, const IDBError& error)
{
    auto [database, contextIdentifier] = databaseFromConnectionIdentifier(databaseConnectionIdentifier);
    if (!database)
        return;

    ScriptExecutionContext::ensureOnContextThreadForCrossThreadTask(*contextIdentifier, createCrossThreadTask(*database,  &IDBDatabase::didCloseFromServer, error));
}

void IDBConnectionProxy::connectionToServerLost(const IDBError& error)
{
    Vector<IDBDatabaseConnectionIdentifier> databaseConnectionIdentifiers;
    {
        Locker locker { m_databaseConnectionMapLock };
        databaseConnectionIdentifiers = copyToVector(m_databaseConnectionMap.keys());
    }

    for (auto connectionIdentifier : databaseConnectionIdentifiers) {
        auto [database, contextIdentifier] = databaseFromConnectionIdentifier(connectionIdentifier);
        if (!database)
            continue;

        ScriptExecutionContext::ensureOnContextThreadForCrossThreadTask(*contextIdentifier, createCrossThreadTask(*database,  &IDBDatabase::connectionToServerLost, error));
    }

    Vector<IDBResourceIdentifier> openDBRequestIdentifiers;
    {
        Locker locker { m_openDBRequestMapLock };
        openDBRequestIdentifiers = copyToVector(m_openDBRequestMap.keys());
    }

    for (auto& requestIdentifier : openDBRequestIdentifiers) {
        RefPtr<IDBOpenDBRequest> request;
        {
            Locker locker { m_openDBRequestMapLock };
            request = m_openDBRequestMap.get(requestIdentifier);
        }

        if (!request)
            continue;

        auto result = IDBResultData::error(requestIdentifier, error);
        request->performCallbackOnOriginThread(*request, &IDBOpenDBRequest::requestCompleted, result);
    }

    Vector<IDBResourceIdentifier> infoCallbackIdentifiers;
    {
        Locker locker { m_databaseInfoMapLock };
        infoCallbackIdentifiers = copyToVector(m_databaseInfoCallbacks.keys());
    }

    for (auto& requestIdentifier : infoCallbackIdentifiers)
        didGetAllDatabaseNamesAndVersions(requestIdentifier, { });
}

void IDBConnectionProxy::scheduleMainThreadTasks()
{
    Locker locker { m_mainThreadTaskLock };
    if (m_hasScheduledMainThreadTasks)
        return;

    m_hasScheduledMainThreadTasks = true;
    callOnMainThread([protectedThis = Ref { *this }] {
        protectedThis->handleMainThreadTasks();
    });
}

void IDBConnectionProxy::handleMainThreadTasks()
{
    {
        Locker locker { m_mainThreadTaskLock };
        ASSERT(m_hasScheduledMainThreadTasks);
        m_hasScheduledMainThreadTasks = false;
    }

    while (auto task = m_mainThreadQueue.tryGetMessage())
        task->performTask();
}

void IDBConnectionProxy::getAllDatabaseNamesAndVersions(ScriptExecutionContext& context, Function<void(std::optional<Vector<IDBDatabaseNameAndVersion>>&&)>&& callback)
{
    ClientOrigin origin { context.securityOrigin()->data(), context.topOrigin().data() };

    RefPtr<IDBDatabaseNameAndVersionRequest> request;
    {
        Locker locker { m_databaseInfoMapLock };
        auto newRequest = IDBDatabaseNameAndVersionRequest::create(context, *this, WTF::move(callback));
        ASSERT(!m_databaseInfoCallbacks.contains(newRequest->resourceIdentifier()));
        request = newRequest.ptr();
        m_databaseInfoCallbacks.add(newRequest->resourceIdentifier(), WTF::move(newRequest));
    }

    callConnectionOnMainThread(&IDBConnectionToServer::getAllDatabaseNamesAndVersions, request->resourceIdentifier(), origin);
}

void IDBConnectionProxy::didGetAllDatabaseNamesAndVersions(const IDBResourceIdentifier& requestIdentifier, std::optional<Vector<IDBDatabaseNameAndVersion>>&& databases)
{
    RefPtr<IDBDatabaseNameAndVersionRequest> request;
    {
        Locker locker { m_databaseInfoMapLock };
        request = m_databaseInfoCallbacks.take(requestIdentifier);
        if (!request)
            return;
    }

    request->performCallbackOnOriginThread(*request, &IDBDatabaseNameAndVersionRequest::complete, WTF::move(databases));
}

void IDBConnectionProxy::registerDatabaseConnection(IDBDatabase& database, ScriptExecutionContextIdentifier identifier)
{
    Locker locker { m_databaseConnectionMapLock };

    ASSERT(!m_databaseConnectionMap.contains(database.databaseConnectionIdentifier()));
    m_databaseConnectionMap.set(database.databaseConnectionIdentifier(), WeakIDBDatabase { &database, identifier });
}

void IDBConnectionProxy::unregisterDatabaseConnection(IDBDatabase& database)
{
    Locker locker { m_databaseConnectionMapLock };

    m_databaseConnectionMap.remove(database.databaseConnectionIdentifier());
}

void IDBConnectionProxy::forgetActiveOperations(const Vector<Ref<TransactionOperation>>& operations)
{
    Locker locker { m_transactionOperationLock };

    for (auto& operation : operations)
        m_activeOperations.remove(operation->identifier());
}

void IDBConnectionProxy::forgetTransaction(IDBTransaction& transaction)
{
    Locker locker { m_transactionMapLock };

    m_pendingTransactions.remove(transaction.info().identifier());
    m_committingTransactions.remove(transaction.info().identifier());
    m_abortingTransactions.remove(transaction.info().identifier());
}

template<typename KeyType, typename ValueType, typename FunctionType>
void removeItemsMatchingCurrentThread(HashMap<KeyType, ValueType>& map, FunctionType&& cleanupFunction)
{
    // FIXME: Revisit when introducing WebThread aware thread comparison.
    // https://bugs.webkit.org/show_bug.cgi?id=204345
    map.removeIf([currentThread = RefPtr { &Thread::currentSingleton() }, cleanupFunction = WTF::move(cleanupFunction)](auto& entry) {
        if (&entry.value->originThread() == currentThread.get()) {
            cleanupFunction(entry.value);
            return true;
        }
        return false;
    });
}

template<typename KeyType, typename ValueType>
void setMatchingItemsContextSuspended(ScriptExecutionContext& currentContext, HashMap<KeyType, ValueType>& map, bool isContextSuspended)
{
    // FIXME: Revisit when introducing WebThread aware thread comparison.
    // https://bugs.webkit.org/show_bug.cgi?id=204345
    auto& currentThread = Thread::currentSingleton();
    for (auto& iterator : map) {
        Ref item = iterator.value;
        if (&item->originThread() != &currentThread)
            continue;

        RefPtr context = item->scriptExecutionContext();
        if (!context)
            continue;

        if (context == &currentContext)
            item->setIsContextSuspended(isContextSuspended);
    }
}

void IDBConnectionProxy::abortActivitiesForCurrentThread()
{
    {
        Locker locker { m_openDBRequestMapLock };
        removeItemsMatchingCurrentThread(m_openDBRequestMap, [](auto& request) {
            request->requestCompleted(IDBResultData::error(request->resourceIdentifier(), IDBError { ExceptionCode::InvalidStateError, "Request is removed"_s }));
        });
    }
    {
        Locker locker { m_transactionMapLock };
        removeItemsMatchingCurrentThread(m_pendingTransactions, [](auto& transaction) {
            transaction->didStart(IDBError { ExceptionCode::InvalidStateError, "Transaction is removed"_s });
        });
        removeItemsMatchingCurrentThread(m_committingTransactions, [](auto& transaction) {
            transaction->didCommit(IDBError { ExceptionCode::InvalidStateError, "Transaction is removed"_s });
        });
        removeItemsMatchingCurrentThread(m_abortingTransactions, [](auto& transaction) {
            transaction->didAbort(IDBError { ExceptionCode::InvalidStateError, "Transaction is removed"_s });
        });
    }

    HashSet<Ref<TransactionOperation>> activeOperationsForThread;
    {
        Locker locker { m_transactionOperationLock };
        for (auto& operation : m_activeOperations.values()) {
            if (&operation->originThread() == &Thread::currentSingleton())
                activeOperationsForThread.add(operation);
        }
        for (auto& operation : activeOperationsForThread)
            m_activeOperations.remove(operation->identifier());
    }

    for (auto& operation : activeOperationsForThread) {
        auto result = IDBResultData::error(operation->identifier(), IDBError { ExceptionCode::InvalidStateError, "Operation is removed"_s });
        operation->transitionToCompleteOnThisThread(result);
    }

    {
        Locker locker { m_databaseInfoMapLock };
        removeItemsMatchingCurrentThread(m_databaseInfoCallbacks, [](auto& request) {
            request->complete(std::nullopt);
        });
    }
}

void IDBConnectionProxy::setContextSuspended(ScriptExecutionContext& currentContext, bool isContextSuspended)
{
    {
        auto currentContextIdentifier = currentContext.identifier();
        Locker locker { m_databaseConnectionMapLock };
        for (auto weakDatabase : m_databaseConnectionMap.values()) {
            if (RefPtr database = weakDatabase.database.get()) {
                auto contextIdentifier = weakDatabase.contextIdentifier;
                if (contextIdentifier && contextIdentifier == currentContextIdentifier)
                    database->setIsContextSuspended(isContextSuspended);
            }
        }
    }

    {
        Locker locker { m_openDBRequestMapLock };
        setMatchingItemsContextSuspended(currentContext, m_openDBRequestMap, isContextSuspended);
    }
}

} // namesapce IDBClient
} // namespace WebCore
