diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/Modules/indexeddb/IDBObjectStore.cpp | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebCore/Modules/indexeddb/IDBObjectStore.cpp')
-rw-r--r-- | Source/WebCore/Modules/indexeddb/IDBObjectStore.cpp | 969 |
1 files changed, 539 insertions, 430 deletions
diff --git a/Source/WebCore/Modules/indexeddb/IDBObjectStore.cpp b/Source/WebCore/Modules/indexeddb/IDBObjectStore.cpp index 8d735ad6f..592c67d3b 100644 --- a/Source/WebCore/Modules/indexeddb/IDBObjectStore.cpp +++ b/Source/WebCore/Modules/indexeddb/IDBObjectStore.cpp @@ -1,26 +1,26 @@ /* - * Copyright (C) 2010 Google Inc. All rights reserved. + * 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. * - * 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,565 +29,674 @@ #if ENABLE(INDEXED_DATABASE) #include "DOMStringList.h" -#include "IDBAny.h" +#include "Document.h" +#include "ExceptionCode.h" #include "IDBBindingUtilities.h" -#include "IDBCursorWithValue.h" +#include "IDBCursor.h" #include "IDBDatabase.h" #include "IDBDatabaseException.h" +#include "IDBError.h" +#include "IDBGetRecordData.h" #include "IDBIndex.h" #include "IDBKey.h" -#include "IDBKeyPath.h" -#include "IDBKeyRange.h" +#include "IDBKeyRangeData.h" +#include "IDBRequest.h" #include "IDBTransaction.h" +#include "IndexedDB.h" #include "Logging.h" +#include "Page.h" #include "ScriptExecutionContext.h" +#include "ScriptState.h" #include "SerializedScriptValue.h" -#include "SharedBuffer.h" +#include <heap/HeapInlines.h> +#include <wtf/Locker.h> + +using namespace JSC; namespace WebCore { -IDBObjectStore::IDBObjectStore(const IDBObjectStoreMetadata& metadata, IDBTransaction* transaction) - : m_metadata(metadata) +IDBObjectStore::IDBObjectStore(ScriptExecutionContext& context, const IDBObjectStoreInfo& info, IDBTransaction& transaction) + : ActiveDOMObject(&context) + , m_info(info) + , m_originalInfo(info) , m_transaction(transaction) - , m_deleted(false) { - ASSERT(m_transaction); - // We pass a reference to this object before it can be adopted. - relaxAdoptionRequirement(); + ASSERT(currentThread() == m_transaction.database().originThreadID()); + + suspendIfNeeded(); } -PassRefPtr<DOMStringList> IDBObjectStore::indexNames() const +IDBObjectStore::~IDBObjectStore() { - LOG(StorageAPI, "IDBObjectStore::indexNames"); - RefPtr<DOMStringList> indexNames = DOMStringList::create(); - for (IDBObjectStoreMetadata::IndexMap::const_iterator it = m_metadata.indexes.begin(); it != m_metadata.indexes.end(); ++it) - indexNames->append(it->value.name); - indexNames->sort(); - return indexNames.release(); + ASSERT(currentThread() == m_transaction.database().originThreadID()); } -PassRefPtr<IDBRequest> IDBObjectStore::get(ScriptExecutionContext* context, PassRefPtr<IDBKeyRange> keyRange, ExceptionCode& ec) +const char* IDBObjectStore::activeDOMObjectName() const { - LOG(StorageAPI, "IDBObjectStore::get"); - if (m_deleted) { - ec = IDBDatabaseException::InvalidStateError; - return 0; - } - if (!keyRange) { - ec = IDBDatabaseException::DataError; - return 0; - } - if (!m_transaction->isActive()) { - ec = IDBDatabaseException::TransactionInactiveError; - return 0; - } - RefPtr<IDBRequest> request = IDBRequest::create(context, IDBAny::create(this), m_transaction.get()); - backendDB()->get(m_transaction->id(), id(), IDBIndexMetadata::InvalidId, keyRange, false, request); - return request.release(); + return "IDBObjectStore"; +} + +bool IDBObjectStore::canSuspendForDocumentSuspension() const +{ + return false; +} + +bool IDBObjectStore::hasPendingActivity() const +{ + return !m_transaction.isFinished(); } -PassRefPtr<IDBRequest> IDBObjectStore::get(ScriptExecutionContext* context, const Deprecated::ScriptValue& key, ExceptionCode& ec) +const String& IDBObjectStore::name() const { - RefPtr<IDBKeyRange> keyRange = IDBKeyRange::only(context, key, ec); - if (ec) - return 0; - return get(context, keyRange.release(), ec); + ASSERT(currentThread() == m_transaction.database().originThreadID()); + return m_info.name(); } -static void generateIndexKeysForValue(DOMRequestState* requestState, const IDBIndexMetadata& indexMetadata, const Deprecated::ScriptValue& objectValue, IDBObjectStore::IndexKeys* indexKeys) +ExceptionOr<void> IDBObjectStore::setName(const String& name) { - ASSERT(indexKeys); - RefPtr<IDBKey> indexKey = createIDBKeyFromScriptValueAndKeyPath(requestState, objectValue, indexMetadata.keyPath); + ASSERT(currentThread() == m_transaction.database().originThreadID()); - if (!indexKey) - return; + if (m_deleted) + return Exception { INVALID_STATE_ERR, ASCIILiteral("Failed set property 'name' on 'IDBObjectStore': The object store has been deleted.") }; - if (!indexMetadata.multiEntry || indexKey->type() != IDBKey::ArrayType) { - if (!indexKey->isValid()) - return; + if (!m_transaction.isVersionChange()) + return Exception { INVALID_STATE_ERR, ASCIILiteral("Failed set property 'name' on 'IDBObjectStore': The object store's transaction is not a version change transaction.") }; - indexKeys->append(indexKey); - } else { - ASSERT(indexMetadata.multiEntry); - ASSERT(indexKey->type() == IDBKey::ArrayType); - indexKey = IDBKey::createMultiEntryArray(indexKey->array()); + if (!m_transaction.isActive()) + return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed set property 'name' on 'IDBObjectStore': The object store's transaction is not active.") }; + + if (m_info.name() == name) + return { }; + + if (m_transaction.database().info().hasObjectStore(name)) + return Exception { IDBDatabaseException::ConstraintError, makeString("Failed set property 'name' on 'IDBObjectStore': The database already has an object store named '", name, "'.") }; + + m_transaction.database().renameObjectStore(*this, name); + m_info.rename(name); + + return { }; +} + +const std::optional<IDBKeyPath>& IDBObjectStore::keyPath() const +{ + ASSERT(currentThread() == m_transaction.database().originThreadID()); + return m_info.keyPath(); +} - for (size_t i = 0; i < indexKey->array().size(); ++i) - indexKeys->append(indexKey->array()[i]); +RefPtr<DOMStringList> IDBObjectStore::indexNames() const +{ + ASSERT(currentThread() == m_transaction.database().originThreadID()); + + RefPtr<DOMStringList> indexNames = DOMStringList::create(); + + if (!m_deleted) { + for (auto& name : m_info.indexNames()) + indexNames->append(name); + indexNames->sort(); } + + return indexNames; } -PassRefPtr<IDBRequest> IDBObjectStore::add(JSC::ExecState* state, Deprecated::ScriptValue& value, const Deprecated::ScriptValue& key, ExceptionCode& ec) +IDBTransaction& IDBObjectStore::transaction() { - LOG(StorageAPI, "IDBObjectStore::add"); - return put(IDBDatabaseBackend::AddOnly, IDBAny::create(this), state, value, key, ec); + ASSERT(currentThread() == m_transaction.database().originThreadID()); + return m_transaction; } -PassRefPtr<IDBRequest> IDBObjectStore::add(JSC::ExecState* state, Deprecated::ScriptValue& value, ExceptionCode& ec) +bool IDBObjectStore::autoIncrement() const { - LOG(StorageAPI, "IDBObjectStore::add"); - return put(IDBDatabaseBackend::AddOnly, IDBAny::create(this), state, value, static_cast<IDBKey*>(0), ec); + ASSERT(currentThread() == m_transaction.database().originThreadID()); + return m_info.autoIncrement(); } -PassRefPtr<IDBRequest> IDBObjectStore::put(JSC::ExecState* state, Deprecated::ScriptValue& value, const Deprecated::ScriptValue& key, ExceptionCode& ec) +ExceptionOr<Ref<IDBRequest>> IDBObjectStore::openCursor(ExecState& execState, RefPtr<IDBKeyRange> range, IDBCursorDirection direction) { - LOG(StorageAPI, "IDBObjectStore::put"); - return put(IDBDatabaseBackend::AddOrUpdate, IDBAny::create(this), state, value, key, ec); + LOG(IndexedDB, "IDBObjectStore::openCursor"); + ASSERT(currentThread() == m_transaction.database().originThreadID()); + + if (m_deleted) + return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'openCursor' on 'IDBObjectStore': The object store has been deleted.") }; + + if (!m_transaction.isActive()) + return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'openCursor' on 'IDBObjectStore': The transaction is inactive or finished.") }; + + auto info = IDBCursorInfo::objectStoreCursor(m_transaction, m_info.identifier(), range.get(), direction, IndexedDB::CursorType::KeyAndValue); + return m_transaction.requestOpenCursor(execState, *this, info); } -PassRefPtr<IDBRequest> IDBObjectStore::put(JSC::ExecState* state, Deprecated::ScriptValue& value, ExceptionCode& ec) +ExceptionOr<Ref<IDBRequest>> IDBObjectStore::openCursor(ExecState& execState, JSValue key, IDBCursorDirection direction) { - LOG(StorageAPI, "IDBObjectStore::put"); - return put(IDBDatabaseBackend::AddOrUpdate, IDBAny::create(this), state, value, static_cast<IDBKey*>(0), ec); + auto onlyResult = IDBKeyRange::only(execState, key); + if (onlyResult.hasException()) + return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'openCursor' on 'IDBObjectStore': The parameter is not a valid key.") }; + + return openCursor(execState, onlyResult.releaseReturnValue(), direction); } -PassRefPtr<IDBRequest> IDBObjectStore::put(IDBDatabaseBackend::PutMode putMode, PassRefPtr<IDBAny> source, JSC::ExecState* state, Deprecated::ScriptValue& value, const Deprecated::ScriptValue& keyValue, ExceptionCode& ec) +ExceptionOr<Ref<IDBRequest>> IDBObjectStore::openKeyCursor(ExecState& execState, RefPtr<IDBKeyRange> range, IDBCursorDirection direction) { - ScriptExecutionContext* context = scriptExecutionContextFromExecState(state); - DOMRequestState requestState(context); - RefPtr<IDBKey> key = scriptValueToIDBKey(&requestState, keyValue); - return put(putMode, source, state, value, key.release(), ec); + LOG(IndexedDB, "IDBObjectStore::openCursor"); + ASSERT(currentThread() == m_transaction.database().originThreadID()); + + if (m_deleted) + return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'openKeyCursor' on 'IDBObjectStore': The object store has been deleted.") }; + + if (!m_transaction.isActive()) + return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'openKeyCursor' on 'IDBObjectStore': The transaction is inactive or finished.") }; + + auto info = IDBCursorInfo::objectStoreCursor(m_transaction, m_info.identifier(), range.get(), direction, IndexedDB::CursorType::KeyOnly); + return m_transaction.requestOpenCursor(execState, *this, info); } -PassRefPtr<IDBRequest> IDBObjectStore::put(IDBDatabaseBackend::PutMode putMode, PassRefPtr<IDBAny> source, JSC::ExecState* state, Deprecated::ScriptValue& value, PassRefPtr<IDBKey> prpKey, ExceptionCode& ec) +ExceptionOr<Ref<IDBRequest>> IDBObjectStore::openKeyCursor(ExecState& execState, JSValue key, IDBCursorDirection direction) { - RefPtr<IDBKey> key = prpKey; - if (m_deleted) { - ec = IDBDatabaseException::InvalidStateError; - return 0; - } - if (!m_transaction->isActive()) { - ec = IDBDatabaseException::TransactionInactiveError; - return 0; - } - if (m_transaction->isReadOnly()) { - ec = IDBDatabaseException::ReadOnlyError; - return 0; - } + auto onlyResult = IDBKeyRange::only(execState, key); + if (onlyResult.hasException()) + return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'openKeyCursor' on 'IDBObjectStore': The parameter is not a valid key or key range.") }; - // FIXME: Expose the JS engine exception state through ScriptState. - bool didThrow = false; - RefPtr<SerializedScriptValue> serializedValue = SerializedScriptValue::serialize(value, state, nullptr, nullptr, didThrow); - if (didThrow) { - // Setting an explicit ExceptionCode here would defer handling the already thrown exception. - return 0; - } + return openKeyCursor(execState, onlyResult.releaseReturnValue(), direction); +} - if (serializedValue->hasBlobURLs()) { - // FIXME: Add Blob/File/FileList support - ec = IDBDatabaseException::DataCloneError; - return 0; - } +ExceptionOr<Ref<IDBRequest>> IDBObjectStore::get(ExecState& execState, JSValue key) +{ + LOG(IndexedDB, "IDBObjectStore::get"); + ASSERT(currentThread() == m_transaction.database().originThreadID()); - const IDBKeyPath& keyPath = m_metadata.keyPath; - const bool usesInLineKeys = !keyPath.isNull(); - const bool hasKeyGenerator = autoIncrement(); + if (m_deleted) + return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'get' on 'IDBObjectStore': The object store has been deleted.") }; - ScriptExecutionContext* context = scriptExecutionContextFromExecState(state); - DOMRequestState requestState(context); + if (!m_transaction.isActive()) + return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'get' on 'IDBObjectStore': The transaction is inactive or finished.") }; - if (putMode != IDBDatabaseBackend::CursorUpdate && usesInLineKeys && key) { - ec = IDBDatabaseException::DataError; - return 0; - } - if (!usesInLineKeys && !hasKeyGenerator && !key) { - ec = IDBDatabaseException::DataError; - return 0; - } - if (usesInLineKeys) { - RefPtr<IDBKey> keyPathKey = createIDBKeyFromScriptValueAndKeyPath(&requestState, value, keyPath); - if (keyPathKey && !keyPathKey->isValid()) { - ec = IDBDatabaseException::DataError; - return 0; - } - if (!hasKeyGenerator && !keyPathKey) { - ec = IDBDatabaseException::DataError; - return 0; - } - if (hasKeyGenerator && !keyPathKey) { - if (!canInjectIDBKeyIntoScriptValue(&requestState, value, keyPath)) { - ec = IDBDatabaseException::DataError; - return 0; - } - } - if (keyPathKey) - key = keyPathKey; - } - if (key && !key->isValid()) { - ec = IDBDatabaseException::DataError; - return 0; - } + auto idbKey = scriptValueToIDBKey(execState, key); + if (!idbKey->isValid()) + return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'get' on 'IDBObjectStore': The parameter is not a valid key.") }; - Vector<int64_t> indexIds; - Vector<IndexKeys> indexKeys; - for (IDBObjectStoreMetadata::IndexMap::const_iterator it = m_metadata.indexes.begin(); it != m_metadata.indexes.end(); ++it) { - IndexKeys keys; - generateIndexKeysForValue(&requestState, it->value, value, &keys); - indexIds.append(it->key); - indexKeys.append(keys); - } + return m_transaction.requestGetRecord(execState, *this, { idbKey.ptr(), IDBGetRecordDataType::KeyAndValue }); +} + +ExceptionOr<Ref<IDBRequest>> IDBObjectStore::get(ExecState& execState, IDBKeyRange* keyRange) +{ + LOG(IndexedDB, "IDBObjectStore::get"); + ASSERT(currentThread() == m_transaction.database().originThreadID()); + + if (m_deleted) + return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'get' on 'IDBObjectStore': The object store has been deleted.") }; + + if (!m_transaction.isActive()) + return Exception { IDBDatabaseException::TransactionInactiveError }; - RefPtr<IDBRequest> request = IDBRequest::create(context, source, m_transaction.get()); - Vector<uint8_t> valueBytes = serializedValue->toWireBytes(); - // This is a hack to account for disagreements about whether SerializedScriptValue should deal in Vector<uint8_t> or Vector<char>. - // See https://lists.webkit.org/pipermail/webkit-dev/2013-February/023682.html - Vector<char>* valueBytesSigned = reinterpret_cast<Vector<char>*>(&valueBytes); - RefPtr<SharedBuffer> valueBuffer = SharedBuffer::adoptVector(*valueBytesSigned); - backendDB()->put(m_transaction->id(), id(), valueBuffer, key.release(), static_cast<IDBDatabaseBackend::PutMode>(putMode), request, indexIds, indexKeys); - return request.release(); + IDBKeyRangeData keyRangeData(keyRange); + if (!keyRangeData.isValid()) + return Exception { IDBDatabaseException::DataError }; + + return m_transaction.requestGetRecord(execState, *this, { keyRangeData, IDBGetRecordDataType::KeyAndValue }); } -PassRefPtr<IDBRequest> IDBObjectStore::deleteFunction(ScriptExecutionContext* context, PassRefPtr<IDBKeyRange> keyRange, ExceptionCode& ec) +ExceptionOr<Ref<IDBRequest>> IDBObjectStore::getKey(ExecState& execState, JSValue key) { - LOG(StorageAPI, "IDBObjectStore::delete"); - if (m_deleted) { - ec = IDBDatabaseException::InvalidStateError; - return 0; - } - if (!m_transaction->isActive()) { - ec = IDBDatabaseException::TransactionInactiveError; - return 0; - } - if (m_transaction->isReadOnly()) { - ec = IDBDatabaseException::ReadOnlyError; - return 0; - } - if (!keyRange) { - ec = IDBDatabaseException::DataError; - return 0; - } + LOG(IndexedDB, "IDBObjectStore::getKey"); + ASSERT(currentThread() == m_transaction.database().originThreadID()); + + if (m_deleted) + return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'getKey' on 'IDBObjectStore': The object store has been deleted.") }; - RefPtr<IDBRequest> request = IDBRequest::create(context, IDBAny::create(this), m_transaction.get()); - backendDB()->deleteRange(m_transaction->id(), id(), keyRange, request); - return request.release(); + if (!m_transaction.isActive()) + return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'getKey' on 'IDBObjectStore': The transaction is inactive or finished.") }; + + auto idbKey = scriptValueToIDBKey(execState, key); + if (!idbKey->isValid()) + return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'getKey' on 'IDBObjectStore': The parameter is not a valid key.") }; + + return m_transaction.requestGetRecord(execState, *this, { idbKey.ptr(), IDBGetRecordDataType::KeyOnly }); } -PassRefPtr<IDBRequest> IDBObjectStore::deleteFunction(ScriptExecutionContext* context, const Deprecated::ScriptValue& key, ExceptionCode& ec) +ExceptionOr<Ref<IDBRequest>> IDBObjectStore::getKey(ExecState& execState, IDBKeyRange* keyRange) { - RefPtr<IDBKeyRange> keyRange = IDBKeyRange::only(context, key, ec); - if (ec) - return 0; - return deleteFunction(context, keyRange.release(), ec); + LOG(IndexedDB, "IDBObjectStore::getKey"); + ASSERT(currentThread() == m_transaction.database().originThreadID()); + + if (m_deleted) + return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'getKey' on 'IDBObjectStore': The object store has been deleted.") }; + + if (!m_transaction.isActive()) + return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'getKey' on 'IDBObjectStore': The transaction is inactive or finished.") }; + + IDBKeyRangeData keyRangeData(keyRange); + if (!keyRangeData.isValid()) + return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'getKey' on 'IDBObjectStore': The parameter is not a valid key range.") }; + + return m_transaction.requestGetRecord(execState, *this, { keyRangeData, IDBGetRecordDataType::KeyOnly }); } -PassRefPtr<IDBRequest> IDBObjectStore::clear(ScriptExecutionContext* context, ExceptionCode& ec) +ExceptionOr<Ref<IDBRequest>> IDBObjectStore::add(ExecState& execState, JSValue value, JSValue key) { - LOG(StorageAPI, "IDBObjectStore::clear"); - if (m_deleted) { - ec = IDBDatabaseException::InvalidStateError; - return 0; - } - if (!m_transaction->isActive()) { - ec = IDBDatabaseException::TransactionInactiveError; - return 0; - } - if (m_transaction->isReadOnly()) { - ec = IDBDatabaseException::ReadOnlyError; - return 0; - } + RefPtr<IDBKey> idbKey; + if (!key.isUndefined()) + idbKey = scriptValueToIDBKey(execState, key); + return putOrAdd(execState, value, idbKey, IndexedDB::ObjectStoreOverwriteMode::NoOverwrite, InlineKeyCheck::Perform); +} - RefPtr<IDBRequest> request = IDBRequest::create(context, IDBAny::create(this), m_transaction.get()); - backendDB()->clearObjectStore(m_transaction->id(), id(), request); - return request.release(); +ExceptionOr<Ref<IDBRequest>> IDBObjectStore::put(ExecState& execState, JSValue value, JSValue key) +{ + RefPtr<IDBKey> idbKey; + if (!key.isUndefined()) + idbKey = scriptValueToIDBKey(execState, key); + return putOrAdd(execState, value, idbKey, IndexedDB::ObjectStoreOverwriteMode::Overwrite, InlineKeyCheck::Perform); } -namespace { -// This class creates the index keys for a given index by extracting -// them from the SerializedScriptValue, for all the existing values in -// the objectStore. It only needs to be kept alive by virtue of being -// a listener on an IDBRequest object, in the same way that JavaScript -// cursor success handlers are kept alive. -class IndexPopulator : public EventListener { -public: - static PassRefPtr<IndexPopulator> create(PassRefPtr<IDBDatabaseBackend> backend, int64_t transactionId, int64_t objectStoreId, const IDBIndexMetadata& indexMetadata) - { - return adoptRef(new IndexPopulator(backend, transactionId, objectStoreId, indexMetadata)); +ExceptionOr<Ref<IDBRequest>> IDBObjectStore::putForCursorUpdate(ExecState& state, JSValue value, JSValue key) +{ + return putOrAdd(state, value, scriptValueToIDBKey(state, key), IndexedDB::ObjectStoreOverwriteMode::OverwriteForCursor, InlineKeyCheck::DoNotPerform); +} + +ExceptionOr<Ref<IDBRequest>> IDBObjectStore::putOrAdd(ExecState& state, JSValue value, RefPtr<IDBKey> key, IndexedDB::ObjectStoreOverwriteMode overwriteMode, InlineKeyCheck inlineKeyCheck) +{ + VM& vm = state.vm(); + auto scope = DECLARE_CATCH_SCOPE(vm); + + LOG(IndexedDB, "IDBObjectStore::putOrAdd"); + ASSERT(currentThread() == m_transaction.database().originThreadID()); + + auto context = scriptExecutionContextFromExecState(&state); + if (!context) + return Exception { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to store record in object store because it does not have a valid script execution context") }; + + if (m_deleted) + return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to store record in an IDBObjectStore: The object store has been deleted.") }; + + if (!m_transaction.isActive()) + return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to store record in an IDBObjectStore: The transaction is inactive or finished.") }; + + if (m_transaction.isReadOnly()) + return Exception { IDBDatabaseException::ReadOnlyError, ASCIILiteral("Failed to store record in an IDBObjectStore: The transaction is read-only.") }; + + auto serializedValue = SerializedScriptValue::create(state, value); + if (UNLIKELY(scope.exception())) { + // Clear the DOM exception from the serializer so we can give a more targeted exception. + scope.clearException(); + + return Exception { IDBDatabaseException::DataCloneError, ASCIILiteral("Failed to store record in an IDBObjectStore: An object could not be cloned.") }; } - virtual bool operator==(const EventListener& other) - { - return this == &other; + bool privateBrowsingEnabled = false; + if (context->isDocument()) { + if (auto* page = static_cast<Document*>(context)->page()) + privateBrowsingEnabled = page->sessionID().isEphemeral(); } -private: - IndexPopulator(PassRefPtr<IDBDatabaseBackend> backend, int64_t transactionId, int64_t objectStoreId, const IDBIndexMetadata& indexMetadata) - : EventListener(CPPEventListenerType) - , m_databaseBackend(backend) - , m_transactionId(transactionId) - , m_objectStoreId(objectStoreId) - , m_indexMetadata(indexMetadata) - { + if (serializedValue->hasBlobURLs() && privateBrowsingEnabled) { + // https://bugs.webkit.org/show_bug.cgi?id=156347 - Support Blobs in private browsing. + return Exception { IDBDatabaseException::DataCloneError, ASCIILiteral("Failed to store record in an IDBObjectStore: BlobURLs are not yet supported.") }; } - virtual void handleEvent(ScriptExecutionContext*, Event* event) - { - ASSERT(event->type() == eventNames().successEvent); - EventTarget* target = event->target(); - IDBRequest* request = static_cast<IDBRequest*>(target); - - RefPtr<IDBAny> cursorAny = request->result(ASSERT_NO_EXCEPTION); - RefPtr<IDBCursorWithValue> cursor; - if (cursorAny->type() == IDBAny::IDBCursorWithValueType) - cursor = cursorAny->idbCursorWithValue(); - - Vector<int64_t, 1> indexIds; - indexIds.append(m_indexMetadata.id); - if (cursor) { - cursor->continueFunction(static_cast<IDBKey*>(0), ASSERT_NO_EXCEPTION); - - RefPtr<IDBKey> primaryKey = cursor->idbPrimaryKey(); - Deprecated::ScriptValue value = cursor->value(); - - IDBObjectStore::IndexKeys indexKeys; - generateIndexKeysForValue(request->requestState(), m_indexMetadata, value, &indexKeys); - - Vector<IDBObjectStore::IndexKeys, 1> indexKeysList; - indexKeysList.append(indexKeys); - - m_databaseBackend->setIndexKeys(m_transactionId, m_objectStoreId, primaryKey, indexIds, indexKeysList); - } else { - // Now that we are done indexing, tell the backend to go - // back to processing tasks of type NormalTask. - m_databaseBackend->setIndexesReady(m_transactionId, m_objectStoreId, indexIds); - m_databaseBackend.clear(); + if (key && !key->isValid()) + return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to store record in an IDBObjectStore: The parameter is not a valid key.") }; + + bool usesInlineKeys = !!m_info.keyPath(); + bool usesKeyGenerator = autoIncrement(); + if (usesInlineKeys && inlineKeyCheck == InlineKeyCheck::Perform) { + if (key) + return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to store record in an IDBObjectStore: The object store uses in-line keys and the key parameter was provided.") }; + + RefPtr<IDBKey> keyPathKey = maybeCreateIDBKeyFromScriptValueAndKeyPath(state, value, m_info.keyPath().value()); + if (keyPathKey && !keyPathKey->isValid()) + return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to store record in an IDBObjectStore: Evaluating the object store's key path yielded a value that is not a valid key.") }; + + if (!keyPathKey) { + if (!usesKeyGenerator) + return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to store record in an IDBObjectStore: Evaluating the object store's key path did not yield a value.") }; + if (!canInjectIDBKeyIntoScriptValue(state, value, m_info.keyPath().value())) + return Exception { IDBDatabaseException::DataError }; } - } + if (keyPathKey) { + ASSERT(!key); + key = keyPathKey; + } + } else if (!usesKeyGenerator && !key) + return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to store record in an IDBObjectStore: The object store uses out-of-line keys and has no key generator and the key parameter was not provided.") }; - RefPtr<IDBDatabaseBackend> m_databaseBackend; - const int64_t m_transactionId; - const int64_t m_objectStoreId; - const IDBIndexMetadata m_indexMetadata; -}; + return m_transaction.requestPutOrAdd(state, *this, key.get(), *serializedValue, overwriteMode); } -PassRefPtr<IDBIndex> IDBObjectStore::createIndex(ScriptExecutionContext* context, const String& name, const IDBKeyPath& keyPath, const Dictionary& options, ExceptionCode& ec) +ExceptionOr<Ref<IDBRequest>> IDBObjectStore::deleteFunction(ExecState& execState, IDBKeyRange* keyRange) { - bool unique = false; - options.get("unique", unique); + return doDelete(execState, keyRange); +} + +ExceptionOr<Ref<IDBRequest>> IDBObjectStore::doDelete(ExecState& execState, IDBKeyRange* keyRange) +{ + LOG(IndexedDB, "IDBObjectStore::deleteFunction"); + ASSERT(currentThread() == m_transaction.database().originThreadID()); + + // The IDB spec for several IDBObjectStore methods states that transaction related exceptions should fire before + // the exception for an object store being deleted. + // However, a handful of W3C IDB tests expect the deleted exception even though the transaction inactive exception also applies. + // Additionally, Chrome and Edge agree with the test, as does Legacy IDB in WebKit. + // Until this is sorted out, we'll agree with the test and the majority share browsers. + if (m_deleted) + return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'delete' on 'IDBObjectStore': The object store has been deleted.") }; + + if (!m_transaction.isActive()) + return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'delete' on 'IDBObjectStore': The transaction is inactive or finished.") }; - bool multiEntry = false; - options.get("multiEntry", multiEntry); + if (m_transaction.isReadOnly()) + return Exception { IDBDatabaseException::ReadOnlyError, ASCIILiteral("Failed to execute 'delete' on 'IDBObjectStore': The transaction is read-only.") }; - return createIndex(context, name, keyPath, unique, multiEntry, ec); + IDBKeyRangeData keyRangeData(keyRange); + if (!keyRangeData.isValid()) + return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'delete' on 'IDBObjectStore': The parameter is not a valid key range.") }; + + return m_transaction.requestDeleteRecord(execState, *this, keyRangeData); } -PassRefPtr<IDBIndex> IDBObjectStore::createIndex(ScriptExecutionContext* context, const String& name, const IDBKeyPath& keyPath, bool unique, bool multiEntry, ExceptionCode& ec) +ExceptionOr<Ref<IDBRequest>> IDBObjectStore::deleteFunction(ExecState& execState, JSValue key) { - LOG(StorageAPI, "IDBObjectStore::createIndex"); - if (!m_transaction->isVersionChange() || m_deleted) { - ec = IDBDatabaseException::InvalidStateError; - return 0; - } - if (!m_transaction->isActive()) { - ec = IDBDatabaseException::TransactionInactiveError; - return 0; - } - if (!keyPath.isValid()) { - ec = IDBDatabaseException::SyntaxError; - return 0; - } - if (name.isNull()) { - ec = TypeError; - return 0; - } - if (containsIndex(name)) { - ec = IDBDatabaseException::ConstraintError; - return 0; - } + Ref<IDBKey> idbKey = scriptValueToIDBKey(execState, key); + if (!idbKey->isValid()) + return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'delete' on 'IDBObjectStore': The parameter is not a valid key.") }; + return doDelete(execState, IDBKeyRange::create(WTFMove(idbKey)).ptr()); +} - if (keyPath.type() == IDBKeyPath::ArrayType && multiEntry) { - ec = IDBDatabaseException::InvalidAccessError; - return 0; - } +ExceptionOr<Ref<IDBRequest>> IDBObjectStore::clear(ExecState& execState) +{ + LOG(IndexedDB, "IDBObjectStore::clear"); + ASSERT(currentThread() == m_transaction.database().originThreadID()); + + // The IDB spec for several IDBObjectStore methods states that transaction related exceptions should fire before + // the exception for an object store being deleted. + // However, a handful of W3C IDB tests expect the deleted exception even though the transaction inactive exception also applies. + // Additionally, Chrome and Edge agree with the test, as does Legacy IDB in WebKit. + // Until this is sorted out, we'll agree with the test and the majority share browsers. + if (m_deleted) + return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'clear' on 'IDBObjectStore': The object store has been deleted.") }; + + if (!m_transaction.isActive()) + return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'clear' on 'IDBObjectStore': The transaction is inactive or finished.") }; - int64_t indexId = m_metadata.maxIndexId + 1; - backendDB()->createIndex(m_transaction->id(), id(), indexId, name, keyPath, unique, multiEntry); + if (m_transaction.isReadOnly()) + return Exception { IDBDatabaseException::ReadOnlyError, ASCIILiteral("Failed to execute 'clear' on 'IDBObjectStore': The transaction is read-only.") }; - ++m_metadata.maxIndexId; + return m_transaction.requestClearObjectStore(execState, *this); +} + +ExceptionOr<Ref<IDBIndex>> IDBObjectStore::createIndex(ExecState&, const String& name, IDBKeyPath&& keyPath, const IndexParameters& parameters) +{ + LOG(IndexedDB, "IDBObjectStore::createIndex %s (keyPath: %s, unique: %i, multiEntry: %i)", name.utf8().data(), loggingString(keyPath).utf8().data(), parameters.unique, parameters.multiEntry); + ASSERT(currentThread() == m_transaction.database().originThreadID()); + + if (!m_transaction.isVersionChange()) + return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'createIndex' on 'IDBObjectStore': The database is not running a version change transaction.") }; + + if (m_deleted) + return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'createIndex' on 'IDBObjectStore': The object store has been deleted.") }; + + if (!m_transaction.isActive()) + return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'createIndex' on 'IDBObjectStore': The transaction is inactive.")}; - IDBIndexMetadata metadata(name, indexId, keyPath, unique, multiEntry); - RefPtr<IDBIndex> index = IDBIndex::create(metadata, this, m_transaction.get()); - m_indexMap.set(name, index); - m_metadata.indexes.set(indexId, metadata); + if (m_info.hasIndex(name)) + return Exception { IDBDatabaseException::ConstraintError, ASCIILiteral("Failed to execute 'createIndex' on 'IDBObjectStore': An index with the specified name already exists.") }; - ASSERT(!ec); - if (ec) - return 0; + if (!isIDBKeyPathValid(keyPath)) + return Exception { IDBDatabaseException::SyntaxError, ASCIILiteral("Failed to execute 'createIndex' on 'IDBObjectStore': The keyPath argument contains an invalid key path.") }; - RefPtr<IDBRequest> indexRequest = openCursor(context, static_cast<IDBKeyRange*>(0), IDBCursor::directionNext(), IDBDatabaseBackend::PreemptiveTask, ec); - ASSERT(!ec); - if (ec) - return 0; - indexRequest->preventPropagation(); + if (name.isNull()) + return Exception { TypeError }; - // This is kept alive by being the success handler of the request, which is in turn kept alive by the owning transaction. - RefPtr<IndexPopulator> indexPopulator = IndexPopulator::create(backendDB(), m_transaction->id(), id(), metadata); - indexRequest->setOnsuccess(indexPopulator); + if (parameters.multiEntry && WTF::holds_alternative<Vector<String>>(keyPath)) + return Exception { IDBDatabaseException::InvalidAccessError, ASCIILiteral("Failed to execute 'createIndex' on 'IDBObjectStore': The keyPath argument was an array and the multiEntry option is true.") }; - return index.release(); + // Install the new Index into the ObjectStore's info. + IDBIndexInfo info = m_info.createNewIndex(name, WTFMove(keyPath), parameters.unique, parameters.multiEntry); + m_transaction.database().didCreateIndexInfo(info); + + // Create the actual IDBObjectStore from the transaction, which also schedules the operation server side. + auto index = m_transaction.createIndex(*this, info); + + Ref<IDBIndex> referencedIndex { *index }; + + Locker<Lock> locker(m_referencedIndexLock); + m_referencedIndexes.set(name, WTFMove(index)); + + return WTFMove(referencedIndex); } -PassRefPtr<IDBIndex> IDBObjectStore::index(const String& name, ExceptionCode& ec) +ExceptionOr<Ref<IDBIndex>> IDBObjectStore::index(const String& indexName) { - LOG(StorageAPI, "IDBObjectStore::index"); - if (m_deleted) { - ec = IDBDatabaseException::InvalidStateError; - return 0; - } - if (m_transaction->isFinished()) { - ec = IDBDatabaseException::InvalidStateError; - return 0; - } + LOG(IndexedDB, "IDBObjectStore::index"); + ASSERT(currentThread() == m_transaction.database().originThreadID()); - IDBIndexMap::iterator it = m_indexMap.find(name); - if (it != m_indexMap.end()) - return it->value; + if (!scriptExecutionContext()) + return Exception { IDBDatabaseException::InvalidStateError }; // FIXME: Is this code tested? Is iteven reachable? - int64_t indexId = findIndexId(name); - if (indexId == IDBIndexMetadata::InvalidId) { - ec = IDBDatabaseException::NotFoundError; - return 0; - } + if (m_deleted) + return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'index' on 'IDBObjectStore': The object store has been deleted.") }; + + if (m_transaction.isFinishedOrFinishing()) + return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'index' on 'IDBObjectStore': The transaction is finished.") }; + + Locker<Lock> locker(m_referencedIndexLock); + auto iterator = m_referencedIndexes.find(indexName); + if (iterator != m_referencedIndexes.end()) + return Ref<IDBIndex> { *iterator->value }; + + auto* info = m_info.infoForExistingIndex(indexName); + if (!info) + return Exception { IDBDatabaseException::NotFoundError, ASCIILiteral("Failed to execute 'index' on 'IDBObjectStore': The specified index was not found.") }; + + auto index = std::make_unique<IDBIndex>(*scriptExecutionContext(), *info, *this); - const IDBIndexMetadata* indexMetadata(0); - for (IDBObjectStoreMetadata::IndexMap::const_iterator it = m_metadata.indexes.begin(); it != m_metadata.indexes.end(); ++it) { - if (it->value.name == name) { - indexMetadata = &it->value; - break; + Ref<IDBIndex> referencedIndex { *index }; + + m_referencedIndexes.set(indexName, WTFMove(index)); + + return WTFMove(referencedIndex); +} + +ExceptionOr<void> IDBObjectStore::deleteIndex(const String& name) +{ + LOG(IndexedDB, "IDBObjectStore::deleteIndex %s", name.utf8().data()); + ASSERT(currentThread() == m_transaction.database().originThreadID()); + + if (m_deleted) + return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'deleteIndex' on 'IDBObjectStore': The object store has been deleted.") }; + + if (!m_transaction.isVersionChange()) + return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'deleteIndex' on 'IDBObjectStore': The database is not running a version change transaction.") }; + + if (!m_transaction.isActive()) + return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'deleteIndex' on 'IDBObjectStore': The transaction is inactive or finished.") }; + + if (!m_info.hasIndex(name)) + return Exception { IDBDatabaseException::NotFoundError, ASCIILiteral("Failed to execute 'deleteIndex' on 'IDBObjectStore': The specified index was not found.") }; + + auto* info = m_info.infoForExistingIndex(name); + ASSERT(info); + m_transaction.database().didDeleteIndexInfo(*info); + + m_info.deleteIndex(name); + + { + Locker<Lock> locker(m_referencedIndexLock); + if (auto index = m_referencedIndexes.take(name)) { + index->markAsDeleted(); + m_deletedIndexes.add(index->info().identifier(), WTFMove(index)); } } - ASSERT(indexMetadata); - ASSERT(indexMetadata->id != IDBIndexMetadata::InvalidId); - RefPtr<IDBIndex> index = IDBIndex::create(*indexMetadata, this, m_transaction.get()); - m_indexMap.set(name, index); - return index.release(); + m_transaction.deleteIndex(m_info.identifier(), name); + + return { }; } -void IDBObjectStore::deleteIndex(const String& name, ExceptionCode& ec) +ExceptionOr<Ref<IDBRequest>> IDBObjectStore::count(ExecState& execState, JSValue key) { - LOG(StorageAPI, "IDBObjectStore::deleteIndex"); - if (!m_transaction->isVersionChange() || m_deleted) { - ec = IDBDatabaseException::InvalidStateError; - return; - } - if (!m_transaction->isActive()) { - ec = IDBDatabaseException::TransactionInactiveError; - return; - } - int64_t indexId = findIndexId(name); - if (indexId == IDBIndexMetadata::InvalidId) { - ec = IDBDatabaseException::NotFoundError; - return; - } + LOG(IndexedDB, "IDBObjectStore::count"); - backendDB()->deleteIndex(m_transaction->id(), id(), indexId); + Ref<IDBKey> idbKey = scriptValueToIDBKey(execState, key); + if (!idbKey->isValid()) + return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'count' on 'IDBObjectStore': The parameter is not a valid key.") }; - m_metadata.indexes.remove(indexId); - IDBIndexMap::iterator it = m_indexMap.find(name); - if (it != m_indexMap.end()) { - it->value->markDeleted(); - m_indexMap.remove(name); - } + return doCount(execState, IDBKeyRangeData(idbKey.ptr())); } -PassRefPtr<IDBRequest> IDBObjectStore::openCursor(ScriptExecutionContext* context, ExceptionCode& ec) +ExceptionOr<Ref<IDBRequest>> IDBObjectStore::count(ExecState& execState, IDBKeyRange* range) { - return openCursor(context, static_cast<IDBKeyRange*>(0), ec); + LOG(IndexedDB, "IDBObjectStore::count"); + + return doCount(execState, range ? IDBKeyRangeData(range) : IDBKeyRangeData::allKeys()); } -PassRefPtr<IDBRequest> IDBObjectStore::openCursor(ScriptExecutionContext* context, PassRefPtr<IDBKeyRange> keyRange, ExceptionCode& ec) +ExceptionOr<Ref<IDBRequest>> IDBObjectStore::doCount(ExecState& execState, const IDBKeyRangeData& range) { - return openCursor(context, keyRange, IDBCursor::directionNext(), ec); + ASSERT(currentThread() == m_transaction.database().originThreadID()); + + // The IDB spec for several IDBObjectStore methods states that transaction related exceptions should fire before + // the exception for an object store being deleted. + // However, a handful of W3C IDB tests expect the deleted exception even though the transaction inactive exception also applies. + // Additionally, Chrome and Edge agree with the test, as does Legacy IDB in WebKit. + // Until this is sorted out, we'll agree with the test and the majority share browsers. + if (m_deleted) + return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'count' on 'IDBObjectStore': The object store has been deleted.") }; + + if (!m_transaction.isActive()) + return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'count' on 'IDBObjectStore': The transaction is inactive or finished.") }; + + if (!range.isValid()) + return Exception { IDBDatabaseException::DataError }; + + return m_transaction.requestCount(execState, *this, range); } -PassRefPtr<IDBRequest> IDBObjectStore::openCursor(ScriptExecutionContext* context, const Deprecated::ScriptValue& key, ExceptionCode& ec) +ExceptionOr<Ref<IDBRequest>> IDBObjectStore::getAll(ExecState& execState, RefPtr<IDBKeyRange> range, std::optional<uint32_t> count) { - return openCursor(context, key, IDBCursor::directionNext(), ec); + LOG(IndexedDB, "IDBObjectStore::getAll"); + ASSERT(currentThread() == m_transaction.database().originThreadID()); + + if (m_deleted) + return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'getAll' on 'IDBObjectStore': The object store has been deleted.") }; + + if (!m_transaction.isActive()) + return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'getAll' on 'IDBObjectStore': The transaction is inactive or finished.") }; + + return m_transaction.requestGetAllObjectStoreRecords(execState, *this, range.get(), IndexedDB::GetAllType::Values, count); } -PassRefPtr<IDBRequest> IDBObjectStore::openCursor(ScriptExecutionContext* context, PassRefPtr<IDBKeyRange> range, const String& direction, ExceptionCode& ec) +ExceptionOr<Ref<IDBRequest>> IDBObjectStore::getAll(ExecState& execState, JSValue key, std::optional<uint32_t> count) { - return openCursor(context, range, direction, IDBDatabaseBackend::NormalTask, ec); + auto onlyResult = IDBKeyRange::only(execState, key); + if (onlyResult.hasException()) + return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'getAll' on 'IDBObjectStore': The parameter is not a valid key.") }; + + return getAll(execState, onlyResult.releaseReturnValue(), count); } -PassRefPtr<IDBRequest> IDBObjectStore::openCursor(ScriptExecutionContext* context, PassRefPtr<IDBKeyRange> range, const String& directionString, IDBDatabaseBackend::TaskType taskType, ExceptionCode& ec) +ExceptionOr<Ref<IDBRequest>> IDBObjectStore::getAllKeys(ExecState& execState, RefPtr<IDBKeyRange> range, std::optional<uint32_t> count) { - LOG(StorageAPI, "IDBObjectStore::openCursor"); - if (m_deleted) { - ec = IDBDatabaseException::InvalidStateError; - return 0; - } - if (!m_transaction->isActive()) { - ec = IDBDatabaseException::TransactionInactiveError; - return 0; - } - IndexedDB::CursorDirection direction = IDBCursor::stringToDirection(directionString, ec); - if (ec) - return 0; + LOG(IndexedDB, "IDBObjectStore::getAllKeys"); + ASSERT(currentThread() == m_transaction.database().originThreadID()); + + if (m_deleted) + return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'getAllKeys' on 'IDBObjectStore': The object store has been deleted.") }; - RefPtr<IDBRequest> request = IDBRequest::create(context, IDBAny::create(this), m_transaction.get()); - request->setCursorDetails(IndexedDB::CursorType::KeyAndValue, direction); + if (!m_transaction.isActive()) + return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'getAllKeys' on 'IDBObjectStore': The transaction is inactive or finished.") }; - backendDB()->openCursor(m_transaction->id(), id(), IDBIndexMetadata::InvalidId, range, direction, false, static_cast<IDBDatabaseBackend::TaskType>(taskType), request); - return request.release(); + return m_transaction.requestGetAllObjectStoreRecords(execState, *this, range.get(), IndexedDB::GetAllType::Keys, count); } -PassRefPtr<IDBRequest> IDBObjectStore::openCursor(ScriptExecutionContext* context, const Deprecated::ScriptValue& key, const String& direction, ExceptionCode& ec) +ExceptionOr<Ref<IDBRequest>> IDBObjectStore::getAllKeys(ExecState& execState, JSValue key, std::optional<uint32_t> count) { - RefPtr<IDBKeyRange> keyRange = IDBKeyRange::only(context, key, ec); - if (ec) - return 0; - return openCursor(context, keyRange.release(), direction, ec); + auto onlyResult = IDBKeyRange::only(execState, key); + if (onlyResult.hasException()) + return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'getAllKeys' on 'IDBObjectStore': The parameter is not a valid key.") }; + + return getAllKeys(execState, onlyResult.releaseReturnValue(), count); } -PassRefPtr<IDBRequest> IDBObjectStore::count(ScriptExecutionContext* context, PassRefPtr<IDBKeyRange> range, ExceptionCode& ec) +void IDBObjectStore::markAsDeleted() { - LOG(StorageAPI, "IDBObjectStore::count"); - if (m_deleted) { - ec = IDBDatabaseException::InvalidStateError; - return 0; + ASSERT(currentThread() == m_transaction.database().originThreadID()); + m_deleted = true; +} + +void IDBObjectStore::rollbackForVersionChangeAbort() +{ + ASSERT(currentThread() == m_transaction.database().originThreadID()); + + String currentName = m_info.name(); + m_info = m_originalInfo; + + auto& databaseInfo = transaction().database().info(); + auto* objectStoreInfo = databaseInfo.infoForExistingObjectStore(m_info.identifier()); + if (!objectStoreInfo) { + m_info.rename(currentName); + m_deleted = true; + } else { + m_deleted = false; + + HashSet<uint64_t> indexesToRemove; + for (auto indexIdentifier : objectStoreInfo->indexMap().keys()) { + if (!objectStoreInfo->hasIndex(indexIdentifier)) + indexesToRemove.add(indexIdentifier); + } + + for (auto indexIdentifier : indexesToRemove) + m_info.deleteIndex(indexIdentifier); } - if (!m_transaction->isActive()) { - ec = IDBDatabaseException::TransactionInactiveError; - return 0; + + Locker<Lock> locker(m_referencedIndexLock); + + Vector<uint64_t> identifiersToRemove; + for (auto& iterator : m_deletedIndexes) { + if (m_info.hasIndex(iterator.key)) { + auto name = iterator.value->info().name(); + m_referencedIndexes.set(name, WTFMove(iterator.value)); + identifiersToRemove.append(iterator.key); + } } - RefPtr<IDBRequest> request = IDBRequest::create(context, IDBAny::create(this), m_transaction.get()); - backendDB()->count(m_transaction->id(), id(), IDBIndexMetadata::InvalidId, range, request); - return request.release(); + + for (auto identifier : identifiersToRemove) + m_deletedIndexes.remove(identifier); + + for (auto& index : m_referencedIndexes.values()) + index->rollbackInfoForVersionChangeAbort(); } -PassRefPtr<IDBRequest> IDBObjectStore::count(ScriptExecutionContext* context, const Deprecated::ScriptValue& key, ExceptionCode& ec) +void IDBObjectStore::visitReferencedIndexes(SlotVisitor& visitor) const { - RefPtr<IDBKeyRange> keyRange = IDBKeyRange::only(context, key, ec); - if (ec) - return 0; - return count(context, keyRange.release(), ec); + Locker<Lock> locker(m_referencedIndexLock); + for (auto& index : m_referencedIndexes.values()) + visitor.addOpaqueRoot(index.get()); + for (auto& index : m_deletedIndexes.values()) + visitor.addOpaqueRoot(index.get()); } -void IDBObjectStore::transactionFinished() +void IDBObjectStore::renameReferencedIndex(IDBIndex& index, const String& newName) { - ASSERT(m_transaction->isFinished()); + LOG(IndexedDB, "IDBObjectStore::renameReferencedIndex"); + + auto* indexInfo = m_info.infoForExistingIndex(index.info().identifier()); + ASSERT(indexInfo); + indexInfo->rename(newName); + + ASSERT(m_referencedIndexes.contains(index.info().name())); + ASSERT(!m_referencedIndexes.contains(newName)); + ASSERT(m_referencedIndexes.get(index.info().name()) == &index); - // Break reference cycles. - m_indexMap.clear(); + m_referencedIndexes.set(newName, m_referencedIndexes.take(index.info().name())); } -int64_t IDBObjectStore::findIndexId(const String& name) const +void IDBObjectStore::ref() { - for (IDBObjectStoreMetadata::IndexMap::const_iterator it = m_metadata.indexes.begin(); it != m_metadata.indexes.end(); ++it) { - if (it->value.name == name) { - ASSERT(it->key != IDBIndexMetadata::InvalidId); - return it->key; - } - } - return IDBIndexMetadata::InvalidId; + m_transaction.ref(); } -IDBDatabaseBackend* IDBObjectStore::backendDB() const +void IDBObjectStore::deref() { - return m_transaction->backendDB(); + m_transaction.deref(); } } // namespace WebCore |