diff options
Diffstat (limited to 'Source/WebCore/Modules/indexeddb/IDBDatabase.cpp')
-rw-r--r-- | Source/WebCore/Modules/indexeddb/IDBDatabase.cpp | 626 |
1 files changed, 375 insertions, 251 deletions
diff --git a/Source/WebCore/Modules/indexeddb/IDBDatabase.cpp b/Source/WebCore/Modules/indexeddb/IDBDatabase.cpp index e8ca31c29..fad585ca1 100644 --- a/Source/WebCore/Modules/indexeddb/IDBDatabase.cpp +++ b/Source/WebCore/Modules/indexeddb/IDBDatabase.cpp @@ -1,26 +1,26 @@ /* - * Copyright (C) 2010 Google Inc. All rights reserved. + * Copyright (C) 2015, 2016 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" @@ -29,358 +29,482 @@ #if ENABLE(INDEXED_DATABASE) #include "DOMStringList.h" +#include "EventNames.h" #include "EventQueue.h" #include "ExceptionCode.h" -#include "HistogramSupport.h" -#include "IDBAny.h" -#include "IDBDatabaseCallbacks.h" -#include "IDBDatabaseError.h" +#include "IDBConnectionProxy.h" +#include "IDBConnectionToServer.h" #include "IDBDatabaseException.h" -#include "IDBEventDispatcher.h" -#include "IDBHistograms.h" #include "IDBIndex.h" -#include "IDBKeyPath.h" #include "IDBObjectStore.h" +#include "IDBOpenDBRequest.h" +#include "IDBResultData.h" #include "IDBTransaction.h" #include "IDBVersionChangeEvent.h" #include "Logging.h" -#include "ScriptCallStack.h" #include "ScriptExecutionContext.h" -#include <atomic> -#include <limits> -#include <wtf/Atomics.h> +#include <heap/HeapInlines.h> namespace WebCore { -PassRefPtr<IDBDatabase> IDBDatabase::create(ScriptExecutionContext* context, PassRefPtr<IDBDatabaseBackend> database, PassRefPtr<IDBDatabaseCallbacks> callbacks) +Ref<IDBDatabase> IDBDatabase::create(ScriptExecutionContext& context, IDBClient::IDBConnectionProxy& connectionProxy, const IDBResultData& resultData) { - RefPtr<IDBDatabase> idbDatabase(adoptRef(new IDBDatabase(context, database, callbacks))); - idbDatabase->suspendIfNeeded(); - return idbDatabase.release(); + return adoptRef(*new IDBDatabase(context, connectionProxy, resultData)); } -IDBDatabase::IDBDatabase(ScriptExecutionContext* context, PassRefPtr<IDBDatabaseBackend> backend, PassRefPtr<IDBDatabaseCallbacks> callbacks) - : ActiveDOMObject(context) - , m_backend(backend) - , m_closePending(false) - , m_contextStopped(false) - , m_databaseCallbacks(callbacks) +IDBDatabase::IDBDatabase(ScriptExecutionContext& context, IDBClient::IDBConnectionProxy& connectionProxy, const IDBResultData& resultData) + : IDBActiveDOMObject(&context) + , m_connectionProxy(connectionProxy) + , m_info(resultData.databaseInfo()) + , m_databaseConnectionIdentifier(resultData.databaseConnectionIdentifier()) + , m_eventNames(eventNames()) { - // We pass a reference of this object before it can be adopted. - relaxAdoptionRequirement(); + LOG(IndexedDB, "IDBDatabase::IDBDatabase - Creating database %s with version %" PRIu64 " connection %" PRIu64 " (%p)", m_info.name().utf8().data(), m_info.version(), m_databaseConnectionIdentifier, this); + suspendIfNeeded(); + m_connectionProxy->registerDatabaseConnection(*this); } IDBDatabase::~IDBDatabase() { - // This does what IDBDatabase::close does, but without any ref/deref of the - // database since it is already in the process of being deleted. The logic here - // is also simpler since we know there are no transactions (since they ref the - // database when they are alive). + ASSERT(currentThread() == originThreadID()); - ASSERT(m_transactions.isEmpty()); + if (!m_closedInServer) + m_connectionProxy->databaseConnectionClosed(*this); - if (!m_closePending) { - m_closePending = true; - m_backend->close(m_databaseCallbacks); - } + m_connectionProxy->unregisterDatabaseConnection(*this); +} - if (auto* context = scriptExecutionContext()) { - // Remove any pending versionchange events scheduled to fire on this - // connection. They would have been scheduled by the backend when another - // connection called setVersion, but the frontend connection is being - // closed before they could fire. - for (auto& event : m_enqueuedEvents) - context->eventQueue().cancelEvent(*event); - } +bool IDBDatabase::hasPendingActivity() const +{ + ASSERT(currentThread() == originThreadID() || mayBeGCThread()); + + if (m_closedInServer) + return false; + + if (!m_activeTransactions.isEmpty() || !m_committingTransactions.isEmpty() || !m_abortingTransactions.isEmpty()) + return true; + + return hasEventListeners(m_eventNames.abortEvent) || hasEventListeners(m_eventNames.errorEvent) || hasEventListeners(m_eventNames.versionchangeEvent); } -int64_t IDBDatabase::nextTransactionId() +const String IDBDatabase::name() const { - // Only keep a 32-bit counter to allow ports to use the other 32 - // bits of the id. - static std::atomic<uint32_t> currentTransactionId; + ASSERT(currentThread() == originThreadID()); + return m_info.name(); +} - return ++currentTransactionId; +uint64_t IDBDatabase::version() const +{ + ASSERT(currentThread() == originThreadID()); + return m_info.version(); } -void IDBDatabase::transactionCreated(IDBTransaction* transaction) +RefPtr<DOMStringList> IDBDatabase::objectStoreNames() const { - ASSERT(transaction); - ASSERT(!m_transactions.contains(transaction->id())); - m_transactions.add(transaction->id(), transaction); + ASSERT(currentThread() == originThreadID()); - if (transaction->isVersionChange()) { - ASSERT(!m_versionChangeTransaction); - m_versionChangeTransaction = transaction; - } + RefPtr<DOMStringList> objectStoreNames = DOMStringList::create(); + for (auto& name : m_info.objectStoreNames()) + objectStoreNames->append(name); + objectStoreNames->sort(); + return objectStoreNames; } -void IDBDatabase::transactionFinished(IDBTransaction* transaction) +void IDBDatabase::renameObjectStore(IDBObjectStore& objectStore, const String& newName) { - ASSERT(transaction); - ASSERT(m_transactions.contains(transaction->id())); - ASSERT(m_transactions.get(transaction->id()) == transaction); - m_transactions.remove(transaction->id()); - - if (transaction->isVersionChange()) { - ASSERT(m_versionChangeTransaction == transaction); - m_versionChangeTransaction = 0; - } + ASSERT(currentThread() == originThreadID()); + ASSERT(m_versionChangeTransaction); + ASSERT(m_info.hasObjectStore(objectStore.info().name())); + + m_info.renameObjectStore(objectStore.info().identifier(), newName); - if (m_closePending && m_transactions.isEmpty()) - closeConnection(); + m_versionChangeTransaction->renameObjectStore(objectStore, newName); } -void IDBDatabase::onAbort(int64_t transactionId, PassRefPtr<IDBDatabaseError> error) +void IDBDatabase::renameIndex(IDBIndex& index, const String& newName) { - ASSERT(m_transactions.contains(transactionId)); - m_transactions.get(transactionId)->onAbort(error); + ASSERT(currentThread() == originThreadID()); + ASSERT(m_versionChangeTransaction); + ASSERT(m_info.hasObjectStore(index.objectStore().info().name())); + ASSERT(m_info.infoForExistingObjectStore(index.objectStore().info().name())->hasIndex(index.info().name())); + + m_info.infoForExistingObjectStore(index.objectStore().info().name())->infoForExistingIndex(index.info().identifier())->rename(newName); + + m_versionChangeTransaction->renameIndex(index, newName); } -void IDBDatabase::onComplete(int64_t transactionId) +ExceptionOr<Ref<IDBObjectStore>> IDBDatabase::createObjectStore(const String& name, ObjectStoreParameters&& parameters) { - ASSERT(m_transactions.contains(transactionId)); - m_transactions.get(transactionId)->onComplete(); + LOG(IndexedDB, "IDBDatabase::createObjectStore - (%s %s)", m_info.name().utf8().data(), name.utf8().data()); + + ASSERT(currentThread() == originThreadID()); + ASSERT(!m_versionChangeTransaction || m_versionChangeTransaction->isVersionChange()); + + if (!m_versionChangeTransaction) + return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'createObjectStore' on 'IDBDatabase': The database is not running a version change transaction.") }; + + if (!m_versionChangeTransaction->isActive()) + return Exception { IDBDatabaseException::TransactionInactiveError }; + + if (m_info.hasObjectStore(name)) + return Exception { IDBDatabaseException::ConstraintError, ASCIILiteral("Failed to execute 'createObjectStore' on 'IDBDatabase': An object store with the specified name already exists.") }; + + auto& keyPath = parameters.keyPath; + if (keyPath && !isIDBKeyPathValid(keyPath.value())) + return Exception { IDBDatabaseException::SyntaxError, ASCIILiteral("Failed to execute 'createObjectStore' on 'IDBDatabase': The keyPath option is not a valid key path.") }; + + if (keyPath && parameters.autoIncrement && ((WTF::holds_alternative<String>(keyPath.value()) && WTF::get<String>(keyPath.value()).isEmpty()) || WTF::holds_alternative<Vector<String>>(keyPath.value()))) + return Exception { IDBDatabaseException::InvalidAccessError, ASCIILiteral("Failed to execute 'createObjectStore' on 'IDBDatabase': The autoIncrement option was set but the keyPath option was empty or an array.") }; + + // Install the new ObjectStore into the connection's metadata. + auto info = m_info.createNewObjectStore(name, WTFMove(keyPath), parameters.autoIncrement); + + // Create the actual IDBObjectStore from the transaction, which also schedules the operation server side. + return m_versionChangeTransaction->createObjectStore(info); } -PassRefPtr<DOMStringList> IDBDatabase::objectStoreNames() const +ExceptionOr<Ref<IDBTransaction>> IDBDatabase::transaction(StringOrVectorOfStrings&& storeNames, IDBTransactionMode mode) { - RefPtr<DOMStringList> objectStoreNames = DOMStringList::create(); - for (IDBDatabaseMetadata::ObjectStoreMap::const_iterator it = m_metadata.objectStores.begin(); it != m_metadata.objectStores.end(); ++it) - objectStoreNames->append(it->value.name); - objectStoreNames->sort(); - return objectStoreNames.release(); + LOG(IndexedDB, "IDBDatabase::transaction"); + + ASSERT(currentThread() == originThreadID()); + + if (m_closePending) + return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'transaction' on 'IDBDatabase': The database connection is closing.") }; + + Vector<String> objectStores; + if (WTF::holds_alternative<Vector<String>>(storeNames)) + objectStores = WTFMove(WTF::get<Vector<String>>(storeNames)); + else + objectStores.append(WTFMove(WTF::get<String>(storeNames))); + + if (objectStores.isEmpty()) + return Exception { IDBDatabaseException::InvalidAccessError, ASCIILiteral("Failed to execute 'transaction' on 'IDBDatabase': The storeNames parameter was empty.") }; + + if (mode != IDBTransactionMode::Readonly && mode != IDBTransactionMode::Readwrite) + return Exception { TypeError }; + + if (m_versionChangeTransaction && !m_versionChangeTransaction->isFinishedOrFinishing()) + return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'transaction' on 'IDBDatabase': A version change transaction is running.") }; + + // It is valid for javascript to pass in a list of object store names with the same name listed twice, + // so we need to put them all in a set to get a unique list. + HashSet<String> objectStoreSet; + for (auto& objectStore : objectStores) + objectStoreSet.add(objectStore); + + objectStores.clear(); + copyToVector(objectStoreSet, objectStores); + + for (auto& objectStoreName : objectStores) { + if (m_info.hasObjectStore(objectStoreName)) + continue; + return Exception { IDBDatabaseException::NotFoundError, ASCIILiteral("Failed to execute 'transaction' on 'IDBDatabase': One of the specified object stores was not found.") }; + } + + auto info = IDBTransactionInfo::clientTransaction(m_connectionProxy.get(), objectStores, mode); + + LOG(IndexedDBOperations, "IDB creating transaction: %s", info.loggingString().utf8().data()); + auto transaction = IDBTransaction::create(*this, info); + + LOG(IndexedDB, "IDBDatabase::transaction - Added active transaction %s", info.identifier().loggingString().utf8().data()); + + m_activeTransactions.set(info.identifier(), transaction.ptr()); + + return WTFMove(transaction); } -uint64_t IDBDatabase::version() const +ExceptionOr<void> IDBDatabase::deleteObjectStore(const String& objectStoreName) { - return m_metadata.version; + LOG(IndexedDB, "IDBDatabase::deleteObjectStore"); + + ASSERT(currentThread() == originThreadID()); + + if (!m_versionChangeTransaction) + return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'deleteObjectStore' on 'IDBDatabase': The database is not running a version change transaction.") }; + + if (!m_versionChangeTransaction->isActive()) + return Exception { IDBDatabaseException::TransactionInactiveError }; + + if (!m_info.hasObjectStore(objectStoreName)) + return Exception { IDBDatabaseException::NotFoundError, ASCIILiteral("Failed to execute 'deleteObjectStore' on 'IDBDatabase': The specified object store was not found.") }; + + m_info.deleteObjectStore(objectStoreName); + m_versionChangeTransaction->deleteObjectStore(objectStoreName); + + return { }; } -PassRefPtr<IDBObjectStore> IDBDatabase::createObjectStore(const String& name, const Dictionary& options, ExceptionCode& ec) +void IDBDatabase::close() { - IDBKeyPath keyPath; - bool autoIncrement = false; - if (!options.isUndefinedOrNull()) { - String keyPathString; - Vector<String> keyPathArray; - if (options.get("keyPath", keyPathArray)) - keyPath = IDBKeyPath(keyPathArray); - else if (options.getWithUndefinedOrNullCheck("keyPath", keyPathString)) - keyPath = IDBKeyPath(keyPathString); - - options.get("autoIncrement", autoIncrement); + LOG(IndexedDB, "IDBDatabase::close - %" PRIu64, m_databaseConnectionIdentifier); + + ASSERT(currentThread() == originThreadID()); + + if (!m_closePending) { + m_closePending = true; + m_connectionProxy->databaseConnectionPendingClose(*this); } - return createObjectStore(name, keyPath, autoIncrement, ec); + maybeCloseInServer(); } -PassRefPtr<IDBObjectStore> IDBDatabase::createObjectStore(const String& name, const IDBKeyPath& keyPath, bool autoIncrement, ExceptionCode& ec) +void IDBDatabase::didCloseFromServer(const IDBError& error) { - LOG(StorageAPI, "IDBDatabase::createObjectStore"); - HistogramSupport::histogramEnumeration("WebCore.IndexedDB.FrontEndAPICalls", IDBCreateObjectStoreCall, IDBMethodsMax); - if (!m_versionChangeTransaction) { - ec = IDBDatabaseException::InvalidStateError; - return 0; - } - if (!m_versionChangeTransaction->isActive()) { - ec = IDBDatabaseException::TransactionInactiveError; - return 0; - } + LOG(IndexedDB, "IDBDatabase::didCloseFromServer - %" PRIu64, m_databaseConnectionIdentifier); - if (containsObjectStore(name)) { - ec = IDBDatabaseException::ConstraintError; - return 0; - } + connectionToServerLost(error); - if (!keyPath.isNull() && !keyPath.isValid()) { - ec = IDBDatabaseException::SyntaxError; - return 0; - } + m_connectionProxy->confirmDidCloseFromServer(*this); +} - if (autoIncrement && ((keyPath.type() == IDBKeyPath::StringType && keyPath.string().isEmpty()) || keyPath.type() == IDBKeyPath::ArrayType)) { - ec = IDBDatabaseException::InvalidAccessError; - return 0; - } +void IDBDatabase::connectionToServerLost(const IDBError& error) +{ + LOG(IndexedDB, "IDBDatabase::connectionToServerLost - %" PRIu64, m_databaseConnectionIdentifier); + + ASSERT(currentThread() == originThreadID()); + + m_closePending = true; + m_closedInServer = true; + + for (auto& transaction : m_activeTransactions.values()) + transaction->connectionClosedFromServer(error); + + auto errorEvent = Event::create(m_eventNames.errorEvent, true, false); + errorEvent->setTarget(this); - int64_t objectStoreId = m_metadata.maxObjectStoreId + 1; - m_backend->createObjectStore(m_versionChangeTransaction->id(), objectStoreId, name, keyPath, autoIncrement); + if (auto* context = scriptExecutionContext()) + context->eventQueue().enqueueEvent(WTFMove(errorEvent)); - IDBObjectStoreMetadata metadata(name, objectStoreId, keyPath, autoIncrement, IDBDatabaseBackend::MinimumIndexId); - RefPtr<IDBObjectStore> objectStore = IDBObjectStore::create(metadata, m_versionChangeTransaction.get()); - m_metadata.objectStores.set(metadata.id, metadata); - ++m_metadata.maxObjectStoreId; + auto closeEvent = Event::create(m_eventNames.closeEvent, true, false); + closeEvent->setTarget(this); - m_versionChangeTransaction->objectStoreCreated(name, objectStore); - return objectStore.release(); + if (auto* context = scriptExecutionContext()) + context->eventQueue().enqueueEvent(WTFMove(closeEvent)); } -void IDBDatabase::deleteObjectStore(const String& name, ExceptionCode& ec) +void IDBDatabase::maybeCloseInServer() { - LOG(StorageAPI, "IDBDatabase::deleteObjectStore"); - HistogramSupport::histogramEnumeration("WebCore.IndexedDB.FrontEndAPICalls", IDBDeleteObjectStoreCall, IDBMethodsMax); - if (!m_versionChangeTransaction) { - ec = IDBDatabaseException::InvalidStateError; - return; - } - if (!m_versionChangeTransaction->isActive()) { - ec = IDBDatabaseException::TransactionInactiveError; + LOG(IndexedDB, "IDBDatabase::maybeCloseInServer - %" PRIu64, m_databaseConnectionIdentifier); + + ASSERT(currentThread() == originThreadID()); + + if (m_closedInServer) return; - } - int64_t objectStoreId = findObjectStoreId(name); - if (objectStoreId == IDBObjectStoreMetadata::InvalidId) { - ec = IDBDatabaseException::NotFoundError; + // 3.3.9 Database closing steps + // Wait for all transactions created using this connection to complete. + // Once they are complete, this connection is closed. + if (!m_activeTransactions.isEmpty() || !m_committingTransactions.isEmpty()) return; - } - m_backend->deleteObjectStore(m_versionChangeTransaction->id(), objectStoreId); - m_versionChangeTransaction->objectStoreDeleted(name); - m_metadata.objectStores.remove(objectStoreId); + m_closedInServer = true; + m_connectionProxy->databaseConnectionClosed(*this); } -PassRefPtr<IDBTransaction> IDBDatabase::transaction(ScriptExecutionContext* context, const Vector<String>& scope, const String& modeString, ExceptionCode& ec) +const char* IDBDatabase::activeDOMObjectName() const { - LOG(StorageAPI, "IDBDatabase::transaction"); - HistogramSupport::histogramEnumeration("WebCore.IndexedDB.FrontEndAPICalls", IDBTransactionCall, IDBMethodsMax); - if (!scope.size()) { - ec = IDBDatabaseException::InvalidAccessError; - return 0; - } + ASSERT(currentThread() == originThreadID()); + return "IDBDatabase"; +} - IndexedDB::TransactionMode mode = IDBTransaction::stringToMode(modeString, ec); - if (ec) - return 0; +bool IDBDatabase::canSuspendForDocumentSuspension() const +{ + ASSERT(currentThread() == originThreadID()); - if (m_versionChangeTransaction || m_closePending) { - ec = IDBDatabaseException::InvalidStateError; - return 0; - } + // FIXME: This value will sometimes be false when database operations are actually in progress. + // Such database operations do not yet exist. + return true; +} + +void IDBDatabase::stop() +{ + LOG(IndexedDB, "IDBDatabase::stop - %" PRIu64, m_databaseConnectionIdentifier); - Vector<int64_t> objectStoreIds; - for (size_t i = 0; i < scope.size(); ++i) { - int64_t objectStoreId = findObjectStoreId(scope[i]); - if (objectStoreId == IDBObjectStoreMetadata::InvalidId) { - ec = IDBDatabaseException::NotFoundError; - return 0; - } - objectStoreIds.append(objectStoreId); + ASSERT(currentThread() == originThreadID()); + + removeAllEventListeners(); + + Vector<IDBResourceIdentifier> transactionIdentifiers; + transactionIdentifiers.reserveInitialCapacity(m_activeTransactions.size()); + + for (auto& id : m_activeTransactions.keys()) + transactionIdentifiers.uncheckedAppend(id); + + for (auto& id : transactionIdentifiers) { + IDBTransaction* transaction = m_activeTransactions.get(id); + if (transaction) + transaction->stop(); } - int64_t transactionId = nextTransactionId(); - m_backend->createTransaction(transactionId, m_databaseCallbacks, objectStoreIds, mode); + close(); +} + +Ref<IDBTransaction> IDBDatabase::startVersionChangeTransaction(const IDBTransactionInfo& info, IDBOpenDBRequest& request) +{ + LOG(IndexedDB, "IDBDatabase::startVersionChangeTransaction %s", info.identifier().loggingString().utf8().data()); - RefPtr<IDBTransaction> transaction = IDBTransaction::create(context, transactionId, scope, mode, this); - return transaction.release(); + ASSERT(currentThread() == originThreadID()); + ASSERT(!m_versionChangeTransaction); + ASSERT(info.mode() == IDBTransactionMode::Versionchange); + ASSERT(!m_closePending); + ASSERT(scriptExecutionContext()); + + Ref<IDBTransaction> transaction = IDBTransaction::create(*this, info, request); + m_versionChangeTransaction = &transaction.get(); + + m_activeTransactions.set(transaction->info().identifier(), &transaction.get()); + + return transaction; } -PassRefPtr<IDBTransaction> IDBDatabase::transaction(ScriptExecutionContext* context, const String& storeName, const String& mode, ExceptionCode& ec) +void IDBDatabase::didStartTransaction(IDBTransaction& transaction) { - RefPtr<DOMStringList> storeNames = DOMStringList::create(); - storeNames->append(storeName); - return transaction(context, storeNames, mode, ec); + LOG(IndexedDB, "IDBDatabase::didStartTransaction %s", transaction.info().identifier().loggingString().utf8().data()); + ASSERT(!m_versionChangeTransaction); + ASSERT(currentThread() == originThreadID()); + + // It is possible for the client to have aborted a transaction before the server replies back that it has started. + if (m_abortingTransactions.contains(transaction.info().identifier())) + return; + + m_activeTransactions.set(transaction.info().identifier(), &transaction); } -void IDBDatabase::forceClose() +void IDBDatabase::willCommitTransaction(IDBTransaction& transaction) { - for (TransactionMap::const_iterator::Values it = m_transactions.begin().values(), end = m_transactions.end().values(); it != end; ++it) - (*it)->abort(IGNORE_EXCEPTION); - this->close(); + LOG(IndexedDB, "IDBDatabase::willCommitTransaction %s", transaction.info().identifier().loggingString().utf8().data()); + + ASSERT(currentThread() == originThreadID()); + + auto refTransaction = m_activeTransactions.take(transaction.info().identifier()); + ASSERT(refTransaction); + m_committingTransactions.set(transaction.info().identifier(), WTFMove(refTransaction)); } -void IDBDatabase::close() +void IDBDatabase::didCommitTransaction(IDBTransaction& transaction) { - LOG(StorageAPI, "IDBDatabase::close"); - if (m_closePending) - return; + LOG(IndexedDB, "IDBDatabase::didCommitTransaction %s", transaction.info().identifier().loggingString().utf8().data()); - m_closePending = true; + ASSERT(currentThread() == originThreadID()); + + if (m_versionChangeTransaction == &transaction) + m_info.setVersion(transaction.info().newVersion()); - if (m_transactions.isEmpty()) - closeConnection(); + didCommitOrAbortTransaction(transaction); } -void IDBDatabase::closeConnection() +void IDBDatabase::willAbortTransaction(IDBTransaction& transaction) { - ASSERT(m_closePending); - ASSERT(m_transactions.isEmpty()); + LOG(IndexedDB, "IDBDatabase::willAbortTransaction %s", transaction.info().identifier().loggingString().utf8().data()); - // Closing may result in deallocating the last transaction, which could result in deleting - // this IDBDatabase. We need the deallocation to happen after we are through. - Ref<IDBDatabase> protect(*this); + ASSERT(currentThread() == originThreadID()); - m_backend->close(m_databaseCallbacks); + auto refTransaction = m_activeTransactions.take(transaction.info().identifier()); + if (!refTransaction) + refTransaction = m_committingTransactions.take(transaction.info().identifier()); - if (m_contextStopped || !scriptExecutionContext()) - return; + ASSERT(refTransaction); + m_abortingTransactions.set(transaction.info().identifier(), WTFMove(refTransaction)); - EventQueue& eventQueue = scriptExecutionContext()->eventQueue(); - // Remove any pending versionchange events scheduled to fire on this - // connection. They would have been scheduled by the backend when another - // connection called setVersion, but the frontend connection is being - // closed before they could fire. - for (size_t i = 0; i < m_enqueuedEvents.size(); ++i) { - bool removed = eventQueue.cancelEvent(*m_enqueuedEvents[i]); - ASSERT_UNUSED(removed, removed); + if (transaction.isVersionChange()) { + ASSERT(transaction.originalDatabaseInfo()); + m_info = *transaction.originalDatabaseInfo(); + m_closePending = true; } } -void IDBDatabase::onVersionChange(uint64_t oldVersion, uint64_t newVersion, IndexedDB::VersionNullness newVersionNullness) +void IDBDatabase::didAbortTransaction(IDBTransaction& transaction) { - LOG(StorageAPI, "IDBDatabase::onVersionChange"); - if (m_contextStopped || !scriptExecutionContext()) - return; + LOG(IndexedDB, "IDBDatabase::didAbortTransaction %s", transaction.info().identifier().loggingString().utf8().data()); - if (m_closePending) - return; + ASSERT(currentThread() == originThreadID()); + + if (transaction.isVersionChange()) { + ASSERT(transaction.originalDatabaseInfo()); + ASSERT(m_info.version() == transaction.originalDatabaseInfo()->version()); + m_closePending = true; + maybeCloseInServer(); + } - enqueueEvent(IDBVersionChangeEvent::create(oldVersion, newVersion, newVersionNullness)); + didCommitOrAbortTransaction(transaction); } -void IDBDatabase::enqueueEvent(PassRefPtr<Event> event) +void IDBDatabase::didCommitOrAbortTransaction(IDBTransaction& transaction) { - ASSERT(!m_contextStopped); - ASSERT(scriptExecutionContext()); - event->setTarget(this); - scriptExecutionContext()->eventQueue().enqueueEvent(event.get()); - m_enqueuedEvents.append(event); + LOG(IndexedDB, "IDBDatabase::didCommitOrAbortTransaction %s", transaction.info().identifier().loggingString().utf8().data()); + + ASSERT(currentThread() == originThreadID()); + + if (m_versionChangeTransaction == &transaction) + m_versionChangeTransaction = nullptr; + +#ifndef NDBEBUG + unsigned count = 0; + if (m_activeTransactions.contains(transaction.info().identifier())) + ++count; + if (m_committingTransactions.contains(transaction.info().identifier())) + ++count; + if (m_abortingTransactions.contains(transaction.info().identifier())) + ++count; + + ASSERT(count == 1); +#endif + + m_activeTransactions.remove(transaction.info().identifier()); + m_committingTransactions.remove(transaction.info().identifier()); + m_abortingTransactions.remove(transaction.info().identifier()); + + if (m_closePending) + maybeCloseInServer(); } -bool IDBDatabase::dispatchEvent(PassRefPtr<Event> event) +void IDBDatabase::fireVersionChangeEvent(const IDBResourceIdentifier& requestIdentifier, uint64_t requestedVersion) { - LOG(StorageAPI, "IDBDatabase::dispatchEvent"); - ASSERT(event->type() == eventNames().versionchangeEvent); - for (size_t i = 0; i < m_enqueuedEvents.size(); ++i) { - if (m_enqueuedEvents[i].get() == event.get()) - m_enqueuedEvents.remove(i); + uint64_t currentVersion = m_info.version(); + LOG(IndexedDB, "IDBDatabase::fireVersionChangeEvent - current version %" PRIu64 ", requested version %" PRIu64 ", connection %" PRIu64 " (%p)", currentVersion, requestedVersion, m_databaseConnectionIdentifier, this); + + ASSERT(currentThread() == originThreadID()); + + if (!scriptExecutionContext() || m_closePending) { + connectionProxy().didFireVersionChangeEvent(m_databaseConnectionIdentifier, requestIdentifier); + return; } - return EventTarget::dispatchEvent(event.get()); + + Ref<Event> event = IDBVersionChangeEvent::create(requestIdentifier, currentVersion, requestedVersion, m_eventNames.versionchangeEvent); + event->setTarget(this); + scriptExecutionContext()->eventQueue().enqueueEvent(WTFMove(event)); } -int64_t IDBDatabase::findObjectStoreId(const String& name) const +bool IDBDatabase::dispatchEvent(Event& event) { - for (IDBDatabaseMetadata::ObjectStoreMap::const_iterator it = m_metadata.objectStores.begin(); it != m_metadata.objectStores.end(); ++it) { - if (it->value.name == name) { - ASSERT(it->key != IDBObjectStoreMetadata::InvalidId); - return it->key; - } - } - return IDBObjectStoreMetadata::InvalidId; + LOG(IndexedDB, "IDBDatabase::dispatchEvent (%" PRIu64 ") (%p)", m_databaseConnectionIdentifier, this); + ASSERT(currentThread() == originThreadID()); + + bool result = EventTargetWithInlineData::dispatchEvent(event); + + if (event.isVersionChangeEvent() && event.type() == m_eventNames.versionchangeEvent) + connectionProxy().didFireVersionChangeEvent(m_databaseConnectionIdentifier, downcast<IDBVersionChangeEvent>(event).requestIdentifier()); + + return result; } -bool IDBDatabase::hasPendingActivity() const +void IDBDatabase::didCreateIndexInfo(const IDBIndexInfo& info) { - // The script wrapper must not be collected before the object is closed or - // we can't fire a "versionchange" event to let script manually close the connection. - return !m_closePending && hasEventListeners() && !m_contextStopped; + ASSERT(currentThread() == originThreadID()); + + auto* objectStore = m_info.infoForExistingObjectStore(info.objectStoreIdentifier()); + ASSERT(objectStore); + objectStore->addExistingIndex(info); } -void IDBDatabase::stop() +void IDBDatabase::didDeleteIndexInfo(const IDBIndexInfo& info) { - // Stop fires at a deterministic time, so we need to call close in it. - close(); + ASSERT(currentThread() == originThreadID()); - m_contextStopped = true; + auto* objectStore = m_info.infoForExistingObjectStore(info.objectStoreIdentifier()); + ASSERT(objectStore); + objectStore->deleteIndex(info.name()); } } // namespace WebCore |