aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2021-02-17 17:38:47 +0100
committerFlorian Dold <florian@dold.me>2021-02-17 17:39:28 +0100
commit4b4640dbcb2ddc9703a21dd6702a4e264a7331a1 (patch)
treedcb534824212b7e8e4b4e55dc024a66fe0cfebbe
parent69b62c62a0e417985f6e104edd9b8a7fd75a0f81 (diff)
idb: make more tests pass, implement Cursor.advance()
-rw-r--r--packages/idb-bridge/src/MemoryBackend.ts67
-rw-r--r--packages/idb-bridge/src/bridge-idb.ts95
-rw-r--r--packages/idb-bridge/src/idb-wpt-ported/idbfactory-open.test.ts12
3 files changed, 135 insertions, 39 deletions
diff --git a/packages/idb-bridge/src/MemoryBackend.ts b/packages/idb-bridge/src/MemoryBackend.ts
index 4fdcf257c..0051005ed 100644
--- a/packages/idb-bridge/src/MemoryBackend.ts
+++ b/packages/idb-bridge/src/MemoryBackend.ts
@@ -46,11 +46,10 @@ type Key = IDBValidKey;
type Value = unknown;
enum TransactionLevel {
- Disconnected = 0,
- Connected = 1,
- Read = 2,
- Write = 3,
- VersionChange = 4,
+ None = 0,
+ Read = 1,
+ Write = 2,
+ VersionChange = 3,
}
interface ObjectStore {
@@ -83,12 +82,18 @@ interface Database {
txLevel: TransactionLevel;
+ txOwnerConnectionCookie?: string;
+ txOwnerTransactionCookie?: string;
+
/**
* Object stores that the transaction is allowed to access.
*/
txRestrictObjectStores: string[] | undefined;
- connectionCookie: string | undefined;
+ /**
+ * Connection cookies of current connections.
+ */
+ connectionCookies: string[];
}
/** @public */
@@ -245,7 +250,7 @@ export class MemoryBackend implements Backend {
private disconnectCond: AsyncCondition = new AsyncCondition();
/**
- * Conditation that is triggered whenever a transaction finishes.
+ * Condition that is triggered whenever a transaction finishes.
*/
private transactionDoneCond: AsyncCondition = new AsyncCondition();
@@ -327,8 +332,8 @@ export class MemoryBackend implements Backend {
deleted: false,
committedObjectStores: objectStores,
committedSchema: structuredClone(schema),
- connectionCookie: undefined,
- txLevel: TransactionLevel.Disconnected,
+ connectionCookies: [],
+ txLevel: TransactionLevel.None,
txRestrictObjectStores: undefined,
};
this.databases[dbName] = db;
@@ -425,9 +430,9 @@ export class MemoryBackend implements Backend {
if (myDb.txLevel < TransactionLevel.VersionChange) {
throw new InvalidStateError();
}
- if (myDb.connectionCookie !== tx.transactionCookie) {
- throw new InvalidAccessError();
- }
+ // if (myDb.connectionCookie !== tx.transactionCookie) {
+ // throw new InvalidAccessError();
+ // }
myDb.deleted = true;
}
@@ -449,20 +454,18 @@ export class MemoryBackend implements Backend {
committedSchema: schema,
deleted: false,
committedObjectStores: {},
- txLevel: TransactionLevel.Disconnected,
- connectionCookie: undefined,
+ txLevel: TransactionLevel.None,
+ connectionCookies: [],
txRestrictObjectStores: undefined,
};
this.databases[name] = database;
}
- while (database.txLevel !== TransactionLevel.Disconnected) {
- await this.disconnectCond.wait();
+ if (database.connectionCookies.includes(connectionCookie)) {
+ throw Error("already connected");
}
- database.txLevel = TransactionLevel.Connected;
- database.txRestrictObjectStores = undefined;
- database.connectionCookie = connectionCookie;
+ database.connectionCookies.push(connectionCookie);
const myConn: Connection = {
dbName: name,
@@ -494,7 +497,7 @@ export class MemoryBackend implements Backend {
throw Error("db not found");
}
- while (myDb.txLevel !== TransactionLevel.Connected) {
+ while (myDb.txLevel !== TransactionLevel.None) {
if (this.enableTracing) {
console.log(`TRACING: beginTransaction -- waiting for others to close`);
}
@@ -533,11 +536,13 @@ export class MemoryBackend implements Backend {
throw Error("db not found");
}
- while (myDb.txLevel !== TransactionLevel.Connected) {
+ while (myDb.txLevel !== TransactionLevel.None) {
await this.transactionDoneCond.wait();
}
myDb.txLevel = TransactionLevel.VersionChange;
+ myDb.txOwnerConnectionCookie = conn.connectionCookie;
+ myDb.txOwnerTransactionCookie = transactionCookie;
myDb.txRestrictObjectStores = undefined;
this.connectionsByTransaction[transactionCookie] = myConn;
@@ -557,11 +562,13 @@ export class MemoryBackend implements Backend {
}
if (!myConn.deleted) {
const myDb = this.databases[myConn.dbName];
- if (myDb.txLevel != TransactionLevel.Connected) {
- throw Error("invalid state");
- }
- myDb.txLevel = TransactionLevel.Disconnected;
- myDb.txRestrictObjectStores = undefined;
+ // if (myDb.connectionCookies.includes(conn.connectionCookie)) {
+ // throw Error("invalid state");
+ // }
+ // FIXME: what if we're still in a transaction?
+ myDb.connectionCookies = myDb.connectionCookies.filter(
+ (x) => x != conn.connectionCookie,
+ );
}
delete this.connections[conn.connectionCookie];
this.disconnectCond.trigger();
@@ -1390,7 +1397,7 @@ export class MemoryBackend implements Backend {
throw Error("db not found");
}
if (db.txLevel < TransactionLevel.Write) {
- throw Error("only allowed while running a transaction");
+ throw Error("store operation only allowed while running a transaction");
}
if (
db.txRestrictObjectStores &&
@@ -1588,9 +1595,9 @@ export class MemoryBackend implements Backend {
throw Error("db not found");
}
if (db.txLevel < TransactionLevel.Read) {
- throw Error("only allowed while running a transaction");
+ throw Error("rollback is only allowed while running a transaction");
}
- db.txLevel = TransactionLevel.Connected;
+ db.txLevel = TransactionLevel.None;
db.txRestrictObjectStores = undefined;
myConn.modifiedSchema = structuredClone(db.committedSchema);
myConn.objectStoreMap = this.makeObjectStoreMap(db);
@@ -1633,7 +1640,7 @@ export class MemoryBackend implements Backend {
}
db.committedSchema = structuredClone(myConn.modifiedSchema);
- db.txLevel = TransactionLevel.Connected;
+ db.txLevel = TransactionLevel.None;
db.txRestrictObjectStores = undefined;
db.committedObjectStores = {};
diff --git a/packages/idb-bridge/src/bridge-idb.ts b/packages/idb-bridge/src/bridge-idb.ts
index e23c78d4a..6ca6633a9 100644
--- a/packages/idb-bridge/src/bridge-idb.ts
+++ b/packages/idb-bridge/src/bridge-idb.ts
@@ -312,7 +312,43 @@ export class BridgeIDBCursor implements IDBCursor {
* http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBCursor-advance-void-unsigned-long-count
*/
public advance(count: number) {
- throw Error("not implemented");
+ const transaction = this._effectiveObjectStore._transaction;
+
+ if (!transaction._active) {
+ throw new TransactionInactiveError();
+ }
+
+ if (this._effectiveObjectStore._deleted) {
+ throw new InvalidStateError();
+ }
+ if (
+ !(this.source instanceof BridgeIDBObjectStore) &&
+ this.source._deleted
+ ) {
+ throw new InvalidStateError();
+ }
+
+ if (!this._gotValue) {
+ throw new InvalidStateError();
+ }
+
+ if (this._request) {
+ this._request.readyState = "pending";
+ }
+
+ const operation = async () => {
+ for (let i = 0; i < count; i++) {
+ await this._iterate();
+ }
+ };
+
+ transaction._execRequestAsync({
+ operation,
+ request: this._request,
+ source: this.source,
+ });
+
+ this._gotValue = false;
}
/**
@@ -760,8 +796,23 @@ export class BridgeIDBFactory {
queueTask(async () => {
let dbconn: DatabaseConnection;
try {
+ if (BridgeIDBFactory.enableTracing) {
+ console.log(
+ "TRACE: connecting to database",
+ );
+ }
dbconn = await this.backend.connectDatabase(name);
+ if (BridgeIDBFactory.enableTracing) {
+ console.log(
+ "TRACE: connected!",
+ );
+ }
} catch (err) {
+ if (BridgeIDBFactory.enableTracing) {
+ console.log(
+ "TRACE: caught exception while trying to connect with backend",
+ );
+ }
request._finishWithError(err);
return;
}
@@ -796,11 +847,24 @@ export class BridgeIDBFactory {
cancelable: false,
});
event2.eventPath = [];
+ if (BridgeIDBFactory.enableTracing) {
+ console.log(
+ "open() requested same version, dispatching 'success' event on transaction",
+ );
+ }
request.dispatchEvent(event2);
} else if (existingVersion < requestedVersion) {
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-running-a-versionchange-transaction
for (const otherConn of this.connections) {
+ if (otherConn._closePending) {
+ continue;
+ }
+ if (BridgeIDBFactory.enableTracing) {
+ console.log(
+ "dispatching 'versionchange' event to other connection",
+ );
+ }
const event = new BridgeIDBVersionChangeEvent("versionchange", {
newVersion: version,
oldVersion: existingVersion,
@@ -809,6 +873,11 @@ export class BridgeIDBFactory {
}
if (this._anyOpen()) {
+ if (BridgeIDBFactory.enableTracing) {
+ console.log(
+ "other connections are still open, dispatching 'blocked' event to other connection",
+ );
+ }
const event = new BridgeIDBVersionChangeEvent("blocked", {
newVersion: version,
oldVersion: existingVersion,
@@ -835,6 +904,10 @@ export class BridgeIDBFactory {
db._upgradeTransaction = transaction;
+ if (BridgeIDBFactory.enableTracing) {
+ console.log("dispatching upgradeneeded event");
+ }
+
const event = new BridgeIDBVersionChangeEvent("upgradeneeded", {
newVersion: version,
oldVersion: existingVersion,
@@ -866,6 +939,10 @@ export class BridgeIDBFactory {
event2.eventPath = [];
request.dispatchEvent(event2);
} else {
+ if (BridgeIDBFactory.enableTracing) {
+ console.log("dispatching 'success' event for opening db");
+ }
+
const event2 = new FakeEvent("success", {
bubbles: false,
cancelable: false,
@@ -1801,10 +1878,10 @@ export class BridgeIDBRequest extends FakeEventTarget implements IDBRequest {
_result: any = null;
_error: Error | null | undefined = null;
get source(): IDBObjectStore | IDBIndex | IDBCursor {
- if (this._source) {
- return this._source;
+ if (!this._source) {
+ throw Error("internal invariant failed: source is null");
}
- throw Error("source is null");
+ return this._source;
}
_source:
| BridgeIDBCursor
@@ -1875,6 +1952,16 @@ export class BridgeIDBOpenDBRequest
public onupgradeneeded: EventListener | null = null;
public onblocked: EventListener | null = null;
+ get source(): IDBObjectStore | IDBIndex | IDBCursor {
+ // This is a type safety violation, but it is required by the
+ // IndexedDB standard.
+ // On the one hand, IDBOpenDBRequest implements IDBRequest.
+ // But that's technically impossible, as the "source" of the
+ // IDBOpenDB request may be null, while the one from IDBRequest
+ // may not be null.
+ return this._source as any;
+ }
+
constructor() {
super();
// https://www.w3.org/TR/IndexedDB/#open-requests
diff --git a/packages/idb-bridge/src/idb-wpt-ported/idbfactory-open.test.ts b/packages/idb-bridge/src/idb-wpt-ported/idbfactory-open.test.ts
index 4ba7caa6f..0d1f24c4b 100644
--- a/packages/idb-bridge/src/idb-wpt-ported/idbfactory-open.test.ts
+++ b/packages/idb-bridge/src/idb-wpt-ported/idbfactory-open.test.ts
@@ -20,7 +20,7 @@ test("WPT idbfactory-open.htm", async (t) => {
// IDBFactory.open() - database 'name' and 'version' are correctly set
test("WPT idbfactory-open2.htm", async (t) => {
await new Promise<void>((resolve, reject) => {
- var database_name = __filename + "-database_name";
+ var database_name = t.title + "-database_name";
var open_rq = createdb(t, database_name, 13);
open_rq.onupgradeneeded = function (e) {};
@@ -28,7 +28,7 @@ test("WPT idbfactory-open2.htm", async (t) => {
var db = e.target.result;
t.deepEqual(db.name, database_name, "db.name");
t.deepEqual(db.version, 13, "db.version");
- resolve;
+ resolve();
};
});
t.pass();
@@ -63,7 +63,7 @@ test("WPT idbfactory-open3.htm", async (t) => {
test("WPT idbfactory-open4.htm", async (t) => {
const indexedDB = idbFactory;
await new Promise<void>((resolve, reject) => {
- var open_rq = createdb(t, __filename + "-database_name");
+ var open_rq = createdb(t, t.title + "-database_name");
open_rq.onupgradeneeded = function (e: any) {
t.deepEqual(e.target.result.version, 1, "db.version");
@@ -80,7 +80,7 @@ test("WPT idbfactory-open4.htm", async (t) => {
test("WPT idbfactory-open5.htm", async (t) => {
const indexedDB = idbFactory;
await new Promise<void>((resolve, reject) => {
- var open_rq = createdb(t, __filename + "-database_name");
+ var open_rq = createdb(t, t.title + "-database_name");
open_rq.onupgradeneeded = function () {};
open_rq.onsuccess = function (e: any) {
@@ -100,7 +100,6 @@ test("WPT idbfactory-open6.htm", async (t) => {
const indexedDB = idbFactory;
await new Promise<void>((resolve, reject) => {
var open_rq = createdb(t, undefined, 13);
- var did_upgrade = false;
var open_rq2: any;
open_rq.onupgradeneeded = function () {};
@@ -115,8 +114,10 @@ test("WPT idbfactory-open6.htm", async (t) => {
};
function open_previous_db(e: any) {
+ t.log("opening previous DB");
var open_rq3 = indexedDB.open(e.target.result.name, 13);
open_rq3.onerror = function (e: any) {
+ t.log("got open error");
t.deepEqual(e.target.error.name, "VersionError", "e.target.error.name");
open_rq2.result.close();
resolve();
@@ -506,6 +507,7 @@ test("WPT idbfactory-open12.htm", async (t) => {
* Second test
*/
db.onversionchange = function () {
+ t.log("onversionchange called");
db.close();
};