Bug 920800 - 'Add openKeyCursor() to IDBObjectStore'. r=janv.
☠☠ backed out by a11f8859f7d8 ☠ ☠
authorBen Turner <bent.mozilla@gmail.com>
Wed, 25 Sep 2013 16:11:47 -0700
changeset 163676 87a72129c007ff13b4ee9d8f0108a96eb85ea41a
parent 163675 809c2ffacd342894ad4b13328a20dd32d8482a8e
child 163677 20855adc0ef108c99eaee803422d3bc0abaceeda
push id428
push userbbajaj@mozilla.com
push dateTue, 28 Jan 2014 00:16:25 +0000
treeherdermozilla-release@cd72a7ff3a75 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjanv
bugs920800
milestone27.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 920800 - 'Add openKeyCursor() to IDBObjectStore'. r=janv.
dom/indexedDB/IDBCursor.cpp
dom/indexedDB/IDBCursor.h
dom/indexedDB/IDBIndex.cpp
dom/indexedDB/IDBObjectStore.cpp
dom/indexedDB/IDBObjectStore.h
dom/indexedDB/ipc/IndexedDBChild.cpp
dom/indexedDB/ipc/IndexedDBParams.ipdlh
dom/indexedDB/ipc/IndexedDBParent.cpp
dom/indexedDB/ipc/IndexedDBParent.h
dom/indexedDB/ipc/PIndexedDBIndex.ipdl
dom/indexedDB/ipc/PIndexedDBObjectStore.ipdl
dom/indexedDB/ipc/unit/xpcshell.ini
dom/indexedDB/test/Makefile.in
dom/indexedDB/test/test_objectStore_openKeyCursor.html
dom/indexedDB/test/unit/Makefile.in
dom/indexedDB/test/unit/test_objectStore_openKeyCursor.js
dom/indexedDB/test/unit/xpcshell.ini
dom/webidl/IDBObjectStore.webidl
--- a/dom/indexedDB/IDBCursor.cpp
+++ b/dom/indexedDB/IDBCursor.cpp
@@ -31,18 +31,17 @@
 
 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");
+              "Relying on conversion between size_t and IDBCursor::Direction");
 
 namespace {
 
 class CursorHelper : public AsyncConnectionHelper
 {
 public:
   CursorHelper(IDBCursor* aCursor)
   : AsyncConnectionHelper(aCursor->Transaction(), aCursor->Request()),
@@ -57,16 +56,19 @@ public:
 
   virtual nsresult
   PackArgumentsForParentProcess(CursorRequestParams& aParams) = 0;
 
   virtual nsresult
   UnpackResponseFromParentProcess(const ResponseValue& aResponseValue) = 0;
 
 protected:
+  virtual ~CursorHelper()
+  { }
+
   nsRefPtr<IDBCursor> mCursor;
 
 private:
   IndexedDBCursorRequestChild* mActor;
 };
 
 } // anonymous namespace
 
@@ -74,43 +76,46 @@ BEGIN_INDEXEDDB_NAMESPACE
 
 class ContinueHelper : public CursorHelper
 {
 public:
   ContinueHelper(IDBCursor* aCursor,
                  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<JS::Value> aVal) MOZ_OVERRIDE;
+                                    JS::MutableHandle<JS::Value> 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()
   {
@@ -119,20 +124,20 @@ protected:
     mCursor->mCachedValue = JSVAL_VOID;
     mCursor->mHaveCachedKey = false;
     mCursor->mHaveCachedPrimaryKey = false;
     mCursor->mHaveCachedValue = false;
     mCursor->mContinueCalled = false;
 
     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;
       mCursor->mObjectKey = mObjectKey;
       mCursor->mContinueToKey.Unset();
 
       mCursor->mCloneReadInfo.Swap(mCloneReadInfo);
       mCloneReadInfo.mCloneBuffer.clear();
@@ -148,43 +153,70 @@ protected:
 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>
 IDBCursor::Create(IDBRequest* aRequest,
@@ -212,16 +244,42 @@ IDBCursor::Create(IDBRequest* aRequest,
 
   return cursor.forget();
 }
 
 // static
 already_AddRefed<IDBCursor>
 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<IDBCursor> 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>
+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)
 {
@@ -288,17 +346,17 @@ IDBCursor::ConvertDirection(mozilla::dom
 
     case mozilla::dom::IDBCursorDirection::Prev:
       return PREV;
 
     case mozilla::dom::IDBCursorDirection::Prevunique:
       return PREV_UNIQUE;
 
     default:
-      MOZ_CRASH("Unknown direction!");
+      MOZ_ASSUME_UNREACHABLE("Unknown direction!");
   }
 }
 
 // static
 already_AddRefed<IDBCursor>
 IDBCursor::CreateCommon(IDBRequest* aRequest,
                         IDBTransaction* aTransaction,
                         IDBObjectStore* aObjectStore,
@@ -391,54 +449,55 @@ IDBCursor::DropJSObjects()
   mRooted = false;
   mHaveValue = false;
   mozilla::DropJSObjects(this);
 }
 
 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);
     return;
   }
 
   if (!mHaveValue || mContinueCalled) {
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
     return;
   }
 
   mContinueToKey = aKey;
 
-#ifdef DEBUG
-  NS_ASSERTION(mRequest->ReadyState() == IDBRequestReadyState::Done,
-               "Should be DONE!");
-#endif
+  MOZ_ASSERT(mRequest->ReadyState() == IDBRequestReadyState::Done);
 
   mRequest->Reset();
 
   nsRefPtr<ContinueHelper> 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:
-      NS_NOTREACHED("Unknown cursor type!");
+      MOZ_ASSUME_UNREACHABLE("Unknown cursor type!");
   }
 
   nsresult rv = helper->DispatchToTransactionPool();
   if (NS_FAILED(rv)) {
     NS_WARNING("Failed to dispatch!");
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
     return;
   }
@@ -484,66 +543,83 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(IDBCursor)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(IDBCursor)
 
 JSObject*
 IDBCursor::WrapObject(JSContext* aCx, JS::Handle<JSObject*> 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:
       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;
 
-    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());
+
+  switch (mType) {
+    case OBJECTSTORE:
+    case OBJECTSTOREKEY:
+      MOZ_ASSERT(mObjectStore);
+      aSource.SetAsIDBObjectStore() = mObjectStore;
+      break;
 
-  if (mType == OBJECTSTORE) {
-    aSource.SetAsIDBObjectStore() = mObjectStore;
-  }
-  else {
-    aSource.SetAsIDBIndex() = mIndex;
+    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;
   }
 
   if (!mHaveCachedKey) {
     if (!mRooted) {
       mozilla::HoldJSObjects(this);
@@ -557,49 +633,46 @@ IDBCursor::GetKey(JSContext* aCx, ErrorR
   }
 
   return mCachedKey;
 }
 
 JS::Value
 IDBCursor::GetPrimaryKey(JSContext* aCx, ErrorResult& aRv)
 {
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  MOZ_ASSERT(NS_IsMainThread());
 
   if (!mHaveValue) {
     return JSVAL_VOID;
   }
 
   if (!mHaveCachedPrimaryKey) {
     if (!mRooted) {
       mozilla::HoldJSObjects(this);
       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);
 
     mHaveCachedPrimaryKey = true;
   }
 
   return mCachedPrimaryKey;
 }
 
 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;
   }
 
   if (!mHaveCachedValue) {
     if (!mRooted) {
       mozilla::HoldJSObjects(this);
@@ -621,17 +694,17 @@ IDBCursor::GetValue(JSContext* aCx, Erro
   return mCachedValue;
 }
 
 void
 IDBCursor::Continue(JSContext* aCx,
                     const Optional<JS::Handle<JS::Value> >& aKey,
                     ErrorResult &aRv)
 {
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  MOZ_ASSERT(NS_IsMainThread());
 
   Key key;
   if (aKey.WasPassed()) {
     aRv = key.SetFromJSVal(aCx, aKey.Value());
     ENSURE_SUCCESS_VOID(aRv);
   }
 
   if (!key.IsUnset()) {
@@ -648,27 +721,27 @@ IDBCursor::Continue(JSContext* aCx,
       case IDBCursor::PREV_UNIQUE:
         if (key >= mKey) {
           aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
           return;
         }
         break;
 
       default:
-        NS_NOTREACHED("Unknown direction type!");
+        MOZ_ASSUME_UNREACHABLE("Unknown direction type!");
     }
   }
 
   ContinueInternal(key, 1, aRv);
   if (aRv.Failed()) {
     return;
   }
 
 #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)",
                       "IDBRequest[%llu] MT IDBCursor.continue()",
                       Request()->GetSerialNumber(),
                       IDB_PROFILER_STRING(Transaction()->Database()),
                       IDB_PROFILER_STRING(Transaction()),
                       IDB_PROFILER_STRING(mObjectStore),
@@ -690,40 +763,39 @@ IDBCursor::Continue(JSContext* aCx,
   }
 #endif
 }
 
 already_AddRefed<IDBRequest>
 IDBCursor::Update(JSContext* aCx, JS::Handle<JS::Value> 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);
     return nullptr;
   }
 
   if (!mTransaction->IsWriteAllowed()) {
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR);
     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<IDBRequest> 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);
@@ -791,46 +863,45 @@ IDBCursor::Update(JSContext* aCx, JS::Ha
 #endif
 
   return request.forget();
 }
 
 already_AddRefed<IDBRequest>
 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);
     return nullptr;
   }
 
   if (!mTransaction->IsWriteAllowed()) {
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR);
     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<JS::Value> key(aCx);
   aRv = objectKey.ToJSVal(aCx, &key);
   ENSURE_SUCCESS(aRv, nullptr);
 
   nsRefPtr<IDBRequest> request = mObjectStore->Delete(aCx, key, aRv);
-  if (aRv.Failed()) {
-    return nullptr;
-  }
+  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)",
@@ -861,30 +932,30 @@ IDBCursor::Delete(JSContext* aCx, ErrorR
 #endif
 
   return request.forget();
 }
 
 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);
     return;
   }
 
   Key key;
   ContinueInternal(key, int32_t(aCount), aRv);
   ENSURE_SUCCESS_VOID(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)",
                         "IDBRequest[%llu] MT IDBCursor.advance()",
                         Request()->GetSerialNumber(),
                         IDB_PROFILER_STRING(Transaction()->Database()),
                         IDB_PROFILER_STRING(Transaction()),
                         IDB_PROFILER_STRING(mObjectStore),
@@ -1130,16 +1201,19 @@ ContinueHelper::UnpackResponseFromParent
                                        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());
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   NS_NAMED_LITERAL_CSTRING(currentKeyName, "current_key");
   NS_NAMED_LITERAL_CSTRING(rangeKeyName, "range_key");
 
@@ -1161,22 +1235,39 @@ ContinueObjectStoreHelper::BindArguments
 
   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);
+                                                               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)
 {
--- a/dom/indexedDB/IDBCursor.h
+++ b/dom/indexedDB/IDBCursor.h
@@ -50,16 +50,17 @@ class IDBCursor MOZ_FINAL : public nsISu
 
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(IDBCursor)
 
   enum Type
   {
     OBJECTSTORE = 0,
+    OBJECTSTOREKEY,
     INDEXKEY,
     INDEXOBJECT
   };
 
   enum Direction
   {
     NEXT = 0,
     NEXT_UNIQUE,
@@ -78,16 +79,28 @@ public:
          IDBObjectStore* aObjectStore,
          Direction aDirection,
          const Key& aRangeKey,
          const nsACString& aContinueQuery,
          const nsACString& aContinueToQuery,
          const Key& aKey,
          StructuredCloneReadInfo& aCloneReadInfo);
 
+  // For OBJECTSTOREKEY cursors.
+  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);
+
   // For INDEXKEY cursors.
   static
   already_AddRefed<IDBCursor>
   Create(IDBRequest* aRequest,
          IDBTransaction* aTransaction,
          IDBIndex* aIndex,
          Direction aDirection,
          const Key& aRangeKey,
--- a/dom/indexedDB/IDBIndex.cpp
+++ b/dom/indexedDB/IDBIndex.cpp
@@ -1986,17 +1986,17 @@ OpenKeyCursorHelper::ReleaseMainThreadOb
 nsresult
 OpenKeyCursorHelper::PackArgumentsForParentProcess(IndexRequestParams& aParams)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(!IndexedDatabaseManager::IsMainProcess(), "Wrong process!");
 
   PROFILER_MAIN_THREAD_LABEL("IndexedDB",
                              "OpenKeyCursorHelper::"
-                             "PackArgumentsForParentProcess");
+                             "PackArgumentsForParentProcess [IDBIndex.cpp]");
 
   OpenKeyCursorParams params;
 
   if (mKeyRange) {
     KeyRange keyRange;
     mKeyRange->ToSerializedKeyRange(keyRange);
     params.optionalKeyRange() = keyRange;
   }
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -340,16 +340,67 @@ private:
   nsCString mContinueToQuery;
   Key mRangeKey;
 
   // Only used in the parent process.
   nsRefPtr<IDBCursor> mCursor;
   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<IDBKeyRange> mKeyRange;
+  const IDBCursor::Direction mDirection;
+
+  // Out-params.
+  Key mKey;
+  nsCString mContinueQuery;
+  nsCString mContinueToQuery;
+  Key mRangeKey;
+
+  // Only used in the parent process.
+  nsRefPtr<IDBCursor> mCursor;
+};
+
 class CreateIndexHelper : public NoRequestObjectStoreHelper
 {
 public:
   CreateIndexHelper(IDBTransaction* aTransaction, IDBIndex* aIndex)
   : NoRequestObjectStoreHelper(aTransaction, aIndex->ObjectStore()),
     mIndex(aIndex)
   {
     if (sTLSIndex == BAD_TLS_INDEX) {
@@ -2363,16 +2414,79 @@ IDBObjectStore::OpenCursorFromChildProce
   NS_ENSURE_TRUE(cursor, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   NS_ASSERTION(!cloneInfo.mCloneBuffer.data(), "Should have swapped!");
 
   cursor.forget(_retval);
   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<IDBCursor::Direction>(aDirection);
+
+  nsRefPtr<IDBCursor> 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<IDBRequest>
+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<IDBRequest> 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<IDBCursor::Direction>(aDirection);
+
+  nsRefPtr<OpenKeyCursorHelper> 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)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread");
   NS_ASSERTION(aInfo != mInfo, "This is nonsense");
 
   mInfo = aInfo;
 }
@@ -2870,16 +2984,39 @@ IDBObjectStore::GetAllKeys(JSContext* aC
   uint32_t limit = UINT32_MAX;
   if (aLimit.WasPassed() && aLimit.Value() != 0) {
     limit = aLimit.Value();
   }
 
   return GetAllKeysInternal(keyRange, limit, aRv);
 }
 
+already_AddRefed<IDBRequest>
+IDBObjectStore::OpenKeyCursor(JSContext* aCx,
+                              const Optional<JS::HandleValue>& aRange,
+                              IDBCursorDirection aDirection,  ErrorResult& aRv)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!mTransaction->IsOpen()) {
+    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
+    return nullptr;
+  }
+
+  nsRefPtr<IDBKeyRange> 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<size_t>(direction), aRv);
+}
+
 inline nsresult
 CopyData(nsIInputStream* aInputStream, nsIOutputStream* aOutputStream)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!");
 
   PROFILER_LABEL("IndexedDB", "CopyData");
 
@@ -3907,17 +4044,17 @@ OpenCursorHelper::SendResponseToChildPro
       NS_ASSERTION(mSerializedCloneReadInfo.data &&
                    mSerializedCloneReadInfo.dataLength,
                    "Shouldn't be possible!");
 
       ObjectStoreCursorConstructorParams params;
       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)) {
         return Error;
       }
     }
 
     response = openCursorResponse;
@@ -3964,16 +4101,321 @@ OpenCursorHelper::UnpackResponseFromPare
       NS_NOTREACHED("Unknown response union type!");
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   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<mozIStorageStatement> 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<IndexedDBCursorChild*>(
+          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)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!");
 
   PROFILER_LABEL("IndexedDB", "CreateIndexHelper::DoDatabaseWork");
 
   if (IndexedDatabaseManager::InLowDiskSpaceMode()) {
--- a/dom/indexedDB/IDBObjectStore.h
+++ b/dom/indexedDB/IDBObjectStore.h
@@ -234,25 +234,36 @@ public:
   CountInternal(IDBKeyRange* aKeyRange,
                 ErrorResult& aRv);
 
   already_AddRefed<IDBRequest>
   OpenCursorInternal(IDBKeyRange* aKeyRange,
                      size_t aDirection,
                      ErrorResult& aRv);
 
+  already_AddRefed<IDBRequest>
+  OpenKeyCursorInternal(IDBKeyRange* aKeyRange,
+                        size_t aDirection,
+                        ErrorResult& aRv);
+
   nsresult
   OpenCursorFromChildProcess(
                             IDBRequest* aRequest,
                             size_t aDirection,
                             const Key& aKey,
                             const SerializedStructuredCloneReadInfo& aCloneInfo,
                             nsTArray<StructuredCloneFile>& aBlobs,
                             IDBCursor** _retval);
 
+  nsresult
+  OpenCursorFromChildProcess(IDBRequest* aRequest,
+                             size_t aDirection,
+                             const Key& aKey,
+                             IDBCursor** _retval);
+
   void
   SetInfo(ObjectStoreInfo* aInfo);
 
   static const JSClass sDummyPropJSClass;
 
   // nsWrapperCache
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
@@ -342,16 +353,20 @@ public:
   already_AddRefed<IDBRequest>
   GetAll(JSContext* aCx, const Optional<JS::Handle<JS::Value> >& aKey,
          const Optional<uint32_t>& aLimit, ErrorResult& aRv);
 
   already_AddRefed<IDBRequest>
   GetAllKeys(JSContext* aCx, const Optional<JS::HandleValue>& aKey,
              const Optional<uint32_t>& aLimit, ErrorResult& aRv);
 
+  already_AddRefed<IDBRequest>
+  OpenKeyCursor(JSContext* aCx, const Optional<JS::HandleValue>& aRange,
+                IDBCursorDirection aDirection, ErrorResult& aRv);
+
 protected:
   IDBObjectStore();
   ~IDBObjectStore();
 
   nsresult GetAddInfo(JSContext* aCx,
                       JS::Handle<JS::Value> aValue,
                       JS::Handle<JS::Value> aKeyVal,
                       StructuredCloneWriteInfo& aCloneWriteInfo,
--- a/dom/indexedDB/ipc/IndexedDBChild.cpp
+++ b/dom/indexedDB/ipc/IndexedDBChild.cpp
@@ -737,27 +737,50 @@ IndexedDBObjectStoreChild::RecvPIndexedD
     static_cast<IndexedDBObjectStoreRequestChild*>(aParams.requestChild());
   NS_ASSERTION(requestActor, "Must have an actor here!");
 
   nsRefPtr<IDBRequest> request = requestActor->GetRequest();
   NS_ASSERTION(request, "Must have a request here!");
 
   size_t direction = static_cast<size_t>(aParams.direction());
 
-  nsTArray<StructuredCloneFile> blobs;
-  IDBObjectStore::ConvertActorsToBlobs(aParams.blobsChild(), blobs);
+  nsRefPtr<IDBCursor> cursor;
+  nsresult rv;
+
+  typedef ipc::OptionalStructuredCloneReadInfo CursorUnionType;
+
+  switch (aParams.optionalCloneInfo().type()) {
+    case CursorUnionType::TSerializedStructuredCloneReadInfo: {
+      nsTArray<StructuredCloneFile> blobs;
+      IDBObjectStore::ConvertActorsToBlobs(aParams.blobsChild(), blobs);
+
+      const SerializedStructuredCloneReadInfo& cloneInfo =
+        aParams.optionalCloneInfo().get_SerializedStructuredCloneReadInfo();
 
-  nsRefPtr<IDBCursor> cursor;
-  nsresult rv =
-    mObjectStore->OpenCursorFromChildProcess(request, direction, aParams.key(),
-                                             aParams.cloneInfo(), blobs,
-                                             getter_AddRefs(cursor));
-  NS_ENSURE_SUCCESS(rv, false);
+      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;
 
-  MOZ_ASSERT(blobs.IsEmpty(), "Should have swapped blob elements!");
+    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;
 }
 
 PIndexedDBRequestChild*
 IndexedDBObjectStoreChild::AllocPIndexedDBRequestChild(
                                         const ObjectStoreRequestParams& aParams)
@@ -1083,17 +1106,18 @@ IndexedDBObjectStoreRequestChild::Recv__
       break;
     case ResponseValue::TClearResponse:
       MOZ_ASSERT(mRequestType == ParamsUnionType::TClearParams);
       break;
     case ResponseValue::TCountResponse:
       MOZ_ASSERT(mRequestType == ParamsUnionType::TCountParams);
       break;
     case ResponseValue::TOpenCursorResponse:
-      MOZ_ASSERT(mRequestType == ParamsUnionType::TOpenCursorParams);
+      MOZ_ASSERT(mRequestType == ParamsUnionType::TOpenCursorParams ||
+                 mRequestType == ParamsUnionType::TOpenKeyCursorParams);
       break;
 
     default:
       MOZ_CRASH("Received invalid response parameters!");
   }
 
   nsresult rv = mHelper->OnParentProcessRequestComplete(aResponse);
   NS_ENSURE_SUCCESS(rv, false);
--- a/dom/indexedDB/ipc/IndexedDBParams.ipdlh
+++ b/dom/indexedDB/ipc/IndexedDBParams.ipdlh
@@ -1,16 +1,17 @@
 /* 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 "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;
 
 namespace mozilla {
 namespace dom {
 namespace indexedDB {
 namespace ipc {
 
@@ -52,12 +53,24 @@ struct CountParams
 };
 
 struct OpenCursorParams
 {
   OptionalKeyRange optionalKeyRange;
   Direction direction;
 };
 
+struct OpenKeyCursorParams
+{
+  OptionalKeyRange optionalKeyRange;
+  Direction direction;
+};
+
+union OptionalStructuredCloneReadInfo
+{
+  SerializedStructuredCloneReadInfo;
+  void_t;
+};
+
 } // namespace ipc
 } // namespace indexedDB
 } // namespace dom
 } // namespace mozilla
--- a/dom/indexedDB/ipc/IndexedDBParent.cpp
+++ b/dom/indexedDB/ipc/IndexedDBParent.cpp
@@ -1113,16 +1113,19 @@ IndexedDBObjectStoreParent::RecvPIndexed
       return actor->Clear(aParams.get_ClearParams());
 
     case ObjectStoreRequestParams::TCountParams:
       return actor->Count(aParams.get_CountParams());
 
     case ObjectStoreRequestParams::TOpenCursorParams:
       return actor->OpenCursor(aParams.get_OpenCursorParams());
 
+    case ObjectStoreRequestParams::TOpenKeyCursorParams:
+      return actor->OpenKeyCursor(aParams.get_OpenKeyCursorParams());
+
     default:
       MOZ_CRASH("Unknown type!");
   }
 
   MOZ_CRASH("Should never get here!");
 }
 
 bool
@@ -1770,16 +1773,57 @@ IndexedDBObjectStoreRequestParent::OpenC
     ENSURE_SUCCESS(rv, false);
   }
 
   request->SetActor(this);
   mRequest.swap(request);
   return true;
 }
 
+bool
+IndexedDBObjectStoreRequestParent::OpenKeyCursor(
+                                             const OpenKeyCursorParams& aParams)
+{
+  MOZ_ASSERT(mRequestType == ParamsUnionType::TOpenKeyCursorParams);
+  MOZ_ASSERT(mObjectStore);
+
+  const ipc::OptionalKeyRange keyRangeUnion = aParams.optionalKeyRange();
+
+  nsRefPtr<IDBKeyRange> 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<size_t>(aParams.direction());
+
+  nsRefPtr<IDBRequest> 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
  ******************************************************************************/
 
 IndexedDBIndexRequestParent::IndexedDBIndexRequestParent(
                                                        IDBIndex* aIndex,
                                                        RequestType aRequestType)
 : mIndex(aIndex), mRequestType(aRequestType)
--- a/dom/indexedDB/ipc/IndexedDBParent.h
+++ b/dom/indexedDB/ipc/IndexedDBParent.h
@@ -705,16 +705,17 @@ class IndexedDBObjectStoreRequestParent 
   typedef ipc::PutParams PutParams;
   typedef ipc::ClearParams ClearParams;
   typedef ipc::DeleteParams DeleteParams;
   typedef ipc::GetParams GetParams;
   typedef ipc::GetAllParams GetAllParams;
   typedef ipc::GetAllKeysParams GetAllKeysParams;
   typedef ipc::CountParams CountParams;
   typedef ipc::OpenCursorParams OpenCursorParams;
+  typedef ipc::OpenKeyCursorParams OpenKeyCursorParams;
 
 public:
   IndexedDBObjectStoreRequestParent(IDBObjectStore* aObjectStore,
                                     RequestType aRequestType);
   virtual ~IndexedDBObjectStoreRequestParent();
 
   bool
   Get(const GetParams& aParams);
@@ -738,16 +739,19 @@ public:
   Clear(const ClearParams& aParams);
 
   bool
   Count(const CountParams& aParams);
 
   bool
   OpenCursor(const OpenCursorParams& aParams);
 
+  bool
+  OpenKeyCursor(const OpenKeyCursorParams& aParams);
+
 protected:
   void
   ConvertBlobActors(const InfallibleTArray<PBlobParent*>& aActors,
                     nsTArray<nsCOMPtr<nsIDOMBlob> >& aBlobs);
 
 private:
   virtual bool
   IsDisconnected() MOZ_OVERRIDE;
--- a/dom/indexedDB/ipc/PIndexedDBIndex.ipdl
+++ b/dom/indexedDB/ipc/PIndexedDBIndex.ipdl
@@ -4,52 +4,38 @@
 
 include protocol PBlob;
 include protocol PIndexedDBCursor;
 include protocol PIndexedDBObjectStore;
 include protocol PIndexedDBRequest;
 
 include IndexedDBParams;
 
-using mozilla::dom::indexedDB::SerializedStructuredCloneReadInfo;
-
 namespace mozilla {
 namespace dom {
 namespace indexedDB {
 
 namespace ipc {
 
 struct GetKeyParams
 {
   KeyRange keyRange;
 };
 
-struct OpenKeyCursorParams
-{
-  OptionalKeyRange optionalKeyRange;
-  Direction direction;
-};
-
 union IndexRequestParams
 {
   GetParams;
   GetKeyParams;
   GetAllParams;
   GetAllKeysParams;
   CountParams;
   OpenCursorParams;
   OpenKeyCursorParams;
 };
 
-union OptionalStructuredCloneReadInfo
-{
-  SerializedStructuredCloneReadInfo;
-  void_t;
-};
-
 struct IndexCursorConstructorParams
 {
   PIndexedDBRequest request;
   Direction direction;
   Key key;
   Key objectKey;
   OptionalStructuredCloneReadInfo optionalCloneInfo;
   PBlob[] blobs;
--- a/dom/indexedDB/ipc/PIndexedDBObjectStore.ipdl
+++ b/dom/indexedDB/ipc/PIndexedDBObjectStore.ipdl
@@ -7,17 +7,16 @@ include protocol PIndexedDBCursor;
 include protocol PIndexedDBIndex;
 include protocol PIndexedDBRequest;
 include protocol PIndexedDBTransaction;
 
 include IndexedDBParams;
 
 using mozilla::dom::indexedDB::IndexInfo;
 using mozilla::dom::indexedDB::IndexUpdateInfo;
-using mozilla::dom::indexedDB::SerializedStructuredCloneReadInfo;
 using mozilla::dom::indexedDB::SerializedStructuredCloneWriteInfo;
 
 namespace mozilla {
 namespace dom {
 namespace indexedDB {
 
 namespace ipc {
 
@@ -54,16 +53,17 @@ union ObjectStoreRequestParams
   GetAllParams;
   GetAllKeysParams;
   AddParams;
   PutParams;
   DeleteParams;
   ClearParams;
   CountParams;
   OpenCursorParams;
+  OpenKeyCursorParams;
 };
 
 struct CreateIndexParams
 {
   IndexInfo info;
 };
 
 struct GetIndexParams
@@ -77,17 +77,17 @@ union IndexConstructorParams
   GetIndexParams;
 };
 
 struct ObjectStoreCursorConstructorParams
 {
   PIndexedDBRequest request;
   Direction direction;
   Key key;
-  SerializedStructuredCloneReadInfo cloneInfo;
+  OptionalStructuredCloneReadInfo optionalCloneInfo;
   PBlob[] blobs;
 };
 
 } // namespace ipc
 
 protocol PIndexedDBObjectStore
 {
   manager PIndexedDBTransaction;
--- a/dom/indexedDB/ipc/unit/xpcshell.ini
+++ b/dom/indexedDB/ipc/unit/xpcshell.ini
@@ -33,16 +33,17 @@ tail =
 [test_key_requirements.js]
 [test_keys.js]
 [test_multientry.js]
 [test_names_sorted.js]
 [test_object_identity.js]
 [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]
 [test_open_objectStore.js]
 [test_optionalArguments.js]
 [test_overlapping_transactions.js]
 [test_put_get_values.js]
 [test_put_get_values_autoIncrement.js]
--- a/dom/indexedDB/test/Makefile.in
+++ b/dom/indexedDB/test/Makefile.in
@@ -67,16 +67,17 @@ MOCHITEST_FILES = \
   test_keys.html \
   test_leaving_page.html \
   test_lowDiskSpace.html \
   test_multientry.html \
   test_names_sorted.html \
   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 \
   test_open_empty_db.html \
   test_open_for_principal.html \
   test_open_objectStore.html \
   test_optionalArguments.html \
   test_overlapping_transactions.html \
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_objectStore_openKeyCursor.html
@@ -0,0 +1,19 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Indexed Database Property Test</title>
+
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript;version=1.7" src="unit/test_objectStore_openKeyCursor.js"></script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
--- a/dom/indexedDB/test/unit/Makefile.in
+++ b/dom/indexedDB/test/unit/Makefile.in
@@ -35,16 +35,17 @@ MOCHITEST_FILES = \
   test_keys.js \
   test_lowDiskSpace.js \
   test_multientry.js \
   test_names_sorted.js \
   test_object_identity.js \
   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 \
   test_open_for_principal.js \
   test_open_objectStore.js \
   test_optionalArguments.js \
   test_overlapping_transactions.js \
   test_persistenceType.js \
new file mode 100644
--- /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;
+}
--- a/dom/indexedDB/test/unit/xpcshell.ini
+++ b/dom/indexedDB/test/unit/xpcshell.ini
@@ -36,16 +36,17 @@ tail =
 [test_keys.js]
 [test_lowDiskSpace.js]
 [test_multientry.js]
 [test_names_sorted.js]
 [test_object_identity.js]
 [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]
 [test_open_for_principal.js]
 [test_open_objectStore.js]
 [test_optionalArguments.js]
 [test_overlapping_transactions.js]
 [test_persistenceType.js]
--- a/dom/webidl/IDBObjectStore.webidl
+++ b/dom/webidl/IDBObjectStore.webidl
@@ -66,9 +66,12 @@ partial interface IDBObjectStore {
     [Throws]
     IDBRequest mozGetAll (optional any key, optional unsigned long limit);
 
     [Pref="dom.indexedDB.experimental", Throws]
     IDBRequest getAll (optional any key, optional unsigned long limit);
 
     [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");
 };