blob: ff8ad8a80603610903cb81f47e27df0567951eb2 [file] [log] [blame]
/*
* 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 "IDBBindingUtilities.h"
#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 "SerializedScriptValue.h"
#include "StorageQuotaManager.h"
#include "UniqueIDBDatabaseConnection.h"
#include <JavaScriptCore/AuxiliaryBarrierInlines.h>
#include <JavaScriptCore/JSCInlines.h>
#include <JavaScriptCore/StrongInlines.h>
#include <wtf/MainThread.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/Scope.h>
namespace WebCore {
using namespace JSC;
namespace IDBServer {
static const uint64_t defaultWriteOperationCost = 4;
static inline uint64_t estimateSize(const IDBKeyData& keyData)
{
uint64_t size = 4;
switch (keyData.type()) {
case IndexedDB::KeyType::String:
size += keyData.string().sizeInBytes();
break;
case IndexedDB::KeyType::Binary: {
size += keyData.binary().size();
break;
}
case IndexedDB::KeyType::Array:
for (auto& data : keyData.array())
size += estimateSize(data);
break;
default:
break;
}
return size;
}
static inline uint64_t estimateSize(const IDBValue& value)
{
uint64_t size = 4;
size += value.data().size();
for (auto& url : value.blobURLs())
size += url.sizeInBytes();
for (auto& path : value.blobFilePaths())
size += path.sizeInBytes();
return size;
}
static inline uint64_t estimateSize(const IDBKeyPath& keyPath)
{
return WTF::switchOn(keyPath, [](const String& path) {
return static_cast<uint64_t>(path.sizeInBytes());
}, [](const Vector<String>& paths) {
uint64_t size = 0;
for (auto path : paths)
size += path.sizeInBytes();
return size;
});
}
static inline uint64_t estimateSize(const IDBIndexInfo& info)
{
uint64_t size = 4;
size += info.name().sizeInBytes();
size += estimateSize(info.keyPath());
return size;
}
static inline uint64_t estimateSize(const IDBObjectStoreInfo& info)
{
uint64_t size = 4;
size += info.name().sizeInBytes();
if (auto keyPath = info.keyPath())
size += estimateSize(*keyPath);
return size;
}
UniqueIDBDatabase::UniqueIDBDatabase(IDBServer& server, const IDBDatabaseIdentifier& identifier)
: m_server(server)
, m_identifier(identifier)
, m_operationAndTransactionTimer(*this, &UniqueIDBDatabase::operationAndTransactionTimerFired)
{
m_server->addDatabase(*this);
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());
RELEASE_ASSERT(m_databaseQueue.isKilled());
RELEASE_ASSERT(m_databaseReplyQueue.isKilled());
RELEASE_ASSERT(!m_backingStore);
m_server->removeDatabase(*this);
}
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);
ASSERT(isMainThread());
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;
}
static inline String quotaErrorMessageName(const char* taskName)
{
return makeString("Failed to ", taskName, " in database because not enough space for domain");
}
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(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;
{
LockHolder locker(m_backingStoreLock);
if (m_backingStore) {
m_backingStore->deleteBackingStore(locker);
m_backingStore = nullptr;
m_backingStoreSupportsSimultaneousTransactions = false;
m_backingStoreIsEphemeral = false;
} else {
auto backingStore = m_server->createBackingStore(identifier);
IDBDatabaseInfo databaseInfo;
auto error = backingStore->getOrEstablishDatabaseInfo(databaseInfo, locker);
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(locker);
}
}
postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didDeleteBackingStore, deletedVersion));
}
void UniqueIDBDatabase::performUnconditionalDeleteBackingStore()
{
ASSERT(!isMainThread());
LOG(IndexedDB, "(db) UniqueIDBDatabase::performUnconditionalDeleteBackingStore");
{
LockHolder locker(m_backingStoreLock);
if (m_backingStore)
m_backingStore->deleteBackingStore(locker);
}
shutdownForClose();
}
void UniqueIDBDatabase::scheduleShutdownForClose()
{
ASSERT(isMainThread());
m_operationAndTransactionTimer.stop();
RELEASE_ASSERT(!m_owningPointerForClose);
m_owningPointerForClose = m_server->closeAndTakeUniqueIDBDatabase(*this);
postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::shutdownForClose));
}
void UniqueIDBDatabase::shutdownForClose()
{
ASSERT(!isMainThread());
ASSERT(m_owningPointerForClose.get() == this);
LOG(IndexedDB, "(db) UniqueIDBDatabase::shutdownForClose");
if (m_backingStore) {
LockHolder locker(m_backingStoreLock);
m_backingStore->close(locker);
m_backingStore = nullptr;
}
m_backingStoreSupportsSimultaneousTransactions = false;
m_backingStoreIsEphemeral = false;
if (!m_databaseQueue.isEmpty()) {
postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::shutdownForClose));
return;
}
m_databaseQueue.kill();
postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didShutdownForClose));
}
void UniqueIDBDatabase::didShutdownForClose()
{
ASSERT(m_databaseReplyQueue.isEmpty());
m_databaseReplyQueue.kill();
}
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());
ASSERT(!m_backingStore);
// 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 = makeUnique<IDBDatabaseInfo>(m_identifier.databaseName(), deletedVersion);
if (m_currentOpenDBRequest) {
m_currentOpenDBRequest->notifyDidDeleteDatabase(*m_mostRecentDeletedDatabaseInfo);
m_currentOpenDBRequest = nullptr;
}
m_deleteBackingStoreInProgress = false;
if (m_hardClosedForUserDelete)
return;
invokeOperationAndTransactionTimer();
}
void UniqueIDBDatabase::clearStalePendingOpenDBRequests()
{
while (!m_pendingOpenDBRequests.isEmpty() && m_pendingOpenDBRequests.first()->connection().isClosed())
m_pendingOpenDBRequests.removeFirst();
}
void UniqueIDBDatabase::handleDatabaseOperations()
{
ASSERT(isMainThread());
LOG(IndexedDB, "(main) UniqueIDBDatabase::handleDatabaseOperations - There are %u pending", m_pendingOpenDBRequests.size());
ASSERT(!m_hardClosedForUserDelete);
if (m_deleteBackingStoreInProgress || m_isOpeningBackingStore)
return;
clearStalePendingOpenDBRequests();
if (m_versionChangeDatabaseConnection || m_versionChangeTransaction || (m_currentOpenDBRequest && !m_currentOpenDBRequest->connection().isClosed())) {
// 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()) {
m_currentOpenDBRequest = nullptr;
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);
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;
}
uint64_t UniqueIDBDatabase::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, WTFMove(callback));
m_callbackQueue.append(identifier);
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, WTFMove(callback));
m_callbackQueue.append(identifier);
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, WTFMove(callback));
m_callbackQueue.append(identifier);
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, WTFMove(callback));
m_callbackQueue.append(identifier);
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, WTFMove(callback));
m_callbackQueue.append(identifier);
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);
uint64_t requestedVersion = m_currentOpenDBRequest->requestData().requestedVersion();
if (!requestedVersion)
requestedVersion = m_databaseInfo->version() ? m_databaseInfo->version() : 1;
m_versionChangeTransaction = &m_versionChangeDatabaseConnection->createVersionChangeTransaction(requestedVersion);
m_inProgressTransactions.set(m_versionChangeTransaction->info().identifier(), m_versionChangeTransaction);
postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performStartVersionChangeTransaction, m_versionChangeTransaction->info()));
}
void UniqueIDBDatabase::performStartVersionChangeTransaction(const IDBTransactionInfo& info)
{
LOG(IndexedDB, "(db) UniqueIDBDatabase::performStartVersionChangeTransaction");
IDBError error;
{
LockHolder locker(m_backingStoreLock);
error = m_backingStore->beginTransaction(info, locker);
}
postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformStartVersionChangeTransaction, error));
}
void UniqueIDBDatabase::didPerformStartVersionChangeTransaction(const IDBError& error)
{
LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformStartVersionChangeTransaction");
// Open request may already be canceled by client or user, or connection to client is lost.
if (!m_versionChangeDatabaseConnection)
return;
ASSERT(m_currentOpenDBRequest);
ASSERT(m_versionChangeTransaction);
auto operation = WTFMove(m_currentOpenDBRequest);
IDBResultData result;
if (error.isNull()) {
addOpenDatabaseConnection(*m_versionChangeDatabaseConnection);
m_databaseInfo->setVersion(m_versionChangeTransaction->info().newVersion());
result = IDBResultData::openDatabaseUpgradeNeeded(operation->requestData().requestIdentifier(), *m_versionChangeTransaction);
operation->connection().didOpenDatabase(result);
} else {
m_versionChangeDatabaseConnection->abortTransactionWithoutCallback(*m_versionChangeTransaction);
m_versionChangeDatabaseConnection = nullptr;
result = IDBResultData::error(operation->requestData().requestIdentifier(), error);
operation->connection().didOpenDatabase(result);
}
invokeOperationAndTransactionTimer();
}
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 (const 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::clearTransactionsOnConnection(UniqueIDBDatabaseConnection& connection)
{
Deque<RefPtr<UniqueIDBDatabaseTransaction>> pendingTransactions;
while (!m_pendingTransactions.isEmpty()) {
auto transaction = m_pendingTransactions.takeFirst();
if (&transaction->databaseConnection() != &connection)
pendingTransactions.append(WTFMove(transaction));
else
connection.deleteTransaction(*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();
}
void UniqueIDBDatabase::didFireVersionChangeEvent(UniqueIDBDatabaseConnection& connection, const IDBResourceIdentifier& requestIdentifier, IndexedDB::ConnectionClosedOnBehalfOfServer connectionClosedOnBehalfOfServer)
{
LOG(IndexedDB, "UniqueIDBDatabase::didFireVersionChangeEvent");
if (!m_currentOpenDBRequest)
return;
ASSERT_UNUSED(requestIdentifier, m_currentOpenDBRequest->requestData().requestIdentifier() == requestIdentifier);
if (connectionClosedOnBehalfOfServer == IndexedDB::ConnectionClosedOnBehalfOfServer::Yes) {
if (m_openDatabaseConnections.contains(&connection)) {
clearTransactionsOnConnection(connection);
m_openDatabaseConnections.remove(&connection);
}
}
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);
IDBDatabaseInfo databaseInfo;
m_origin = identifier.origin();
// Quota check.
auto decision = m_server->requestSpace(m_origin, defaultWriteOperationCost);
if (decision == StorageQuotaManager::Decision::Deny) {
postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didOpenBackingStore, databaseInfo, IDBError(QuotaExceededError, quotaErrorMessageName("OpenBackingStore"))));
return;
}
ASSERT(!m_backingStore);
IDBError error;
{
LockHolder locker(m_backingStoreLock);
m_backingStore = m_server->createBackingStore(identifier);
m_backingStoreSupportsSimultaneousTransactions = m_backingStore->supportsSimultaneousTransactions(locker);
m_backingStoreIsEphemeral = m_backingStore->isEphemeral(locker);
error = m_backingStore->getOrEstablishDatabaseInfo(databaseInfo, locker);
}
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 = makeUnique<IDBDatabaseInfo>(info);
m_backingStoreOpenError = error;
ASSERT(m_isOpeningBackingStore);
m_isOpeningBackingStore = false;
if (m_hardClosedForUserDelete)
return;
handleDatabaseOperations();
}
void UniqueIDBDatabase::createObjectStore(UniqueIDBDatabaseTransaction& transaction, const IDBObjectStoreInfo& info, ErrorCallback callback)
{
ASSERT(isMainThread());
LOG(IndexedDB, "(main) UniqueIDBDatabase::createObjectStore");
uint64_t callbackID = storeCallbackOrFireError(WTFMove(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");
// Quota check.
auto taskSize = defaultWriteOperationCost + estimateSize(info);
if (m_server->requestSpace(m_origin, taskSize) == StorageQuotaManager::Decision::Deny) {
postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformCreateObjectStore, callbackIdentifier, IDBError(QuotaExceededError, quotaErrorMessageName("CreateObjectStore")), info));
return;
}
ASSERT(m_backingStore);
IDBError error;
{
LockHolder locker(m_backingStoreLock);
error = m_backingStore->createObjectStore(transactionIdentifier, info, locker);
}
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(WTFMove(callback));
if (!callbackID)
return;
auto* info = m_databaseInfo->infoForExistingObjectStore(objectStoreName);
if (!info) {
performErrorCallback(callbackID, IDBError { UnknownError, "Attempt to delete non-existant object store"_s });
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);
IDBError error;
{
LockHolder locker(m_backingStoreLock);
error = m_backingStore->deleteObjectStore(transactionIdentifier, objectStoreIdentifier, locker);
}
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(WTFMove(callback));
if (!callbackID)
return;
IDBError error;
auto* info = m_databaseInfo->infoForExistingObjectStore(objectStoreIdentifier);
if (!info)
error = IDBError { UnknownError, "Attempt to rename non-existant object store"_s };
postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performRenameObjectStore, callbackID, transaction.info().identifier(), objectStoreIdentifier, newName, error));
}
void UniqueIDBDatabase::performRenameObjectStore(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const String& newName, const IDBError& error)
{
ASSERT(!isMainThread());
LOG(IndexedDB, "(db) UniqueIDBDatabase::performRenameObjectStore");
// Quota check.
auto taskSize = defaultWriteOperationCost + newName.sizeInBytes();
if (m_server->requestSpace(m_origin, taskSize) == StorageQuotaManager::Decision::Deny) {
postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformRenameObjectStore, callbackIdentifier, IDBError(QuotaExceededError, quotaErrorMessageName("RenameObjectStore")), objectStoreIdentifier, newName));
return;
}
ASSERT(m_backingStore);
{
LockHolder locker(m_backingStoreLock);
m_backingStore->renameObjectStore(transactionIdentifier, objectStoreIdentifier, newName, locker);
}
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(WTFMove(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);
IDBError error;
{
LockHolder locker(m_backingStoreLock);
error = m_backingStore->clearObjectStore(transactionIdentifier, objectStoreIdentifier, locker);
}
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(WTFMove(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");
auto taskSize = defaultWriteOperationCost + estimateSize(info);
if (m_server->requestSpace(m_origin, taskSize) == StorageQuotaManager::Decision::Deny) {
postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformCreateIndex, callbackIdentifier, IDBError(QuotaExceededError, quotaErrorMessageName("CreateIndex")), info));
return;
}
IDBError error;
ASSERT(m_backingStore);
{
LockHolder locker(m_backingStoreLock);
if (!m_backingStore) {
RELEASE_LOG_ERROR(IndexedDB, "%p - UniqueIDBDatabase::performCreateIndex: m_backingStore is null", this);
error = IDBError(InvalidStateError, "Backing store is invalid for call to create index"_s);
postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformCreateIndex, callbackIdentifier, error, info));
return;
}
error = m_backingStore->createIndex(transactionIdentifier, info, locker);
}
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(WTFMove(callback));
if (!callbackID)
return;
auto* objectStoreInfo = m_databaseInfo->infoForExistingObjectStore(objectStoreIdentifier);
if (!objectStoreInfo) {
performErrorCallback(callbackID, IDBError { UnknownError, "Attempt to delete index from non-existant object store"_s });
return;
}
auto* indexInfo = objectStoreInfo->infoForExistingIndex(indexName);
if (!indexInfo) {
performErrorCallback(callbackID, IDBError { UnknownError, "Attempt to delete non-existant index"_s });
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);
IDBError error;
{
LockHolder locker(m_backingStoreLock);
error = m_backingStore->deleteIndex(transactionIdentifier, objectStoreIdentifier, indexIdentifier, locker);
}
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(WTFMove(callback));
if (!callbackID)
return;
IDBError error;
auto* objectStoreInfo = m_databaseInfo->infoForExistingObjectStore(objectStoreIdentifier);
if (!objectStoreInfo)
error = IDBError { UnknownError, "Attempt to rename index in non-existant object store"_s };
auto* indexInfo = objectStoreInfo->infoForExistingIndex(indexIdentifier);
if (!indexInfo)
error = IDBError { UnknownError, "Attempt to rename non-existant index"_s };
postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performRenameIndex, callbackID, transaction.info().identifier(), objectStoreIdentifier, indexIdentifier, newName, error));
}
void UniqueIDBDatabase::performRenameIndex(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, const String& newName, const IDBError& error)
{
ASSERT(!isMainThread());
LOG(IndexedDB, "(db) UniqueIDBDatabase::performRenameIndex");
// Quota check.
auto taskSize = defaultWriteOperationCost + newName.sizeInBytes();
if (m_server->requestSpace(m_origin, taskSize) == StorageQuotaManager::Decision::Deny) {
postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformRenameIndex, callbackIdentifier, IDBError(QuotaExceededError, quotaErrorMessageName("RenameIndex")), objectStoreIdentifier, indexIdentifier, newName));
return;
}
ASSERT(m_backingStore);
{
LockHolder locker(m_backingStoreLock);
m_backingStore->renameIndex(transactionIdentifier, objectStoreIdentifier, indexIdentifier, newName, locker);
}
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(WTFMove(callback));
if (!callbackID)
return;
postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performPutOrAdd, callbackID, requestData.transactionIdentifier(), requestData.objectStoreIdentifier(), keyData, value, overwriteMode));
}
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");
IDBKeyData usedKey;
IDBError error;
// Quota check.
auto taskSize = defaultWriteOperationCost + estimateSize(keyData) + estimateSize(originalRecordValue);
ASSERT(m_databaseInfo);
auto* objectStore = m_databaseInfo->infoForExistingObjectStore(objectStoreIdentifier);
if (objectStore)
taskSize += objectStore->indexNames().size() * taskSize;
if (m_server->requestSpace(m_origin, taskSize) == StorageQuotaManager::Decision::Deny) {
postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformPutOrAdd, callbackIdentifier, IDBError(QuotaExceededError, quotaErrorMessageName("PutOrAdd")), usedKey));
return;
}
ASSERT(m_backingStore);
ASSERT(objectStoreIdentifier);
{
LockHolder locker(m_backingStoreLock);
if (!m_backingStore) {
RELEASE_LOG_ERROR(IndexedDB, "%p - UniqueIDBDatabase::performPutOrAdd: m_backingStore is null", this);
error = IDBError(InvalidStateError, "Backing store is invalid for call to put or add"_s);
postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformPutOrAdd, callbackIdentifier, error, usedKey));
return;
}
auto* objectStoreInfo = m_backingStore->infoForObjectStore(objectStoreIdentifier, locker);
if (!objectStoreInfo) {
error = IDBError(InvalidStateError, "Object store cannot be found in the backing store"_s);
postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformPutOrAdd, callbackIdentifier, error, usedKey));
return;
}
bool usedKeyIsGenerated = false;
uint64_t keyNumber;
auto generatedKeyResetter = WTF::makeScopeExit([this, transactionIdentifier, objectStoreIdentifier, &keyNumber, &usedKeyIsGenerated, &locker]() {
if (usedKeyIsGenerated)
m_backingStore->revertGeneratedKeyNumber(transactionIdentifier, objectStoreIdentifier, keyNumber, locker);
});
if (objectStoreInfo->autoIncrement() && !keyData.isValid()) {
error = m_backingStore->generateKeyNumber(transactionIdentifier, objectStoreIdentifier, keyNumber, locker);
if (!error.isNull()) {
postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformPutOrAdd, callbackIdentifier, error, usedKey));
return;
}
usedKey.setNumberValue(keyNumber);
usedKeyIsGenerated = true;
} else
usedKey = keyData;
if (overwriteMode == IndexedDB::ObjectStoreOverwriteMode::NoOverwrite) {
bool keyExists;
error = m_backingStore->keyExistsInObjectStore(transactionIdentifier, objectStoreIdentifier, usedKey, keyExists, locker);
if (error.isNull() && keyExists)
error = IDBError(ConstraintError, "Key already exists in the object store"_s);
if (!error.isNull()) {
postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformPutOrAdd, callbackIdentifier, error, usedKey));
return;
}
}
// 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, locker);
if (!error.isNull()) {
postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformPutOrAdd, callbackIdentifier, error, usedKey));
return;
}
error = m_backingStore->addRecord(transactionIdentifier, *objectStoreInfo, usedKey, originalRecordValue, locker);
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(), locker);
generatedKeyResetter.release();
}
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(WTFMove(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(WTFMove(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;
{
LockHolder locker(m_backingStoreLock);
error = m_backingStore->getRecord(transactionIdentifier, objectStoreIdentifier, keyRangeData, type, result, locker);
}
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;
{
LockHolder locker(m_backingStoreLock);
error = m_backingStore->getIndexRecord(transactionIdentifier, objectStoreIdentifier, indexIdentifier, recordType, range, result, locker);
}
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;
{
LockHolder locker(m_backingStoreLock);
error = m_backingStore->getAllRecords(transactionIdentifier, getAllRecordsData, result, locker);
}
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(WTFMove(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;
{
LockHolder locker(m_backingStoreLock);
error = m_backingStore->getCount(transactionIdentifier, objectStoreIdentifier, indexIdentifier, keyRangeData, count, locker);
}
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(WTFMove(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;
{
LockHolder locker(m_backingStoreLock);
error = m_backingStore->deleteRange(transactionIdentifier, objectStoreIdentifier, range, locker);
}
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(WTFMove(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;
{
LockHolder locker(m_backingStoreLock);
error = m_backingStore->openCursor(transactionIdentifier, info, result, locker);
}
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(WTFMove(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;
{
LockHolder locker(m_backingStoreLock);
error = m_backingStore->iterateCursor(transactionIdentifier, cursorIdentifier, data, result, locker);
}
if (error.isNull()) {
auto addResult = m_cursorPrefetches.add(cursorIdentifier);
if (addResult.isNewEntry)
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_cursorPrefetches.contains(cursorIdentifier));
LOG(IndexedDB, "(db) UniqueIDBDatabase::performPrefetchCursor");
if (m_hardClosedForUserDelete) {
m_cursorPrefetches.remove(cursorIdentifier);
return;
}
{
LockHolder locker(m_backingStoreLock);
if (!m_backingStore->prefetchCursor(transactionIdentifier, cursorIdentifier, locker)) {
m_cursorPrefetches.remove(cursorIdentifier);
return;
}
}
postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performPrefetchCursor, transactionIdentifier, 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, UniqueIDBDatabaseTransaction::State state)
{
auto takenTransaction = m_inProgressTransactions.take(transaction.info().identifier());
if (!takenTransaction)
return false;
ASSERT(!m_finishingTransactions.contains(transaction.info().identifier()));
takenTransaction->setState(state);
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(WTFMove(callback));
if (!callbackID)
return;
if (!prepareToFinishTransaction(transaction, UniqueIDBDatabaseTransaction::State::Committing)) {
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, IDBError { UnknownError, "Attempt to commit transaction that is already finishing"_s });
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;
{
LockHolder locker(m_backingStoreLock);
error = m_backingStore->commitTransaction(transactionIdentifier, locker);
}
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());
IDBError result = error;
auto transaction = m_finishingTransactions.get(transactionIdentifier);
switch (transaction->state()) {
case UniqueIDBDatabaseTransaction::State::Aborted:
result = IDBError { UnknownError, "Transaction is already aborted"_s };
break;
case UniqueIDBDatabaseTransaction::State::Committed:
result = transaction->result();
break;
case UniqueIDBDatabaseTransaction::State::Committing:
break;
case UniqueIDBDatabaseTransaction::State::Running:
case UniqueIDBDatabaseTransaction::State::Aborting:
ASSERT_NOT_REACHED();
}
performErrorCallback(callbackIdentifier, result);
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(WTFMove(callback));
if (!callbackID)
return;
if (!prepareToFinishTransaction(transaction, UniqueIDBDatabaseTransaction::State::Aborting)) {
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, IDBError { UnknownError, "Attempt to abort transaction that is already finishing"_s });
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;
{
LockHolder locker(m_backingStoreLock);
error = m_backingStore->abortTransaction(transactionIdentifier, locker);
}
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 = makeUnique<IDBDatabaseInfo>(*m_versionChangeTransaction->originalDatabaseInfo());
}
IDBError result = transaction->state() == UniqueIDBDatabaseTransaction::State::Aborted ? transaction->result() : error;
performErrorCallback(callbackIdentifier, result);
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());
if (m_serverClosePendingDatabaseConnections.contains(&connection)) {
m_serverClosePendingDatabaseConnections.remove(&connection);
if (m_hardClosedForUserDelete)
maybeFinishHardClose();
return;
}
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;
}
clearTransactionsOnConnection(connection);
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());
if (!m_serverClosePendingDatabaseConnections.contains(&connection))
return;
m_serverClosePendingDatabaseConnections.remove(&connection);
if (m_hardClosedForUserDelete)
maybeFinishHardClose();
}
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()");
RELEASE_ASSERT(!m_hardClosedForUserDelete);
RELEASE_ASSERT(!m_owningPointerForClose);
if (!m_operationAndTransactionTimer.isActive())
m_operationAndTransactionTimer.startOneShot(0_s);
}
void UniqueIDBDatabase::operationAndTransactionTimerFired()
{
LOG(IndexedDB, "(main) UniqueIDBDatabase::operationAndTransactionTimerFired");
ASSERT(!m_hardClosedForUserDelete);
ASSERT(isMainThread());
// 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());
scheduleShutdownForClose();
return;
}
// The current operation might require multiple attempts to handle, so try to
// make further progress on it now.
if (m_currentOpenDBRequest && !m_currentOpenDBRequest->connection().isClosed())
handleCurrentOperation();
else
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 || m_hardClosedForUserDelete);
}
}
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");
ASSERT(isMainThread());
RefPtr<UniqueIDBDatabaseTransaction> refTransaction(&transaction);
ErrorCallback callback = [refTransaction](const IDBError& error) {
refTransaction->didActivateInBackingStore(error);
};
uint64_t callbackID = storeCallbackOrFireError(WTFMove(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;
{
LockHolder locker(m_backingStoreLock);
error = m_backingStore->beginTransaction(info, locker);
}
postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformActivateTransactionInBackingStore, callbackIdentifier, error));
}
void UniqueIDBDatabase::didPerformActivateTransactionInBackingStore(uint64_t callbackIdentifier, const IDBError& error)
{
LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformActivateTransactionInBackingStore");
if (m_hardClosedForUserDelete)
return;
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()));
ASSERT(isMainThread());
for (auto objectStore : transaction->objectStoreIdentifiers()) {
if (!transaction->isReadOnly()) {
m_objectStoreWriteTransactions.remove(objectStore);
ASSERT(m_objectStoreTransactionCounts.count(objectStore) == 1 || m_hardClosedForUserDelete);
}
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) {
scheduleShutdownForClose();
return;
}
// Previously blocked operations might be runnable.
if (!m_hardClosedForUserDelete)
invokeOperationAndTransactionTimer();
else
maybeFinishHardClose();
}
void UniqueIDBDatabase::postDatabaseTask(CrossThreadTask&& task)
{
m_databaseQueue.append(WTFMove(task));
m_server->postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::executeNextDatabaseTask));
}
void UniqueIDBDatabase::postDatabaseTaskReply(CrossThreadTask&& task)
{
m_databaseReplyQueue.append(WTFMove(task));
m_server->postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::executeNextDatabaseTaskReply));
}
void UniqueIDBDatabase::executeNextDatabaseTask()
{
ASSERT(!isMainThread());
ASSERT(!m_databaseQueue.isKilled());
auto task = m_databaseQueue.tryGetMessage();
ASSERT(task);
task->performTask();
}
void UniqueIDBDatabase::executeNextDatabaseTaskReply()
{
ASSERT(isMainThread());
ASSERT(!m_databaseReplyQueue.isKilled());
auto task = m_databaseReplyQueue.tryGetMessage();
ASSERT(task);
task->performTask();
// 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_owningPointerForClose && isDoneWithHardClose()) {
if (m_owningPointerReleaseScheduled)
return;
m_owningPointerReleaseScheduled = true;
callOnMainThread([this] {
ASSERT(isDoneWithHardClose());
m_owningPointerForClose = nullptr;
});
}
}
bool UniqueIDBDatabase::isDoneWithHardClose()
{
return m_databaseReplyQueue.isKilled() && 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());
ASSERT(isMainThread());
// Error out all transactions
for (auto& identifier : copyToVector(m_inProgressTransactions.keys()))
m_inProgressTransactions.get(identifier)->abortWithoutCallback();
ASSERT(m_inProgressTransactions.isEmpty());
for (auto& transaction : m_pendingTransactions)
transaction->databaseConnection().deleteTransaction(*transaction);
m_pendingTransactions.clear();
m_objectStoreTransactionCounts.clear();
m_objectStoreWriteTransactions.clear();
// Error out all pending callbacks
IDBError error = IDBError::userDeleteError();
IDBKeyData keyData;
IDBGetResult getResult;
IDBGetAllResult getAllResult;
while (!m_callbackQueue.isEmpty()) {
auto identifier = m_callbackQueue.first();
if (m_errorCallbacks.contains(identifier))
performErrorCallback(identifier, error);
else if (m_keyDataCallbacks.contains(identifier))
performKeyDataCallback(identifier, error, keyData);
else if (m_getResultCallbacks.contains(identifier))
performGetResultCallback(identifier, error, getResult);
else if (m_countCallbacks.contains(identifier))
performCountCallback(identifier, error, 0);
else if (m_getAllResultsCallbacks.contains(identifier))
performGetAllResultsCallback(identifier, error, getAllResult);
else
ASSERT_NOT_REACHED();
}
// 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
auto openDatabaseConnections = m_openDatabaseConnections;
for (auto& connection : openDatabaseConnections)
connectionClosedFromServer(*connection);
if (m_versionChangeDatabaseConnection) {
connectionClosedFromServer(*m_versionChangeDatabaseConnection);
m_versionChangeDatabaseConnection = nullptr;
}
// 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;
// If this database already owns itself, it is already closing on the background thread.
// After that close completes, the next database thread task will be "delete all currently closed databases"
// which will also cover this database.
if (m_owningPointerForClose)
return;
// Otherwise, this database is still potentially active.
// So we'll have it own itself and then perform a clean unconditional delete on the background thread.
m_owningPointerForClose = m_server->closeAndTakeUniqueIDBDatabase(*this);
postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performUnconditionalDeleteBackingStore));
}
void UniqueIDBDatabase::performErrorCallback(uint64_t callbackIdentifier, const IDBError& error)
{
auto callback = m_errorCallbacks.take(callbackIdentifier);
ASSERT(callback || m_hardClosedForUserDelete);
if (callback) {
callback(error);
ASSERT(m_callbackQueue.first() == callbackIdentifier);
m_callbackQueue.removeFirst();
}
}
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);
ASSERT(m_callbackQueue.first() == callbackIdentifier);
m_callbackQueue.removeFirst();
}
}
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);
ASSERT(m_callbackQueue.first() == callbackIdentifier);
m_callbackQueue.removeFirst();
}
}
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);
ASSERT(m_callbackQueue.first() == callbackIdentifier);
m_callbackQueue.removeFirst();
}
}
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);
ASSERT(m_callbackQueue.first() == callbackIdentifier);
m_callbackQueue.removeFirst();
}
}
void UniqueIDBDatabase::forgetErrorCallback(uint64_t callbackIdentifier)
{
ASSERT(m_errorCallbacks.contains(callbackIdentifier));
ASSERT(m_callbackQueue.last() == callbackIdentifier);
m_callbackQueue.removeLast();
m_errorCallbacks.remove(callbackIdentifier);
}
void UniqueIDBDatabase::abortTransactionOnMainThread(UniqueIDBDatabaseTransaction& transaction, const LockHolder& locker)
{
transaction.setResult(m_backingStore->abortTransaction(transaction.info().identifier(), locker));
transaction.setState(UniqueIDBDatabaseTransaction::State::Aborted);
}
void UniqueIDBDatabase::commitTransactionOnMainThread(UniqueIDBDatabaseTransaction& transaction, const LockHolder& locker)
{
transaction.setResult(m_backingStore->commitTransaction(transaction.info().identifier(), locker));
transaction.setState(UniqueIDBDatabaseTransaction::State::Committed);
}
void UniqueIDBDatabase::suspend()
{
ASSERT(isMainThread());
if (m_isSuspended)
return;
m_isSuspended = true;
{
LockHolder locker(m_backingStoreLock);
for (auto& identifier : copyToVector(m_inProgressTransactions.keys())) {
auto transaction = m_inProgressTransactions.get(identifier);
abortTransactionOnMainThread(*transaction, locker);
}
for (auto& identifier : copyToVector(m_finishingTransactions.keys())) {
if (!m_backingStore->hasTransaction(identifier, locker))
continue;
auto transaction = m_finishingTransactions.get(identifier);
switch (transaction->state()) {
case UniqueIDBDatabaseTransaction::State::Aborting:
abortTransactionOnMainThread(*transaction, locker);
break;
case UniqueIDBDatabaseTransaction::State::Committing:
commitTransactionOnMainThread(*transaction, locker);
break;
case UniqueIDBDatabaseTransaction::State::Running:
case UniqueIDBDatabaseTransaction::State::Aborted:
case UniqueIDBDatabaseTransaction::State::Committed:
ASSERT_NOT_REACHED();
}
}
}
m_backingStoreLock.lock();
}
void UniqueIDBDatabase::resume()
{
ASSERT(isMainThread());
if (!m_isSuspended)
return;
m_isSuspended = false;
ASSERT(m_backingStoreLock.isHeld());
m_backingStoreLock.unlock();
}
} // namespace IDBServer
} // namespace WebCore
#endif // ENABLE(INDEXED_DATABASE)