idb: fix 'prevunique' iteration and other bugs

This commit is contained in:
Florian Dold 2021-02-22 20:49:36 +01:00
parent 3eced74a88
commit f0d820d8c6
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
10 changed files with 814 additions and 132 deletions

View File

@ -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);

View File

@ -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;
}
if (
req.lastIndexPosition !== undefined &&
compareKeys(indexPos, req.lastIndexPosition) === 0
) {
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 (!skip) {
if (this.enableTracing) {
console.log(`not skipping!, subPos=${primkeySubPos}`);
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 (
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;
} 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,

View File

@ -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;

View File

@ -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,8 @@ 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";
@ -951,6 +953,23 @@ export class BridgeIDBFactory {
});
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";
const event2 = new FakeEvent("error", {
bubbles: false,
cancelable: false,
});
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,

View File

@ -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
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)

View File

@ -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();
};
});

View File

@ -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",
);
};
}
});

View File

@ -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);
});
},
);
});

View File

@ -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++;
};
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();
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();
};
};
});
});

View File

@ -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 = {