aboutsummaryrefslogtreecommitdiff
path: root/packages/idb-bridge/src
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2021-02-22 20:49:36 +0100
committerFlorian Dold <florian@dold.me>2021-02-22 20:49:36 +0100
commitf0d820d8c6492cc490e4128f744544999933146b (patch)
tree87b9361cba6f469e48b912804d076f8940a13736 /packages/idb-bridge/src
parent3eced74a88de43ab9afe542fcce20a8db8e3fe60 (diff)
idb: fix 'prevunique' iteration and other bugs
Diffstat (limited to 'packages/idb-bridge/src')
-rw-r--r--packages/idb-bridge/src/MemoryBackend.test.ts2
-rw-r--r--packages/idb-bridge/src/MemoryBackend.ts193
-rw-r--r--packages/idb-bridge/src/backend-interface.ts2
-rw-r--r--packages/idb-bridge/src/bridge-idb.ts26
-rw-r--r--packages/idb-bridge/src/idb-wpt-ported/README9
-rw-r--r--packages/idb-bridge/src/idb-wpt-ported/close-in-upgradeneeded.test.ts44
-rw-r--r--packages/idb-bridge/src/idb-wpt-ported/cursor-overloads.test.ts114
-rw-r--r--packages/idb-bridge/src/idb-wpt-ported/event-dispatch-active-flag.test.ts149
-rw-r--r--packages/idb-bridge/src/idb-wpt-ported/idbcursor-continue-index.test.ts399
-rw-r--r--packages/idb-bridge/src/idb-wpt-ported/wptsupport.ts4
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 8f988bb99..292f1b495 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 53355bf77..2317fb163 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 164996e77..3d2953847 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 ceba618db..02fca9d1e 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 e0b665aab..801450bb2 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 000000000..96abe3918
--- /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 000000000..2f1797a6f
--- /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 f5668c90b..b8151f465 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 040fb75fd..02f2e5c99 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 9ec46c765..5f6b0a040 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 = {