summaryrefslogtreecommitdiff
path: root/Source/WebCore/Modules/indexeddb/IDBDatabase.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/Modules/indexeddb/IDBDatabase.cpp')
-rw-r--r--Source/WebCore/Modules/indexeddb/IDBDatabase.cpp626
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