IndexedDB 2.0: Implement new IDBCursor.continuePrimaryKey function.
authorbeidson@apple.com <beidson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 9 Nov 2016 23:29:38 +0000 (23:29 +0000)
committerbeidson@apple.com <beidson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 9 Nov 2016 23:29:38 +0000 (23:29 +0000)
https://bugs.webkit.org/show_bug.cgi?id=164404

Reviewed by Alex Christensen.

LayoutTests/imported/w3c:

* web-platform-tests/IndexedDB/idbcursor-continuePrimaryKey-exception-order-expected.txt:

Source/WebCore:

Tests: storage/indexeddb/modern/idbcursor-continue-primary-key-1-private.html
       storage/indexeddb/modern/idbcursor-continue-primary-key-1.html
       Also covered by existing tests.

* Modules/indexeddb/IDBCursor.cpp:
(WebCore::IDBCursor::continuePrimaryKey):
(WebCore::IDBCursor::uncheckedIterateCursor):
* Modules/indexeddb/IDBCursor.h:
* Modules/indexeddb/IDBCursor.idl:

* Modules/indexeddb/IDBKeyData.h:
(WebCore::IDBKeyData::operator>):
(WebCore::IDBKeyData::operator<=):
(WebCore::IDBKeyData::operator>=):

* Modules/indexeddb/server/MemoryCursor.h:

* Modules/indexeddb/server/MemoryIDBBackingStore.cpp:
(WebCore::IDBServer::MemoryIDBBackingStore::iterateCursor):

* Modules/indexeddb/server/MemoryIndexCursor.cpp:
(WebCore::IDBServer::MemoryIndexCursor::iterate):
* Modules/indexeddb/server/MemoryIndexCursor.h:

* Modules/indexeddb/server/MemoryObjectStoreCursor.cpp:
(WebCore::IDBServer::MemoryObjectStoreCursor::iterate):
* Modules/indexeddb/server/MemoryObjectStoreCursor.h:

* Modules/indexeddb/server/SQLiteIDBBackingStore.cpp:
(WebCore::IDBServer::SQLiteIDBBackingStore::iterateCursor):

* Modules/indexeddb/server/SQLiteIDBCursor.cpp:
(WebCore::IDBServer::SQLiteIDBCursor::iterate):
* Modules/indexeddb/server/SQLiteIDBCursor.h:

* Modules/indexeddb/shared/IDBIterateCursorData.cpp:
(WebCore::IDBIterateCursorData::isolatedCopy):
* Modules/indexeddb/shared/IDBIterateCursorData.h:
(WebCore::IDBIterateCursorData::encode):
(WebCore::IDBIterateCursorData::decode):

LayoutTests:

* storage/indexeddb/cursor-basics-expected.txt:
* storage/indexeddb/cursor-basics-private-expected.txt:
* storage/indexeddb/modern/idbcursor-continue-primary-key-1-expected.txt: Added.
* storage/indexeddb/modern/idbcursor-continue-primary-key-1-private-expected.txt: Added.
* storage/indexeddb/modern/idbcursor-continue-primary-key-1-private.html: Added.
* storage/indexeddb/modern/idbcursor-continue-primary-key-1.html: Added.
* storage/indexeddb/modern/resources/idbcursor-continue-primary-key-1.js: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@208500 268f45cc-cd09-0410-ab3c-d52691b4dbfc

26 files changed:
LayoutTests/ChangeLog
LayoutTests/imported/w3c/ChangeLog
LayoutTests/imported/w3c/web-platform-tests/IndexedDB/idbcursor-continuePrimaryKey-exception-order-expected.txt
LayoutTests/storage/indexeddb/cursor-basics-expected.txt
LayoutTests/storage/indexeddb/cursor-basics-private-expected.txt
LayoutTests/storage/indexeddb/modern/idbcursor-continue-primary-key-1-expected.txt [new file with mode: 0644]
LayoutTests/storage/indexeddb/modern/idbcursor-continue-primary-key-1-private-expected.txt [new file with mode: 0644]
LayoutTests/storage/indexeddb/modern/idbcursor-continue-primary-key-1-private.html [new file with mode: 0644]
LayoutTests/storage/indexeddb/modern/idbcursor-continue-primary-key-1.html [new file with mode: 0644]
LayoutTests/storage/indexeddb/modern/resources/idbcursor-continue-primary-key-1.js [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/Modules/indexeddb/IDBCursor.cpp
Source/WebCore/Modules/indexeddb/IDBCursor.h
Source/WebCore/Modules/indexeddb/IDBCursor.idl
Source/WebCore/Modules/indexeddb/IDBKeyData.h
Source/WebCore/Modules/indexeddb/server/MemoryCursor.h
Source/WebCore/Modules/indexeddb/server/MemoryIDBBackingStore.cpp
Source/WebCore/Modules/indexeddb/server/MemoryIndexCursor.cpp
Source/WebCore/Modules/indexeddb/server/MemoryIndexCursor.h
Source/WebCore/Modules/indexeddb/server/MemoryObjectStoreCursor.cpp
Source/WebCore/Modules/indexeddb/server/MemoryObjectStoreCursor.h
Source/WebCore/Modules/indexeddb/server/SQLiteIDBBackingStore.cpp
Source/WebCore/Modules/indexeddb/server/SQLiteIDBCursor.cpp
Source/WebCore/Modules/indexeddb/server/SQLiteIDBCursor.h
Source/WebCore/Modules/indexeddb/shared/IDBIterateCursorData.cpp
Source/WebCore/Modules/indexeddb/shared/IDBIterateCursorData.h

index 779edde..e074c21 100644 (file)
@@ -1,3 +1,18 @@
+2016-11-09  Brady Eidson  <beidson@apple.com>
+
+        IndexedDB 2.0: Implement new IDBCursor.continuePrimaryKey function.
+        https://bugs.webkit.org/show_bug.cgi?id=164404
+
+        Reviewed by Alex Christensen.
+
+        * storage/indexeddb/cursor-basics-expected.txt:
+        * storage/indexeddb/cursor-basics-private-expected.txt:
+        * storage/indexeddb/modern/idbcursor-continue-primary-key-1-expected.txt: Added.
+        * storage/indexeddb/modern/idbcursor-continue-primary-key-1-private-expected.txt: Added.
+        * storage/indexeddb/modern/idbcursor-continue-primary-key-1-private.html: Added.
+        * storage/indexeddb/modern/idbcursor-continue-primary-key-1.html: Added.
+        * storage/indexeddb/modern/resources/idbcursor-continue-primary-key-1.js: Added.
+
 2016-11-09  Antoine Quint  <graouts@apple.com>
 
         [Modern Media Controls] Media Controller: set status label according to media state
index b99edab..e8fd50e 100644 (file)
@@ -1,5 +1,14 @@
 2016-11-09  Brady Eidson  <beidson@apple.com>
 
+        IndexedDB 2.0: Implement new IDBCursor.continuePrimaryKey function.
+        https://bugs.webkit.org/show_bug.cgi?id=164404
+
+        Reviewed by Alex Christensen.
+
+        * web-platform-tests/IndexedDB/idbcursor-continuePrimaryKey-exception-order-expected.txt:
+
+2016-11-09  Brady Eidson  <beidson@apple.com>
+
         IndexedDB 2.0: Clean up more transaction abort behavior, including tweaks to Index/ObjectStore lifetime.
         https://bugs.webkit.org/show_bug.cgi?id=164466
 
index e7bb19b..fd64450 100644 (file)
@@ -1,32 +1,15 @@
-CONSOLE MESSAGE: line 2422: Error: assert_throws: transaction-state check should precede deletion check function "function () {
-                cursor.continuePrimaryKey("..." threw object "TypeError: cursor.continuePrimaryKey is not a function. (..." that is not a DOMException TransactionInactiveError: property "code" is equal to undefined, expected 0
 
-Harness Error (FAIL), message = Error: assert_throws: transaction-state check should precede deletion check function "function () {
-                cursor.continuePrimaryKey("..." threw object "TypeError: cursor.continuePrimaryKey is not a function. (..." that is not a DOMException TransactionInactiveError: property "code" is equal to undefined, expected 0
-
-TIMEOUT TransactionInactiveError v.s. InvalidStateError(deleted index) Test timed out
-FAIL InvalidStateError(deleted source) v.s. InvalidAccessError(incorrect source) assert_throws: deletion check should precede index source check function "function () {
-                cursor.continuePrimaryKey("..." threw object "TypeError: cursor.continuePrimaryKey is not a function. (..." that is not a DOMException InvalidStateError: property "code" is equal to undefined, expected 11
-FAIL InvalidStateError(deleted source) v.s. InvalidAccessError(incorrect direction) assert_throws: deletion check should precede cursor direction check function "function () {
-              cursor.continuePrimaryKey("A"..." threw object "TypeError: cursor.continuePrimaryKey is not a function. (..." that is not a DOMException InvalidStateError: property "code" is equal to undefined, expected 11
-FAIL InvalidAccessError(incorrect direction) v.s. InvalidStateError(iteration complete) assert_throws: direction check should precede got_value_flag check function "function () {
-                cursor.continuePrimaryKey("..." threw object "TypeError: cursor.continuePrimaryKey is not a function. (..." that is not a DOMException InvalidAccessError: property "code" is equal to undefined, expected 15
-FAIL InvalidAccessError(incorrect direction) v.s. InvalidStateError(iteration ongoing) assert_throws: direction check should precede iteration ongoing check function "function () {
-                    cursor.continuePrimaryK..." threw object "TypeError: cursor.continuePrimaryKey is not a function. (..." that is not a DOMException InvalidAccessError: property "code" is equal to undefined, expected 15
-FAIL InvalidAccessError(incorrect source) v.s. InvalidStateError(iteration ongoing) assert_throws: index source check should precede iteration ongoing check function "function () {
-                    cursor.continuePrimaryK..." threw object "TypeError: cursor.continuePrimaryKey is not a function. (..." that is not a DOMException InvalidAccessError: property "code" is equal to undefined, expected 15
-FAIL InvalidAccessError(incorrect source) v.s. InvalidStateError(iteration complete) assert_throws: index source check should precede got_value_flag check function "function () {
-                cursor.continuePrimaryKey("..." threw object "TypeError: cursor.continuePrimaryKey is not a function. (..." that is not a DOMException InvalidAccessError: property "code" is equal to undefined, expected 15
-FAIL InvalidStateError(iteration ongoing) v.s. DataError(unset key) assert_throws: iteration ongoing check should precede unset key check function "function () {
-                    cursor.continuePrimaryK..." threw object "TypeError: cursor.continuePrimaryKey is not a function. (..." that is not a DOMException InvalidStateError: property "code" is equal to undefined, expected 11
-FAIL InvalidStateError(iteration complete) v.s. DataError(unset key) assert_throws: got_value_flag check should precede unset key check function "function () {
-                cursor.continuePrimaryKey(n..." threw object "TypeError: cursor.continuePrimaryKey is not a function. (..." that is not a DOMException InvalidStateError: property "code" is equal to undefined, expected 11
-FAIL DataError(unset key) assert_throws: DataError is expected if key is unset. function "function () {
-                cursor.continuePrimaryKey(n..." threw object "TypeError: cursor.continuePrimaryKey is not a function. (..." that is not a DOMException DataError: property "code" is equal to undefined, expected 0
-FAIL DataError(unset primary key) assert_throws: DataError is expected if primary key is unset. function "function () {
-                cursor.continuePrimaryKey("..." threw object "TypeError: cursor.continuePrimaryKey is not a function. (..." that is not a DOMException DataError: property "code" is equal to undefined, expected 0
-FAIL DataError(keys are lower then current one) in 'next' direction assert_throws: DataError is expected if key is lower then current one. function "function () {
-                cursor.continuePrimaryKey("..." threw object "TypeError: cursor.continuePrimaryKey is not a function. (..." that is not a DOMException DataError: property "code" is equal to undefined, expected 0
-FAIL DataError(keys are larger then current one) in 'prev' direction assert_throws: DataError is expected if key is larger then current one. function "function () {
-                cursor.continuePrimaryKey("..." threw object "TypeError: cursor.continuePrimaryKey is not a function. (..." that is not a DOMException DataError: property "code" is equal to undefined, expected 0
+PASS TransactionInactiveError v.s. InvalidStateError(deleted index) 
+PASS InvalidStateError(deleted source) v.s. InvalidAccessError(incorrect source) 
+PASS InvalidStateError(deleted source) v.s. InvalidAccessError(incorrect direction) 
+PASS InvalidAccessError(incorrect direction) v.s. InvalidStateError(iteration complete) 
+PASS InvalidAccessError(incorrect direction) v.s. InvalidStateError(iteration ongoing) 
+PASS InvalidAccessError(incorrect source) v.s. InvalidStateError(iteration ongoing) 
+PASS InvalidAccessError(incorrect source) v.s. InvalidStateError(iteration complete) 
+PASS InvalidStateError(iteration ongoing) v.s. DataError(unset key) 
+PASS InvalidStateError(iteration complete) v.s. DataError(unset key) 
+PASS DataError(unset key) 
+PASS DataError(unset primary key) 
+PASS DataError(keys are lower then current one) in 'next' direction 
+PASS DataError(keys are larger then current one) in 'prev' direction 
 
index 33d9cb4..def8967 100644 (file)
@@ -24,8 +24,8 @@ PASS 'key' in cursor is true
 PASS 'primaryKey' in cursor is true
 PASS 'continue' in cursor is true
 PASS typeof cursor.continue is "function"
-FAIL 'continuePrimaryKey' in cursor should be true. Was false.
-FAIL typeof cursor.continuePrimaryKey should be function. Was undefined.
+PASS 'continuePrimaryKey' in cursor is true
+PASS typeof cursor.continuePrimaryKey is "function"
 PASS 'advance' in cursor is true
 PASS typeof cursor.advance is "function"
 PASS 'update' in cursor is true
@@ -46,8 +46,8 @@ PASS 'key' in cursor is true
 PASS 'primaryKey' in cursor is true
 PASS 'continue' in cursor is true
 PASS typeof cursor.continue is "function"
-FAIL 'continuePrimaryKey' in cursor should be true. Was false.
-FAIL typeof cursor.continuePrimaryKey should be function. Was undefined.
+PASS 'continuePrimaryKey' in cursor is true
+PASS typeof cursor.continuePrimaryKey is "function"
 PASS 'advance' in cursor is true
 PASS typeof cursor.advance is "function"
 PASS 'update' in cursor is true
@@ -68,8 +68,8 @@ PASS 'key' in cursor is true
 PASS 'primaryKey' in cursor is true
 PASS 'continue' in cursor is true
 PASS typeof cursor.continue is "function"
-FAIL 'continuePrimaryKey' in cursor should be true. Was false.
-FAIL typeof cursor.continuePrimaryKey should be function. Was undefined.
+PASS 'continuePrimaryKey' in cursor is true
+PASS typeof cursor.continuePrimaryKey is "function"
 PASS 'advance' in cursor is true
 PASS typeof cursor.advance is "function"
 PASS 'update' in cursor is true
@@ -82,7 +82,6 @@ PASS cursor instanceof IDBCursorWithValue is false
 PASS cursor.primaryKey is 0
 PASS 'value' in cursor is false
 PASS successfullyParsed is true
-Some tests failed.
 
 TEST COMPLETE
 
index 33d9cb4..def8967 100644 (file)
@@ -24,8 +24,8 @@ PASS 'key' in cursor is true
 PASS 'primaryKey' in cursor is true
 PASS 'continue' in cursor is true
 PASS typeof cursor.continue is "function"
-FAIL 'continuePrimaryKey' in cursor should be true. Was false.
-FAIL typeof cursor.continuePrimaryKey should be function. Was undefined.
+PASS 'continuePrimaryKey' in cursor is true
+PASS typeof cursor.continuePrimaryKey is "function"
 PASS 'advance' in cursor is true
 PASS typeof cursor.advance is "function"
 PASS 'update' in cursor is true
@@ -46,8 +46,8 @@ PASS 'key' in cursor is true
 PASS 'primaryKey' in cursor is true
 PASS 'continue' in cursor is true
 PASS typeof cursor.continue is "function"
-FAIL 'continuePrimaryKey' in cursor should be true. Was false.
-FAIL typeof cursor.continuePrimaryKey should be function. Was undefined.
+PASS 'continuePrimaryKey' in cursor is true
+PASS typeof cursor.continuePrimaryKey is "function"
 PASS 'advance' in cursor is true
 PASS typeof cursor.advance is "function"
 PASS 'update' in cursor is true
@@ -68,8 +68,8 @@ PASS 'key' in cursor is true
 PASS 'primaryKey' in cursor is true
 PASS 'continue' in cursor is true
 PASS typeof cursor.continue is "function"
-FAIL 'continuePrimaryKey' in cursor should be true. Was false.
-FAIL typeof cursor.continuePrimaryKey should be function. Was undefined.
+PASS 'continuePrimaryKey' in cursor is true
+PASS typeof cursor.continuePrimaryKey is "function"
 PASS 'advance' in cursor is true
 PASS typeof cursor.advance is "function"
 PASS 'update' in cursor is true
@@ -82,7 +82,6 @@ PASS cursor instanceof IDBCursorWithValue is false
 PASS cursor.primaryKey is 0
 PASS 'value' in cursor is false
 PASS successfullyParsed is true
-Some tests failed.
 
 TEST COMPLETE
 
diff --git a/LayoutTests/storage/indexeddb/modern/idbcursor-continue-primary-key-1-expected.txt b/LayoutTests/storage/indexeddb/modern/idbcursor-continue-primary-key-1-expected.txt
new file mode 100644 (file)
index 0000000..27b2998
--- /dev/null
@@ -0,0 +1,36 @@
+This test verifies the basic functionality of IDBCursor.continuePrimaryKey().
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS !!cursor is true
+PASS "transaction-state check should precede deletion check" threw as expected
+PASS !!cursor is true
+PASS "deletion check should precede index source check" threw as expected
+PASS !!cursor is true
+PASS "deletion check should precede cursor direction check" threw as expected
+PASS "direction check should precede got_value_flag check" threw as expected
+PASS !!cursor is true
+PASS "direction check should precede iteration ongoing check" threw as expected
+PASS !!cursor is true
+PASS "index source check should precede iteration ongoing check" threw as expected
+PASS "index source check should precede got_value_flag check" threw as expected
+PASS !!cursor is true
+PASS "iteration ongoing check should precede unset key check" threw as expected
+PASS "got_value_flag check should precede unset key check" threw as expected
+PASS !!cursor is true
+PASS "DataError is expected if key is unset." threw as expected
+PASS !!cursor is true
+PASS "DataError is expected if primary key is unset." threw as expected
+PASS !!cursor is true
+PASS "DataError is expected if key is lower then current one." threw as expected
+PASS "DataError is expected if primary key is equal to current one." threw as expected
+PASS "DataError is expected if primary key is lower than current one." threw as expected
+PASS !!cursor is true
+PASS "DataError is expected if key is larger then current one." threw as expected
+PASS "DataError is expected if primary key is equal to current one." threw as expected
+PASS "DataError is expected if primary key is larger than current one." threw as expected
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/storage/indexeddb/modern/idbcursor-continue-primary-key-1-private-expected.txt b/LayoutTests/storage/indexeddb/modern/idbcursor-continue-primary-key-1-private-expected.txt
new file mode 100644 (file)
index 0000000..27b2998
--- /dev/null
@@ -0,0 +1,36 @@
+This test verifies the basic functionality of IDBCursor.continuePrimaryKey().
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS !!cursor is true
+PASS "transaction-state check should precede deletion check" threw as expected
+PASS !!cursor is true
+PASS "deletion check should precede index source check" threw as expected
+PASS !!cursor is true
+PASS "deletion check should precede cursor direction check" threw as expected
+PASS "direction check should precede got_value_flag check" threw as expected
+PASS !!cursor is true
+PASS "direction check should precede iteration ongoing check" threw as expected
+PASS !!cursor is true
+PASS "index source check should precede iteration ongoing check" threw as expected
+PASS "index source check should precede got_value_flag check" threw as expected
+PASS !!cursor is true
+PASS "iteration ongoing check should precede unset key check" threw as expected
+PASS "got_value_flag check should precede unset key check" threw as expected
+PASS !!cursor is true
+PASS "DataError is expected if key is unset." threw as expected
+PASS !!cursor is true
+PASS "DataError is expected if primary key is unset." threw as expected
+PASS !!cursor is true
+PASS "DataError is expected if key is lower then current one." threw as expected
+PASS "DataError is expected if primary key is equal to current one." threw as expected
+PASS "DataError is expected if primary key is lower than current one." threw as expected
+PASS !!cursor is true
+PASS "DataError is expected if key is larger then current one." threw as expected
+PASS "DataError is expected if primary key is equal to current one." threw as expected
+PASS "DataError is expected if primary key is larger than current one." threw as expected
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/storage/indexeddb/modern/idbcursor-continue-primary-key-1-private.html b/LayoutTests/storage/indexeddb/modern/idbcursor-continue-primary-key-1-private.html
new file mode 100644 (file)
index 0000000..63a3d9e
--- /dev/null
@@ -0,0 +1,13 @@
+<html>
+<head>
+<script>
+enablePrivateBrowsing = true;
+</script>
+<script src="../../../resources/js-test.js"></script>
+<script src="../resources/shared.js"></script>
+</head>
+<body>
+
+<script src="resources/idbcursor-continue-primary-key-1.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/storage/indexeddb/modern/idbcursor-continue-primary-key-1.html b/LayoutTests/storage/indexeddb/modern/idbcursor-continue-primary-key-1.html
new file mode 100644 (file)
index 0000000..3d4b69b
--- /dev/null
@@ -0,0 +1,10 @@
+<html>
+<head>
+<script src="../../../resources/js-test.js"></script>
+<script src="../resources/shared.js"></script>
+</head>
+<body>
+
+<script src="resources/idbcursor-continue-primary-key-1.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/storage/indexeddb/modern/resources/idbcursor-continue-primary-key-1.js b/LayoutTests/storage/indexeddb/modern/resources/idbcursor-continue-primary-key-1.js
new file mode 100644 (file)
index 0000000..7d27764
--- /dev/null
@@ -0,0 +1,483 @@
+// Test modeled after imported/w3c/web-platform-tests/IndexedDB/idbcursor-continuePrimaryKey-exception-order.htm
+
+description("This test verifies the basic functionality of IDBCursor.continuePrimaryKey().");
+
+function setup_test_store(db) {
+       var records = [ { iKey: "A", pKey: 1 },
+                       { iKey: "A", pKey: 2 },
+                       { iKey: "A", pKey: 3 },
+                       { iKey: "A", pKey: 4 },
+                       { iKey: "B", pKey: 5 },
+                       { iKey: "B", pKey: 6 },
+                       { iKey: "B", pKey: 7 },
+                       { iKey: "C", pKey: 8 },
+                       { iKey: "C", pKey: 9 },
+                       { iKey: "D", pKey: 10 } ];
+
+    var store = db.createObjectStore("test", { keyPath: "pKey" });
+    var index = store.createIndex("idx", "iKey");
+
+    for(var i = 0; i < records.length; i++)
+        store.add(records[i]);
+
+    return store;
+}
+
+function log(message)
+{
+    debug(message);
+}
+
+function next()
+{
+    testGenerator.next();
+}
+
+function asyncNext()
+{
+    setTimeout("testGenerator.next();", 0);
+}
+
+function unreached(description)
+{
+       return function() {
+               debug("FAIL: " + description);
+               finishJSTest();
+       }
+};
+
+var dbName = window.location.href + " test";
+var db;
+
+var testGenerator = testSteps();
+testGenerator.next();
+
+function shouldThrowType(name, func, text) {
+    var errorText = text + " should throw an exception";
+    try {
+        func();
+        testFailed(errorText);
+    } catch (e) {
+               if (e.name == name)
+               testPassed('"' + text + "\" threw as expected");
+        else
+                       testFailed(text + " should throw exception type " + name + ", but threw " + e.name + " instead");
+    }
+}
+
+var db;
+function* testSteps()
+{
+       indexedDB.deleteDatabase(dbName).onsuccess = next;
+       yield;
+       
+       request = indexedDB.open(dbName);
+       request.onupgradeneeded = function() {
+               db = request.result;
+               var txn = request.transaction;
+
+               var store = setup_test_store(db);
+               var index = store.index("idx");
+               var cursor_rq = index.openCursor();
+
+               cursor_rq.onerror = unreached('openCursor should succeed');
+               cursor_rq.onsuccess = function(e) {
+                   cursor = e.target.result;
+            shouldBeTrue("!!cursor");
+
+                   store.deleteIndex("idx");
+               };
+               txn.oncomplete = function() {
+                   shouldThrowType("TransactionInactiveError", function() {
+                       cursor.continuePrimaryKey("A", 4);
+                   }, "transaction-state check should precede deletion check");
+                       db.close();
+                       next();
+               };
+       };
+       yield;
+
+       cursor = undefined;
+       indexedDB.deleteDatabase(dbName).onsuccess = next;
+       yield;
+
+       request = indexedDB.open(dbName);
+       request.onupgradeneeded = function() {
+               db = request.result;
+
+               var store = setup_test_store(db);
+        var cursor_rq = store.openCursor();
+
+        cursor_rq.onerror = unreached('openCursor should succeed');
+        cursor_rq.onsuccess = function(e) {
+            cursor = e.target.result;
+            shouldBeTrue("!!cursor");
+
+            db.deleteObjectStore("test");
+
+            shouldThrowType("InvalidStateError", function() {
+                cursor.continuePrimaryKey("A", 4);
+            }, "deletion check should precede index source check");
+
+                       db.close();
+            next();
+        };
+       };
+       yield;
+
+       cursor = undefined;
+       indexedDB.deleteDatabase(dbName).onsuccess = next;
+       yield;
+
+       request = indexedDB.open(dbName);
+       request.onupgradeneeded = function() {
+               db = request.result;
+               
+        var store = setup_test_store(db);
+        var index = store.index("idx");
+        var cursor_rq = index.openCursor(null, "nextunique");
+
+        cursor_rq.onerror = unreached('openCursor should succeed');
+        cursor_rq.onsuccess = function(e) {
+            cursor = e.target.result;
+            shouldBeTrue("!!cursor");
+
+            store.deleteIndex("idx");
+
+            shouldThrowType("InvalidStateError", function() {
+              cursor.continuePrimaryKey("A", 4);
+            }, "deletion check should precede cursor direction check");
+                       
+                       db.close();
+            next();
+        };
+       };
+       yield;
+
+       cursor = undefined;
+       indexedDB.deleteDatabase(dbName).onsuccess = next;
+       yield;
+
+       request = indexedDB.open(dbName);
+       request.onupgradeneeded = function() {
+               db = request.result;
+               
+        var store = db.createObjectStore("test", {keyPath:"pKey"});
+        var index = store.createIndex("idx", "iKey");
+
+        store.add({ iKey: "A", pKey: 1 });
+
+        var cursor_rq = index.openCursor(null, "nextunique");
+
+        cursor_rq.onerror = unreached('openCursor should succeed');
+        cursor_rq.onsuccess = function(e) {
+            if (e.target.result) {
+                cursor = e.target.result;
+                cursor.continue();
+                return;
+            }
+
+            shouldThrowType("InvalidAccessError", function() {
+                cursor.continuePrimaryKey("A", 4);
+            }, "direction check should precede got_value_flag check");
+
+                       db.close();
+            next();
+        };
+       };
+       yield;
+
+       indexedDB.deleteDatabase(dbName).onsuccess = next;
+       cursor = undefined;
+       yield;
+       
+       request = indexedDB.open(dbName);
+       request.onupgradeneeded = function() {
+               db = request.result;
+       
+        var store = db.createObjectStore("test", {keyPath:"pKey"});
+        var index = store.createIndex("idx", "iKey");
+
+        store.add({ iKey: "A", pKey: 1 });
+
+        var cursor_rq = index.openCursor(null, "nextunique");
+
+        cursor_rq.onerror = unreached('openCursor should succeed');
+        cursor_rq.onsuccess = function(e) {
+            if (!cursor) {
+                cursor = e.target.result;
+                shouldBeTrue("!!cursor");
+
+                cursor.continue();
+
+                shouldThrowType("InvalidAccessError", function() {
+                    cursor.continuePrimaryKey("A", 4);
+                }, "direction check should precede iteration ongoing check");
+
+                               cursor_rq.onsuccess = undefined;
+                               db.close();
+                next();
+            }
+        };
+       };
+       yield;
+       
+       indexedDB.deleteDatabase(dbName).onsuccess = next;
+       cursor = undefined;
+       yield;
+       
+       request = indexedDB.open(dbName);
+       request.onupgradeneeded = function() {
+               db = request.result;
+               
+        var store = setup_test_store(db);
+        var cursor_rq = store.openCursor();
+
+        cursor_rq.onerror = unreached('openCursor should succeed');
+        cursor_rq.onsuccess = function(e) {
+            if (!cursor) {
+                cursor = e.target.result;
+                shouldBeTrue("!!cursor");
+
+                cursor.continue();
+
+                shouldThrowType("InvalidAccessError", function() {
+                    cursor.continuePrimaryKey("A", 4);
+                }, "index source check should precede iteration ongoing check");
+
+                               cursor_rq.onsuccess = undefined;
+                       db.close();
+                next();
+            }
+               };
+       };
+       yield;
+
+       indexedDB.deleteDatabase(dbName).onsuccess = next;
+       cursor = undefined;
+       yield;
+       
+       request = indexedDB.open(dbName);
+       request.onupgradeneeded = function() {
+               db = request.result;
+               
+        var store = db.createObjectStore("test", {keyPath:"pKey"});
+
+        store.add({ iKey: "A", pKey: 1 });
+
+        var cursor_rq = store.openCursor();
+
+        cursor_rq.onerror = unreached('openCursor should succeed');
+        cursor_rq.onsuccess = function(e) {
+            if (e.target.result) {
+                cursor = e.target.result;
+                cursor.continue();
+                return;
+            }
+
+            shouldThrowType("InvalidAccessError", function() {
+                cursor.continuePrimaryKey("A", 4);
+            }, "index source check should precede got_value_flag check");
+                   db.close();
+            next();
+        };
+       };
+       yield;
+
+       indexedDB.deleteDatabase(dbName).onsuccess = next;
+       cursor = undefined;
+       yield;
+       
+       request = indexedDB.open(dbName);
+       request.onupgradeneeded = function() {
+               db = request.result;
+               
+        var store = setup_test_store(db);
+        var index = store.index("idx");
+        var cursor_rq = index.openCursor();
+
+        cursor_rq.onerror = unreached('openCursor should succeed');
+        cursor_rq.onsuccess = function(e) {
+            if (!cursor) {
+                cursor = e.target.result;
+                shouldBeTrue("!!cursor");
+
+                cursor.continue();
+
+                shouldThrowType("InvalidStateError", function() {
+                    cursor.continuePrimaryKey(null, 4);
+                }, "iteration ongoing check should precede unset key check");
+
+                               cursor_rq.onsuccess = undefined;
+                       db.close();
+                next();
+            }
+        };
+       };
+       yield;
+
+       indexedDB.deleteDatabase(dbName).onsuccess = next;
+       cursor = undefined;
+       yield;
+       
+       request = indexedDB.open(dbName);
+       request.onupgradeneeded = function() {
+               db = request.result;
+               
+        var store = db.createObjectStore("test", {keyPath:"pKey"});
+        var index = store.createIndex("idx", "iKey");
+
+        store.add({ iKey: "A", pKey: 1 });
+
+        var cursor_rq = index.openCursor();
+
+        cursor_rq.onerror = unreached('openCursor should succeed');
+        cursor_rq.onsuccess = function(e) {
+            if (e.target.result) {
+                cursor = e.target.result;
+                cursor.continue();
+                return;
+            }
+
+            shouldThrowType("InvalidStateError", function() {
+                cursor.continuePrimaryKey(null, 4);
+            }, "got_value_flag check should precede unset key check");
+                       
+                       db.close();
+            next();
+        };
+       };
+       yield;
+
+       indexedDB.deleteDatabase(dbName).onsuccess = next;
+       cursor = undefined;
+       yield;
+       
+       request = indexedDB.open(dbName);
+       request.onupgradeneeded = function() {
+               db = request.result;
+
+        var store = setup_test_store(db);
+        var index = store.index("idx");
+        var cursor_rq = index.openCursor();
+
+        cursor_rq.onerror = unreached('openCursor should succeed');
+        cursor_rq.onsuccess = function(e) {
+            cursor = e.target.result;
+            shouldBeTrue("!!cursor");
+
+            shouldThrowType("DataError", function() {
+                cursor.continuePrimaryKey(null, 4);
+            }, "DataError is expected if key is unset.");
+
+                       db.close();
+            next();
+        };
+       };
+       yield;
+
+       indexedDB.deleteDatabase(dbName).onsuccess = next;
+       cursor = undefined;
+       yield;
+       
+       request = indexedDB.open(dbName);
+       request.onupgradeneeded = function() {
+               db = request.result;
+
+        var store = setup_test_store(db);
+        var index = store.index("idx");
+        var cursor_rq = index.openCursor();
+
+        cursor_rq.onerror = unreached('openCursor should succeed');
+        cursor_rq.onsuccess = function(e) {
+            cursor = e.target.result;
+            shouldBeTrue("!!cursor");
+
+            shouldThrowType("DataError", function() {
+                cursor.continuePrimaryKey("A", null);
+            }, "DataError is expected if primary key is unset.");
+
+                       db.close();
+            next();
+        };
+       };
+       yield;
+       
+       indexedDB.deleteDatabase(dbName).onsuccess = next;
+       cursor = undefined;
+       yield;
+       
+       request = indexedDB.open(dbName);
+       request.onupgradeneeded = function() {
+               db = request.result;
+
+        var store = setup_test_store(db);
+        var index = store.index("idx");
+        var cursor_rq = index.openCursor(IDBKeyRange.lowerBound("B"));
+
+        cursor_rq.onerror = unreached('openCursor should succeed');
+        cursor_rq.onsuccess = function(e) {
+            cursor = e.target.result;
+            shouldBeTrue("!!cursor");
+
+            shouldBeTrue("cursor.key == 'B'", "expected key");
+            shouldBeTrue("cursor.primaryKey == 5", "expected primary key");
+
+            shouldThrowType("DataError", function() {
+                cursor.continuePrimaryKey("A", 6);
+            }, "DataError is expected if key is lower then current one.");
+
+            shouldThrowType("DataError", function() {
+                cursor.continuePrimaryKey("B", 5);
+            }, "DataError is expected if primary key is equal to current one.");
+
+            shouldThrowType("DataError", function() {
+                cursor.continuePrimaryKey("B", 4);
+            }, "DataError is expected if primary key is lower than current one.");
+
+                       db.close();
+            next();
+        };
+       };
+       yield;
+
+       indexedDB.deleteDatabase(dbName).onsuccess = next;
+       cursor = undefined;
+       yield;
+       
+       request = indexedDB.open(dbName);
+       request.onupgradeneeded = function() {
+               db = request.result;
+       
+        var store = setup_test_store(db);
+        var index = store.index("idx");
+        var cursor_rq = index.openCursor(IDBKeyRange.upperBound("B"), "prev");
+
+        cursor_rq.onerror = unreached('openCursor should succeed');
+        cursor_rq.onsuccess = function(e) {
+            cursor = e.target.result;
+            shouldBeTrue("!!cursor");
+
+            shouldBeTrue("cursor.key == 'B'", "expected key");
+            shouldBeTrue("cursor.primaryKey == 7", "expected primary key");
+
+            shouldThrowType("DataError", function() {
+                cursor.continuePrimaryKey("C", 6);
+            }, "DataError is expected if key is larger then current one.");
+
+            shouldThrowType("DataError", function() {
+                cursor.continuePrimaryKey("B", 7);
+            }, "DataError is expected if primary key is equal to current one.");
+
+            shouldThrowType("DataError", function() {
+                cursor.continuePrimaryKey("B", 8);
+            }, "DataError is expected if primary key is larger than current one.");
+
+                       db.close();
+            next();
+        };
+       };
+       yield;
+
+       finishJSTest();
+}
index d4bd8c0..4b8f726 100644 (file)
@@ -1,3 +1,51 @@
+2016-11-09  Brady Eidson  <beidson@apple.com>
+
+        IndexedDB 2.0: Implement new IDBCursor.continuePrimaryKey function.
+        https://bugs.webkit.org/show_bug.cgi?id=164404
+
+        Reviewed by Alex Christensen.
+
+        Tests: storage/indexeddb/modern/idbcursor-continue-primary-key-1-private.html
+               storage/indexeddb/modern/idbcursor-continue-primary-key-1.html
+               Also covered by existing tests.
+
+        * Modules/indexeddb/IDBCursor.cpp:
+        (WebCore::IDBCursor::continuePrimaryKey):
+        (WebCore::IDBCursor::uncheckedIterateCursor):
+        * Modules/indexeddb/IDBCursor.h:
+        * Modules/indexeddb/IDBCursor.idl:
+
+        * Modules/indexeddb/IDBKeyData.h:
+        (WebCore::IDBKeyData::operator>):
+        (WebCore::IDBKeyData::operator<=):
+        (WebCore::IDBKeyData::operator>=):
+
+        * Modules/indexeddb/server/MemoryCursor.h:
+
+        * Modules/indexeddb/server/MemoryIDBBackingStore.cpp:
+        (WebCore::IDBServer::MemoryIDBBackingStore::iterateCursor):
+
+        * Modules/indexeddb/server/MemoryIndexCursor.cpp:
+        (WebCore::IDBServer::MemoryIndexCursor::iterate):
+        * Modules/indexeddb/server/MemoryIndexCursor.h:
+
+        * Modules/indexeddb/server/MemoryObjectStoreCursor.cpp:
+        (WebCore::IDBServer::MemoryObjectStoreCursor::iterate):
+        * Modules/indexeddb/server/MemoryObjectStoreCursor.h:
+
+        * Modules/indexeddb/server/SQLiteIDBBackingStore.cpp:
+        (WebCore::IDBServer::SQLiteIDBBackingStore::iterateCursor):
+
+        * Modules/indexeddb/server/SQLiteIDBCursor.cpp:
+        (WebCore::IDBServer::SQLiteIDBCursor::iterate):
+        * Modules/indexeddb/server/SQLiteIDBCursor.h:
+
+        * Modules/indexeddb/shared/IDBIterateCursorData.cpp:
+        (WebCore::IDBIterateCursorData::isolatedCopy):
+        * Modules/indexeddb/shared/IDBIterateCursorData.h:
+        (WebCore::IDBIterateCursorData::encode):
+        (WebCore::IDBIterateCursorData::decode):
+
 2016-11-09  Antoine Quint  <graouts@apple.com>
 
         [Modern Media Controls] Media Controller: set status label according to media state
index 2c05d82..d23444d 100644 (file)
@@ -242,6 +242,55 @@ ExceptionOr<void> IDBCursor::advance(unsigned count)
     return { };
 }
 
+ExceptionOr<void> IDBCursor::continuePrimaryKey(ExecState& state, JSValue keyValue, JSValue primaryKeyValue)
+{
+    if (!transaction().isActive())
+        return Exception { IDBDatabaseException::TransactionInactiveError, ASCIILiteral("Failed to execute 'continuePrimaryKey' on 'IDBCursor': The transaction is inactive or finished.") };
+
+    if (sourcesDeleted())
+        return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'continuePrimaryKey' on 'IDBCursor': The cursor's source or effective object store has been deleted.") };
+
+    if (!m_index)
+        return Exception { IDBDatabaseException::InvalidAccessError, ASCIILiteral("Failed to execute 'continuePrimaryKey' on 'IDBCursor': The cursor's source is not an index.") };
+
+    auto direction = m_info.cursorDirection();
+    if (direction != IndexedDB::CursorDirection::Next && direction != IndexedDB::CursorDirection::Prev)
+        return Exception { IDBDatabaseException::InvalidAccessError, ASCIILiteral("Failed to execute 'continuePrimaryKey' on 'IDBCursor': The cursor's direction must be either \"next\" or \"prev\".") };
+
+    if (!m_gotValue)
+        return Exception { IDBDatabaseException::InvalidStateError, ASCIILiteral("Failed to execute 'continuePrimaryKey' on 'IDBCursor': The cursor is being iterated or has iterated past its end.") };
+
+    RefPtr<IDBKey> key = scriptValueToIDBKey(state, keyValue);
+    if (!key->isValid())
+        return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'continuePrimaryKey' on 'IDBCursor': The first parameter is not a valid key.") };
+
+    RefPtr<IDBKey> primaryKey = scriptValueToIDBKey(state, primaryKeyValue);
+    if (!primaryKey->isValid())
+        return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'continuePrimaryKey' on 'IDBCursor': The second parameter is not a valid key.") };
+
+    IDBKeyData keyData = { key.get() };
+    IDBKeyData primaryKeyData = { primaryKey.get() };
+
+    if (keyData < m_currentKeyData && direction == IndexedDB::CursorDirection::Next)
+        return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'continuePrimaryKey' on 'IDBCursor': The first parameter is less than this cursor's position and this cursor's direction is \"next\".") };
+
+    if (keyData > m_currentKeyData && direction == IndexedDB::CursorDirection::Prev)
+        return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'continuePrimaryKey' on 'IDBCursor': The first parameter is greater than this cursor's position and this cursor's direction is \"prev\".") };
+
+    if (keyData == m_currentKeyData) {
+        if (primaryKeyData <= m_currentPrimaryKeyData && direction == IndexedDB::CursorDirection::Next)
+            return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'continuePrimaryKey' on 'IDBCursor': The key parameters represent a position less-than-or-equal-to this cursor's position and this cursor's direction is \"next\".") };
+        if (primaryKeyData >= m_currentPrimaryKeyData && direction == IndexedDB::CursorDirection::Prev)
+            return Exception { IDBDatabaseException::DataError, ASCIILiteral("Failed to execute 'continuePrimaryKey' on 'IDBCursor': The key parameters represent a position greater-than-or-equal-to this cursor's position and this cursor's direction is \"prev\".") };
+    }
+
+    m_gotValue = false;
+
+    uncheckedIterateCursor(keyData, primaryKeyData);
+
+    return { };
+}
+
 ExceptionOr<void> IDBCursor::continueFunction(ExecState& execState, JSValue keyValue)
 {
     RefPtr<IDBKey> key;
@@ -293,7 +342,17 @@ void IDBCursor::uncheckedIterateCursor(const IDBKeyData& key, unsigned count)
     ++m_outstandingRequestCount;
 
     m_request->willIterateCursor(*this);
-    transaction().iterateCursor(*this, { key, count });
+    transaction().iterateCursor(*this, { key, { }, count });
+}
+
+void IDBCursor::uncheckedIterateCursor(const IDBKeyData& key, const IDBKeyData& primaryKey)
+{
+    ASSERT(currentThread() == effectiveObjectStore().transaction().database().originThreadID());
+
+    ++m_outstandingRequestCount;
+
+    m_request->willIterateCursor(*this);
+    transaction().iterateCursor(*this, { key, primaryKey, 0 });
 }
 
 ExceptionOr<Ref<WebCore::IDBRequest>> IDBCursor::deleteFunction(ExecState& state)
index 2aa8882..d0176de 100644 (file)
@@ -65,6 +65,7 @@ public:
     ExceptionOr<Ref<IDBRequest>> update(JSC::ExecState&, JSC::JSValue);
     ExceptionOr<void> advance(unsigned);
     ExceptionOr<void> continueFunction(JSC::ExecState&, JSC::JSValue key);
+    ExceptionOr<void> continuePrimaryKey(JSC::ExecState&, JSC::JSValue key, JSC::JSValue primaryKey);
     ExceptionOr<Ref<IDBRequest>> deleteFunction(JSC::ExecState&);
 
     ExceptionOr<void> continueFunction(const IDBKeyData&);
@@ -96,6 +97,7 @@ private:
     IDBTransaction& transaction() const;
 
     void uncheckedIterateCursor(const IDBKeyData&, unsigned count);
+    void uncheckedIterateCursor(const IDBKeyData&, const IDBKeyData&);
 
     // Cursors are created with an outstanding iteration request.
     unsigned m_outstandingRequestCount { 1 };
index 0d66547..4f64071 100644 (file)
@@ -39,5 +39,6 @@
     [CallWith=ScriptState, MayThrowException] IDBRequest update(any value);
     [MayThrowException] void advance([EnforceRange] unsigned long count);
     [CallWith=ScriptState, ImplementedAs=continueFunction, MayThrowException] void continue(optional any key);
+    [CallWith=ScriptState, MayThrowException] void continuePrimaryKey(any key, any primaryKey);
     [CallWith=ScriptState, ImplementedAs=deleteFunction, MayThrowException] IDBRequest delete();
 };
index 848870a..0bafd54 100644 (file)
@@ -95,6 +95,21 @@ public:
     KeyType type() const { return m_type; }
 
     bool operator<(const IDBKeyData&) const;
+    bool operator>(const IDBKeyData& other) const
+    {
+        return !(*this < other) && !(*this == other);
+    }
+
+    bool operator<=(const IDBKeyData& other) const
+    {
+        return !(*this > other);
+    }
+
+    bool operator>=(const IDBKeyData& other) const
+    {
+        return !(*this < other);
+    }
+
     bool operator==(const IDBKeyData& other) const;
     bool operator!=(const IDBKeyData& other) const
     {
index cd3f938..db5e8de 100644 (file)
@@ -43,7 +43,7 @@ public:
     virtual ~MemoryCursor();
 
     virtual void currentData(IDBGetResult&) = 0;
-    virtual void iterate(const IDBKeyData&, uint32_t count, IDBGetResult&) = 0;
+    virtual void iterate(const IDBKeyData&, const IDBKeyData& primaryKey, uint32_t count, IDBGetResult&) = 0;
 
     static MemoryCursor* cursorForIdentifier(const IDBResourceIdentifier&);
 
index b8e1a8a..0a1652a 100644 (file)
@@ -538,7 +538,7 @@ IDBError MemoryIDBBackingStore::iterateCursor(const IDBResourceIdentifier& trans
     if (!cursor)
         return { IDBDatabaseException::UnknownError, ASCIILiteral("No backing store cursor found in which to iterate cursor") };
 
-    cursor->iterate(data.keyData, data.count, outData);
+    cursor->iterate(data.keyData, data.primaryKeyData, data.count, outData);
 
     return { };
 }
index 0468501..eb3bb1d 100644 (file)
@@ -81,12 +81,17 @@ void MemoryIndexCursor::currentData(IDBGetResult& getResult)
     }
 }
 
-void MemoryIndexCursor::iterate(const IDBKeyData& key, uint32_t count, IDBGetResult& getResult)
+void MemoryIndexCursor::iterate(const IDBKeyData& key, const IDBKeyData& primaryKey, uint32_t count, IDBGetResult& getResult)
 {
     LOG(IndexedDB, "MemoryIndexCursor::iterate to key %s, %u count", key.loggingString().utf8().data(), count);
 
+#ifndef NDEBUG
+    if (primaryKey.isValid())
+        ASSERT(key.isValid());
+#endif
+
     if (key.isValid()) {
-        // Cannot iterator by both a count and to a key
+        // Cannot iterate by both a count and to a key
         ASSERT(!count);
 
         auto* valueStore = m_index.valueStore();
@@ -97,10 +102,17 @@ void MemoryIndexCursor::iterate(const IDBKeyData& key, uint32_t count, IDBGetRes
             return;
         }
 
-        if (m_info.isDirectionForward())
-            m_currentIterator = valueStore->find(key);
-        else
-            m_currentIterator = valueStore->reverseFind(key, m_info.duplicity());
+        if (primaryKey.isValid()) {
+            if (m_info.isDirectionForward())
+                m_currentIterator = valueStore->find(key, primaryKey);
+            else
+                m_currentIterator = valueStore->reverseFind(key, primaryKey, m_info.duplicity());
+        } else {
+            if (m_info.isDirectionForward())
+                m_currentIterator = valueStore->find(key);
+            else
+                m_currentIterator = valueStore->reverseFind(key, m_info.duplicity());
+        }
 
         if (m_currentIterator.isValid() && !m_info.range().containsKey(m_currentIterator.key()))
             m_currentIterator.invalidate();
index f207450..205c0f1 100644 (file)
@@ -47,7 +47,7 @@ public:
 
 private:
     void currentData(IDBGetResult&) final;
-    void iterate(const IDBKeyData&, uint32_t count, IDBGetResult&) final;
+    void iterate(const IDBKeyData&, const IDBKeyData& primaryKey, uint32_t count, IDBGetResult&) final;
 
     MemoryIndex& m_index;
 
index 22696b2..8ab7743 100644 (file)
@@ -314,10 +314,12 @@ void MemoryObjectStoreCursor::incrementReverseIterator(std::set<IDBKeyData>& set
     }
 }
 
-void MemoryObjectStoreCursor::iterate(const IDBKeyData& key, uint32_t count, IDBGetResult& outData)
+void MemoryObjectStoreCursor::iterate(const IDBKeyData& key, const IDBKeyData& primaryKeyData, uint32_t count, IDBGetResult& outData)
 {
     LOG(IndexedDB, "MemoryObjectStoreCursor::iterate to key %s", key.loggingString().utf8().data());
 
+    ASSERT_UNUSED(primaryKeyData, primaryKeyData.isNull());
+
     if (!m_objectStore.orderedKeys()) {
         m_currentPositionKey = { };
         outData = { };
index 2ce5989..4fb73e3 100644 (file)
@@ -49,7 +49,7 @@ public:
 
 private:
     void currentData(IDBGetResult&) final;
-    void iterate(const IDBKeyData&, uint32_t count, IDBGetResult&) final;
+    void iterate(const IDBKeyData&, const IDBKeyData& primaryKey, uint32_t count, IDBGetResult&) final;
 
     void setFirstInRemainingRange(std::set<IDBKeyData>&);
     void setForwardIteratorFromRemainingRange(std::set<IDBKeyData>&);
index 78dc50b..3df8352 100644 (file)
@@ -2290,14 +2290,16 @@ IDBError SQLiteIDBBackingStore::iterateCursor(const IDBResourceIdentifier& trans
     }
 
     auto key = data.keyData;
+    auto primaryKey = data.primaryKeyData;
     auto count = data.count;
 
     if (key.isValid()) {
-        if (!cursor->iterate(key)) {
+        if (!cursor->iterate(key, primaryKey)) {
             LOG_ERROR("Attempt to iterate cursor failed");
             return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to iterate cursor failed") };
         }
     } else {
+        ASSERT(!primaryKey.isValid());
         if (!count)
             count = 1;
         if (!cursor->advance(count)) {
index 9ff897e..ef75965 100644 (file)
@@ -440,7 +440,7 @@ SQLiteIDBCursor::AdvanceResult SQLiteIDBCursor::internalAdvanceOnce()
     return AdvanceResult::Success;
 }
 
-bool SQLiteIDBCursor::iterate(const WebCore::IDBKeyData& targetKey)
+bool SQLiteIDBCursor::iterate(const IDBKeyData& targetKey, const IDBKeyData& targetPrimaryKey)
 {
     ASSERT(m_transaction->sqliteTransaction());
     ASSERT(m_statement);
@@ -465,6 +465,22 @@ bool SQLiteIDBCursor::iterate(const WebCore::IDBKeyData& targetKey)
         result = advance(1);
     }
 
+    if (targetPrimaryKey.isValid()) {
+        while (!m_completed && !m_currentKey.compare(targetKey)) {
+            if (!result)
+                return false;
+
+            // Search for the next primary key >= the primary target if the cursor is a Next cursor, or the next key <= if the cursor is a Previous cursor.
+            if (m_cursorDirection == IndexedDB::CursorDirection::Next || m_cursorDirection == IndexedDB::CursorDirection::NextNoDuplicate) {
+                if (m_currentPrimaryKey.compare(targetPrimaryKey) >= 0)
+                    break;
+            } else if (m_currentPrimaryKey.compare(targetPrimaryKey) <= 0)
+                break;
+
+            result = advance(1);
+        }
+    }
+
     return result;
 }
 
index 1d8b0ca..1feec73 100644 (file)
@@ -65,7 +65,7 @@ public:
     IDBValue* currentValue() const { return m_currentValue.get(); }
 
     bool advance(uint64_t count);
-    bool iterate(const IDBKeyData& targetKey);
+    bool iterate(const IDBKeyData& targetKey, const IDBKeyData& targetPrimaryKey);
 
     bool didComplete() const { return m_completed; }
     bool didError() const { return m_errored; }
index 8f24cf0..b865429 100644 (file)
@@ -32,7 +32,7 @@ namespace WebCore {
 
 IDBIterateCursorData IDBIterateCursorData::isolatedCopy() const
 {
-    return { keyData.isolatedCopy(), count };
+    return { keyData.isolatedCopy(), primaryKeyData.isolatedCopy(), count };
 }
 
 } // namespace WebCore
index d96b7c0..6b047e9 100644 (file)
@@ -33,6 +33,7 @@ namespace WebCore {
 
 struct IDBIterateCursorData {
     IDBKeyData keyData;
+    IDBKeyData primaryKeyData;
     unsigned count;
 
     IDBIterateCursorData isolatedCopy() const;
@@ -44,7 +45,7 @@ struct IDBIterateCursorData {
 template<class Encoder>
 void IDBIterateCursorData::encode(Encoder& encoder) const
 {
-    encoder << keyData << static_cast<uint64_t>(count);
+    encoder << keyData << primaryKeyData << static_cast<uint64_t>(count);
 }
 
 template<class Decoder>
@@ -53,6 +54,9 @@ bool IDBIterateCursorData::decode(Decoder& decoder, IDBIterateCursorData& iterat
     if (!decoder.decode(iteratorCursorData.keyData))
         return false;
 
+    if (!decoder.decode(iteratorCursorData.primaryKeyData))
+        return false;
+
     uint64_t count;
     if (!decoder.decode(count))
         return false;