diff -r 000000000000 -r 6474c204b198 dom/indexedDB/IDBCursor.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dom/indexedDB/IDBCursor.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,1347 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "base/basictypes.h" + +#include "IDBCursor.h" + +#include "mozilla/storage.h" +#include "nsComponentManagerUtils.h" +#include "nsJSUtils.h" +#include "nsThreadUtils.h" + +#include "AsyncConnectionHelper.h" +#include "IDBEvents.h" +#include "IDBIndex.h" +#include "IDBObjectStore.h" +#include "IDBTransaction.h" +#include "ProfilerHelpers.h" +#include "ReportInternalError.h" +#include "TransactionThreadPool.h" + +#include "ipc/IndexedDBChild.h" +#include "ipc/IndexedDBParent.h" + +#include "IndexedDatabaseInlines.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/UnionTypes.h" + +USING_INDEXEDDB_NAMESPACE +using namespace mozilla::dom::indexedDB::ipc; +using mozilla::dom::Optional; +using mozilla::dom::OwningIDBObjectStoreOrIDBIndex; +using mozilla::ErrorResult; + +static_assert(sizeof(size_t) >= sizeof(IDBCursor::Direction), + "Relying on conversion between size_t and IDBCursor::Direction"); + +namespace { + +class CursorHelper : public AsyncConnectionHelper +{ +public: + CursorHelper(IDBCursor* aCursor) + : AsyncConnectionHelper(aCursor->Transaction(), aCursor->Request()), + mCursor(aCursor), mActor(nullptr) + { + NS_ASSERTION(aCursor, "Null cursor!"); + } + + virtual void ReleaseMainThreadObjects() MOZ_OVERRIDE; + + virtual nsresult Dispatch(nsIEventTarget* aDatabaseThread) MOZ_OVERRIDE; + + virtual nsresult + PackArgumentsForParentProcess(CursorRequestParams& aParams) = 0; + + virtual nsresult + UnpackResponseFromParentProcess(const ResponseValue& aResponseValue) = 0; + +protected: + virtual ~CursorHelper() + { } + + nsRefPtr mCursor; + +private: + IndexedDBCursorRequestChild* mActor; +}; + +} // anonymous namespace + +BEGIN_INDEXEDDB_NAMESPACE + +class ContinueHelper : public CursorHelper +{ +public: + ContinueHelper(IDBCursor* aCursor, + int32_t aCount) + : CursorHelper(aCursor), mCount(aCount) + { + 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; + + virtual void ReleaseMainThreadObjects() MOZ_OVERRIDE; + + virtual nsresult + PackArgumentsForParentProcess(CursorRequestParams& aParams) MOZ_OVERRIDE; + + virtual ChildProcessSendResult + SendResponseToChildProcess(nsresult aResultCode) MOZ_OVERRIDE; + + virtual nsresult + UnpackResponseFromParentProcess(const ResponseValue& aResponseValue) + MOZ_OVERRIDE; + +protected: + virtual ~ContinueHelper() + { + IDBObjectStore::ClearCloneReadInfo(mCloneReadInfo); + } + + virtual nsresult + BindArgumentsToStatement(mozIStorageStatement* aStatement) = 0; + + virtual nsresult + GatherResultsFromStatement(mozIStorageStatement* aStatement) = 0; + + void UpdateCursorState() + { + mCursor->mCachedKey = JSVAL_VOID; + mCursor->mCachedPrimaryKey = JSVAL_VOID; + mCursor->mCachedValue = JSVAL_VOID; + mCursor->mHaveCachedKey = false; + mCursor->mHaveCachedPrimaryKey = false; + mCursor->mHaveCachedValue = false; + mCursor->mContinueCalled = false; + + if (mKey.IsUnset()) { + mCursor->mHaveValue = false; + } else { + MOZ_ASSERT(mCursor->mType == IDBCursor::OBJECTSTORE || + mCursor->mType == IDBCursor::OBJECTSTOREKEY || + !mObjectKey.IsUnset()); + + // Set new values. + mCursor->mKey = mKey; + mCursor->mObjectKey = mObjectKey; + mCursor->mContinueToKey.Unset(); + + mCursor->mCloneReadInfo = Move(mCloneReadInfo); + mCloneReadInfo.mCloneBuffer.clear(); + } + } + + int32_t mCount; + Key mKey; + Key mObjectKey; + StructuredCloneReadInfo mCloneReadInfo; +}; + +class ContinueObjectStoreHelper : public ContinueHelper +{ +public: + ContinueObjectStoreHelper(IDBCursor* aCursor, + uint32_t aCount) + : 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: + ContinueIndexHelper(IDBCursor* aCursor, + uint32_t aCount) + : ContinueHelper(aCursor, aCount) + { } + +protected: + virtual ~ContinueIndexHelper() + { } + +private: + nsresult BindArgumentsToStatement(mozIStorageStatement* aStatement); + nsresult GatherResultsFromStatement(mozIStorageStatement* aStatement); +}; + +class ContinueIndexObjectHelper : public ContinueIndexHelper +{ +public: + ContinueIndexObjectHelper(IDBCursor* aCursor, + uint32_t aCount) + : ContinueIndexHelper(aCursor, aCount) + { } + +private: + virtual ~ContinueIndexObjectHelper() + { } + + nsresult GatherResultsFromStatement(mozIStorageStatement* aStatement); +}; + +END_INDEXEDDB_NAMESPACE + +// 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, + StructuredCloneReadInfo&& aCloneReadInfo) +{ + NS_ASSERTION(aObjectStore, "Null pointer!"); + NS_ASSERTION(!aKey.IsUnset(), "Bad key!"); + + nsRefPtr cursor = + IDBCursor::CreateCommon(aRequest, aTransaction, aObjectStore, aDirection, + aRangeKey, aContinueQuery, aContinueToQuery); + NS_ASSERTION(cursor, "This shouldn't fail!"); + + cursor->mObjectStore = aObjectStore; + cursor->mType = OBJECTSTORE; + cursor->mKey = aKey; + cursor->mCloneReadInfo = Move(aCloneReadInfo); + + 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, + IDBTransaction* aTransaction, + IDBIndex* aIndex, + Direction aDirection, + const Key& aRangeKey, + const nsACString& aContinueQuery, + const nsACString& aContinueToQuery, + const Key& aKey, + const Key& aObjectKey) +{ + NS_ASSERTION(aIndex, "Null pointer!"); + NS_ASSERTION(!aKey.IsUnset(), "Bad key!"); + NS_ASSERTION(!aObjectKey.IsUnset(), "Bad key!"); + + nsRefPtr cursor = + IDBCursor::CreateCommon(aRequest, aTransaction, aIndex->ObjectStore(), + aDirection, aRangeKey, aContinueQuery, + aContinueToQuery); + NS_ASSERTION(cursor, "This shouldn't fail!"); + + cursor->mIndex = aIndex; + cursor->mType = INDEXKEY; + cursor->mKey = aKey, + cursor->mObjectKey = aObjectKey; + + return cursor.forget(); +} + +// static +already_AddRefed +IDBCursor::Create(IDBRequest* aRequest, + IDBTransaction* aTransaction, + IDBIndex* aIndex, + Direction aDirection, + const Key& aRangeKey, + const nsACString& aContinueQuery, + const nsACString& aContinueToQuery, + const Key& aKey, + const Key& aObjectKey, + StructuredCloneReadInfo&& aCloneReadInfo) +{ + NS_ASSERTION(aIndex, "Null pointer!"); + NS_ASSERTION(!aKey.IsUnset(), "Bad key!"); + + nsRefPtr cursor = + IDBCursor::CreateCommon(aRequest, aTransaction, aIndex->ObjectStore(), + aDirection, aRangeKey, aContinueQuery, + aContinueToQuery); + NS_ASSERTION(cursor, "This shouldn't fail!"); + + cursor->mObjectStore = aIndex->ObjectStore(); + cursor->mIndex = aIndex; + cursor->mType = INDEXOBJECT; + cursor->mKey = aKey; + cursor->mObjectKey = aObjectKey; + cursor->mCloneReadInfo = Move(aCloneReadInfo); + + return cursor.forget(); +} + +// static +IDBCursor::Direction +IDBCursor::ConvertDirection(mozilla::dom::IDBCursorDirection aDirection) +{ + switch (aDirection) { + case mozilla::dom::IDBCursorDirection::Next: + return NEXT; + + case mozilla::dom::IDBCursorDirection::Nextunique: + return NEXT_UNIQUE; + + case mozilla::dom::IDBCursorDirection::Prev: + return PREV; + + case mozilla::dom::IDBCursorDirection::Prevunique: + return PREV_UNIQUE; + + default: + MOZ_ASSUME_UNREACHABLE("Unknown direction!"); + } +} + +// static +already_AddRefed +IDBCursor::CreateCommon(IDBRequest* aRequest, + IDBTransaction* aTransaction, + IDBObjectStore* aObjectStore, + Direction aDirection, + const Key& aRangeKey, + const nsACString& aContinueQuery, + const nsACString& aContinueToQuery) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(aRequest, "Null pointer!"); + NS_ASSERTION(aTransaction, "Null pointer!"); + NS_ASSERTION(aObjectStore, "Null pointer!"); + NS_ASSERTION(!aContinueQuery.IsEmpty() || + !IndexedDatabaseManager::IsMainProcess(), + "Empty query!"); + NS_ASSERTION(!aContinueToQuery.IsEmpty() || + !IndexedDatabaseManager::IsMainProcess(), + "Empty query!"); + + nsRefPtr cursor = new IDBCursor(); + + IDBDatabase* database = aTransaction->Database(); + cursor->mScriptOwner = database->GetScriptOwner(); + + if (cursor->mScriptOwner) { + mozilla::HoldJSObjects(cursor.get()); + cursor->mRooted = true; + } + + cursor->mRequest = aRequest; + cursor->mTransaction = aTransaction; + cursor->mObjectStore = aObjectStore; + cursor->mDirection = aDirection; + cursor->mContinueQuery = aContinueQuery; + cursor->mContinueToQuery = aContinueToQuery; + cursor->mRangeKey = aRangeKey; + + return cursor.forget(); +} + +IDBCursor::IDBCursor() +: mScriptOwner(nullptr), + mType(OBJECTSTORE), + mDirection(IDBCursor::NEXT), + mCachedKey(JSVAL_VOID), + mCachedPrimaryKey(JSVAL_VOID), + mCachedValue(JSVAL_VOID), + mActorChild(nullptr), + mActorParent(nullptr), + mHaveCachedKey(false), + mHaveCachedPrimaryKey(false), + mHaveCachedValue(false), + mRooted(false), + mContinueCalled(false), + mHaveValue(true) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + SetIsDOMBinding(); +} + +IDBCursor::~IDBCursor() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + NS_ASSERTION(!mActorParent, "Actor parent owns us, how can we be dying?!"); + if (mActorChild) { + NS_ASSERTION(!IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); + mActorChild->Send__delete__(mActorChild); + NS_ASSERTION(!mActorChild, "Should have cleared in Send__delete__!"); + } + + DropJSObjects(); + IDBObjectStore::ClearCloneReadInfo(mCloneReadInfo); +} + +void +IDBCursor::DropJSObjects() +{ + if (!mRooted) { + return; + } + mScriptOwner = nullptr; + mCachedKey = JSVAL_VOID; + mCachedPrimaryKey = JSVAL_VOID; + mCachedValue = JSVAL_VOID; + mHaveCachedKey = false; + mHaveCachedPrimaryKey = false; + mHaveCachedValue = false; + mRooted = false; + mHaveValue = false; + mozilla::DropJSObjects(this); +} + +void +IDBCursor::ContinueInternal(const Key& aKey, int32_t aCount, ErrorResult& aRv) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aCount > 0); + + if (!mTransaction->IsOpen()) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); + return; + } + + if (!mHaveValue || mContinueCalled) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); + return; + } + + mContinueToKey = aKey; + + MOZ_ASSERT(mRequest->ReadyState() == IDBRequestReadyState::Done); + + mRequest->Reset(); + + nsRefPtr helper; + switch (mType) { + case OBJECTSTORE: + helper = new ContinueObjectStoreHelper(this, aCount); + break; + + case OBJECTSTOREKEY: + helper = new ContinueObjectStoreKeyHelper(this, aCount); + break; + + case INDEXKEY: + helper = new ContinueIndexHelper(this, aCount); + break; + + case INDEXOBJECT: + helper = new ContinueIndexObjectHelper(this, aCount); + break; + + default: + MOZ_ASSUME_UNREACHABLE("Unknown cursor type!"); + } + + nsresult rv = helper->DispatchToTransactionPool(); + if (NS_FAILED(rv)) { + IDB_WARNING("Failed to dispatch!"); + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + return; + } + + mContinueCalled = true; +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(IDBCursor) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IDBCursor) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRequest) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTransaction) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObjectStore) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIndex) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(IDBCursor) + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER + NS_ASSERTION(tmp->mHaveCachedKey || JSVAL_IS_VOID(tmp->mCachedKey), + "Should have a cached key"); + NS_ASSERTION(tmp->mHaveCachedPrimaryKey || + JSVAL_IS_VOID(tmp->mCachedPrimaryKey), + "Should have a cached primary key"); + NS_ASSERTION(tmp->mHaveCachedValue || JSVAL_IS_VOID(tmp->mCachedValue), + "Should have a cached value"); + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mScriptOwner) + NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mCachedKey) + NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mCachedPrimaryKey) + NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mCachedValue) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IDBCursor) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER + // Don't unlink mObjectStore, mIndex, or mTransaction! + tmp->DropJSObjects(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mRequest) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IDBCursor) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(IDBCursor) +NS_IMPL_CYCLE_COLLECTING_RELEASE(IDBCursor) + +JSObject* +IDBCursor::WrapObject(JSContext* aCx) +{ + MOZ_ASSERT(NS_IsMainThread()); + + switch (mType) { + case OBJECTSTORE: + case INDEXOBJECT: + return IDBCursorWithValueBinding::Wrap(aCx, this); + + case OBJECTSTOREKEY: + case INDEXKEY: + return IDBCursorBinding::Wrap(aCx, this); + + default: + MOZ_ASSUME_UNREACHABLE("Bad type!"); + } +} + +mozilla::dom::IDBCursorDirection +IDBCursor::GetDirection() const +{ + MOZ_ASSERT(NS_IsMainThread()); + + switch (mDirection) { + case NEXT: + return mozilla::dom::IDBCursorDirection::Next; + + case NEXT_UNIQUE: + return mozilla::dom::IDBCursorDirection::Nextunique; + + case PREV: + return mozilla::dom::IDBCursorDirection::Prev; + + case PREV_UNIQUE: + return mozilla::dom::IDBCursorDirection::Prevunique; + + default: + MOZ_ASSUME_UNREACHABLE("Bad direction!"); + } +} + +void +IDBCursor::GetSource(OwningIDBObjectStoreOrIDBIndex& aSource) const +{ + MOZ_ASSERT(NS_IsMainThread()); + + 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!"); + } +} + +void +IDBCursor::GetKey(JSContext* aCx, JS::MutableHandle aResult, + ErrorResult& aRv) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mKey.IsUnset() || !mHaveValue); + + if (!mHaveValue) { + aResult.setUndefined(); + return; + } + + if (!mHaveCachedKey) { + if (!mRooted) { + mozilla::HoldJSObjects(this); + mRooted = true; + } + + aRv = mKey.ToJSVal(aCx, mCachedKey); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + mHaveCachedKey = true; + } + + JS::ExposeValueToActiveJS(mCachedKey); + aResult.set(mCachedKey); +} + +void +IDBCursor::GetPrimaryKey(JSContext* aCx, JS::MutableHandle aResult, + ErrorResult& aRv) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!mHaveValue) { + aResult.setUndefined(); + return; + } + + if (!mHaveCachedPrimaryKey) { + if (!mRooted) { + mozilla::HoldJSObjects(this); + mRooted = true; + } + + const Key& key = + (mType == OBJECTSTORE || mType == OBJECTSTOREKEY) ? mKey : mObjectKey; + MOZ_ASSERT(!key.IsUnset()); + + aRv = key.ToJSVal(aCx, mCachedPrimaryKey); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + mHaveCachedPrimaryKey = true; + } + + JS::ExposeValueToActiveJS(mCachedPrimaryKey); + aResult.set(mCachedPrimaryKey); +} + +void +IDBCursor::GetValue(JSContext* aCx, JS::MutableHandle aResult, + ErrorResult& aRv) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mType == OBJECTSTORE || mType == INDEXOBJECT); + + if (!mHaveValue) { + aResult.setUndefined(); + return; + } + + if (!mHaveCachedValue) { + if (!mRooted) { + mozilla::HoldJSObjects(this); + mRooted = true; + } + + JS::Rooted val(aCx); + if (!IDBObjectStore::DeserializeValue(aCx, mCloneReadInfo, &val)) { + aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); + return; + } + + mCloneReadInfo.mCloneBuffer.clear(); + + mCachedValue = val; + mHaveCachedValue = true; + } + + JS::ExposeValueToActiveJS(mCachedValue); + aResult.set(mCachedValue); +} + +void +IDBCursor::Continue(JSContext* aCx, + JS::Handle aKey, + ErrorResult &aRv) +{ + MOZ_ASSERT(NS_IsMainThread()); + + Key key; + aRv = key.SetFromJSVal(aCx, aKey); + ENSURE_SUCCESS_VOID(aRv); + + if (!key.IsUnset()) { + switch (mDirection) { + case IDBCursor::NEXT: + case IDBCursor::NEXT_UNIQUE: + if (key <= mKey) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR); + return; + } + break; + + case IDBCursor::PREV: + case IDBCursor::PREV_UNIQUE: + if (key >= mKey) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR); + return; + } + break; + + default: + MOZ_ASSUME_UNREACHABLE("Unknown direction type!"); + } + } + + ContinueInternal(key, 1, aRv); + if (aRv.Failed()) { + return; + } + +#ifdef IDB_PROFILER_USE_MARKS + if (mType == OBJECTSTORE || mType == OBJECTSTOREKEY) { + IDB_PROFILER_MARK("IndexedDB Request %llu: " + "database(%s).transaction(%s).objectStore(%s).cursor(%s)." + "continue(%s)", + "IDBRequest[%llu] MT IDBCursor.continue()", + Request()->GetSerialNumber(), + IDB_PROFILER_STRING(Transaction()->Database()), + IDB_PROFILER_STRING(Transaction()), + IDB_PROFILER_STRING(mObjectStore), + IDB_PROFILER_STRING(mDirection), + key.IsUnset() ? "" : IDB_PROFILER_STRING(key)); + } + else { + IDB_PROFILER_MARK("IndexedDB Request %llu: " + "database(%s).transaction(%s).objectStore(%s).index(%s)." + "cursor(%s).continue(%s)", + "IDBRequest[%llu] MT IDBCursor.continue()", + Request()->GetSerialNumber(), + IDB_PROFILER_STRING(Transaction()->Database()), + IDB_PROFILER_STRING(Transaction()), + IDB_PROFILER_STRING(mObjectStore), + IDB_PROFILER_STRING(mIndex), + IDB_PROFILER_STRING(mDirection), + key.IsUnset() ? "" : IDB_PROFILER_STRING(key)); + } +#endif +} + +already_AddRefed +IDBCursor::Update(JSContext* aCx, JS::Handle aValue, + ErrorResult& aRv) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!mTransaction->IsOpen()) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); + return nullptr; + } + + if (!mTransaction->IsWriteAllowed()) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR); + return nullptr; + } + + if (!mHaveValue || mType == OBJECTSTOREKEY || mType == INDEXKEY) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); + return nullptr; + } + + MOZ_ASSERT(mObjectStore); + MOZ_ASSERT(!mKey.IsUnset()); + MOZ_ASSERT(mType == OBJECTSTORE || mType == INDEXOBJECT); + MOZ_ASSERT_IF(mType == INDEXOBJECT, !mObjectKey.IsUnset()); + + const Key& objectKey = (mType == OBJECTSTORE) ? mKey : mObjectKey; + + nsRefPtr request; + if (mObjectStore->HasValidKeyPath()) { + // Make sure the object given has the correct keyPath value set on it. + const KeyPath& keyPath = mObjectStore->GetKeyPath(); + Key key; + + aRv = keyPath.ExtractKey(aCx, aValue, key); + if (aRv.Failed()) { + return nullptr; + } + + if (key != objectKey) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR); + return nullptr; + } + + request = mObjectStore->Put(aCx, aValue, JS::UndefinedHandleValue, aRv); + if (aRv.Failed()) { + return nullptr; + } + } + else { + JS::Rooted keyVal(aCx); + aRv = objectKey.ToJSVal(aCx, &keyVal); + ENSURE_SUCCESS(aRv, nullptr); + + request = mObjectStore->Put(aCx, aValue, keyVal, aRv); + if (aRv.Failed()) { + return nullptr; + } + } + +#ifdef IDB_PROFILER_USE_MARKS + { + uint64_t requestSerial = + static_cast(request.get())->GetSerialNumber(); + if (mType == OBJECTSTORE) { + IDB_PROFILER_MARK("IndexedDB Request %llu: " + "database(%s).transaction(%s).objectStore(%s)." + "cursor(%s).update(%s)", + "IDBRequest[%llu] MT IDBCursor.update()", + requestSerial, + IDB_PROFILER_STRING(mTransaction->Database()), + IDB_PROFILER_STRING(mTransaction), + IDB_PROFILER_STRING(mObjectStore), + IDB_PROFILER_STRING(mDirection), + mObjectStore->HasValidKeyPath() ? "" : + IDB_PROFILER_STRING(objectKey)); + } + else { + IDB_PROFILER_MARK("IndexedDB Request %llu: " + "database(%s).transaction(%s).objectStore(%s)." + "index(%s).cursor(%s).update(%s)", + "IDBRequest[%llu] MT IDBCursor.update()", + requestSerial, + IDB_PROFILER_STRING(mTransaction->Database()), + IDB_PROFILER_STRING(mTransaction), + IDB_PROFILER_STRING(mObjectStore), + IDB_PROFILER_STRING(mIndex), + IDB_PROFILER_STRING(mDirection), + mObjectStore->HasValidKeyPath() ? "" : + IDB_PROFILER_STRING(objectKey)); + } + } +#endif + + return request.forget(); +} + +already_AddRefed +IDBCursor::Delete(JSContext* aCx, ErrorResult& aRv) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!mTransaction->IsOpen()) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR); + return nullptr; + } + + if (!mTransaction->IsWriteAllowed()) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR); + return nullptr; + } + + if (!mHaveValue || mType == OBJECTSTOREKEY || mType == INDEXKEY) { + aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR); + return nullptr; + } + + MOZ_ASSERT(mObjectStore); + MOZ_ASSERT(mType == OBJECTSTORE || mType == INDEXOBJECT); + MOZ_ASSERT(!mKey.IsUnset()); + + 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); + ENSURE_SUCCESS(aRv, nullptr); + +#ifdef IDB_PROFILER_USE_MARKS + { + uint64_t requestSerial = request->GetSerialNumber(); + if (mType == OBJECTSTORE) { + IDB_PROFILER_MARK("IndexedDB Request %llu: " + "database(%s).transaction(%s).objectStore(%s)." + "cursor(%s).delete(%s)", + "IDBRequest[%llu] MT IDBCursor.delete()", + requestSerial, + IDB_PROFILER_STRING(mTransaction->Database()), + IDB_PROFILER_STRING(mTransaction), + IDB_PROFILER_STRING(mObjectStore), + IDB_PROFILER_STRING(mDirection), + mObjectStore->HasValidKeyPath() ? "" : + IDB_PROFILER_STRING(objectKey)); + } + else { + IDB_PROFILER_MARK("IndexedDB Request %llu: " + "database(%s).transaction(%s).objectStore(%s)." + "index(%s).cursor(%s).delete(%s)", + "IDBRequest[%llu] MT IDBCursor.delete()", + requestSerial, + IDB_PROFILER_STRING(mTransaction->Database()), + IDB_PROFILER_STRING(mTransaction), + IDB_PROFILER_STRING(mObjectStore), + IDB_PROFILER_STRING(mIndex), + IDB_PROFILER_STRING(mDirection), + mObjectStore->HasValidKeyPath() ? "" : + IDB_PROFILER_STRING(objectKey)); + } + } +#endif + + return request.forget(); +} + +void +IDBCursor::Advance(uint32_t aCount, ErrorResult &aRv) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (aCount < 1) { + aRv.ThrowTypeError(MSG_INVALID_ADVANCE_COUNT); + return; + } + + Key key; + ContinueInternal(key, int32_t(aCount), aRv); + ENSURE_SUCCESS_VOID(aRv); + +#ifdef IDB_PROFILER_USE_MARKS + { + if (mType == OBJECTSTORE || mType == OBJECTSTOREKEY) { + IDB_PROFILER_MARK("IndexedDB Request %llu: " + "database(%s).transaction(%s).objectStore(%s)." + "cursor(%s).advance(%ld)", + "IDBRequest[%llu] MT IDBCursor.advance()", + Request()->GetSerialNumber(), + IDB_PROFILER_STRING(Transaction()->Database()), + IDB_PROFILER_STRING(Transaction()), + IDB_PROFILER_STRING(mObjectStore), + IDB_PROFILER_STRING(mDirection), aCount); + } + else { + IDB_PROFILER_MARK("IndexedDB Request %llu: " + "database(%s).transaction(%s).objectStore(%s)." + "index(%s).cursor(%s).advance(%ld)", + "IDBRequest[%llu] MT IDBCursor.advance()", + Request()->GetSerialNumber(), + IDB_PROFILER_STRING(Transaction()->Database()), + IDB_PROFILER_STRING(Transaction()), + IDB_PROFILER_STRING(mObjectStore), + IDB_PROFILER_STRING(mIndex), + IDB_PROFILER_STRING(mDirection), aCount); + } + } +#endif +} + +void +CursorHelper::ReleaseMainThreadObjects() +{ + mCursor = nullptr; + AsyncConnectionHelper::ReleaseMainThreadObjects(); +} + +nsresult +CursorHelper::Dispatch(nsIEventTarget* aDatabaseThread) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + PROFILER_MAIN_THREAD_LABEL("IndexedDB", "CursorHelper::Dispatch"); + + if (IndexedDatabaseManager::IsMainProcess()) { + return AsyncConnectionHelper::Dispatch(aDatabaseThread); + } + + // If we've been invalidated then there's no point sending anything to the + // parent process. + if (mCursor->Transaction()->Database()->IsInvalidated()) { + IDB_REPORT_INTERNAL_ERR(); + return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + } + + IndexedDBCursorChild* cursorActor = mCursor->GetActorChild(); + NS_ASSERTION(cursorActor, "Must have an actor here!"); + + CursorRequestParams params; + nsresult rv = PackArgumentsForParentProcess(params); + IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + NoDispatchEventTarget target; + rv = AsyncConnectionHelper::Dispatch(&target); + IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + mActor = new IndexedDBCursorRequestChild(this, mCursor, params.type()); + cursorActor->SendPIndexedDBRequestConstructor(mActor, params); + + return NS_OK; +} + +nsresult +ContinueHelper::DoDatabaseWork(mozIStorageConnection* aConnection) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); + + PROFILER_LABEL("IndexedDB", "ContinueHelper::DoDatabaseWork"); + + // We need to pick a query based on whether or not the cursor's mContinueToKey + // is set. If it is unset then othing was passed to continue so we'll grab the + // next item in the database that is greater than (less than, if we're running + // a PREV cursor) the current key. If it is set then a key was passed to + // continue so we'll grab the next item in the database that is greater than + // (less than, if we're running a PREV cursor) or equal to the key that was + // specified. + + nsAutoCString query; + if (mCursor->mContinueToKey.IsUnset()) { + query.Assign(mCursor->mContinueQuery); + } + else { + query.Assign(mCursor->mContinueToQuery); + } + NS_ASSERTION(!query.IsEmpty(), "Bad query!"); + + query.AppendInt(mCount); + + nsCOMPtr stmt = mTransaction->GetCachedStatement(query); + IDB_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + mozStorageStatementScoper scoper(stmt); + + nsresult rv = BindArgumentsToStatement(stmt); + IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + NS_ASSERTION(mCount > 0, "Not ok!"); + + bool hasResult; + for (int32_t index = 0; index < mCount; index++) { + rv = stmt->ExecuteStep(&hasResult); + IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + if (!hasResult) { + break; + } + } + + if (hasResult) { + rv = GatherResultsFromStatement(stmt); + IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + } + else { + mKey.Unset(); + } + + return NS_OK; +} + +nsresult +ContinueHelper::GetSuccessResult(JSContext* aCx, + JS::MutableHandle aVal) +{ + UpdateCursorState(); + + if (mKey.IsUnset()) { + aVal.setNull(); + } + else { + nsresult rv = WrapNative(aCx, mCursor, aVal); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +void +ContinueHelper::ReleaseMainThreadObjects() +{ + IDBObjectStore::ClearCloneReadInfo(mCloneReadInfo); + CursorHelper::ReleaseMainThreadObjects(); +} + +nsresult +ContinueHelper::PackArgumentsForParentProcess(CursorRequestParams& aParams) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(!IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); + + PROFILER_MAIN_THREAD_LABEL("IndexedDB", + "ContinueHelper::PackArgumentsForParentProcess"); + + ContinueParams params; + + params.key() = mCursor->mContinueToKey; + params.count() = uint32_t(mCount); + + aParams = params; + return NS_OK; +} + +AsyncConnectionHelper::ChildProcessSendResult +ContinueHelper::SendResponseToChildProcess(nsresult aResultCode) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); + + PROFILER_MAIN_THREAD_LABEL("IndexedDB", + "ContinueHelper::SendResponseToChildProcess"); + + IndexedDBRequestParentBase* actor = mRequest->GetActorParent(); + NS_ASSERTION(actor, "How did we get this far without an actor?"); + + InfallibleTArray blobsParent; + + if (NS_SUCCEEDED(aResultCode)) { + IDBDatabase* database = mTransaction->Database(); + NS_ASSERTION(database, "This should never be null!"); + + ContentParent* contentParent = database->GetContentParent(); + NS_ASSERTION(contentParent, "This should never be null!"); + + FileManager* fileManager = database->Manager(); + NS_ASSERTION(fileManager, "This should never be null!"); + + const nsTArray& files = mCloneReadInfo.mFiles; + + aResultCode = + IDBObjectStore::ConvertBlobsToActors(contentParent, fileManager, files, + blobsParent); + if (NS_FAILED(aResultCode)) { + NS_WARNING("ConvertBlobsToActors failed!"); + } + } + + ResponseValue response; + if (NS_FAILED(aResultCode)) { + response = aResultCode; + } + else { + ContinueResponse continueResponse; + continueResponse.key() = mKey; + continueResponse.objectKey() = mObjectKey; + continueResponse.cloneInfo() = mCloneReadInfo; + continueResponse.blobsParent().SwapElements(blobsParent); + response = continueResponse; + } + + if (!actor->SendResponse(response)) { + return Error; + } + + UpdateCursorState(); + + return Success_Sent; +} + +nsresult +ContinueHelper::UnpackResponseFromParentProcess( + const ResponseValue& aResponseValue) +{ + NS_ASSERTION(aResponseValue.type() == ResponseValue::TContinueResponse, + "Bad response type!"); + + const ContinueResponse& response = aResponseValue.get_ContinueResponse(); + + mKey = response.key(); + mObjectKey = response.objectKey(); + + const SerializedStructuredCloneReadInfo& cloneInfo = response.cloneInfo(); + + NS_ASSERTION((!cloneInfo.dataLength && !cloneInfo.data) || + (cloneInfo.dataLength && cloneInfo.data), + "Inconsistent clone info!"); + + if (!mCloneReadInfo.SetFromSerialized(cloneInfo)) { + IDB_WARNING("Failed to copy clone buffer!"); + return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + } + + IDBObjectStore::ConvertActorsToBlobs(response.blobsChild(), + mCloneReadInfo.mFiles); + return NS_OK; +} + +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()); + IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + NS_NAMED_LITERAL_CSTRING(currentKeyName, "current_key"); + NS_NAMED_LITERAL_CSTRING(rangeKeyName, "range_key"); + + // Bind current key. + const Key& currentKey = mCursor->mContinueToKey.IsUnset() ? + mCursor->mKey : + mCursor->mContinueToKey; + + rv = currentKey.BindToStatement(aStatement, currentKeyName); + NS_ENSURE_SUCCESS(rv, rv); + + // Bind range key if it is specified. + const Key& rangeKey = mCursor->mRangeKey; + + if (!rangeKey.IsUnset()) { + rv = rangeKey.BindToStatement(aStatement, rangeKeyName); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +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); + 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; +} + +nsresult +ContinueIndexHelper::BindArgumentsToStatement(mozIStorageStatement* aStatement) +{ + // Bind index id. + nsresult rv = aStatement->BindInt64ByName(NS_LITERAL_CSTRING("id"), + mCursor->mIndex->Id()); + IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + NS_NAMED_LITERAL_CSTRING(currentKeyName, "current_key"); + + // Bind current key. + const Key& currentKey = mCursor->mContinueToKey.IsUnset() ? + mCursor->mKey : + mCursor->mContinueToKey; + + rv = currentKey.BindToStatement(aStatement, currentKeyName); + NS_ENSURE_SUCCESS(rv, rv); + + // Bind range key if it is specified. + if (!mCursor->mRangeKey.IsUnset()) { + NS_NAMED_LITERAL_CSTRING(rangeKeyName, "range_key"); + rv = mCursor->mRangeKey.BindToStatement(aStatement, rangeKeyName); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Bind object key if duplicates are allowed and we're not continuing to a + // specific key. + if ((mCursor->mDirection == IDBCursor::NEXT || + mCursor->mDirection == IDBCursor::PREV) && + mCursor->mContinueToKey.IsUnset()) { + NS_ASSERTION(!mCursor->mObjectKey.IsUnset(), "Bad key!"); + + NS_NAMED_LITERAL_CSTRING(objectKeyName, "object_key"); + rv = mCursor->mObjectKey.BindToStatement(aStatement, objectKeyName); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +nsresult +ContinueIndexHelper::GatherResultsFromStatement( + mozIStorageStatement* aStatement) +{ + nsresult rv = mKey.SetFromStatement(aStatement, 0); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mObjectKey.SetFromStatement(aStatement, 1); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +ContinueIndexObjectHelper::GatherResultsFromStatement( + mozIStorageStatement* aStatement) +{ + nsresult rv = mKey.SetFromStatement(aStatement, 0); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mObjectKey.SetFromStatement(aStatement, 1); + NS_ENSURE_SUCCESS(rv, rv); + + rv = IDBObjectStore::GetStructuredCloneReadInfoFromStatement(aStatement, 2, 3, + mDatabase, mCloneReadInfo); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +}