/* * Copyright (C) 2015 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 "IDBDatabaseImpl.h" #if ENABLE(INDEXED_DATABASE) #include "EventQueue.h" #include "IDBConnectionToServer.h" #include "IDBDatabaseException.h" #include "IDBOpenDBRequestImpl.h" #include "IDBResultData.h" #include "IDBTransactionImpl.h" #include "IDBVersionChangeEventImpl.h" #include "Logging.h" #include "ScriptExecutionContext.h" namespace WebCore { namespace IDBClient { Ref IDBDatabase::create(ScriptExecutionContext& context, IDBConnectionToServer& connection, const IDBResultData& resultData) { return adoptRef(*new IDBDatabase(context, connection, resultData)); } IDBDatabase::IDBDatabase(ScriptExecutionContext& context, IDBConnectionToServer& connection, const IDBResultData& resultData) : WebCore::IDBDatabase(&context) , m_serverConnection(connection) , m_info(resultData.databaseInfo()) , m_databaseConnectionIdentifier(resultData.databaseConnectionIdentifier()) { LOG(IndexedDB, "IDBDatabase::IDBDatabase - Creating database %s with version %" PRIu64 " connection %" PRIu64, m_info.name().utf8().data(), m_info.version(), m_databaseConnectionIdentifier); suspendIfNeeded(); relaxAdoptionRequirement(); m_serverConnection->registerDatabaseConnection(*this); } IDBDatabase::~IDBDatabase() { m_serverConnection->unregisterDatabaseConnection(*this); } bool IDBDatabase::hasPendingActivity() const { return !m_closedInServer; } const String IDBDatabase::name() const { return m_info.name(); } uint64_t IDBDatabase::version() const { return m_info.version(); } RefPtr IDBDatabase::objectStoreNames() const { RefPtr objectStoreNames = DOMStringList::create(); for (auto& name : m_info.objectStoreNames()) objectStoreNames->append(name); objectStoreNames->sort(); return objectStoreNames; } RefPtr IDBDatabase::createObjectStore(const String&, const Dictionary&, ExceptionCodeWithMessage&) { ASSERT_NOT_REACHED(); return nullptr; } RefPtr IDBDatabase::createObjectStore(const String& name, const IDBKeyPath& keyPath, bool autoIncrement, ExceptionCodeWithMessage& ec) { LOG(IndexedDB, "IDBDatabase::createObjectStore"); ASSERT(!m_versionChangeTransaction || m_versionChangeTransaction->isVersionChange()); if (!m_versionChangeTransaction) { ec.code = IDBDatabaseException::InvalidStateError; ec.message = ASCIILiteral("Failed to execute 'createObjectStore' on 'IDBDatabase': The database is not running a version change transaction."); return nullptr; } if (!m_versionChangeTransaction->isActive()) { ec.code = IDBDatabaseException::TransactionInactiveError; return nullptr; } if (m_info.hasObjectStore(name)) { ec.code = IDBDatabaseException::ConstraintError; ec.message = ASCIILiteral("Failed to execute 'createObjectStore' on 'IDBDatabase': An object store with the specified name already exists."); return nullptr; } if (!keyPath.isNull() && !keyPath.isValid()) { ec.code = IDBDatabaseException::SyntaxError; ec.message = ASCIILiteral("Failed to execute 'createObjectStore' on 'IDBDatabase': The keyPath option is not a valid key path."); return nullptr; } if (autoIncrement && !keyPath.isNull()) { if ((keyPath.type() == IndexedDB::KeyPathType::String && keyPath.string().isEmpty()) || keyPath.type() == IndexedDB::KeyPathType::Array) { ec.code = IDBDatabaseException::InvalidAccessError; ec.message = ASCIILiteral("Failed to execute 'createObjectStore' on 'IDBDatabase': The autoIncrement option was set but the keyPath option was empty or an array."); return nullptr; } } // Install the new ObjectStore into the connection's metadata. IDBObjectStoreInfo info = m_info.createNewObjectStore(name, keyPath, autoIncrement); // Create the actual IDBObjectStore from the transaction, which also schedules the operation server side. Ref objectStore = m_versionChangeTransaction->createObjectStore(info); return adoptRef(&objectStore.leakRef()); } RefPtr IDBDatabase::transaction(ScriptExecutionContext*, const Vector& objectStores, const String& modeString, ExceptionCodeWithMessage& ec) { LOG(IndexedDB, "IDBDatabase::transaction"); if (m_closePending) { ec.code = IDBDatabaseException::InvalidStateError; ec.message = ASCIILiteral("Failed to execute 'transaction' on 'IDBDatabase': The database connection is closing."); return nullptr; } if (objectStores.isEmpty()) { ec.code = IDBDatabaseException::InvalidAccessError; ec.message = ASCIILiteral("Failed to execute 'transaction' on 'IDBDatabase': The storeNames parameter was empty."); return nullptr; } IndexedDB::TransactionMode mode = IDBTransaction::stringToMode(modeString, ec.code); if (ec.code) { ec.message = makeString(ASCIILiteral("Failed to execute 'transaction' on 'IDBDatabase': The mode provided ('"), modeString, ASCIILiteral("') is not one of 'readonly' or 'readwrite'.")); return nullptr; } if (mode != IndexedDB::TransactionMode::ReadOnly && mode != IndexedDB::TransactionMode::ReadWrite) { ec.code = TypeError; return nullptr; } if (m_versionChangeTransaction && !m_versionChangeTransaction->isFinishedOrFinishing()) { ec.code = IDBDatabaseException::InvalidStateError; ec.message = ASCIILiteral("Failed to execute 'transaction' on 'IDBDatabase': A version change transaction is running."); return nullptr; } for (auto& objectStoreName : objectStores) { if (m_info.hasObjectStore(objectStoreName)) continue; ec.code = IDBDatabaseException::NotFoundError; ec.message = ASCIILiteral("Failed to execute 'transaction' on 'IDBDatabase': One of the specified object stores was not found."); return nullptr; } auto info = IDBTransactionInfo::clientTransaction(m_serverConnection.get(), objectStores, mode); 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.get()); return adoptRef(&transaction.leakRef()); } RefPtr IDBDatabase::transaction(ScriptExecutionContext* context, const String& objectStore, const String& mode, ExceptionCodeWithMessage& ec) { Vector objectStores(1); objectStores[0] = objectStore; return transaction(context, objectStores, mode, ec); } void IDBDatabase::deleteObjectStore(const String& objectStoreName, ExceptionCodeWithMessage& ec) { LOG(IndexedDB, "IDBDatabase::deleteObjectStore"); if (!m_versionChangeTransaction) { ec.code = IDBDatabaseException::InvalidStateError; ec.message = ASCIILiteral("Failed to execute 'deleteObjectStore' on 'IDBDatabase': The database is not running a version change transaction."); return; } if (!m_versionChangeTransaction->isActive()) { ec.code = IDBDatabaseException::TransactionInactiveError; return; } if (!m_info.hasObjectStore(objectStoreName)) { ec.code = IDBDatabaseException::NotFoundError; ec.message = ASCIILiteral("Failed to execute 'deleteObjectStore' on 'IDBDatabase': The specified object store was not found."); return; } m_info.deleteObjectStore(objectStoreName); m_versionChangeTransaction->deleteObjectStore(objectStoreName); } void IDBDatabase::close() { LOG(IndexedDB, "IDBDatabase::close - %" PRIu64, m_databaseConnectionIdentifier); m_closePending = true; maybeCloseInServer(); } void IDBDatabase::maybeCloseInServer() { LOG(IndexedDB, "IDBDatabase::maybeCloseInServer - %" PRIu64, m_databaseConnectionIdentifier); if (m_closedInServer) return; // 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()) return; m_closedInServer = true; m_serverConnection->databaseConnectionClosed(*this); } const char* IDBDatabase::activeDOMObjectName() const { return "IDBDatabase"; } bool IDBDatabase::canSuspendForDocumentSuspension() const { // 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 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(); } close(); } Ref IDBDatabase::startVersionChangeTransaction(const IDBTransactionInfo& info, IDBOpenDBRequest& request) { LOG(IndexedDB, "IDBDatabase::startVersionChangeTransaction %s", info.identifier().loggingString().utf8().data()); ASSERT(!m_versionChangeTransaction); ASSERT(info.mode() == IndexedDB::TransactionMode::VersionChange); ASSERT(!m_closePending); ASSERT(scriptExecutionContext()); Ref transaction = IDBTransaction::create(*this, info, request); m_versionChangeTransaction = &transaction.get(); m_activeTransactions.set(transaction->info().identifier(), &transaction.get()); return transaction; } void IDBDatabase::didStartTransaction(IDBTransaction& transaction) { LOG(IndexedDB, "IDBDatabase::didStartTransaction %s", transaction.info().identifier().loggingString().utf8().data()); ASSERT(!m_versionChangeTransaction); // 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::willCommitTransaction(IDBTransaction& transaction) { LOG(IndexedDB, "IDBDatabase::willCommitTransaction %s", transaction.info().identifier().loggingString().utf8().data()); auto refTransaction = m_activeTransactions.take(transaction.info().identifier()); ASSERT(refTransaction); m_committingTransactions.set(transaction.info().identifier(), WTFMove(refTransaction)); } void IDBDatabase::didCommitTransaction(IDBTransaction& transaction) { LOG(IndexedDB, "IDBDatabase::didCommitTransaction %s", transaction.info().identifier().loggingString().utf8().data()); if (m_versionChangeTransaction == &transaction) m_info.setVersion(transaction.info().newVersion()); didCommitOrAbortTransaction(transaction); } void IDBDatabase::willAbortTransaction(IDBTransaction& transaction) { LOG(IndexedDB, "IDBDatabase::willAbortTransaction %s", transaction.info().identifier().loggingString().utf8().data()); auto refTransaction = m_activeTransactions.take(transaction.info().identifier()); if (!refTransaction) refTransaction = m_committingTransactions.take(transaction.info().identifier()); ASSERT(refTransaction); m_abortingTransactions.set(transaction.info().identifier(), WTFMove(refTransaction)); if (transaction.isVersionChange()) { ASSERT(transaction.originalDatabaseInfo()); m_info = *transaction.originalDatabaseInfo(); m_closePending = true; } } void IDBDatabase::didAbortTransaction(IDBTransaction& transaction) { LOG(IndexedDB, "IDBDatabase::didAbortTransaction %s", transaction.info().identifier().loggingString().utf8().data()); if (transaction.isVersionChange()) { ASSERT(transaction.originalDatabaseInfo()); ASSERT(m_info.version() == transaction.originalDatabaseInfo()->version()); m_closePending = true; maybeCloseInServer(); } didCommitOrAbortTransaction(transaction); } void IDBDatabase::didCommitOrAbortTransaction(IDBTransaction& transaction) { LOG(IndexedDB, "IDBDatabase::didCommitOrAbortTransaction %s", transaction.info().identifier().loggingString().utf8().data()); 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(); } void IDBDatabase::fireVersionChangeEvent(const IDBResourceIdentifier& requestIdentifier, uint64_t requestedVersion) { uint64_t currentVersion = m_info.version(); LOG(IndexedDB, "IDBDatabase::fireVersionChangeEvent - current version %" PRIu64 ", requested version %" PRIu64 ", connection %" PRIu64, currentVersion, requestedVersion, m_databaseConnectionIdentifier); if (!scriptExecutionContext() || m_closePending) { serverConnection().didFireVersionChangeEvent(m_databaseConnectionIdentifier, requestIdentifier); return; } Ref event = IDBVersionChangeEvent::create(requestIdentifier, currentVersion, requestedVersion, eventNames().versionchangeEvent); event->setTarget(this); scriptExecutionContext()->eventQueue().enqueueEvent(WTFMove(event)); } bool IDBDatabase::dispatchEvent(Event& event) { LOG(IndexedDB, "IDBDatabase::dispatchEvent (%" PRIu64 ")", m_databaseConnectionIdentifier); bool result = WebCore::IDBDatabase::dispatchEvent(event); if (event.isVersionChangeEvent() && event.type() == eventNames().versionchangeEvent) serverConnection().didFireVersionChangeEvent(m_databaseConnectionIdentifier, downcast(event).requestIdentifier()); return result; } void IDBDatabase::didCreateIndexInfo(const IDBIndexInfo& info) { auto* objectStore = m_info.infoForExistingObjectStore(info.objectStoreIdentifier()); ASSERT(objectStore); objectStore->addExistingIndex(info); } void IDBDatabase::didDeleteIndexInfo(const IDBIndexInfo& info) { auto* objectStore = m_info.infoForExistingObjectStore(info.objectStoreIdentifier()); ASSERT(objectStore); objectStore->deleteIndex(info.name()); } } // namespace IDBClient } // namespace WebCore #endif // ENABLE(INDEXED_DATABASE)