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); await promiseFromRequest(request7);
cursor = request7.result; cursor = request7.result;
t.is(cursor.value.author, "Fred"); t.is(cursor.value.author, "Fred");
t.is(cursor.value.isbn, 234567); t.is(cursor.value.isbn, 123456);
cursor.continue(); cursor.continue();
await promiseFromRequest(request7); await promiseFromRequest(request7);

View File

@ -1137,127 +1137,140 @@ export class MemoryBackend implements Backend {
let indexEntry: IndexRecord | undefined; let indexEntry: IndexRecord | undefined;
indexEntry = indexData.get(indexPos); indexEntry = indexData.get(indexPos);
if (!indexEntry) { if (!indexEntry) {
const res = indexData.nextHigherPair(indexPos); const res = forward
? indexData.nextHigherPair(indexPos)
: indexData.nextLowerPair(indexPos);
if (res) { if (res) {
indexEntry = res[1]; indexEntry = res[1];
indexPos = indexEntry.indexKey; indexPos = indexEntry.indexKey;
} }
} }
let primkeySubPos = 0; if (unique) {
while (1) {
// Sort out the case where the index key is the same, so we have if (req.limit != 0 && numResults == req.limit) {
// 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; break;
} }
pos += forward ? 1 : -1; if (indexPos === undefined) {
this.enableTracing && console.log("now pos is", pos); break;
} while (pos >= 0 && pos < indexEntry.primaryKeys.length); }
if (!range.includes(indexPos)) {
// Make sure we're at least at advancedPrimaryPos break;
while ( }
primaryPos !== undefined && if (indexEntry === undefined) {
pos >= 0 &&
pos < indexEntry.primaryKeys.length
) {
const cmpResult = compareKeys(
primaryPos,
indexEntry.primaryKeys[pos],
);
if ((forward && cmpResult <= 0) || (!forward && cmpResult >= 0)) {
break; break;
} }
pos += forward ? 1 : -1;
}
primkeySubPos = pos;
} else if (indexEntry !== undefined) {
primkeySubPos = forward ? 0 : indexEntry.primaryKeys.length - 1;
}
if (this.enableTracing) { if (
console.log("subPos=", primkeySubPos); req.lastIndexPosition === null ||
console.log("indexPos=", indexPos); 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 const res: any = forward
? indexData.nextHigherPair(indexPos) ? indexData.nextHigherPair(indexPos)
: indexData.nextLowerPair(indexPos); : indexData.nextLowerPair(indexPos);
if (res) { if (res) {
indexPos = res[1].indexKey; indexPos = res[1].indexKey;
indexEntry = res[1]; indexEntry = res[1] as IndexRecord;
primkeySubPos = forward ? 0 : indexEntry!.primaryKeys.length - 1;
continue;
} else { } else {
break; break;
} }
} }
} else {
let primkeySubPos = 0;
// Skip repeated index keys if unique results are requested. // Sort out the case where the index key is the same, so we have
let skip = false; // to get the prev/next primary key
if (unique) { if (
if ( indexEntry !== undefined &&
indexKeys.length > 0 && req.lastIndexPosition !== undefined &&
compareKeys( compareKeys(indexEntry.indexKey, req.lastIndexPosition) === 0
indexEntry.indexKey, ) {
indexKeys[indexKeys.length - 1], let pos = forward ? 0 : indexEntry.primaryKeys.length - 1;
) === 0 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,
if ( indexEntry.primaryKeys[pos],
req.lastIndexPosition !== undefined && );
compareKeys(indexPos, req.lastIndexPosition) === 0 if ((forward && cmpResult <= 0) || (!forward && cmpResult >= 0)) {
) { break;
skip = true; }
pos += forward ? 1 : -1;
} }
primkeySubPos = pos;
} else if (indexEntry !== undefined) {
primkeySubPos = forward ? 0 : indexEntry.primaryKeys.length - 1;
} }
if (!skip) {
if (this.enableTracing) { if (this.enableTracing) {
console.log(`not skipping!, subPos=${primkeySubPos}`); 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); indexKeys.push(indexEntry.indexKey);
primaryKeys.push(indexEntry.primaryKeys[primkeySubPos]); primaryKeys.push(indexEntry.primaryKeys[primkeySubPos]);
numResults++; numResults++;
} else { primkeySubPos += forward ? 1 : -1;
if (this.enableTracing) {
console.log("skipping!");
}
} }
primkeySubPos += forward ? 1 : -1;
} }
// Now we can collect the values based on the primary keys, // Now we can collect the values based on the primary keys,

View File

@ -102,7 +102,7 @@ export interface RecordGetRequest {
*/ */
advancePrimaryKey?: IDBValidKey; advancePrimaryKey?: IDBValidKey;
/** /**
* Maximum number of resuts to return. * Maximum number of results to return.
* If -1, return all available results * If -1, return all available results
*/ */
limit: number; limit: number;

View File

@ -702,7 +702,8 @@ export class BridgeIDBDatabase extends FakeEventTarget implements IDBDatabase {
this._transactions.push(tx); this._transactions.push(tx);
queueTask(() => { queueTask(() => {
console.log("TRACE: calling auto-commit", this._getReadableName()); BridgeIDBFactory.enableTracing &&
console.log("TRACE: calling auto-commit", this._getReadableName());
tx._start(); tx._start();
}); });
if (BridgeIDBFactory.enableTracing) { if (BridgeIDBFactory.enableTracing) {
@ -941,7 +942,8 @@ export class BridgeIDBFactory {
// We re-use the same transaction (as per spec) here. // We re-use the same transaction (as per spec) here.
transaction._active = true; transaction._active = true;
if (transaction._aborted) {
if (db._closed || db._closePending) {
request.result = undefined; request.result = undefined;
request.error = new AbortError(); request.error = new AbortError();
request.readyState = "done"; request.readyState = "done";
@ -951,6 +953,23 @@ export class BridgeIDBFactory {
}); });
event2.eventPath = []; event2.eventPath = [];
request.dispatchEvent(event2); 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 { } else {
if (BridgeIDBFactory.enableTracing) { if (BridgeIDBFactory.enableTracing) {
console.log("dispatching 'success' event for opening db"); 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 // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-fire-an-error-event
this._active = true; this._active = true;
queueTask(() => {
this._active = false;
});
event = new FakeEvent("error", { event = new FakeEvent("error", {
bubbles: true, bubbles: true,
cancelable: true, cancelable: true,

View File

@ -1,3 +1,10 @@
This directory contains test cases from the W3C Web Platform Tests suite for IndexedDB. 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, keep_alive,
} from "./wptsupport"; } 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 // Transactions are active during success handlers
await indexeddb_test( await indexeddb_test(
t, t,
@ -24,10 +24,9 @@ test("WPT test abort-in-initial-upgradeneeded.htm", async (t) => {
); );
const request = tx.objectStore("store").get(4242); 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.onerror = () => t.fail("request should succeed");
request.onsuccess = () => { request.onsuccess = () => {
t.true( t.true(
is_transaction_active(t, tx, "store"), is_transaction_active(t, tx, "store"),
"Transaction should be active during success handler", "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 test from "ava";
import { BridgeIDBCursor } from ".."; import { BridgeIDBCursor } from "..";
import { BridgeIDBCursorWithValue } from "../bridge-idb";
import { createdb } from "./wptsupport"; import { createdb } from "./wptsupport";
test("WPT test idbcursor_continue_index.htm", async (t) => { test.cb("WPT test idbcursor_continue_index.htm", (t) => {
await new Promise<void>((resolve, reject) => { var db: any;
var db: any; let count = 0;
let count = 0; const records = [
const records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" }, { pKey: "primaryKey_0", iKey: "indexKey_0" },
{ pKey: "primaryKey_1", iKey: "indexKey_1" }, { pKey: "primaryKey_1", iKey: "indexKey_1" },
{ pKey: "primaryKey_1-2", iKey: "indexKey_1" } ]; { pKey: "primaryKey_1-2", iKey: "indexKey_1" },
];
var open_rq = createdb(t); var open_rq = createdb(t);
open_rq.onupgradeneeded = function(e: any) { open_rq.onupgradeneeded = function (e: any) {
db = e.target.result; db = e.target.result;
var objStore = db.createObjectStore("test", { keyPath:"pKey" }); var objStore = db.createObjectStore("test", { keyPath: "pKey" });
objStore.createIndex("index", "iKey"); objStore.createIndex("index", "iKey");
for (var i = 0; i < records.length; i++) for (var i = 0; i < records.length; i++) objStore.add(records[i]);
objStore.add(records[i]);
}; };
open_rq.onsuccess = function(e) { open_rq.onsuccess = function (e) {
var cursor_rq = db.transaction("test") var cursor_rq = db
.objectStore("test") .transaction("test")
.index("index") .objectStore("test")
.openCursor(); .index("index")
.openCursor();
cursor_rq.onsuccess = function(e: any) { cursor_rq.onsuccess = function (e: any) {
var cursor = e.target.result; var cursor = e.target.result;
if (!cursor) { if (!cursor) {
t.deepEqual(count, records.length, "cursor run count"); t.deepEqual(count, records.length, "cursor run count");
resolve(); t.end();
} return;
}
var record = cursor.value; var record = cursor.value;
t.deepEqual(record.pKey, records[count].pKey, "primary key"); t.deepEqual(record.pKey, records[count].pKey, "primary key");
t.deepEqual(record.iKey, records[count].iKey, "index key"); t.deepEqual(record.iKey, records[count].iKey, "index key");
cursor.continue(); cursor.continue();
count++; 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 { MemoryBackend } from "../MemoryBackend";
import { compareKeys } from "../util/cmp"; import { compareKeys } from "../util/cmp";
BridgeIDBFactory.enableTracing = true; BridgeIDBFactory.enableTracing = false;
const backend = new MemoryBackend(); const backend = new MemoryBackend();
backend.enableTracing = true; backend.enableTracing = false;
export const idbFactory = new BridgeIDBFactory(backend); export const idbFactory = new BridgeIDBFactory(backend);
const self = { const self = {