From 4b928f4a3e17189f2a8726f6d01ff14dd47c7f46 Mon Sep 17 00:00:00 2001 From: Ben Turner Date: Wed, 25 Sep 2013 16:11:47 -0700 Subject: [PATCH] Bug 920800 - 'Add openKeyCursor() to IDBObjectStore'. r=janv. --- dom/indexedDB/IDBCursor.cpp | 229 ++++++--- dom/indexedDB/IDBCursor.h | 13 + dom/indexedDB/IDBIndex.cpp | 2 +- dom/indexedDB/IDBObjectStore.cpp | 444 +++++++++++++++++- dom/indexedDB/IDBObjectStore.h | 15 + dom/indexedDB/ipc/IndexedDBChild.cpp | 44 +- dom/indexedDB/ipc/IndexedDBParams.ipdlh | 13 + dom/indexedDB/ipc/IndexedDBParent.cpp | 44 ++ dom/indexedDB/ipc/IndexedDBParent.h | 4 + dom/indexedDB/ipc/PIndexedDBIndex.ipdl | 14 - dom/indexedDB/ipc/PIndexedDBObjectStore.ipdl | 4 +- dom/indexedDB/ipc/unit/xpcshell.ini | 1 + dom/indexedDB/test/Makefile.in | 1 + .../test/test_objectStore_openKeyCursor.html | 19 + dom/indexedDB/test/unit/Makefile.in | 1 + .../unit/test_objectStore_openKeyCursor.js | 400 ++++++++++++++++ dom/indexedDB/test/unit/xpcshell.ini | 1 + dom/webidl/IDBObjectStore.webidl | 3 + 18 files changed, 1155 insertions(+), 97 deletions(-) create mode 100644 dom/indexedDB/test/test_objectStore_openKeyCursor.html create mode 100644 dom/indexedDB/test/unit/test_objectStore_openKeyCursor.js diff --git a/dom/indexedDB/IDBCursor.cpp b/dom/indexedDB/IDBCursor.cpp index f2aa660fd8e8..304d270e9e05 100644 --- a/dom/indexedDB/IDBCursor.cpp +++ b/dom/indexedDB/IDBCursor.cpp @@ -36,8 +36,7 @@ using mozilla::dom::OwningIDBObjectStoreOrIDBIndex; using mozilla::ErrorResult; static_assert(sizeof(size_t) >= sizeof(IDBCursor::Direction), - "Relying on conversion between size_t and " - "IDBCursor::Direction"); + "Relying on conversion between size_t and IDBCursor::Direction"); namespace { @@ -62,6 +61,9 @@ public: UnpackResponseFromParentProcess(const ResponseValue& aResponseValue) = 0; protected: + virtual ~CursorHelper() + { } + nsRefPtr mCursor; private: @@ -79,19 +81,17 @@ public: int32_t aCount) : CursorHelper(aCursor), mCount(aCount) { - NS_ASSERTION(aCount > 0, "Must have a count!"); - } - - ~ContinueHelper() - { - IDBObjectStore::ClearCloneReadInfo(mCloneReadInfo); + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aCursor); + MOZ_ASSERT(aCount > 0); } virtual nsresult DoDatabaseWork(mozIStorageConnection* aConnection) MOZ_OVERRIDE; virtual nsresult GetSuccessResult(JSContext* aCx, - JS::MutableHandle aVal) MOZ_OVERRIDE; + JS::MutableHandle aVal) + MOZ_OVERRIDE; virtual void ReleaseMainThreadObjects() MOZ_OVERRIDE; @@ -106,6 +106,11 @@ public: MOZ_OVERRIDE; protected: + virtual ~ContinueHelper() + { + IDBObjectStore::ClearCloneReadInfo(mCloneReadInfo); + } + virtual nsresult BindArgumentsToStatement(mozIStorageStatement* aStatement) = 0; @@ -124,10 +129,10 @@ protected: if (mKey.IsUnset()) { mCursor->mHaveValue = false; - } - else { - NS_ASSERTION(mCursor->mType == IDBCursor::OBJECTSTORE || - !mObjectKey.IsUnset(), "Bad key!"); + } else { + MOZ_ASSERT(mCursor->mType == IDBCursor::OBJECTSTORE || + mCursor->mType == IDBCursor::OBJECTSTOREKEY || + !mObjectKey.IsUnset()); // Set new values. mCursor->mKey = mKey; @@ -153,11 +158,31 @@ public: : ContinueHelper(aCursor, aCount) { } +protected: + virtual ~ContinueObjectStoreHelper() + { } + private: nsresult BindArgumentsToStatement(mozIStorageStatement* aStatement); nsresult GatherResultsFromStatement(mozIStorageStatement* aStatement); }; +class ContinueObjectStoreKeyHelper : public ContinueObjectStoreHelper +{ +public: + ContinueObjectStoreKeyHelper(IDBCursor* aCursor, + uint32_t aCount) + : ContinueObjectStoreHelper(aCursor, aCount) + { } + +private: + virtual ~ContinueObjectStoreKeyHelper() + { } + + virtual nsresult + GatherResultsFromStatement(mozIStorageStatement* aStatement) MOZ_OVERRIDE; +}; + class ContinueIndexHelper : public ContinueHelper { public: @@ -166,6 +191,10 @@ public: : ContinueHelper(aCursor, aCount) { } +protected: + virtual ~ContinueIndexHelper() + { } + private: nsresult BindArgumentsToStatement(mozIStorageStatement* aStatement); nsresult GatherResultsFromStatement(mozIStorageStatement* aStatement); @@ -180,6 +209,9 @@ public: { } private: + virtual ~ContinueIndexObjectHelper() + { } + nsresult GatherResultsFromStatement(mozIStorageStatement* aStatement); }; @@ -213,6 +245,32 @@ IDBCursor::Create(IDBRequest* aRequest, return cursor.forget(); } +// static +already_AddRefed +IDBCursor::Create(IDBRequest* aRequest, + IDBTransaction* aTransaction, + IDBObjectStore* aObjectStore, + Direction aDirection, + const Key& aRangeKey, + const nsACString& aContinueQuery, + const nsACString& aContinueToQuery, + const Key& aKey) +{ + MOZ_ASSERT(aObjectStore); + MOZ_ASSERT(!aKey.IsUnset()); + + nsRefPtr cursor = + IDBCursor::CreateCommon(aRequest, aTransaction, aObjectStore, aDirection, + aRangeKey, aContinueQuery, aContinueToQuery); + NS_ASSERTION(cursor, "This shouldn't fail!"); + + cursor->mObjectStore = aObjectStore; + cursor->mType = OBJECTSTOREKEY; + cursor->mKey = aKey; + + return cursor.forget(); +} + // static already_AddRefed IDBCursor::Create(IDBRequest* aRequest, @@ -293,7 +351,7 @@ IDBCursor::ConvertDirection(mozilla::dom::IDBCursorDirection aDirection) return PREV_UNIQUE; default: - MOZ_CRASH("Unknown direction!"); + MOZ_ASSUME_UNREACHABLE("Unknown direction!"); } } @@ -396,8 +454,8 @@ IDBCursor::DropJSObjects() void IDBCursor::ContinueInternal(const Key& aKey, int32_t aCount, ErrorResult& aRv) { - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - NS_ASSERTION(aCount > 0, "Must have a count!"); + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aCount > 0); if (!mTransaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); @@ -411,10 +469,7 @@ IDBCursor::ContinueInternal(const Key& aKey, int32_t aCount, ErrorResult& aRv) mContinueToKey = aKey; -#ifdef DEBUG - NS_ASSERTION(mRequest->ReadyState() == IDBRequestReadyState::Done, - "Should be DONE!"); -#endif + MOZ_ASSERT(mRequest->ReadyState() == IDBRequestReadyState::Done); mRequest->Reset(); @@ -424,6 +479,10 @@ IDBCursor::ContinueInternal(const Key& aKey, int32_t aCount, ErrorResult& aRv) helper = new ContinueObjectStoreHelper(this, aCount); break; + case OBJECTSTOREKEY: + helper = new ContinueObjectStoreKeyHelper(this, aCount); + break; + case INDEXKEY: helper = new ContinueIndexHelper(this, aCount); break; @@ -433,7 +492,7 @@ IDBCursor::ContinueInternal(const Key& aKey, int32_t aCount, ErrorResult& aRv) break; default: - NS_NOTREACHED("Unknown cursor type!"); + MOZ_ASSUME_UNREACHABLE("Unknown cursor type!"); } nsresult rv = helper->DispatchToTransactionPool(); @@ -489,15 +548,26 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(IDBCursor) JSObject* IDBCursor::WrapObject(JSContext* aCx, JS::Handle aScope) { - return mType != INDEXKEY - ? IDBCursorWithValueBinding::Wrap(aCx, aScope, this) - : IDBCursorBinding::Wrap(aCx, aScope, this); + MOZ_ASSERT(NS_IsMainThread()); + + switch (mType) { + case OBJECTSTORE: + case INDEXOBJECT: + return IDBCursorWithValueBinding::Wrap(aCx, aScope, this); + + case OBJECTSTOREKEY: + case INDEXKEY: + return IDBCursorBinding::Wrap(aCx, aScope, this); + + default: + MOZ_ASSUME_UNREACHABLE("Bad type!"); + } } mozilla::dom::IDBCursorDirection IDBCursor::GetDirection() const { - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + MOZ_ASSERT(NS_IsMainThread()); switch (mDirection) { case NEXT: @@ -512,33 +582,39 @@ IDBCursor::GetDirection() const case PREV_UNIQUE: return mozilla::dom::IDBCursorDirection::Prevunique; - case DIRECTION_INVALID: default: - MOZ_CRASH("Unknown direction!"); - return mozilla::dom::IDBCursorDirection::Next; + MOZ_ASSUME_UNREACHABLE("Bad direction!"); } } - void IDBCursor::GetSource(OwningIDBObjectStoreOrIDBIndex& aSource) const { - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + MOZ_ASSERT(NS_IsMainThread()); - if (mType == OBJECTSTORE) { - aSource.SetAsIDBObjectStore() = mObjectStore; - } - else { - aSource.SetAsIDBIndex() = mIndex; + switch (mType) { + case OBJECTSTORE: + case OBJECTSTOREKEY: + MOZ_ASSERT(mObjectStore); + aSource.SetAsIDBObjectStore() = mObjectStore; + break; + + case INDEXKEY: + case INDEXOBJECT: + MOZ_ASSERT(mIndex); + aSource.SetAsIDBIndex() = mIndex; + break; + + default: + MOZ_ASSUME_UNREACHABLE("Bad type!"); } } JS::Value IDBCursor::GetKey(JSContext* aCx, ErrorResult& aRv) { - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - - NS_ASSERTION(!mKey.IsUnset() || !mHaveValue, "Bad key!"); + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mKey.IsUnset() || !mHaveValue); if (!mHaveValue) { return JSVAL_VOID; @@ -562,7 +638,7 @@ IDBCursor::GetKey(JSContext* aCx, ErrorResult& aRv) JS::Value IDBCursor::GetPrimaryKey(JSContext* aCx, ErrorResult& aRv) { - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + MOZ_ASSERT(NS_IsMainThread()); if (!mHaveValue) { return JSVAL_VOID; @@ -574,12 +650,9 @@ IDBCursor::GetPrimaryKey(JSContext* aCx, ErrorResult& aRv) mRooted = true; } - JSAutoRequest ar(aCx); - - NS_ASSERTION(mType == OBJECTSTORE ? !mKey.IsUnset() : - !mObjectKey.IsUnset(), "Bad key!"); - - const Key& key = mType == OBJECTSTORE ? mKey : mObjectKey; + const Key& key = + (mType == OBJECTSTORE || mType == OBJECTSTOREKEY) ? mKey : mObjectKey; + MOZ_ASSERT(!key.IsUnset()); aRv = key.ToJSVal(aCx, mCachedPrimaryKey); ENSURE_SUCCESS(aRv, JSVAL_VOID); @@ -593,8 +666,8 @@ IDBCursor::GetPrimaryKey(JSContext* aCx, ErrorResult& aRv) JS::Value IDBCursor::GetValue(JSContext* aCx, ErrorResult& aRv) { - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - NS_ASSERTION(mType != INDEXKEY, "GetValue shouldn't exist on index keys"); + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mType == OBJECTSTORE || mType == INDEXOBJECT); if (!mHaveValue) { return JSVAL_VOID; @@ -626,7 +699,7 @@ IDBCursor::Continue(JSContext* aCx, const Optional >& aKey, ErrorResult &aRv) { - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + MOZ_ASSERT(NS_IsMainThread()); Key key; if (aKey.WasPassed()) { @@ -653,7 +726,7 @@ IDBCursor::Continue(JSContext* aCx, break; default: - NS_NOTREACHED("Unknown direction type!"); + MOZ_ASSUME_UNREACHABLE("Unknown direction type!"); } } @@ -663,7 +736,7 @@ IDBCursor::Continue(JSContext* aCx, } #ifdef IDB_PROFILER_USE_MARKS - if (mType == OBJECTSTORE) { + if (mType == OBJECTSTORE || mType == OBJECTSTOREKEY) { IDB_PROFILER_MARK("IndexedDB Request %llu: " "database(%s).transaction(%s).objectStore(%s).cursor(%s)." "continue(%s)", @@ -695,7 +768,7 @@ already_AddRefed IDBCursor::Update(JSContext* aCx, JS::Handle aValue, ErrorResult& aRv) { - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + MOZ_ASSERT(NS_IsMainThread()); if (!mTransaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); @@ -707,18 +780,17 @@ IDBCursor::Update(JSContext* aCx, JS::Handle aValue, return nullptr; } - if (!mHaveValue || mType == INDEXKEY) { + if (!mHaveValue || mType == OBJECTSTOREKEY || mType == INDEXKEY) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); return nullptr; } - NS_ASSERTION(mObjectStore, "This cannot be null!"); - NS_ASSERTION(!mKey.IsUnset() , "Bad key!"); - NS_ASSERTION(mType != INDEXOBJECT || !mObjectKey.IsUnset(), "Bad key!"); + MOZ_ASSERT(mObjectStore); + MOZ_ASSERT(!mKey.IsUnset()); + MOZ_ASSERT(mType == OBJECTSTORE || mType == INDEXOBJECT); + MOZ_ASSERT_IF(mType == INDEXOBJECT, !mObjectKey.IsUnset()); - JSAutoRequest ar(aCx); - - Key& objectKey = (mType == OBJECTSTORE) ? mKey : mObjectKey; + const Key& objectKey = (mType == OBJECTSTORE) ? mKey : mObjectKey; nsRefPtr request; if (mObjectStore->HasValidKeyPath()) { @@ -796,7 +868,7 @@ IDBCursor::Update(JSContext* aCx, JS::Handle aValue, already_AddRefed IDBCursor::Delete(JSContext* aCx, ErrorResult& aRv) { - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + MOZ_ASSERT(NS_IsMainThread()); if (!mTransaction->IsOpen()) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); @@ -808,24 +880,23 @@ IDBCursor::Delete(JSContext* aCx, ErrorResult& aRv) return nullptr; } - if (!mHaveValue || mType == INDEXKEY) { + if (!mHaveValue || mType == OBJECTSTOREKEY || mType == INDEXKEY) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); return nullptr; } - NS_ASSERTION(mObjectStore, "This cannot be null!"); - NS_ASSERTION(!mKey.IsUnset() , "Bad key!"); + MOZ_ASSERT(mObjectStore); + MOZ_ASSERT(mType == OBJECTSTORE || mType == INDEXOBJECT); + MOZ_ASSERT(!mKey.IsUnset()); - Key& objectKey = (mType == OBJECTSTORE) ? mKey : mObjectKey; + const Key& objectKey = (mType == OBJECTSTORE) ? mKey : mObjectKey; JS::Rooted key(aCx); aRv = objectKey.ToJSVal(aCx, &key); ENSURE_SUCCESS(aRv, nullptr); nsRefPtr request = mObjectStore->Delete(aCx, key, aRv); - if (aRv.Failed()) { - return nullptr; - } + ENSURE_SUCCESS(aRv, nullptr); #ifdef IDB_PROFILER_USE_MARKS { @@ -866,7 +937,7 @@ IDBCursor::Delete(JSContext* aCx, ErrorResult& aRv) void IDBCursor::Advance(uint32_t aCount, ErrorResult &aRv) { - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + MOZ_ASSERT(NS_IsMainThread()); if (aCount < 1) { aRv.ThrowTypeError(MSG_INVALID_ADVANCE_COUNT); @@ -879,7 +950,7 @@ IDBCursor::Advance(uint32_t aCount, ErrorResult &aRv) #ifdef IDB_PROFILER_USE_MARKS { - if (mType == OBJECTSTORE) { + if (mType == OBJECTSTORE || mType == OBJECTSTOREKEY) { IDB_PROFILER_MARK("IndexedDB Request %llu: " "database(%s).transaction(%s).objectStore(%s)." "cursor(%s).advance(%ld)", @@ -1135,6 +1206,9 @@ nsresult ContinueObjectStoreHelper::BindArgumentsToStatement( mozIStorageStatement* aStatement) { + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(aStatement); + // Bind object store id. nsresult rv = aStatement->BindInt64ByName(NS_LITERAL_CSTRING("id"), mCursor->mObjectStore->Id()); @@ -1166,12 +1240,29 @@ nsresult ContinueObjectStoreHelper::GatherResultsFromStatement( mozIStorageStatement* aStatement) { + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(aStatement); + // Figure out what kind of key we have next. nsresult rv = mKey.SetFromStatement(aStatement, 0); NS_ENSURE_SUCCESS(rv, rv); rv = IDBObjectStore::GetStructuredCloneReadInfoFromStatement(aStatement, 1, 2, - mDatabase, mCloneReadInfo); + mDatabase, + mCloneReadInfo); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +ContinueObjectStoreKeyHelper::GatherResultsFromStatement( + mozIStorageStatement* aStatement) +{ + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(aStatement); + + nsresult rv = mKey.SetFromStatement(aStatement, 0); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; diff --git a/dom/indexedDB/IDBCursor.h b/dom/indexedDB/IDBCursor.h index 27f5f2e01ca9..a67f125c21a0 100644 --- a/dom/indexedDB/IDBCursor.h +++ b/dom/indexedDB/IDBCursor.h @@ -55,6 +55,7 @@ public: enum Type { OBJECTSTORE = 0, + OBJECTSTOREKEY, INDEXKEY, INDEXOBJECT }; @@ -83,6 +84,18 @@ public: const Key& aKey, StructuredCloneReadInfo& aCloneReadInfo); + // For OBJECTSTOREKEY cursors. + static + already_AddRefed + Create(IDBRequest* aRequest, + IDBTransaction* aTransaction, + IDBObjectStore* aObjectStore, + Direction aDirection, + const Key& aRangeKey, + const nsACString& aContinueQuery, + const nsACString& aContinueToQuery, + const Key& aKey); + // For INDEXKEY cursors. static already_AddRefed diff --git a/dom/indexedDB/IDBIndex.cpp b/dom/indexedDB/IDBIndex.cpp index f5274455a484..9c29a853aac8 100644 --- a/dom/indexedDB/IDBIndex.cpp +++ b/dom/indexedDB/IDBIndex.cpp @@ -1991,7 +1991,7 @@ OpenKeyCursorHelper::PackArgumentsForParentProcess(IndexRequestParams& aParams) PROFILER_MAIN_THREAD_LABEL("IndexedDB", "OpenKeyCursorHelper::" - "PackArgumentsForParentProcess"); + "PackArgumentsForParentProcess [IDBIndex.cpp]"); OpenKeyCursorParams params; diff --git a/dom/indexedDB/IDBObjectStore.cpp b/dom/indexedDB/IDBObjectStore.cpp index ca488fc7b469..33cac1183401 100644 --- a/dom/indexedDB/IDBObjectStore.cpp +++ b/dom/indexedDB/IDBObjectStore.cpp @@ -345,6 +345,57 @@ private: SerializedStructuredCloneReadInfo mSerializedCloneReadInfo; }; +class OpenKeyCursorHelper MOZ_FINAL : public ObjectStoreHelper +{ +public: + OpenKeyCursorHelper(IDBTransaction* aTransaction, + IDBRequest* aRequest, + IDBObjectStore* aObjectStore, + IDBKeyRange* aKeyRange, + IDBCursor::Direction aDirection) + : ObjectStoreHelper(aTransaction, aRequest, aObjectStore), + mKeyRange(aKeyRange), mDirection(aDirection) + { } + + virtual nsresult + DoDatabaseWork(mozIStorageConnection* aConnection) MOZ_OVERRIDE; + + virtual nsresult + GetSuccessResult(JSContext* aCx, JS::MutableHandleValue aVal) MOZ_OVERRIDE; + + virtual void + ReleaseMainThreadObjects() MOZ_OVERRIDE; + + virtual nsresult + PackArgumentsForParentProcess(ObjectStoreRequestParams& aParams) MOZ_OVERRIDE; + + virtual ChildProcessSendResult + SendResponseToChildProcess(nsresult aResultCode) MOZ_OVERRIDE; + + virtual nsresult + UnpackResponseFromParentProcess(const ResponseValue& aResponseValue) + MOZ_OVERRIDE; + +private: + ~OpenKeyCursorHelper() + { } + + nsresult EnsureCursor(); + + // In-params. + nsRefPtr mKeyRange; + const IDBCursor::Direction mDirection; + + // Out-params. + Key mKey; + nsCString mContinueQuery; + nsCString mContinueToQuery; + Key mRangeKey; + + // Only used in the parent process. + nsRefPtr mCursor; +}; + class CreateIndexHelper : public NoRequestObjectStoreHelper { public: @@ -2368,6 +2419,69 @@ IDBObjectStore::OpenCursorFromChildProcess( return NS_OK; } +nsresult +IDBObjectStore::OpenCursorFromChildProcess(IDBRequest* aRequest, + size_t aDirection, + const Key& aKey, + IDBCursor** _retval) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aRequest); + + auto direction = static_cast(aDirection); + + nsRefPtr cursor = + IDBCursor::Create(aRequest, mTransaction, this, direction, Key(), + EmptyCString(), EmptyCString(), aKey); + NS_ENSURE_TRUE(cursor, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + cursor.forget(_retval); + return NS_OK; +} + +already_AddRefed +IDBObjectStore::OpenKeyCursorInternal(IDBKeyRange* aKeyRange, size_t aDirection, + ErrorResult& aRv) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!mTransaction->IsOpen()) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); + return nullptr; + } + + nsRefPtr request = GenerateRequest(this); + if (!request) { + NS_WARNING("Failed to generate request!"); + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + return nullptr; + } + + auto direction = static_cast(aDirection); + + nsRefPtr helper = + new OpenKeyCursorHelper(mTransaction, request, this, aKeyRange, direction); + + nsresult rv = helper->DispatchToTransactionPool(); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to dispatch!"); + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + return nullptr; + } + + IDB_PROFILER_MARK("IndexedDB Request %llu: " + "database(%s).transaction(%s).objectStore(%s)." + "openKeyCursor(%s, %s)", + "IDBRequest[%llu] MT IDBObjectStore.openKeyCursor()", + request->GetSerialNumber(), + IDB_PROFILER_STRING(Transaction()->Database()), + IDB_PROFILER_STRING(Transaction()), + IDB_PROFILER_STRING(this), IDB_PROFILER_STRING(aKeyRange), + IDB_PROFILER_STRING(direction)); + + return request.forget(); +} + void IDBObjectStore::SetInfo(ObjectStoreInfo* aInfo) { @@ -2875,6 +2989,29 @@ IDBObjectStore::GetAllKeys(JSContext* aCx, return GetAllKeysInternal(keyRange, limit, aRv); } +already_AddRefed +IDBObjectStore::OpenKeyCursor(JSContext* aCx, + const Optional& aRange, + IDBCursorDirection aDirection, ErrorResult& aRv) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!mTransaction->IsOpen()) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); + return nullptr; + } + + nsRefPtr keyRange; + if (aRange.WasPassed()) { + aRv = IDBKeyRange::FromJSVal(aCx, aRange.Value(), getter_AddRefs(keyRange)); + ENSURE_SUCCESS(aRv, nullptr); + } + + IDBCursor::Direction direction = IDBCursor::ConvertDirection(aDirection); + + return OpenKeyCursorInternal(keyRange, static_cast(direction), aRv); +} + inline nsresult CopyData(nsIInputStream* aInputStream, nsIOutputStream* aOutputStream) { @@ -3912,7 +4049,7 @@ OpenCursorHelper::SendResponseToChildProcess(nsresult aResultCode) params.requestParent() = requestActor; params.direction() = mDirection; params.key() = mKey; - params.cloneInfo() = mSerializedCloneReadInfo; + params.optionalCloneInfo() = mSerializedCloneReadInfo; params.blobsParent().SwapElements(blobsParent); if (!objectStoreActor->OpenCursor(mCursor, params, openCursorResponse)) { @@ -3968,6 +4105,311 @@ OpenCursorHelper::UnpackResponseFromParentProcess( return NS_OK; } +nsresult +OpenKeyCursorHelper::DoDatabaseWork(mozIStorageConnection* /* aConnection */) +{ + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(IndexedDatabaseManager::IsMainProcess()); + + PROFILER_LABEL("IndexedDB", + "OpenKeyCursorHelper::DoDatabaseWork [IDBObjectStore.cpp]"); + + NS_NAMED_LITERAL_CSTRING(keyValue, "key_value"); + NS_NAMED_LITERAL_CSTRING(id, "id"); + NS_NAMED_LITERAL_CSTRING(openLimit, " LIMIT "); + + // Don't actually allocate space for this string yet. + const nsCSubstringTuple queryStart = + NS_LITERAL_CSTRING("SELECT ") + keyValue + + NS_LITERAL_CSTRING(" FROM object_data WHERE object_store_id = :") + id; + + nsAutoCString keyRangeClause; + if (mKeyRange) { + mKeyRange->GetBindingClause(keyValue, keyRangeClause); + } + + nsAutoCString directionClause = NS_LITERAL_CSTRING(" ORDER BY ") + keyValue; + switch (mDirection) { + case IDBCursor::NEXT: + case IDBCursor::NEXT_UNIQUE: + directionClause.AppendLiteral(" ASC"); + break; + + case IDBCursor::PREV: + case IDBCursor::PREV_UNIQUE: + directionClause.AppendLiteral(" DESC"); + break; + + default: + MOZ_ASSUME_UNREACHABLE("Unknown direction type!"); + } + + nsCString firstQuery = queryStart + keyRangeClause + directionClause + + openLimit + NS_LITERAL_CSTRING("1"); + + nsCOMPtr stmt = + mTransaction->GetCachedStatement(firstQuery); + NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + mozStorageStatementScoper scoper(stmt); + + nsresult rv = stmt->BindInt64ByName(id, mObjectStore->Id()); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + if (mKeyRange) { + rv = mKeyRange->BindToStatement(stmt); + NS_ENSURE_SUCCESS(rv, rv); + } + + bool hasResult; + rv = stmt->ExecuteStep(&hasResult); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + if (!hasResult) { + mKey.Unset(); + return NS_OK; + } + + rv = mKey.SetFromStatement(stmt, 0); + NS_ENSURE_SUCCESS(rv, rv); + + // Now we need to make the query to get the next match. + keyRangeClause.Truncate(); + nsAutoCString continueToKeyRangeClause; + + NS_NAMED_LITERAL_CSTRING(currentKey, "current_key"); + NS_NAMED_LITERAL_CSTRING(rangeKey, "range_key"); + + switch (mDirection) { + case IDBCursor::NEXT: + case IDBCursor::NEXT_UNIQUE: + AppendConditionClause(keyValue, currentKey, false, false, + keyRangeClause); + AppendConditionClause(keyValue, currentKey, false, true, + continueToKeyRangeClause); + if (mKeyRange && !mKeyRange->Upper().IsUnset()) { + AppendConditionClause(keyValue, rangeKey, true, + !mKeyRange->IsUpperOpen(), keyRangeClause); + AppendConditionClause(keyValue, rangeKey, true, + !mKeyRange->IsUpperOpen(), + continueToKeyRangeClause); + mRangeKey = mKeyRange->Upper(); + } + break; + + case IDBCursor::PREV: + case IDBCursor::PREV_UNIQUE: + AppendConditionClause(keyValue, currentKey, true, false, keyRangeClause); + AppendConditionClause(keyValue, currentKey, true, true, + continueToKeyRangeClause); + if (mKeyRange && !mKeyRange->Lower().IsUnset()) { + AppendConditionClause(keyValue, rangeKey, false, + !mKeyRange->IsLowerOpen(), keyRangeClause); + AppendConditionClause(keyValue, rangeKey, false, + !mKeyRange->IsLowerOpen(), + continueToKeyRangeClause); + mRangeKey = mKeyRange->Lower(); + } + break; + + default: + MOZ_ASSUME_UNREACHABLE("Unknown direction type!"); + } + + mContinueQuery = queryStart + keyRangeClause + directionClause + openLimit; + mContinueToQuery = queryStart + continueToKeyRangeClause + directionClause + + openLimit; + + return NS_OK; +} + +nsresult +OpenKeyCursorHelper::EnsureCursor() +{ + MOZ_ASSERT(NS_IsMainThread()); + + PROFILER_MAIN_THREAD_LABEL("IndexedDB", + "OpenKeyCursorHelper::EnsureCursor " + "[IDBObjectStore.cpp]"); + + if (mCursor || mKey.IsUnset()) { + return NS_OK; + } + + mCursor = IDBCursor::Create(mRequest, mTransaction, mObjectStore, mDirection, + mRangeKey, mContinueQuery, mContinueToQuery, + mKey); + NS_ENSURE_TRUE(mCursor, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + return NS_OK; +} + +nsresult +OpenKeyCursorHelper::GetSuccessResult(JSContext* aCx, + JS::MutableHandleValue aVal) +{ + MOZ_ASSERT(NS_IsMainThread()); + + PROFILER_MAIN_THREAD_LABEL("IndexedDB", + "OpenKeyCursorHelper::GetSuccessResult " + "[IDBObjectStore.cpp]"); + + nsresult rv = EnsureCursor(); + NS_ENSURE_SUCCESS(rv, rv); + + if (mCursor) { + rv = WrapNative(aCx, mCursor, aVal); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + } + else { + aVal.setUndefined(); + } + + return NS_OK; +} + +void +OpenKeyCursorHelper::ReleaseMainThreadObjects() +{ + MOZ_ASSERT(NS_IsMainThread()); + + mKeyRange = nullptr; + mCursor = nullptr; + + ObjectStoreHelper::ReleaseMainThreadObjects(); +} + +nsresult +OpenKeyCursorHelper::PackArgumentsForParentProcess( + ObjectStoreRequestParams& aParams) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!IndexedDatabaseManager::IsMainProcess()); + + PROFILER_MAIN_THREAD_LABEL("IndexedDB", + "OpenKeyCursorHelper::" + "PackArgumentsForParentProcess " + "[IDBObjectStore.cpp]"); + + OpenKeyCursorParams params; + + if (mKeyRange) { + KeyRange keyRange; + mKeyRange->ToSerializedKeyRange(keyRange); + params.optionalKeyRange() = keyRange; + } + else { + params.optionalKeyRange() = mozilla::void_t(); + } + + params.direction() = mDirection; + + aParams = params; + return NS_OK; +} + +AsyncConnectionHelper::ChildProcessSendResult +OpenKeyCursorHelper::SendResponseToChildProcess(nsresult aResultCode) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(IndexedDatabaseManager::IsMainProcess()); + MOZ_ASSERT(!mCursor); + + PROFILER_MAIN_THREAD_LABEL("IndexedDB", + "OpenKeyCursorHelper::SendResponseToChildProcess " + "[IDBObjectStore.cpp]"); + + IndexedDBRequestParentBase* actor = mRequest->GetActorParent(); + MOZ_ASSERT(actor); + + if (NS_SUCCEEDED(aResultCode)) { + nsresult rv = EnsureCursor(); + if (NS_FAILED(rv)) { + NS_WARNING("EnsureCursor failed!"); + aResultCode = rv; + } + } + + ResponseValue response; + if (NS_FAILED(aResultCode)) { + response = aResultCode; + } else { + OpenCursorResponse openCursorResponse; + + if (!mCursor) { + openCursorResponse = mozilla::void_t(); + } + else { + IndexedDBObjectStoreParent* objectStoreActor = + mObjectStore->GetActorParent(); + MOZ_ASSERT(objectStoreActor); + + IndexedDBRequestParentBase* requestActor = mRequest->GetActorParent(); + MOZ_ASSERT(requestActor); + + ObjectStoreCursorConstructorParams params; + params.requestParent() = requestActor; + params.direction() = mDirection; + params.key() = mKey; + params.optionalCloneInfo() = mozilla::void_t(); + + if (!objectStoreActor->OpenCursor(mCursor, params, openCursorResponse)) { + return Error; + } + } + + response = openCursorResponse; + } + + if (!actor->SendResponse(response)) { + return Error; + } + + return Success_Sent; +} + +nsresult +OpenKeyCursorHelper::UnpackResponseFromParentProcess( + const ResponseValue& aResponseValue) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!IndexedDatabaseManager::IsMainProcess()); + MOZ_ASSERT(aResponseValue.type() == ResponseValue::TOpenCursorResponse); + MOZ_ASSERT(aResponseValue.get_OpenCursorResponse().type() == + OpenCursorResponse::Tvoid_t || + aResponseValue.get_OpenCursorResponse().type() == + OpenCursorResponse::TPIndexedDBCursorChild); + MOZ_ASSERT(!mCursor); + + PROFILER_MAIN_THREAD_LABEL("IndexedDB", + "OpenKeyCursorHelper::" + "UnpackResponseFromParentProcess " + "[IDBObjectStore.cpp]"); + + const OpenCursorResponse& response = + aResponseValue.get_OpenCursorResponse(); + + switch (response.type()) { + case OpenCursorResponse::Tvoid_t: + break; + + case OpenCursorResponse::TPIndexedDBCursorChild: { + IndexedDBCursorChild* actor = + static_cast( + response.get_PIndexedDBCursorChild()); + + mCursor = actor->ForgetStrongCursor(); + NS_ASSERTION(mCursor, "This should never be null!"); + + } break; + + default: + MOZ_CRASH("Unknown response union type!"); + } + + return NS_OK; +} + nsresult CreateIndexHelper::DoDatabaseWork(mozIStorageConnection* aConnection) { diff --git a/dom/indexedDB/IDBObjectStore.h b/dom/indexedDB/IDBObjectStore.h index dfad748f7fdf..d5ff96aa9126 100644 --- a/dom/indexedDB/IDBObjectStore.h +++ b/dom/indexedDB/IDBObjectStore.h @@ -239,6 +239,11 @@ public: size_t aDirection, ErrorResult& aRv); + already_AddRefed + OpenKeyCursorInternal(IDBKeyRange* aKeyRange, + size_t aDirection, + ErrorResult& aRv); + nsresult OpenCursorFromChildProcess( IDBRequest* aRequest, @@ -248,6 +253,12 @@ public: nsTArray& aBlobs, IDBCursor** _retval); + nsresult + OpenCursorFromChildProcess(IDBRequest* aRequest, + size_t aDirection, + const Key& aKey, + IDBCursor** _retval); + void SetInfo(ObjectStoreInfo* aInfo); @@ -347,6 +358,10 @@ public: GetAllKeys(JSContext* aCx, const Optional& aKey, const Optional& aLimit, ErrorResult& aRv); + already_AddRefed + OpenKeyCursor(JSContext* aCx, const Optional& aRange, + IDBCursorDirection aDirection, ErrorResult& aRv); + protected: IDBObjectStore(); ~IDBObjectStore(); diff --git a/dom/indexedDB/ipc/IndexedDBChild.cpp b/dom/indexedDB/ipc/IndexedDBChild.cpp index 038d42978b2e..8ca2120b5bbd 100644 --- a/dom/indexedDB/ipc/IndexedDBChild.cpp +++ b/dom/indexedDB/ipc/IndexedDBChild.cpp @@ -742,17 +742,40 @@ IndexedDBObjectStoreChild::RecvPIndexedDBCursorConstructor( size_t direction = static_cast(aParams.direction()); - nsTArray blobs; - IDBObjectStore::ConvertActorsToBlobs(aParams.blobsChild(), blobs); - nsRefPtr cursor; - nsresult rv = - mObjectStore->OpenCursorFromChildProcess(request, direction, aParams.key(), - aParams.cloneInfo(), blobs, - getter_AddRefs(cursor)); - NS_ENSURE_SUCCESS(rv, false); + nsresult rv; - MOZ_ASSERT(blobs.IsEmpty(), "Should have swapped blob elements!"); + typedef ipc::OptionalStructuredCloneReadInfo CursorUnionType; + + switch (aParams.optionalCloneInfo().type()) { + case CursorUnionType::TSerializedStructuredCloneReadInfo: { + nsTArray blobs; + IDBObjectStore::ConvertActorsToBlobs(aParams.blobsChild(), blobs); + + const SerializedStructuredCloneReadInfo& cloneInfo = + aParams.optionalCloneInfo().get_SerializedStructuredCloneReadInfo(); + + rv = mObjectStore->OpenCursorFromChildProcess(request, direction, + aParams.key(), cloneInfo, + blobs, + getter_AddRefs(cursor)); + NS_ENSURE_SUCCESS(rv, false); + + MOZ_ASSERT(blobs.IsEmpty(), "Should have swapped blob elements!"); + } break; + + case CursorUnionType::Tvoid_t: + MOZ_ASSERT(aParams.blobsChild().IsEmpty()); + + rv = mObjectStore->OpenCursorFromChildProcess(request, direction, + aParams.key(), + getter_AddRefs(cursor)); + NS_ENSURE_SUCCESS(rv, false); + break; + + default: + MOZ_CRASH("Unknown union type!"); + } actor->SetCursor(cursor); return true; @@ -1088,7 +1111,8 @@ IndexedDBObjectStoreRequestChild::Recv__delete__(const ResponseValue& aResponse) MOZ_ASSERT(mRequestType == ParamsUnionType::TCountParams); break; case ResponseValue::TOpenCursorResponse: - MOZ_ASSERT(mRequestType == ParamsUnionType::TOpenCursorParams); + MOZ_ASSERT(mRequestType == ParamsUnionType::TOpenCursorParams || + mRequestType == ParamsUnionType::TOpenKeyCursorParams); break; default: diff --git a/dom/indexedDB/ipc/IndexedDBParams.ipdlh b/dom/indexedDB/ipc/IndexedDBParams.ipdlh index d3e3d2d8bba5..a1222bbadbfd 100644 --- a/dom/indexedDB/ipc/IndexedDBParams.ipdlh +++ b/dom/indexedDB/ipc/IndexedDBParams.ipdlh @@ -6,6 +6,7 @@ include "mozilla/dom/indexedDB/SerializationHelpers.h"; using mozilla::dom::indexedDB::Key; using mozilla::dom::indexedDB::IDBCursor::Direction; +using mozilla::dom::indexedDB::SerializedStructuredCloneReadInfo; using mozilla::void_t; @@ -57,6 +58,18 @@ struct OpenCursorParams Direction direction; }; +struct OpenKeyCursorParams +{ + OptionalKeyRange optionalKeyRange; + Direction direction; +}; + +union OptionalStructuredCloneReadInfo +{ + SerializedStructuredCloneReadInfo; + void_t; +}; + } // namespace ipc } // namespace indexedDB } // namespace dom diff --git a/dom/indexedDB/ipc/IndexedDBParent.cpp b/dom/indexedDB/ipc/IndexedDBParent.cpp index 419cd12716c7..763b8e55c3d3 100644 --- a/dom/indexedDB/ipc/IndexedDBParent.cpp +++ b/dom/indexedDB/ipc/IndexedDBParent.cpp @@ -1118,6 +1118,9 @@ IndexedDBObjectStoreParent::RecvPIndexedDBRequestConstructor( case ObjectStoreRequestParams::TOpenCursorParams: return actor->OpenCursor(aParams.get_OpenCursorParams()); + case ObjectStoreRequestParams::TOpenKeyCursorParams: + return actor->OpenKeyCursor(aParams.get_OpenKeyCursorParams()); + default: MOZ_CRASH("Unknown type!"); } @@ -1775,6 +1778,47 @@ IndexedDBObjectStoreRequestParent::OpenCursor(const OpenCursorParams& aParams) return true; } +bool +IndexedDBObjectStoreRequestParent::OpenKeyCursor( + const OpenKeyCursorParams& aParams) +{ + MOZ_ASSERT(mRequestType == ParamsUnionType::TOpenKeyCursorParams); + MOZ_ASSERT(mObjectStore); + + const ipc::OptionalKeyRange keyRangeUnion = aParams.optionalKeyRange(); + + nsRefPtr keyRange; + + switch (keyRangeUnion.type()) { + case ipc::OptionalKeyRange::TKeyRange: + keyRange = + IDBKeyRange::FromSerializedKeyRange(keyRangeUnion.get_KeyRange()); + break; + + case ipc::OptionalKeyRange::Tvoid_t: + break; + + default: + MOZ_CRASH("Unknown param type!"); + } + + size_t direction = static_cast(aParams.direction()); + + nsRefPtr request; + + { + AutoSetCurrentTransaction asct(mObjectStore->Transaction()); + + ErrorResult rv; + request = mObjectStore->OpenKeyCursorInternal(keyRange, direction, rv); + ENSURE_SUCCESS(rv, false); + } + + request->SetActor(this); + mRequest.swap(request); + return true; +} + /******************************************************************************* * IndexedDBIndexRequestParent ******************************************************************************/ diff --git a/dom/indexedDB/ipc/IndexedDBParent.h b/dom/indexedDB/ipc/IndexedDBParent.h index 092f3f87de2d..51d79b381c12 100644 --- a/dom/indexedDB/ipc/IndexedDBParent.h +++ b/dom/indexedDB/ipc/IndexedDBParent.h @@ -710,6 +710,7 @@ class IndexedDBObjectStoreRequestParent : public IndexedDBRequestParentBase typedef ipc::GetAllKeysParams GetAllKeysParams; typedef ipc::CountParams CountParams; typedef ipc::OpenCursorParams OpenCursorParams; + typedef ipc::OpenKeyCursorParams OpenKeyCursorParams; public: IndexedDBObjectStoreRequestParent(IDBObjectStore* aObjectStore, @@ -743,6 +744,9 @@ public: bool OpenCursor(const OpenCursorParams& aParams); + bool + OpenKeyCursor(const OpenKeyCursorParams& aParams); + protected: void ConvertBlobActors(const InfallibleTArray& aActors, diff --git a/dom/indexedDB/ipc/PIndexedDBIndex.ipdl b/dom/indexedDB/ipc/PIndexedDBIndex.ipdl index 21ff8d89b403..3b402fa5440e 100644 --- a/dom/indexedDB/ipc/PIndexedDBIndex.ipdl +++ b/dom/indexedDB/ipc/PIndexedDBIndex.ipdl @@ -9,8 +9,6 @@ include protocol PIndexedDBRequest; include IndexedDBParams; -using mozilla::dom::indexedDB::SerializedStructuredCloneReadInfo; - namespace mozilla { namespace dom { namespace indexedDB { @@ -22,12 +20,6 @@ struct GetKeyParams KeyRange keyRange; }; -struct OpenKeyCursorParams -{ - OptionalKeyRange optionalKeyRange; - Direction direction; -}; - union IndexRequestParams { GetParams; @@ -39,12 +31,6 @@ union IndexRequestParams OpenKeyCursorParams; }; -union OptionalStructuredCloneReadInfo -{ - SerializedStructuredCloneReadInfo; - void_t; -}; - struct IndexCursorConstructorParams { PIndexedDBRequest request; diff --git a/dom/indexedDB/ipc/PIndexedDBObjectStore.ipdl b/dom/indexedDB/ipc/PIndexedDBObjectStore.ipdl index e3483c346c63..df689242728f 100644 --- a/dom/indexedDB/ipc/PIndexedDBObjectStore.ipdl +++ b/dom/indexedDB/ipc/PIndexedDBObjectStore.ipdl @@ -12,7 +12,6 @@ include IndexedDBParams; using mozilla::dom::indexedDB::IndexInfo; using mozilla::dom::indexedDB::IndexUpdateInfo; -using mozilla::dom::indexedDB::SerializedStructuredCloneReadInfo; using mozilla::dom::indexedDB::SerializedStructuredCloneWriteInfo; namespace mozilla { @@ -59,6 +58,7 @@ union ObjectStoreRequestParams ClearParams; CountParams; OpenCursorParams; + OpenKeyCursorParams; }; struct CreateIndexParams @@ -82,7 +82,7 @@ struct ObjectStoreCursorConstructorParams PIndexedDBRequest request; Direction direction; Key key; - SerializedStructuredCloneReadInfo cloneInfo; + OptionalStructuredCloneReadInfo optionalCloneInfo; PBlob[] blobs; }; diff --git a/dom/indexedDB/ipc/unit/xpcshell.ini b/dom/indexedDB/ipc/unit/xpcshell.ini index 78254e31dc11..c2f6248782cd 100644 --- a/dom/indexedDB/ipc/unit/xpcshell.ini +++ b/dom/indexedDB/ipc/unit/xpcshell.ini @@ -38,6 +38,7 @@ tail = [test_objectCursors.js] [test_objectStore_getAllKeys.js] [test_objectStore_inline_autoincrement_key_added_on_put.js] +[test_objectStore_openKeyCursor.js] [test_objectStore_remove_values.js] [test_odd_result_order.js] [test_open_empty_db.js] diff --git a/dom/indexedDB/test/Makefile.in b/dom/indexedDB/test/Makefile.in index 18e51ca022e3..06974124904b 100644 --- a/dom/indexedDB/test/Makefile.in +++ b/dom/indexedDB/test/Makefile.in @@ -72,6 +72,7 @@ MOCHITEST_FILES = \ test_objectCursors.html \ test_objectStore_getAllKeys.html \ test_objectStore_inline_autoincrement_key_added_on_put.html \ + test_objectStore_openKeyCursor.html \ test_objectStore_remove_values.html \ test_object_identity.html \ test_odd_result_order.html \ diff --git a/dom/indexedDB/test/test_objectStore_openKeyCursor.html b/dom/indexedDB/test/test_objectStore_openKeyCursor.html new file mode 100644 index 000000000000..4c6437071a99 --- /dev/null +++ b/dom/indexedDB/test/test_objectStore_openKeyCursor.html @@ -0,0 +1,19 @@ + + + + Indexed Database Property Test + + + + + + + + + + + + diff --git a/dom/indexedDB/test/unit/Makefile.in b/dom/indexedDB/test/unit/Makefile.in index 8761a2f5e90e..d44968e38573 100644 --- a/dom/indexedDB/test/unit/Makefile.in +++ b/dom/indexedDB/test/unit/Makefile.in @@ -40,6 +40,7 @@ MOCHITEST_FILES = \ test_objectCursors.js \ test_objectStore_getAllKeys.js \ test_objectStore_inline_autoincrement_key_added_on_put.js \ + test_objectStore_openKeyCursor.js \ test_objectStore_remove_values.js \ test_odd_result_order.js \ test_open_empty_db.js \ diff --git a/dom/indexedDB/test/unit/test_objectStore_openKeyCursor.js b/dom/indexedDB/test/unit/test_objectStore_openKeyCursor.js new file mode 100644 index 000000000000..6c46cac4e51d --- /dev/null +++ b/dom/indexedDB/test/unit/test_objectStore_openKeyCursor.js @@ -0,0 +1,400 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +let testGenerator = testSteps(); + +function testSteps() { + const dbName = this.window ? + window.location.pathname : + "test_objectStore_openKeyCursor"; + const dbVersion = 1; + const objectStoreName = "foo"; + const keyCount = 100; + + let request = indexedDB.open(dbName, dbVersion); + request.onerror = errorHandler; + request.onupgradeneeded = grabEventAndContinueHandler; + request.onsuccess = unexpectedSuccessHandler; + + let event = yield undefined; + + info("Creating database"); + + let db = event.target.result; + let objectStore = db.createObjectStore(objectStoreName); + for (let i = 0; i < keyCount; i++) { + objectStore.add(true, i); + } + + request.onupgradeneeded = unexpectedSuccessHandler; + request.onsuccess = grabEventAndContinueHandler; + + event = yield undefined; + + db = event.target.result; + objectStore = db.transaction(objectStoreName, "readwrite") + .objectStore(objectStoreName); + + info("Getting all keys"); + objectStore.getAllKeys().onsuccess = grabEventAndContinueHandler; + event = yield undefined; + + const allKeys = event.target.result; + + ok(Array.isArray(allKeys), "Got an array result"); + is(allKeys.length, keyCount, "Got correct array length"); + + info("Opening normal key cursor"); + + let seenKeys = []; + objectStore.openKeyCursor().onsuccess = event => { + let cursor = event.target.result; + if (!cursor) { + continueToNextStepSync(); + return; + } + + is(cursor.source, objectStore, "Correct source"); + is(cursor.direction, "next", "Correct direction"); + + let exception = null; + try { + cursor.update(10); + } catch(e) { + exception = e; + } + ok(!!exception, "update() throws for key cursor"); + + exception = null; + try { + cursor.delete(); + } catch(e) { + exception = e; + } + ok(!!exception, "delete() throws for key cursor"); + + is(cursor.key, cursor.primaryKey, "key and primaryKey match"); + ok(!("value" in cursor), "No 'value' property on key cursor"); + + seenKeys.push(cursor.key); + cursor.continue(); + }; + yield undefined; + + is(seenKeys.length, allKeys.length, "Saw the right number of keys"); + + let match = true; + for (let i = 0; i < seenKeys.length; i++) { + if (seenKeys[i] !== allKeys[i]) { + match = false; + break; + } + } + ok(match, "All keys matched"); + + info("Opening key cursor with keyRange"); + + let keyRange = IDBKeyRange.bound(10, 20, false, true); + + seenKeys = []; + objectStore.openKeyCursor(keyRange).onsuccess = event => { + let cursor = event.target.result; + if (!cursor) { + continueToNextStepSync(); + return; + } + + is(cursor.source, objectStore, "Correct source"); + is(cursor.direction, "next", "Correct direction"); + + let exception = null; + try { + cursor.update(10); + } catch(e) { + exception = e; + } + ok(!!exception, "update() throws for key cursor"); + + exception = null; + try { + cursor.delete(); + } catch(e) { + exception = e; + } + ok(!!exception, "delete() throws for key cursor"); + + is(cursor.key, cursor.primaryKey, "key and primaryKey match"); + ok(!("value" in cursor), "No 'value' property on key cursor"); + + seenKeys.push(cursor.key); + cursor.continue(); + }; + yield undefined; + + is(seenKeys.length, 10, "Saw the right number of keys"); + + match = true; + for (let i = 0; i < seenKeys.length; i++) { + if (seenKeys[i] !== allKeys[i + 10]) { + match = false; + break; + } + } + ok(match, "All keys matched"); + + info("Opening key cursor with unmatched keyRange"); + + keyRange = IDBKeyRange.bound(10000, 200000); + + seenKeys = []; + objectStore.openKeyCursor(keyRange).onsuccess = event => { + let cursor = event.target.result; + if (!cursor) { + continueToNextStepSync(); + return; + } + + ok(false, "Shouldn't have any keys here"); + cursor.continue(); + }; + yield undefined; + + is(seenKeys.length, 0, "Saw the right number of keys"); + + info("Opening reverse key cursor"); + + seenKeys = []; + objectStore.openKeyCursor(null, "prev").onsuccess = event => { + let cursor = event.target.result; + if (!cursor) { + continueToNextStepSync(); + return; + } + + is(cursor.source, objectStore, "Correct source"); + is(cursor.direction, "prev", "Correct direction"); + + let exception = null; + try { + cursor.update(10); + } catch(e) { + exception = e; + } + ok(!!exception, "update() throws for key cursor"); + + exception = null; + try { + cursor.delete(); + } catch(e) { + exception = e; + } + ok(!!exception, "delete() throws for key cursor"); + + is(cursor.key, cursor.primaryKey, "key and primaryKey match"); + ok(!("value" in cursor), "No 'value' property on key cursor"); + + seenKeys.push(cursor.key); + cursor.continue(); + }; + yield undefined; + + is(seenKeys.length, allKeys.length, "Saw the right number of keys"); + + seenKeys.reverse(); + + match = true; + for (let i = 0; i < seenKeys.length; i++) { + if (seenKeys[i] !== allKeys[i]) { + match = false; + break; + } + } + ok(match, "All keys matched"); + + info("Opening reverse key cursor with key range"); + + keyRange = IDBKeyRange.bound(10, 20, false, true); + + seenKeys = []; + objectStore.openKeyCursor(keyRange, "prev").onsuccess = event => { + let cursor = event.target.result; + if (!cursor) { + continueToNextStepSync(); + return; + } + + is(cursor.source, objectStore, "Correct source"); + is(cursor.direction, "prev", "Correct direction"); + + let exception = null; + try { + cursor.update(10); + } catch(e) { + exception = e; + } + ok(!!exception, "update() throws for key cursor"); + + exception = null; + try { + cursor.delete(); + } catch(e) { + exception = e; + } + ok(!!exception, "delete() throws for key cursor"); + + is(cursor.key, cursor.primaryKey, "key and primaryKey match"); + ok(!("value" in cursor), "No 'value' property on key cursor"); + + seenKeys.push(cursor.key); + cursor.continue(); + }; + yield undefined; + + is(seenKeys.length, 10, "Saw the right number of keys"); + + seenKeys.reverse(); + + match = true; + for (let i = 0; i < 10; i++) { + if (seenKeys[i] !== allKeys[i + 10]) { + match = false; + break; + } + } + ok(match, "All keys matched"); + + info("Opening reverse key cursor with unmatched key range"); + + keyRange = IDBKeyRange.bound(10000, 200000); + + seenKeys = []; + objectStore.openKeyCursor(keyRange, "prev").onsuccess = event => { + let cursor = event.target.result; + if (!cursor) { + continueToNextStepSync(); + return; + } + + ok(false, "Shouldn't have any keys here"); + cursor.continue(); + }; + yield undefined; + + is(seenKeys.length, 0, "Saw the right number of keys"); + + info("Opening key cursor with advance"); + + seenKeys = []; + objectStore.openKeyCursor().onsuccess = event => { + let cursor = event.target.result; + if (!cursor) { + continueToNextStepSync(); + return; + } + + is(cursor.source, objectStore, "Correct source"); + is(cursor.direction, "next", "Correct direction"); + + let exception = null; + try { + cursor.update(10); + } catch(e) { + exception = e; + } + ok(!!exception, "update() throws for key cursor"); + + exception = null; + try { + cursor.delete(); + } catch(e) { + exception = e; + } + ok(!!exception, "delete() throws for key cursor"); + + is(cursor.key, cursor.primaryKey, "key and primaryKey match"); + ok(!("value" in cursor), "No 'value' property on key cursor"); + + seenKeys.push(cursor.key); + if (seenKeys.length == 1) { + cursor.advance(10); + } else { + cursor.continue(); + } + }; + yield undefined; + + is(seenKeys.length, allKeys.length - 9, "Saw the right number of keys"); + + let match = true; + for (let i = 0, j = 0; i < seenKeys.length; i++) { + if (seenKeys[i] !== allKeys[i + j]) { + match = false; + break; + } + if (i == 0) { + j = 9; + } + } + ok(match, "All keys matched"); + + info("Opening key cursor with continue-to-key"); + + seenKeys = []; + objectStore.openKeyCursor().onsuccess = event => { + let cursor = event.target.result; + if (!cursor) { + continueToNextStepSync(); + return; + } + + is(cursor.source, objectStore, "Correct source"); + is(cursor.direction, "next", "Correct direction"); + + let exception = null; + try { + cursor.update(10); + } catch(e) { + exception = e; + } + ok(!!exception, "update() throws for key cursor"); + + exception = null; + try { + cursor.delete(); + } catch(e) { + exception = e; + } + ok(!!exception, "delete() throws for key cursor"); + + is(cursor.key, cursor.primaryKey, "key and primaryKey match"); + ok(!("value" in cursor), "No 'value' property on key cursor"); + + seenKeys.push(cursor.key); + + if (seenKeys.length == 1) { + cursor.continue(10); + } else { + cursor.continue(); + } + }; + yield undefined; + + is(seenKeys.length, allKeys.length - 9, "Saw the right number of keys"); + + let match = true; + for (let i = 0, j = 0; i < seenKeys.length; i++) { + if (seenKeys[i] !== allKeys[i + j]) { + match = false; + break; + } + if (i == 0) { + j = 9; + } + } + ok(match, "All keys matched"); + + finishTest(); + yield undefined; +} diff --git a/dom/indexedDB/test/unit/xpcshell.ini b/dom/indexedDB/test/unit/xpcshell.ini index c098bd9df317..87a716438437 100644 --- a/dom/indexedDB/test/unit/xpcshell.ini +++ b/dom/indexedDB/test/unit/xpcshell.ini @@ -41,6 +41,7 @@ tail = [test_objectCursors.js] [test_objectStore_getAllKeys.js] [test_objectStore_inline_autoincrement_key_added_on_put.js] +[test_objectStore_openKeyCursor.js] [test_objectStore_remove_values.js] [test_odd_result_order.js] [test_open_empty_db.js] diff --git a/dom/webidl/IDBObjectStore.webidl b/dom/webidl/IDBObjectStore.webidl index 0607f832037d..d364ea340bc4 100644 --- a/dom/webidl/IDBObjectStore.webidl +++ b/dom/webidl/IDBObjectStore.webidl @@ -71,4 +71,7 @@ partial interface IDBObjectStore { [Pref="dom.indexedDB.experimental", Throws] IDBRequest getAllKeys (optional any key, optional unsigned long limit); + + [Pref="dom.indexedDB.experimental", Throws] + IDBRequest openKeyCursor (optional any range, optional IDBCursorDirection direction = "next"); };