From f0d820d8c6492cc490e4128f744544999933146b Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 22 Feb 2021 20:49:36 +0100 Subject: 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(-) create mode 100644 packages/idb-bridge/src/idb-wpt-ported/close-in-upgradeneeded.test.ts create mode 100644 packages/idb-bridge/src/idb-wpt-ported/cursor-overloads.test.ts (limited to 'packages/idb-bridge') 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((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 = { -- cgit v1.2.3