gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated: idb: fix 'prevunique' iterati


From: gnunet
Subject: [taler-wallet-core] branch master updated: idb: fix 'prevunique' iteration and other bugs
Date: Mon, 22 Feb 2021 20:49:42 +0100

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository wallet-core.

The following commit(s) were added to refs/heads/master by this push:
     new f0d820d8 idb: fix 'prevunique' iteration and other bugs
f0d820d8 is described below

commit f0d820d8c6492cc490e4128f744544999933146b
Author: Florian Dold <florian@dold.me>
AuthorDate: Mon Feb 22 20:49:36 2021 +0100

    idb: fix 'prevunique' iteration and other bugs
---
 packages/idb-bridge/src/MemoryBackend.test.ts      |   2 +-
 packages/idb-bridge/src/MemoryBackend.ts           | 193 +++++-----
 packages/idb-bridge/src/backend-interface.ts       |   2 +-
 packages/idb-bridge/src/bridge-idb.ts              |  26 +-
 packages/idb-bridge/src/idb-wpt-ported/README      |   9 +-
 .../idb-wpt-ported/close-in-upgradeneeded.test.ts  |  44 +++
 .../src/idb-wpt-ported/cursor-overloads.test.ts    | 114 ++++++
 .../event-dispatch-active-flag.test.ts             | 149 +++++++-
 .../idbcursor-continue-index.test.ts               | 399 +++++++++++++++++++--
 .../idb-bridge/src/idb-wpt-ported/wptsupport.ts    |   4 +-
 10 files changed, 812 insertions(+), 130 deletions(-)

diff --git a/packages/idb-bridge/src/MemoryBackend.test.ts 
b/packages/idb-bridge/src/MemoryBackend.test.ts
index 8f988bb9..292f1b49 100644
--- a/packages/idb-bridge/src/MemoryBackend.test.ts
+++ b/packages/idb-bridge/src/MemoryBackend.test.ts
@@ -234,7 +234,7 @@ test("Spec: Example 1 Part 3", async (t) => {
   await promiseFromRequest(request7);
   cursor = request7.result;
   t.is(cursor.value.author, "Fred");
-  t.is(cursor.value.isbn, 234567);
+  t.is(cursor.value.isbn, 123456);
   cursor.continue();
 
   await promiseFromRequest(request7);
diff --git a/packages/idb-bridge/src/MemoryBackend.ts 
b/packages/idb-bridge/src/MemoryBackend.ts
index 53355bf7..2317fb16 100644
--- a/packages/idb-bridge/src/MemoryBackend.ts
+++ b/packages/idb-bridge/src/MemoryBackend.ts
@@ -1137,127 +1137,140 @@ export class MemoryBackend implements Backend {
       let indexEntry: IndexRecord | undefined;
       indexEntry = indexData.get(indexPos);
       if (!indexEntry) {
-        const res = indexData.nextHigherPair(indexPos);
+        const res = forward
+          ? indexData.nextHigherPair(indexPos)
+          : indexData.nextLowerPair(indexPos);
         if (res) {
           indexEntry = res[1];
           indexPos = indexEntry.indexKey;
         }
       }
 
-      let primkeySubPos = 0;
-
-      // Sort out the case where the index key is the same, so we have
-      // to get the prev/next primary key
-      if (
-        indexEntry !== undefined &&
-        req.lastIndexPosition !== undefined &&
-        compareKeys(indexEntry.indexKey, req.lastIndexPosition) === 0
-      ) {
-        let pos = forward ? 0 : indexEntry.primaryKeys.length - 1;
-        this.enableTracing &&
-          console.log("number of primary keys", indexEntry.primaryKeys.length);
-        this.enableTracing && console.log("start pos is", pos);
-        // Advance past the lastObjectStorePosition
-        do {
-          const cmpResult = compareKeys(
-            req.lastObjectStorePosition,
-            indexEntry.primaryKeys[pos],
-          );
-          this.enableTracing && console.log("cmp result is", cmpResult);
-          if ((forward && cmpResult < 0) || (!forward && cmpResult > 0)) {
+      if (unique) {
+        while (1) {
+          if (req.limit != 0 && numResults == req.limit) {
             break;
           }
-          pos += forward ? 1 : -1;
-          this.enableTracing && console.log("now pos is", pos);
-        } while (pos >= 0 && pos < indexEntry.primaryKeys.length);
-
-        // Make sure we're at least at advancedPrimaryPos
-        while (
-          primaryPos !== undefined &&
-          pos >= 0 &&
-          pos < indexEntry.primaryKeys.length
-        ) {
-          const cmpResult = compareKeys(
-            primaryPos,
-            indexEntry.primaryKeys[pos],
-          );
-          if ((forward && cmpResult <= 0) || (!forward && cmpResult >= 0)) {
+          if (indexPos === undefined) {
+            break;
+          }
+          if (!range.includes(indexPos)) {
+            break;
+          }
+          if (indexEntry === undefined) {
             break;
           }
-          pos += forward ? 1 : -1;
-        }
-        primkeySubPos = pos;
-      } else if (indexEntry !== undefined) {
-        primkeySubPos = forward ? 0 : indexEntry.primaryKeys.length - 1;
-      }
 
-      if (this.enableTracing) {
-        console.log("subPos=", primkeySubPos);
-        console.log("indexPos=", indexPos);
-      }
+          if (
+            req.lastIndexPosition === null ||
+            req.lastIndexPosition === undefined ||
+            compareKeys(indexEntry.indexKey, req.lastIndexPosition) !== 0
+          ) {
+            indexKeys.push(indexEntry.indexKey);
+            primaryKeys.push(indexEntry.primaryKeys[0]);
+            numResults++;
+          }
 
-      while (1) {
-        if (req.limit != 0 && numResults == req.limit) {
-          break;
-        }
-        if (indexPos === undefined) {
-          break;
-        }
-        if (!range.includes(indexPos)) {
-          break;
-        }
-        if (indexEntry === undefined) {
-          break;
-        }
-        if (
-          primkeySubPos < 0 ||
-          primkeySubPos >= indexEntry.primaryKeys.length
-        ) {
           const res: any = forward
             ? indexData.nextHigherPair(indexPos)
             : indexData.nextLowerPair(indexPos);
           if (res) {
             indexPos = res[1].indexKey;
-            indexEntry = res[1];
-            primkeySubPos = forward ? 0 : indexEntry!.primaryKeys.length - 1;
-            continue;
+            indexEntry = res[1] as IndexRecord;
           } else {
             break;
           }
         }
+      } else {
+        let primkeySubPos = 0;
 
-        // Skip repeated index keys if unique results are requested.
-        let skip = false;
-        if (unique) {
-          if (
-            indexKeys.length > 0 &&
-            compareKeys(
-              indexEntry.indexKey,
-              indexKeys[indexKeys.length - 1],
-            ) === 0
+        // Sort out the case where the index key is the same, so we have
+        // to get the prev/next primary key
+        if (
+          indexEntry !== undefined &&
+          req.lastIndexPosition !== undefined &&
+          compareKeys(indexEntry.indexKey, req.lastIndexPosition) === 0
+        ) {
+          let pos = forward ? 0 : indexEntry.primaryKeys.length - 1;
+          this.enableTracing &&
+            console.log(
+              "number of primary keys",
+              indexEntry.primaryKeys.length,
+            );
+          this.enableTracing && console.log("start pos is", pos);
+          // Advance past the lastObjectStorePosition
+          do {
+            const cmpResult = compareKeys(
+              req.lastObjectStorePosition,
+              indexEntry.primaryKeys[pos],
+            );
+            this.enableTracing && console.log("cmp result is", cmpResult);
+            if ((forward && cmpResult < 0) || (!forward && cmpResult > 0)) {
+              break;
+            }
+            pos += forward ? 1 : -1;
+            this.enableTracing && console.log("now pos is", pos);
+          } while (pos >= 0 && pos < indexEntry.primaryKeys.length);
+
+          // Make sure we're at least at advancedPrimaryPos
+          while (
+            primaryPos !== undefined &&
+            pos >= 0 &&
+            pos < indexEntry.primaryKeys.length
           ) {
-            skip = true;
+            const cmpResult = compareKeys(
+              primaryPos,
+              indexEntry.primaryKeys[pos],
+            );
+            if ((forward && cmpResult <= 0) || (!forward && cmpResult >= 0)) {
+              break;
+            }
+            pos += forward ? 1 : -1;
+          }
+          primkeySubPos = pos;
+        } else if (indexEntry !== undefined) {
+          primkeySubPos = forward ? 0 : indexEntry.primaryKeys.length - 1;
+        }
+
+        if (this.enableTracing) {
+          console.log("subPos=", primkeySubPos);
+          console.log("indexPos=", indexPos);
+        }
+
+        while (1) {
+          if (req.limit != 0 && numResults == req.limit) {
+            break;
+          }
+          if (indexPos === undefined) {
+            break;
+          }
+          if (!range.includes(indexPos)) {
+            break;
+          }
+          if (indexEntry === undefined) {
+            break;
           }
           if (
-            req.lastIndexPosition !== undefined &&
-            compareKeys(indexPos, req.lastIndexPosition) === 0
+            primkeySubPos < 0 ||
+            primkeySubPos >= indexEntry.primaryKeys.length
           ) {
-            skip = true;
-          }
-        }
-        if (!skip) {
-          if (this.enableTracing) {
-            console.log(`not skipping!, subPos=${primkeySubPos}`);
+            const res: any = forward
+              ? indexData.nextHigherPair(indexPos)
+              : indexData.nextLowerPair(indexPos);
+            if (res) {
+              indexPos = res[1].indexKey;
+              indexEntry = res[1];
+              primkeySubPos = forward ? 0 : indexEntry!.primaryKeys.length - 1;
+              continue;
+            } else {
+              break;
+            }
           }
           indexKeys.push(indexEntry.indexKey);
           primaryKeys.push(indexEntry.primaryKeys[primkeySubPos]);
           numResults++;
-        } else {
-          if (this.enableTracing) {
-            console.log("skipping!");
-          }
+          primkeySubPos += forward ? 1 : -1;
         }
-        primkeySubPos += forward ? 1 : -1;
       }
 
       // Now we can collect the values based on the primary keys,
diff --git a/packages/idb-bridge/src/backend-interface.ts 
b/packages/idb-bridge/src/backend-interface.ts
index 164996e7..3d295384 100644
--- a/packages/idb-bridge/src/backend-interface.ts
+++ b/packages/idb-bridge/src/backend-interface.ts
@@ -102,7 +102,7 @@ export interface RecordGetRequest {
    */
   advancePrimaryKey?: IDBValidKey;
   /**
-   * Maximum number of resuts to return.
+   * Maximum number of results to return.
    * If -1, return all available results
    */
   limit: number;
diff --git a/packages/idb-bridge/src/bridge-idb.ts 
b/packages/idb-bridge/src/bridge-idb.ts
index ceba618d..02fca9d1 100644
--- a/packages/idb-bridge/src/bridge-idb.ts
+++ b/packages/idb-bridge/src/bridge-idb.ts
@@ -702,7 +702,8 @@ export class BridgeIDBDatabase extends FakeEventTarget 
implements IDBDatabase {
     this._transactions.push(tx);
 
     queueTask(() => {
-      console.log("TRACE: calling auto-commit", this._getReadableName());
+      BridgeIDBFactory.enableTracing &&
+        console.log("TRACE: calling auto-commit", this._getReadableName());
       tx._start();
     });
     if (BridgeIDBFactory.enableTracing) {
@@ -941,7 +942,24 @@ export class BridgeIDBFactory {
 
         // We re-use the same transaction (as per spec) here.
         transaction._active = true;
-        if (transaction._aborted) {
+
+        if (db._closed || db._closePending) {
+          request.result = undefined;
+          request.error = new AbortError();
+          request.readyState = "done";
+          const event2 = new FakeEvent("error", {
+            bubbles: false,
+            cancelable: false,
+          });
+          event2.eventPath = [];
+          request.dispatchEvent(event2);
+        } else if (transaction._aborted) {
+          try {
+            await db._backend.close(db._backendConnection);
+          } catch (e) {
+            console.error("failed to close database");
+          }
+
           request.result = undefined;
           request.error = new AbortError();
           request.readyState = "done";
@@ -951,6 +969,7 @@ export class BridgeIDBFactory {
           });
           event2.eventPath = [];
           request.dispatchEvent(event2);
+
         } else {
           if (BridgeIDBFactory.enableTracing) {
             console.log("dispatching 'success' event for opening db");
@@ -2361,6 +2380,9 @@ export class BridgeIDBTransaction
 
           // 
http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-fire-an-error-event
           this._active = true;
+          queueTask(() => {
+            this._active = false;
+          });
           event = new FakeEvent("error", {
             bubbles: true,
             cancelable: true,
diff --git a/packages/idb-bridge/src/idb-wpt-ported/README 
b/packages/idb-bridge/src/idb-wpt-ported/README
index e0b665aa..801450bb 100644
--- a/packages/idb-bridge/src/idb-wpt-ported/README
+++ b/packages/idb-bridge/src/idb-wpt-ported/README
@@ -1,3 +1,10 @@
 This directory contains test cases from the W3C Web Platform Tests suite for 
IndexedDB.
 
-The original code for these tests can be found here: 
https://github.com/web-platform-tests/wpt/tree/master/IndexedDB
\ No newline at end of file
+The original code for these tests can be found here: 
https://github.com/web-platform-tests/wpt/tree/master/IndexedDB
+
+The following tests are intentionally not included:
+* error-attributes.html (assumes we have a DOM)
+* file_support.sub.html (assumes we have a DOM)
+* fire-error-event-exception.html (ava can't test unhandled rejections)
+* fire-success-event-exception.html (ava can't test unhandled rejections)
+* fire-upgradeneeded-event-exception.html (ava can't test unhandled rejections)
\ No newline at end of file
diff --git 
a/packages/idb-bridge/src/idb-wpt-ported/close-in-upgradeneeded.test.ts 
b/packages/idb-bridge/src/idb-wpt-ported/close-in-upgradeneeded.test.ts
new file mode 100644
index 00000000..96abe391
--- /dev/null
+++ b/packages/idb-bridge/src/idb-wpt-ported/close-in-upgradeneeded.test.ts
@@ -0,0 +1,44 @@
+import test from "ava";
+import { BridgeIDBCursor } from "..";
+import { BridgeIDBCursorWithValue } from "../bridge-idb";
+import { createdb } from "./wptsupport";
+
+// When db.close is called in upgradeneeded, the db is cleaned up on refresh
+test.cb("WPT test close-in-upgradeneeded.htm", (t) => {
+  var db: any;
+  var open_rq = createdb(t);
+  var sawTransactionComplete = false;
+
+  open_rq.onupgradeneeded = function (e: any) {
+    db = e.target.result;
+    t.deepEqual(db.version, 1);
+
+    db.createObjectStore("os");
+    db.close();
+
+    e.target.transaction.oncomplete = function () {
+      sawTransactionComplete = true;
+    };
+  };
+
+  open_rq.onerror = function (e: any) {
+    t.true(sawTransactionComplete, "saw transaction.complete");
+
+    t.deepEqual(e.target.error.name, "AbortError");
+    t.deepEqual(e.result, undefined);
+
+    t.true(!!db);
+    t.deepEqual(db.version, 1);
+    t.deepEqual(db.objectStoreNames.length, 1);
+    t.throws(
+      () => {
+        db.transaction("os");
+      },
+      {
+        name: "InvalidStateError",
+      },
+    );
+
+    t.end();
+  };
+});
diff --git a/packages/idb-bridge/src/idb-wpt-ported/cursor-overloads.test.ts 
b/packages/idb-bridge/src/idb-wpt-ported/cursor-overloads.test.ts
new file mode 100644
index 00000000..2f1797a6
--- /dev/null
+++ b/packages/idb-bridge/src/idb-wpt-ported/cursor-overloads.test.ts
@@ -0,0 +1,114 @@
+import test from "ava";
+import { BridgeIDBCursor } from "..";
+import { BridgeIDBCursorWithValue } from "../bridge-idb";
+import { createdb } from "./wptsupport";
+
+// Validate the overloads of IDBObjectStore.openCursor(), 
IDBIndex.openCursor() and IDBIndex.openKeyCursor()
+test.cb("WPT test cursor-overloads.htm", (t) => {
+  var db: any, trans: any, store: any, index: any;
+
+  var request = createdb(t);
+  request.onupgradeneeded = function (e) {
+    db = request.result;
+    store = db.createObjectStore("store");
+    index = store.createIndex("index", "value");
+    store.put({ value: 0 }, 0);
+    trans = request.transaction;
+    trans.oncomplete = verifyOverloads;
+  };
+
+  function verifyOverloads() {
+    trans = db.transaction("store");
+    store = trans.objectStore("store");
+    index = store.index("index");
+
+    checkCursorDirection("store.openCursor()", "next");
+    checkCursorDirection("store.openCursor(0)", "next");
+    checkCursorDirection("store.openCursor(0, 'next')", "next");
+    checkCursorDirection("store.openCursor(0, 'nextunique')", "nextunique");
+    checkCursorDirection("store.openCursor(0, 'prev')", "prev");
+    checkCursorDirection("store.openCursor(0, 'prevunique')", "prevunique");
+
+    checkCursorDirection("store.openCursor(IDBKeyRange.only(0))", "next");
+    checkCursorDirection(
+      "store.openCursor(IDBKeyRange.only(0), 'next')",
+      "next",
+    );
+    checkCursorDirection(
+      "store.openCursor(IDBKeyRange.only(0), 'nextunique')",
+      "nextunique",
+    );
+    checkCursorDirection(
+      "store.openCursor(IDBKeyRange.only(0), 'prev')",
+      "prev",
+    );
+    checkCursorDirection(
+      "store.openCursor(IDBKeyRange.only(0), 'prevunique')",
+      "prevunique",
+    );
+
+    checkCursorDirection("index.openCursor()", "next");
+    checkCursorDirection("index.openCursor(0)", "next");
+    checkCursorDirection("index.openCursor(0, 'next')", "next");
+    checkCursorDirection("index.openCursor(0, 'nextunique')", "nextunique");
+    checkCursorDirection("index.openCursor(0, 'prev')", "prev");
+    checkCursorDirection("index.openCursor(0, 'prevunique')", "prevunique");
+
+    checkCursorDirection("index.openCursor(IDBKeyRange.only(0))", "next");
+    checkCursorDirection(
+      "index.openCursor(IDBKeyRange.only(0), 'next')",
+      "next",
+    );
+    checkCursorDirection(
+      "index.openCursor(IDBKeyRange.only(0), 'nextunique')",
+      "nextunique",
+    );
+    checkCursorDirection(
+      "index.openCursor(IDBKeyRange.only(0), 'prev')",
+      "prev",
+    );
+    checkCursorDirection(
+      "index.openCursor(IDBKeyRange.only(0), 'prevunique')",
+      "prevunique",
+    );
+
+    checkCursorDirection("index.openKeyCursor()", "next");
+    checkCursorDirection("index.openKeyCursor(0)", "next");
+    checkCursorDirection("index.openKeyCursor(0, 'next')", "next");
+    checkCursorDirection("index.openKeyCursor(0, 'nextunique')", "nextunique");
+    checkCursorDirection("index.openKeyCursor(0, 'prev')", "prev");
+    checkCursorDirection("index.openKeyCursor(0, 'prevunique')", "prevunique");
+
+    checkCursorDirection("index.openKeyCursor(IDBKeyRange.only(0))", "next");
+    checkCursorDirection(
+      "index.openKeyCursor(IDBKeyRange.only(0), 'next')",
+      "next",
+    );
+    checkCursorDirection(
+      "index.openKeyCursor(IDBKeyRange.only(0), 'nextunique')",
+      "nextunique",
+    );
+    checkCursorDirection(
+      "index.openKeyCursor(IDBKeyRange.only(0), 'prev')",
+      "prev",
+    );
+    checkCursorDirection(
+      "index.openKeyCursor(IDBKeyRange.only(0), 'prevunique')",
+      "prevunique",
+    );
+
+    t.end();
+  }
+
+  function checkCursorDirection(statement: string, direction: string) {
+    request = eval(statement);
+    request.onsuccess = function (event: any) {
+      t.notDeepEqual(event.target.result, null, "Check the result is not 
null");
+      t.deepEqual(
+        event.target.result.direction,
+        direction,
+        "Check the result direction",
+      );
+    };
+  }
+});
diff --git 
a/packages/idb-bridge/src/idb-wpt-ported/event-dispatch-active-flag.test.ts 
b/packages/idb-bridge/src/idb-wpt-ported/event-dispatch-active-flag.test.ts
index f5668c90..b8151f46 100644
--- a/packages/idb-bridge/src/idb-wpt-ported/event-dispatch-active-flag.test.ts
+++ b/packages/idb-bridge/src/idb-wpt-ported/event-dispatch-active-flag.test.ts
@@ -7,7 +7,7 @@ import {
   keep_alive,
 } from "./wptsupport";
 
-test("WPT test abort-in-initial-upgradeneeded.htm", async (t) => {
+test("WPT test abort-in-initial-upgradeneeded.htm (subtest 1)", async (t) => {
   // Transactions are active during success handlers
   await indexeddb_test(
     t,
@@ -24,10 +24,9 @@ test("WPT test abort-in-initial-upgradeneeded.htm", async 
(t) => {
       );
 
       const request = tx.objectStore("store").get(4242);
-      (request as BridgeIDBRequest)._debugName = "req-main"; 
+      (request as BridgeIDBRequest)._debugName = "req-main";
       request.onerror = () => t.fail("request should succeed");
       request.onsuccess = () => {
-
         t.true(
           is_transaction_active(t, tx, "store"),
           "Transaction should be active during success handler",
@@ -55,3 +54,147 @@ test("WPT test abort-in-initial-upgradeneeded.htm", async 
(t) => {
     },
   );
 });
+
+test("WPT test abort-in-initial-upgradeneeded.htm (subtest 2)", async (t) => {
+  // Transactions are active during success listeners
+  await indexeddb_test(
+    t,
+    (done, db, tx) => {
+      db.createObjectStore("store");
+    },
+    (done, db) => {
+      const tx = db.transaction("store");
+      const release_tx = keep_alive(t, tx, "store");
+      t.true(
+        is_transaction_active(t, tx, "store"),
+        "Transaction should be active after creation",
+      );
+
+      const request = tx.objectStore("store").get(0);
+      request.onerror = () => t.fail("request should succeed");
+      request.addEventListener("success", () => {
+        t.true(
+          is_transaction_active(t, tx, "store"),
+          "Transaction should be active during success listener",
+        );
+
+        let saw_listener_promise = false;
+        Promise.resolve().then(() => {
+          saw_listener_promise = true;
+          t.true(
+            is_transaction_active(t, tx, "store"),
+            "Transaction should be active in listener's microtasks",
+          );
+        });
+
+        setTimeout(() => {
+          t.true(saw_listener_promise);
+          t.false(
+            is_transaction_active(t, tx, "store"),
+            "Transaction should be inactive in next task",
+          );
+          release_tx();
+          done();
+        }, 0);
+      });
+    },
+  );
+});
+
+test("WPT test abort-in-initial-upgradeneeded.htm (subtest 3)", async (t) => {
+  // Transactions are active during error handlers
+  await indexeddb_test(
+    t,
+    (done, db, tx) => {
+      db.createObjectStore("store");
+    },
+    (done, db) => {
+      const tx = db.transaction("store", "readwrite");
+      const release_tx = keep_alive(t, tx, "store");
+      t.true(
+        is_transaction_active(t, tx, "store"),
+        "Transaction should be active after creation",
+      );
+
+      tx.objectStore("store").put(0, 0);
+      const request = tx.objectStore("store").add(0, 0);
+      request.onsuccess = () => t.fail("request should fail");
+      request.onerror = (e: any) => {
+        e.preventDefault();
+
+        t.true(
+          is_transaction_active(t, tx, "store"),
+          "Transaction should be active during error handler",
+        );
+
+        let saw_handler_promise = false;
+        Promise.resolve().then(() => {
+          saw_handler_promise = true;
+          t.true(
+            is_transaction_active(t, tx, "store"),
+            "Transaction should be active in handler's microtasks",
+          );
+        });
+
+        setTimeout(() => {
+          t.true(saw_handler_promise);
+          t.false(
+            is_transaction_active(t, tx, "store"),
+            "Transaction should be inactive in next task",
+          );
+          release_tx();
+          done();
+        }, 0);
+      };
+    },
+  );
+});
+
+test("WPT test abort-in-initial-upgradeneeded.htm (subtest 4)", async (t) => {
+  // Transactions are active during error listeners
+  await indexeddb_test(
+    t,
+    (done, db, tx) => {
+      db.createObjectStore("store");
+    },
+    (done, db) => {
+      const tx = db.transaction("store", "readwrite");
+      const release_tx = keep_alive(t, tx, "store");
+      t.true(
+        is_transaction_active(t, tx, "store"),
+        "Transaction should be active after creation",
+      );
+
+      tx.objectStore("store").put(0, 0);
+      const request = tx.objectStore("store").add(0, 0);
+      request.onsuccess = () => t.fail("request should fail");
+      request.addEventListener("error", (e) => {
+        e.preventDefault();
+
+        t.true(
+          is_transaction_active(t, tx, "store"),
+          "Transaction should be active during error listener",
+        );
+
+        let saw_listener_promise = false;
+        Promise.resolve().then(() => {
+          saw_listener_promise = true;
+          t.true(
+            is_transaction_active(t, tx, "store"),
+            "Transaction should be active in listener's microtasks",
+          );
+        });
+
+        setTimeout(() => {
+          t.true(saw_listener_promise);
+          t.false(
+            is_transaction_active(t, tx, "store"),
+            "Transaction should be inactive in next task",
+          );
+          release_tx();
+          done();
+        }, 0);
+      });
+    },
+  );
+});
diff --git 
a/packages/idb-bridge/src/idb-wpt-ported/idbcursor-continue-index.test.ts 
b/packages/idb-bridge/src/idb-wpt-ported/idbcursor-continue-index.test.ts
index 040fb75f..02f2e5c9 100644
--- a/packages/idb-bridge/src/idb-wpt-ported/idbcursor-continue-index.test.ts
+++ b/packages/idb-bridge/src/idb-wpt-ported/idbcursor-continue-index.test.ts
@@ -1,46 +1,385 @@
 import test from "ava";
 import { BridgeIDBCursor } from "..";
+import { BridgeIDBCursorWithValue } from "../bridge-idb";
 import { createdb } from "./wptsupport";
 
-test("WPT test idbcursor_continue_index.htm", async (t) => {
-  await new Promise<void>((resolve, reject) => {
-    var db: any;
-    let count = 0;
-    const records = [ { pKey: "primaryKey_0",   iKey: "indexKey_0" },
-                { pKey: "primaryKey_1",   iKey: "indexKey_1" },
-                { pKey: "primaryKey_1-2", iKey: "indexKey_1" } ];
+test.cb("WPT test idbcursor_continue_index.htm", (t) => {
+  var db: any;
+  let count = 0;
+  const records = [
+    { pKey: "primaryKey_0", iKey: "indexKey_0" },
+    { pKey: "primaryKey_1", iKey: "indexKey_1" },
+    { pKey: "primaryKey_1-2", iKey: "indexKey_1" },
+  ];
 
   var open_rq = createdb(t);
-  open_rq.onupgradeneeded = function(e: any) {
-      db = e.target.result;
-      var objStore = db.createObjectStore("test", { keyPath:"pKey" });
+  open_rq.onupgradeneeded = function (e: any) {
+    db = e.target.result;
+    var objStore = db.createObjectStore("test", { keyPath: "pKey" });
 
-      objStore.createIndex("index", "iKey");
+    objStore.createIndex("index", "iKey");
 
-      for (var i = 0; i < records.length; i++)
-          objStore.add(records[i]);
+    for (var i = 0; i < records.length; i++) objStore.add(records[i]);
   };
 
-  open_rq.onsuccess = function(e) {
-      var cursor_rq = db.transaction("test")
-                        .objectStore("test")
-                        .index("index")
-                        .openCursor();
+  open_rq.onsuccess = function (e) {
+    var cursor_rq = db
+      .transaction("test")
+      .objectStore("test")
+      .index("index")
+      .openCursor();
 
-      cursor_rq.onsuccess = function(e: any) {
-          var cursor = e.target.result;
-          if (!cursor) {
-              t.deepEqual(count, records.length, "cursor run count");
-              resolve();
-          }
+    cursor_rq.onsuccess = function (e: any) {
+      var cursor = e.target.result;
+      if (!cursor) {
+        t.deepEqual(count, records.length, "cursor run count");
+        t.end();
+        return;
+      }
 
-          var record = cursor.value;
-          t.deepEqual(record.pKey, records[count].pKey, "primary key");
-          t.deepEqual(record.iKey, records[count].iKey, "index key");
+      var record = cursor.value;
+      t.deepEqual(record.pKey, records[count].pKey, "primary key");
+      t.deepEqual(record.iKey, records[count].iKey, "index key");
 
+      cursor.continue();
+      count++;
+    };
+  };
+});
+
+// IDBCursor.continue() - index - attempt to pass a key parameter that is not 
a valid key
+test.cb("WPT idbcursor-continue-index2.htm", (t) => {
+  var db: any;
+  let records = [
+    { pKey: "primaryKey_0", iKey: "indexKey_0" },
+    { pKey: "primaryKey_1", iKey: "indexKey_1" },
+  ];
+
+  var open_rq = createdb(t);
+  open_rq.onupgradeneeded = function (e: any) {
+    db = e.target.result;
+    var objStore = db.createObjectStore("test", { keyPath: "pKey" });
+
+    objStore.createIndex("index", "iKey");
+
+    for (var i = 0; i < records.length; i++) objStore.add(records[i]);
+  };
+
+  open_rq.onsuccess = function (e) {
+    var cursor_rq = db
+      .transaction("test")
+      .objectStore("test")
+      .index("index")
+      .openCursor();
+
+    cursor_rq.onsuccess = function (e: any) {
+      var cursor = e.target.result;
+
+      t.throws(
+        () => {
+          cursor.continue({ foo: "bar" });
+        },
+        { name: "DataError" },
+      );
+
+      t.true(cursor instanceof BridgeIDBCursorWithValue, "cursor");
+
+      t.end();
+    };
+  };
+});
+
+// IDBCursor.continue() - index - attempt to iterate to the previous
+// record when the direction is set for the next record
+test.cb("WPT idbcursor-continue-index3.htm", (t) => {
+  var db: any;
+  const records = [
+    { pKey: "primaryKey_0", iKey: "indexKey_0" },
+    { pKey: "primaryKey_1", iKey: "indexKey_1" },
+  ];
+
+  var open_rq = createdb(t);
+  open_rq.onupgradeneeded = function (e: any) {
+    db = e.target.result;
+    var objStore = db.createObjectStore("test", { keyPath: "pKey" });
+
+    objStore.createIndex("index", "iKey");
+
+    for (var i = 0; i < records.length; i++) objStore.add(records[i]);
+  };
+
+  open_rq.onsuccess = function (e) {
+    var count = 0;
+    var cursor_rq = db
+      .transaction("test")
+      .objectStore("test")
+      .index("index")
+      .openCursor(undefined, "next"); // XXX: Fx has issue with "undefined"
+
+    cursor_rq.onsuccess = function (e: any) {
+      var cursor = e.target.result;
+      if (!cursor) {
+        t.deepEqual(count, 2, "ran number of times");
+        t.end();
+        return;
+      }
+
+      // First time checks key equal, second time checks key less than
+      t.throws(
+        () => {
+          cursor.continue(records[0].iKey);
+        },
+        { name: "DataError" },
+      );
+
+      cursor.continue();
+
+      count++;
+    };
+  };
+});
+
+// IDBCursor.continue() - index - attempt to iterate to the next
+// record when the direction is set for the previous record
+test.cb("WPT idbcursor-continue-index4.htm", (t) => {
+  var db: any;
+  const records = [
+    { pKey: "primaryKey_0", iKey: "indexKey_0" },
+    { pKey: "primaryKey_1", iKey: "indexKey_1" },
+    { pKey: "primaryKey_2", iKey: "indexKey_2" },
+  ];
+
+  var open_rq = createdb(t);
+  open_rq.onupgradeneeded = function (e: any) {
+    db = e.target.result;
+    var objStore = db.createObjectStore("test", { keyPath: "pKey" });
+
+    objStore.createIndex("index", "iKey");
+
+    for (var i = 0; i < records.length; i++) objStore.add(records[i]);
+  };
+
+  open_rq.onsuccess = function (e) {
+    var count = 0,
+      cursor_rq = db
+        .transaction("test")
+        .objectStore("test")
+        .index("index")
+        .openCursor(undefined, "prev"); // XXX Fx issues w undefined
+
+    cursor_rq.onsuccess = function (e: any) {
+      var cursor = e.target.result,
+        record = cursor.value;
+
+      switch (count) {
+        case 0:
+          t.deepEqual(record.pKey, records[2].pKey, "first pKey");
+          t.deepEqual(record.iKey, records[2].iKey, "first iKey");
           cursor.continue();
-          count++;
-      };
+          break;
+
+        case 1:
+          t.deepEqual(record.pKey, records[1].pKey, "second pKey");
+          t.deepEqual(record.iKey, records[1].iKey, "second iKey");
+          t.throws(
+            () => {
+              cursor.continue("indexKey_2");
+            },
+            { name: "DataError" },
+          );
+          t.end();
+          break;
+
+        default:
+          t.fail("Unexpected count value: " + count);
+      }
+
+      count++;
+    };
+  };
+});
+
+// IDBCursor.continue() - index - iterate using 'prevunique'
+test.cb("WPT idbcursor-continue-index5.htm", (t) => {
+  var db: any;
+  const records = [
+    { pKey: "primaryKey_0", iKey: "indexKey_0" },
+    { pKey: "primaryKey_1", iKey: "indexKey_1" },
+    { pKey: "primaryKey_1-2", iKey: "indexKey_1" },
+    { pKey: "primaryKey_2", iKey: "indexKey_2" },
+  ];
+  const expected = [
+    { pKey: "primaryKey_2", iKey: "indexKey_2" },
+    { pKey: "primaryKey_1", iKey: "indexKey_1" },
+    { pKey: "primaryKey_0", iKey: "indexKey_0" },
+  ];
+
+  var open_rq = createdb(t);
+  open_rq.onupgradeneeded = function (e: any) {
+    db = e.target.result;
+    var objStore = db.createObjectStore("test", { keyPath: "pKey" });
+
+    objStore.createIndex("index", "iKey");
+
+    for (var i = 0; i < records.length; i++) objStore.add(records[i]);
+  };
+
+  open_rq.onsuccess = function (e) {
+    var count = 0,
+      cursor_rq = db
+        .transaction("test")
+        .objectStore("test")
+        .index("index")
+        .openCursor(undefined, "prevunique");
+
+    cursor_rq.onsuccess = function (e: any) {
+      if (!e.target.result) {
+        t.deepEqual(count, expected.length, "count");
+        t.end();
+        return;
+      }
+      const cursor = e.target.result;
+      const record = cursor.value;
+      t.deepEqual(record.pKey, expected[count].pKey, "pKey #" + count);
+      t.deepEqual(record.iKey, expected[count].iKey, "iKey #" + count);
+
+      t.deepEqual(cursor.key, expected[count].iKey, "cursor.key #" + count);
+      t.deepEqual(
+        cursor.primaryKey,
+        expected[count].pKey,
+        "cursor.primaryKey #" + count,
+      );
+
+      count++;
+      cursor.continue(expected[count] ? expected[count].iKey : undefined);
+    };
+  };
+});
+
+// IDBCursor.continue() - index - iterate using nextunique
+test.cb("WPT idbcursor-continue-index6.htm", (t) => {
+  var db: any;
+  const records = [
+    { pKey: "primaryKey_0", iKey: "indexKey_0" },
+    { pKey: "primaryKey_1", iKey: "indexKey_1" },
+    { pKey: "primaryKey_1-2", iKey: "indexKey_1" },
+    { pKey: "primaryKey_2", iKey: "indexKey_2" },
+  ];
+  const expected = [
+    { pKey: "primaryKey_0", iKey: "indexKey_0" },
+    { pKey: "primaryKey_1", iKey: "indexKey_1" },
+    { pKey: "primaryKey_2", iKey: "indexKey_2" },
+  ];
+
+  var open_rq = createdb(t);
+  open_rq.onupgradeneeded = function (e: any) {
+    db = e.target.result;
+    var objStore = db.createObjectStore("test", { keyPath: "pKey" });
+
+    objStore.createIndex("index", "iKey");
+
+    for (var i = 0; i < records.length; i++) objStore.add(records[i]);
+  };
+
+  open_rq.onsuccess = function (e) {
+    var count = 0,
+      cursor_rq = db
+        .transaction("test")
+        .objectStore("test")
+        .index("index")
+        .openCursor(undefined, "nextunique");
+
+    cursor_rq.onsuccess = function (e: any) {
+      if (!e.target.result) {
+        t.deepEqual(count, expected.length, "count");
+        t.end();
+        return;
+      }
+      var cursor = e.target.result,
+        record = cursor.value;
+
+      t.deepEqual(record.pKey, expected[count].pKey, "pKey #" + count);
+      t.deepEqual(record.iKey, expected[count].iKey, "iKey #" + count);
+
+      t.deepEqual(cursor.key, expected[count].iKey, "cursor.key #" + count);
+      t.deepEqual(
+        cursor.primaryKey,
+        expected[count].pKey,
+        "cursor.primaryKey #" + count,
+      );
+
+      count++;
+      cursor.continue(expected[count] ? expected[count].iKey : undefined);
+    };
+  };
+});
+
+// IDBCursor.continue() - index - throw TransactionInactiveError
+test.cb("WPT idbcursor-continue-index7.htm", (t) => {
+  var db,
+    records = [
+      { pKey: "primaryKey_0", iKey: "indexKey_0" },
+      { pKey: "primaryKey_1", iKey: "indexKey_1" },
+    ];
+
+  var open_rq = createdb(t);
+  open_rq.onupgradeneeded = function (event: any) {
+    db = event.target.result;
+    var objStore = db.createObjectStore("store", { keyPath: "pKey" });
+    objStore.createIndex("index", "iKey");
+    for (var i = 0; i < records.length; i++) {
+      objStore.add(records[i]);
+    }
+    var rq = objStore.index("index").openCursor();
+    rq.onsuccess = function (event: any) {
+      var cursor = event.target.result;
+      t.true(cursor instanceof BridgeIDBCursor);
+
+      event.target.transaction.abort();
+      t.throws(
+        () => {
+          cursor.continue();
+        },
+        { name: "TransactionInactiveError" },
+        "Calling continue() should throws an exception 
TransactionInactiveError when the transaction is not active.",
+      );
+      t.end();
+    };
+  };
+});
+
+// IDBCursor.continue() - index - throw InvalidStateError caused by object 
store been deleted
+test.cb("WPT idbcursor-continue-index8.htm", (t) => {
+  var db: any,
+    records = [
+      { pKey: "primaryKey_0", iKey: "indexKey_0" },
+      { pKey: "primaryKey_1", iKey: "indexKey_1" },
+    ];
+
+  var open_rq = createdb(t);
+  open_rq.onupgradeneeded = function (event: any) {
+    db = event.target.result;
+    var objStore = db.createObjectStore("store", { keyPath: "pKey" });
+    objStore.createIndex("index", "iKey");
+    for (var i = 0; i < records.length; i++) {
+      objStore.add(records[i]);
+    }
+    var rq = objStore.index("index").openCursor();
+    rq.onsuccess = function (event: any) {
+      var cursor = event.target.result;
+      t.true(cursor instanceof BridgeIDBCursor);
+
+      db.deleteObjectStore("store");
+
+      t.throws(
+        () => {
+          cursor.continue();
+        },
+        { name: "InvalidStateError" },
+        "If the cursor's source or effective object store has been deleted, 
the implementation MUST throw a DOMException of type InvalidStateError",
+      );
+
+      t.end();
+    };
   };
-  });
 });
diff --git a/packages/idb-bridge/src/idb-wpt-ported/wptsupport.ts 
b/packages/idb-bridge/src/idb-wpt-ported/wptsupport.ts
index 9ec46c76..5f6b0a04 100644
--- a/packages/idb-bridge/src/idb-wpt-ported/wptsupport.ts
+++ b/packages/idb-bridge/src/idb-wpt-ported/wptsupport.ts
@@ -12,9 +12,9 @@ import {
 import { MemoryBackend } from "../MemoryBackend";
 import { compareKeys } from "../util/cmp";
 
-BridgeIDBFactory.enableTracing = true;
+BridgeIDBFactory.enableTracing = false;
 const backend = new MemoryBackend();
-backend.enableTracing = true;
+backend.enableTracing = false;
 export const idbFactory = new BridgeIDBFactory(backend);
 
 const self = {

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]