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