Bug 692628 - 'IndexedDB: Support IDBCursor.advance'. r=sicking.
authorBen Turner <bent.mozilla@gmail.com>
Mon, 07 Nov 2011 15:37:19 -0800
changeset 79919 b7baa7788c391417a1a4b47ea43a189d7696018a
parent 79918 f4041047f76f0e244922f5af182589921722de76
child 79920 0d63a5e24889edcffb66ce6c83f3c2d557a9ebcd
push id21440
push userbturner@mozilla.com
push dateMon, 07 Nov 2011 23:43:07 +0000
treeherdermozilla-central@b7baa7788c39 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssicking
bugs692628
milestone10.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 692628 - 'IndexedDB: Support IDBCursor.advance'. r=sicking.
dom/indexedDB/IDBCursor.cpp
dom/indexedDB/IDBCursor.h
dom/indexedDB/IDBIndex.cpp
dom/indexedDB/IDBObjectStore.cpp
dom/indexedDB/nsIIDBCursor.idl
dom/indexedDB/test/Makefile.in
dom/indexedDB/test/test_advance.html
--- a/dom/indexedDB/IDBCursor.cpp
+++ b/dom/indexedDB/IDBCursor.cpp
@@ -72,20 +72,23 @@ GenerateRequest(IDBCursor* aCursor)
 
 } // anonymous namespace
 
 BEGIN_INDEXEDDB_NAMESPACE
 
 class ContinueHelper : public AsyncConnectionHelper
 {
 public:
-  ContinueHelper(IDBCursor* aCursor)
+  ContinueHelper(IDBCursor* aCursor,
+                 PRInt32 aCount)
   : AsyncConnectionHelper(aCursor->mTransaction, aCursor->mRequest),
-    mCursor(aCursor)
-  { }
+    mCursor(aCursor), mCount(aCount)
+  {
+    NS_ASSERTION(aCount > 0, "Must have a count!");
+  }
 
   ~ContinueHelper()
   {
     IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer);
   }
 
   nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
   nsresult GetSuccessResult(JSContext* aCx,
@@ -101,50 +104,54 @@ protected:
   virtual nsresult
   BindArgumentsToStatement(mozIStorageStatement* aStatement) = 0;
 
   virtual nsresult
   GatherResultsFromStatement(mozIStorageStatement* aStatement) = 0;
 
 protected:
   nsRefPtr<IDBCursor> mCursor;
+  PRInt32 mCount;
   Key mKey;
   Key mObjectKey;
   JSAutoStructuredCloneBuffer mCloneBuffer;
 };
 
 class ContinueObjectStoreHelper : public ContinueHelper
 {
 public:
-  ContinueObjectStoreHelper(IDBCursor* aCursor)
-  : ContinueHelper(aCursor)
+  ContinueObjectStoreHelper(IDBCursor* aCursor,
+                            PRUint32 aCount)
+  : ContinueHelper(aCursor, aCount)
   { }
 
 private:
   nsresult BindArgumentsToStatement(mozIStorageStatement* aStatement);
   nsresult GatherResultsFromStatement(mozIStorageStatement* aStatement);
 };
 
 class ContinueIndexHelper : public ContinueHelper
 {
 public:
-  ContinueIndexHelper(IDBCursor* aCursor)
-  : ContinueHelper(aCursor)
+  ContinueIndexHelper(IDBCursor* aCursor,
+                      PRUint32 aCount)
+  : ContinueHelper(aCursor, aCount)
   { }
 
 private:
   nsresult BindArgumentsToStatement(mozIStorageStatement* aStatement);
   nsresult GatherResultsFromStatement(mozIStorageStatement* aStatement);
 };
 
 class ContinueIndexObjectHelper : public ContinueIndexHelper
 {
 public:
-  ContinueIndexObjectHelper(IDBCursor* aCursor)
-  : ContinueIndexHelper(aCursor)
+  ContinueIndexObjectHelper(IDBCursor* aCursor,
+                            PRUint32 aCount)
+  : ContinueIndexHelper(aCursor, aCount)
   { }
 
 private:
   nsresult GatherResultsFromStatement(mozIStorageStatement* aStatement);
 };
 
 END_INDEXEDDB_NAMESPACE
 
@@ -291,16 +298,70 @@ IDBCursor::~IDBCursor()
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   if (mRooted) {
     NS_DROP_JS_OBJECTS(this, IDBCursor);
   }
   IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer);
 }
 
+nsresult
+IDBCursor::ContinueInternal(const Key& aKey,
+                            PRInt32 aCount)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(aCount > 0, "Must have a count!");
+
+  if (!mTransaction->IsOpen()) {
+    return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR;
+  }
+
+  if (!mHaveValue || mContinueCalled) {
+    return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
+  }
+
+  mContinueToKey = aKey;
+
+#ifdef DEBUG
+  {
+    PRUint16 readyState;
+    if (NS_FAILED(mRequest->GetReadyState(&readyState))) {
+      NS_ERROR("This should never fail!");
+    }
+    NS_ASSERTION(readyState == nsIIDBRequest::DONE, "Should be DONE!");
+  }
+#endif
+
+  mRequest->Reset();
+
+  nsRefPtr<ContinueHelper> helper;
+  switch (mType) {
+    case OBJECTSTORE:
+      helper = new ContinueObjectStoreHelper(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!");
+  }
+
+  nsresult rv = helper->DispatchToTransactionPool();
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  mContinueCalled = true;
+  return NS_OK;
+}
+
 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_NSCOMPTR_AMBIGUOUS(mRequest,
                                                        nsIDOMEventTarget)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mTransaction,
                                                        nsIDOMEventTarget)
@@ -482,24 +543,16 @@ IDBCursor::GetValue(JSContext* aCx,
 }
 
 NS_IMETHODIMP
 IDBCursor::Continue(const jsval &aKey,
                     JSContext* aCx)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  if (!mTransaction->IsOpen()) {
-    return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR;
-  }
-
-  if (!mHaveValue || mContinueCalled) {
-    return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
-  }
-
   Key key;
   nsresult rv = key.SetFromJSVal(aCx, aKey);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!key.IsUnset()) {
     switch (mDirection) {
       case nsIIDBCursor::NEXT:
       case nsIIDBCursor::NEXT_NO_DUPLICATE:
@@ -515,53 +568,17 @@ IDBCursor::Continue(const jsval &aKey,
         }
         break;
 
       default:
         NS_NOTREACHED("Unknown direction type!");
     }
   }
 
-  mContinueToKey = key;
-
-#ifdef DEBUG
-  {
-    PRUint16 readyState;
-    if (NS_FAILED(mRequest->GetReadyState(&readyState))) {
-      NS_ERROR("This should never fail!");
-    }
-    NS_ASSERTION(readyState == nsIIDBRequest::DONE, "Should be DONE!");
-  }
-#endif
-
-  mRequest->Reset();
-
-  nsRefPtr<ContinueHelper> helper;
-  switch (mType) {
-    case OBJECTSTORE:
-      helper = new ContinueObjectStoreHelper(this);
-      break;
-
-    case INDEXKEY:
-      helper = new ContinueIndexHelper(this);
-      break;
-
-    case INDEXOBJECT:
-      helper = new ContinueIndexObjectHelper(this);
-      break;
-
-    default:
-      NS_NOTREACHED("Unknown cursor type!");
-  }
-
-  rv = helper->DispatchToTransactionPool();
-  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
-
-  mContinueCalled = true;
-  return NS_OK;
+  return ContinueInternal(key, 1);
 }
 
 NS_IMETHODIMP
 IDBCursor::Update(const jsval& aValue,
                   JSContext* aCx,
                   nsIIDBRequest** _retval)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
@@ -648,43 +665,70 @@ IDBCursor::Delete(JSContext* aCx,
 
   jsval key;
   nsresult rv = objectKey.ToJSVal(aCx, &key);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return mObjectStore->Delete(key, aCx, _retval);
 }
 
+NS_IMETHODIMP
+IDBCursor::Advance(PRInt32 aCount)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  if (aCount < 1) {
+    return NS_ERROR_DOM_TYPE_ERR;
+  }
+
+  Key key;
+  return ContinueInternal(key, aCount);
+}
+
 nsresult
 ContinueHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
 {
   // 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.
 
-  const nsCString& query = mCursor->mContinueToKey.IsUnset() ?
-                           mCursor->mContinueQuery :
-                           mCursor->mContinueToQuery;
+  nsCAutoString query;
+  if (mCursor->mContinueToKey.IsUnset()) {
+    query.Assign(mCursor->mContinueQuery);
+  }
+  else {
+    query.Assign(mCursor->mContinueToQuery);
+  }
   NS_ASSERTION(!query.IsEmpty(), "Bad query!");
 
+  query.AppendInt(mCount);
+
   nsCOMPtr<mozIStorageStatement> stmt = mTransaction->GetCachedStatement(query);
   NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   mozStorageStatementScoper scoper(stmt);
 
   nsresult rv = BindArgumentsToStatement(stmt);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
+  NS_ASSERTION(mCount > 0, "Not ok!");
+
   bool hasResult;
-  rv = stmt->ExecuteStep(&hasResult);
-  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+  for (PRInt32 index = 0; index < mCount; index++) {
+    rv = stmt->ExecuteStep(&hasResult);
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+    if (!hasResult) {
+      break;
+    }
+  }
 
   if (hasResult) {
     rv = GatherResultsFromStatement(stmt);
     NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
   }
   else {
     mKey.Unset();
   }
--- a/dom/indexedDB/IDBCursor.h
+++ b/dom/indexedDB/IDBCursor.h
@@ -138,16 +138,20 @@ protected:
   CreateCommon(IDBRequest* aRequest,
                IDBTransaction* aTransaction,
                IDBObjectStore* aObjectStore,
                PRUint16 aDirection,
                const Key& aRangeKey,
                const nsACString& aContinueQuery,
                const nsACString& aContinueToQuery);
 
+  nsresult
+  ContinueInternal(const Key& aKey,
+                   PRInt32 aCount);
+
   nsRefPtr<IDBRequest> mRequest;
   nsRefPtr<IDBTransaction> mTransaction;
   nsRefPtr<IDBObjectStore> mObjectStore;
   nsRefPtr<IDBIndex> mIndex;
 
   nsCOMPtr<nsIScriptContext> mScriptContext;
   nsCOMPtr<nsPIDOMWindow> mOwner;
 
--- a/dom/indexedDB/IDBIndex.cpp
+++ b/dom/indexedDB/IDBIndex.cpp
@@ -1159,61 +1159,61 @@ OpenKeyCursorHelper::DoDatabaseWork(mozI
       }
       mContinueQuery = queryStart + NS_LITERAL_CSTRING(" AND ( ( value = :") +
                        currentKey + NS_LITERAL_CSTRING(" AND ") + keyColumn +
                        NS_LITERAL_CSTRING(" > :") + objectKey +
                        NS_LITERAL_CSTRING(" ) OR ( value > :") + currentKey +
                        NS_LITERAL_CSTRING(" ) )") + directionClause +
                        NS_LITERAL_CSTRING(" LIMIT 1");
       mContinueToQuery = queryStart + NS_LITERAL_CSTRING(" AND value >= :") +
-                         currentKey + NS_LITERAL_CSTRING(" LIMIT 1");
+                         currentKey + NS_LITERAL_CSTRING(" LIMIT ");
       break;
 
     case nsIIDBCursor::NEXT_NO_DUPLICATE:
       if (mKeyRange && !mKeyRange->Upper().IsUnset()) {
         AppendConditionClause(value, rangeKey, true, !mKeyRange->IsUpperOpen(),
                               queryStart);
         mRangeKey = mKeyRange->Upper();
       }
       mContinueQuery = queryStart + NS_LITERAL_CSTRING(" AND value > :") +
                        currentKey + directionClause +
                        NS_LITERAL_CSTRING(" LIMIT 1");
       mContinueToQuery = queryStart + NS_LITERAL_CSTRING(" AND value >= :") +
                          currentKey + directionClause +
-                         NS_LITERAL_CSTRING(" LIMIT 1");
+                         NS_LITERAL_CSTRING(" LIMIT ");
       break;
 
     case nsIIDBCursor::PREV:
       if (mKeyRange && !mKeyRange->Lower().IsUnset()) {
         AppendConditionClause(value, rangeKey, false, !mKeyRange->IsLowerOpen(),
                               queryStart);
         mRangeKey = mKeyRange->Lower();
       }
       mContinueQuery = queryStart + NS_LITERAL_CSTRING(" AND ( ( value = :") +
                        currentKey + NS_LITERAL_CSTRING(" AND ") + keyColumn +
                        NS_LITERAL_CSTRING(" < :") + objectKey +
                        NS_LITERAL_CSTRING(" ) OR ( value < :") + currentKey +
                        NS_LITERAL_CSTRING(" ) )") + directionClause +
                        NS_LITERAL_CSTRING(" LIMIT 1");
       mContinueToQuery = queryStart + NS_LITERAL_CSTRING(" AND value <= :") +
-                         currentKey + NS_LITERAL_CSTRING(" LIMIT 1");
+                         currentKey + NS_LITERAL_CSTRING(" LIMIT ");
       break;
 
     case nsIIDBCursor::PREV_NO_DUPLICATE:
       if (mKeyRange && !mKeyRange->Lower().IsUnset()) {
         AppendConditionClause(value, rangeKey, false, !mKeyRange->IsLowerOpen(),
                               queryStart);
         mRangeKey = mKeyRange->Lower();
       }
       mContinueQuery = queryStart + NS_LITERAL_CSTRING(" AND value < :") +
                        currentKey + directionClause +
                        NS_LITERAL_CSTRING(" LIMIT 1");
       mContinueToQuery = queryStart + NS_LITERAL_CSTRING(" AND value <= :") +
                          currentKey + directionClause +
-                         NS_LITERAL_CSTRING(" LIMIT 1");
+                         NS_LITERAL_CSTRING(" LIMIT ");
       break;
 
     default:
       NS_NOTREACHED("Unknown direction type!");
   }
 
   return NS_OK;
 }
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -1984,17 +1984,17 @@ OpenCursorHelper::DoDatabaseWork(mozISto
                    NS_LITERAL_CSTRING(" WHERE object_store_id = :") + id +
                    keyRangeClause + directionClause +
                    NS_LITERAL_CSTRING(" LIMIT 1");
 
   mContinueToQuery = NS_LITERAL_CSTRING("SELECT ") + keyColumn +
                      NS_LITERAL_CSTRING(", data FROM ") + table +
                      NS_LITERAL_CSTRING(" WHERE object_store_id = :") + id +
                      continueToKeyRangeClause + directionClause +
-                     NS_LITERAL_CSTRING(" LIMIT 1");
+                     NS_LITERAL_CSTRING(" LIMIT ");
 
   return NS_OK;
 }
 
 nsresult
 OpenCursorHelper::GetSuccessResult(JSContext* aCx,
                                    jsval* aVal)
 {
--- a/dom/indexedDB/nsIIDBCursor.idl
+++ b/dom/indexedDB/nsIIDBCursor.idl
@@ -41,17 +41,17 @@
 
 interface nsIIDBRequest;
 
 /**
  * IDBCursor interface.  See
  * http://dev.w3.org/2006/webapi/WebSimpleDB/#idl-def-IDBCursor for more
  * information.
  */
-[scriptable, builtinclass, uuid(462a3607-b2d6-4f4b-9dd7-8ca0b26d3414)]
+[scriptable, builtinclass, uuid(9d5ddd43-132d-418e-81e8-17d64a6467a2)]
 interface nsIIDBCursor : nsISupports
 {
   const unsigned short NEXT = 0;
   const unsigned short NEXT_NO_DUPLICATE = 1;
   const unsigned short PREV = 2;
   const unsigned short PREV_NO_DUPLICATE = 3;
   readonly attribute unsigned short direction;
 
@@ -70,9 +70,12 @@ interface nsIIDBCursor : nsISupports
 
   // Success fires IDBTransactionEvent, result == key
   [implicit_jscontext]
   nsIIDBRequest update(in jsval value);
 
   // Success fires IDBTransactionEvent, result == null
   [implicit_jscontext]
   nsIIDBRequest delete();
+
+  void
+  advance(in long count);
 };
--- a/dom/indexedDB/test/Makefile.in
+++ b/dom/indexedDB/test/Makefile.in
@@ -48,16 +48,17 @@ TEST_FILES = \
   bfcache_iframe1.html \
   bfcache_iframe2.html \
   error_events_abort_transactions_iframe.html \
   event_propagation_iframe.html \
   exceptions_in_success_events_iframe.html \
   helpers.js \
   leaving_page_iframe.html \
   test_add_twice_failure.html \
+  test_advance.html \
   test_autoIncrement_indexes.html \
   test_bad_keypath.html \
   test_bfcache.html \
   test_clear.html \
   test_count.html \
   test_create_index.html \
   test_create_index_with_integer_keys.html \
   test_create_objectStore.html \
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_advance.html
@@ -0,0 +1,204 @@
+<!--
+  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">
+    function testSteps()
+    {
+      const dataCount = 30;
+
+      let request = mozIndexedDB.open(window.location.pathname, 1);
+      request.onerror = errorHandler;
+      request.onupgradeneeded = grabEventAndContinueHandler;
+      let event = yield;
+
+      let db = event.target.result;
+      db.onerror = errorHandler;
+
+      event.target.onsuccess = continueToNextStep;
+
+      let objectStore = db.createObjectStore("", { keyPath: "key" });
+      objectStore.createIndex("", "index");
+
+      for (let i = 0; i < dataCount; i++) {
+        objectStore.add({ key: i, index: i });
+      }
+      yield;
+
+      function getObjectStore() {
+        return db.transaction("").objectStore("");
+      }
+
+      function getIndex() {
+        return db.transaction("").objectStore("").index("");
+      }
+
+      let count = 0;
+
+      getObjectStore().openCursor().onsuccess = function(event) {
+        let cursor = event.target.result;
+        if (cursor) {
+          count++;
+          cursor.continue();
+        }
+        else {
+          continueToNextStep();
+        }
+      };
+      yield;
+
+      is(count, dataCount, "Saw all data");
+
+      count = 0;
+
+      getObjectStore().openCursor().onsuccess = function(event) {
+        let cursor = event.target.result;
+        if (cursor) {
+          is(cursor.primaryKey, count, "Got correct object");
+          if (count) {
+            count++;
+            cursor.continue();
+          }
+          else {
+            count = 10;
+            cursor.advance(10);
+          }
+        }
+        else {
+          continueToNextStep();
+        }
+      };
+      yield;
+
+      is(count, dataCount, "Saw all data");
+
+      count = 0;
+
+      getIndex().openCursor().onsuccess = function(event) {
+        let cursor = event.target.result;
+        if (cursor) {
+          is(cursor.primaryKey, count, "Got correct object");
+          if (count) {
+            count++;
+            cursor.continue();
+          }
+          else {
+            count = 10;
+            cursor.advance(10);
+          }
+        }
+        else {
+          continueToNextStep();
+        }
+      };
+      yield;
+
+      is(count, dataCount, "Saw all data");
+
+      count = 0;
+
+      getIndex().openKeyCursor().onsuccess = function(event) {
+        let cursor = event.target.result;
+        if (cursor) {
+          is(cursor.primaryKey, count, "Got correct object");
+          if (count) {
+            count++;
+            cursor.continue();
+          }
+          else {
+            count = 10;
+            cursor.advance(10);
+          }
+        }
+        else {
+          continueToNextStep();
+        }
+      };
+      yield;
+
+      is(count, dataCount, "Saw all data");
+
+      count = 0;
+
+      getObjectStore().openCursor().onsuccess = function(event) {
+        let cursor = event.target.result;
+        if (cursor) {
+          is(cursor.primaryKey, count, "Got correct object");
+          if (count == 0) {
+            cursor.advance(dataCount + 1);
+          }
+          else {
+            ok(false, "Should never get here!");
+            cursor.continue();
+          }
+        }
+        else {
+          continueToNextStep();
+        }
+      };
+      yield;
+
+      is(count, 0, "Saw all data");
+
+      count = dataCount - 1;
+
+      getObjectStore().openCursor(null, IDBCursor.PREV).onsuccess = function(event) {
+        let cursor = event.target.result;
+        if (cursor) {
+          is(cursor.primaryKey, count, "Got correct object");
+          count--;
+          if (count == dataCount - 2) {
+            cursor.advance(10);
+            count -= 9;
+          }
+          else {
+            cursor.continue();
+          }
+        }
+        else {
+          continueToNextStep();
+        }
+      };
+      yield;
+
+      is(count, -1, "Saw all data");
+
+      count = dataCount - 1;
+
+      getObjectStore().openCursor(null, IDBCursor.PREV).onsuccess = function(event) {
+        let cursor = event.target.result;
+        if (cursor) {
+          is(cursor.primaryKey, count, "Got correct object");
+          if (count == dataCount - 1) {
+            cursor.advance(dataCount + 1);
+          }
+          else {
+            ok(false, "Should never get here!");
+            cursor.continue();
+          }
+        }
+        else {
+          continueToNextStep();
+        }
+      };
+      yield;
+
+      is(count, dataCount - 1, "Saw all data");
+
+      finishTest();
+      yield;
+    }
+  </script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+</head>
+
+<body onload="runTest();"></body>
+
+</html>