summaryrefslogtreecommitdiff
path: root/packages/idb-bridge
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2021-02-16 14:49:38 +0100
committerFlorian Dold <florian@dold.me>2021-02-16 14:49:38 +0100
commit4d663d2e595b64e6bf1979eccc701d0f3d55d797 (patch)
tree9c125da5b84a928e4da7e2b98886878f49a23563 /packages/idb-bridge
parent987f22de02648485ec6f1d3c1558abcfa6d624a0 (diff)
downloadwallet-core-4d663d2e595b64e6bf1979eccc701d0f3d55d797.tar.gz
wallet-core-4d663d2e595b64e6bf1979eccc701d0f3d55d797.tar.bz2
wallet-core-4d663d2e595b64e6bf1979eccc701d0f3d55d797.zip
synchronous schema rollback
Diffstat (limited to 'packages/idb-bridge')
-rw-r--r--packages/idb-bridge/src/MemoryBackend.ts24
-rw-r--r--packages/idb-bridge/src/backend-interface.ts4
-rw-r--r--packages/idb-bridge/src/bridge-idb.ts132
-rw-r--r--packages/idb-bridge/src/idb-wpt-ported/abort-in-initial-upgradeneeded.test.ts2
-rw-r--r--packages/idb-bridge/src/idb-wpt-ported/idbindex_get.test.ts56
5 files changed, 153 insertions, 65 deletions
diff --git a/packages/idb-bridge/src/MemoryBackend.ts b/packages/idb-bridge/src/MemoryBackend.ts
index 7107756a0..4fdcf257c 100644
--- a/packages/idb-bridge/src/MemoryBackend.ts
+++ b/packages/idb-bridge/src/MemoryBackend.ts
@@ -579,9 +579,33 @@ export class MemoryBackend implements Backend {
if (!db) {
throw Error("db not found");
}
+ return db.committedSchema;
+ }
+
+ getCurrentTransactionSchema(btx: DatabaseTransaction): Schema {
+ const myConn = this.connectionsByTransaction[btx.transactionCookie];
+ if (!myConn) {
+ throw Error("unknown connection");
+ }
+ const db = this.databases[myConn.dbName];
+ if (!db) {
+ throw Error("db not found");
+ }
return myConn.modifiedSchema;
}
+ getInitialTransactionSchema(btx: DatabaseTransaction): Schema {
+ const myConn = this.connectionsByTransaction[btx.transactionCookie];
+ if (!myConn) {
+ throw Error("unknown connection");
+ }
+ const db = this.databases[myConn.dbName];
+ if (!db) {
+ throw Error("db not found");
+ }
+ return db.committedSchema;
+ }
+
renameIndex(
btx: DatabaseTransaction,
objectStoreName: string,
diff --git a/packages/idb-bridge/src/backend-interface.ts b/packages/idb-bridge/src/backend-interface.ts
index 14b5da64a..7b74c35e6 100644
--- a/packages/idb-bridge/src/backend-interface.ts
+++ b/packages/idb-bridge/src/backend-interface.ts
@@ -162,6 +162,10 @@ export interface Backend {
getSchema(db: DatabaseConnection): Schema;
+ getCurrentTransactionSchema(btx: DatabaseTransaction): Schema;
+
+ getInitialTransactionSchema(btx: DatabaseTransaction): Schema;
+
renameIndex(
btx: DatabaseTransaction,
objectStoreName: string,
diff --git a/packages/idb-bridge/src/bridge-idb.ts b/packages/idb-bridge/src/bridge-idb.ts
index f518b4768..86ca66b1b 100644
--- a/packages/idb-bridge/src/bridge-idb.ts
+++ b/packages/idb-bridge/src/bridge-idb.ts
@@ -221,7 +221,7 @@ export class BridgeIDBCursor implements IDBCursor {
resultLevel: this._keyOnly ? ResultLevel.OnlyKeys : ResultLevel.Full,
};
- const { btx } = this.source._confirmActiveTransaction();
+ const { btx } = this.source._confirmStartedBackendTransaction();
let response = await this._backend.getRecords(btx, recordGetRequest);
@@ -305,7 +305,7 @@ export class BridgeIDBCursor implements IDBCursor {
if (BridgeIDBFactory.enableTracing) {
console.log("updating at cursor");
}
- const { btx } = this.source._confirmActiveTransaction();
+ const { btx } = this.source._confirmStartedBackendTransaction();
await this._backend.storeRecord(btx, storeReq);
};
return transaction._execRequestAsync({
@@ -412,7 +412,7 @@ export class BridgeIDBCursor implements IDBCursor {
}
const operation = async () => {
- const { btx } = this.source._confirmActiveTransaction();
+ const { btx } = this.source._confirmStartedBackendTransaction();
this._backend.deleteRecord(
btx,
this._objectStoreName,
@@ -535,13 +535,6 @@ export class BridgeIDBDatabase extends FakeEventTarget implements IDBDatabase {
}
}
- /**
- * Refresh the schema by querying it from the backend.
- */
- _refreshSchema() {
- this._schema = this._backend.getSchema(this._backendConnection);
- }
-
constructor(backend: Backend, backendConnection: DatabaseConnection) {
super();
@@ -596,7 +589,7 @@ export class BridgeIDBDatabase extends FakeEventTarget implements IDBDatabase {
autoIncrement,
);
- this._schema = this._backend.getSchema(this._backendConnection);
+ this._schema = this._backend.getCurrentTransactionSchema(backendTx);
return transaction.objectStore(name);
}
@@ -616,7 +609,6 @@ export class BridgeIDBDatabase extends FakeEventTarget implements IDBDatabase {
os._deleted = true;
transaction._objectStoresCache.delete(name);
}
-
}
public _internalTransaction(
@@ -835,6 +827,9 @@ export class BridgeIDBFactory {
requestedVersion,
);
+ // We need to expose the new version number to the upgrade transaction.
+ db._schema = this.backend.getCurrentTransactionSchema(backendTransaction);
+
const transaction = db._internalTransaction(
[],
"versionchange",
@@ -911,20 +906,6 @@ export class BridgeIDBFactory {
}
}
-const confirmActiveTransaction = (
- index: BridgeIDBIndex,
-): BridgeIDBTransaction => {
- if (index._deleted || index._objectStore._deleted) {
- throw new InvalidStateError();
- }
-
- if (!index._objectStore._transaction._active) {
- throw new TransactionInactiveError();
- }
-
- return index._objectStore._transaction;
-};
-
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#idl-def-IDBIndex
/** @public */
export class BridgeIDBIndex implements IDBIndex {
@@ -957,8 +938,12 @@ export class BridgeIDBIndex implements IDBIndex {
return this._objectStore._backend;
}
- _confirmActiveTransaction(): { btx: DatabaseTransaction } {
- return this._objectStore._confirmActiveTransaction();
+ _confirmStartedBackendTransaction(): { btx: DatabaseTransaction } {
+ return this._objectStore._confirmStartedBackendTransaction();
+ }
+
+ _confirmActiveTransaction(): void {
+ this._objectStore._confirmActiveTransaction();
}
private _name: string;
@@ -986,7 +971,7 @@ export class BridgeIDBIndex implements IDBIndex {
throw new TransactionInactiveError();
}
- const { btx } = this._confirmActiveTransaction();
+ const { btx } = this._confirmStartedBackendTransaction();
const oldName = this._name;
const newName = String(name);
@@ -1008,7 +993,7 @@ export class BridgeIDBIndex implements IDBIndex {
range?: BridgeIDBKeyRange | IDBValidKey | null | undefined,
direction: IDBCursorDirection = "next",
) {
- confirmActiveTransaction(this);
+ this._confirmActiveTransaction();
if (range === null) {
range = undefined;
@@ -1047,7 +1032,7 @@ export class BridgeIDBIndex implements IDBIndex {
range?: BridgeIDBKeyRange | IDBValidKey | null | undefined,
direction: IDBCursorDirection = "next",
) {
- confirmActiveTransaction(this);
+ this._confirmActiveTransaction();
if (range === null) {
range = undefined;
@@ -1077,7 +1062,6 @@ export class BridgeIDBIndex implements IDBIndex {
});
}
-
private _confirmIndexExists() {
const storeSchema = this._schema.objectStores[this._objectStore._name];
if (!storeSchema) {
@@ -1089,8 +1073,8 @@ export class BridgeIDBIndex implements IDBIndex {
}
get(key: BridgeIDBKeyRange | IDBValidKey) {
- confirmActiveTransaction(this);
this._confirmIndexExists();
+ this._confirmActiveTransaction();
if (this._deleted) {
throw new InvalidStateError();
}
@@ -1109,7 +1093,7 @@ export class BridgeIDBIndex implements IDBIndex {
};
const operation = async () => {
- const { btx } = this._confirmActiveTransaction();
+ const { btx } = this._confirmStartedBackendTransaction();
const result = await this._backend.getRecords(btx, getReq);
if (result.count == 0) {
return undefined;
@@ -1137,7 +1121,7 @@ export class BridgeIDBIndex implements IDBIndex {
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBIndex-getKey-IDBRequest-any-key
public getKey(key: BridgeIDBKeyRange | IDBValidKey) {
- confirmActiveTransaction(this);
+ this._confirmActiveTransaction();
if (!(key instanceof BridgeIDBKeyRange)) {
key = BridgeIDBKeyRange._valueToKeyRange(key);
@@ -1153,7 +1137,7 @@ export class BridgeIDBIndex implements IDBIndex {
};
const operation = async () => {
- const { btx } = this._confirmActiveTransaction();
+ const { btx } = this._confirmStartedBackendTransaction();
const result = await this._backend.getRecords(btx, getReq);
if (result.count == 0) {
return undefined;
@@ -1181,7 +1165,7 @@ export class BridgeIDBIndex implements IDBIndex {
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBIndex-count-IDBRequest-any-key
public count(key: BridgeIDBKeyRange | IDBValidKey | null | undefined) {
- confirmActiveTransaction(this);
+ this._confirmActiveTransaction();
if (key === null) {
key = undefined;
@@ -1200,7 +1184,7 @@ export class BridgeIDBIndex implements IDBIndex {
};
const operation = async () => {
- const { btx } = this._confirmActiveTransaction();
+ const { btx } = this._confirmStartedBackendTransaction();
const result = await this._backend.getRecords(btx, getReq);
return result.count;
};
@@ -1380,7 +1364,7 @@ export class BridgeIDBObjectStore implements IDBObjectStore {
return this._transaction._db._backendConnection;
}
- _confirmActiveTransaction(): { btx: DatabaseTransaction } {
+ _confirmStartedBackendTransaction(): { btx: DatabaseTransaction } {
const btx = this._transaction._backendTransaction;
if (!btx) {
throw new InvalidStateError();
@@ -1388,6 +1372,22 @@ export class BridgeIDBObjectStore implements IDBObjectStore {
return { btx };
}
+ /**
+ * Confirm that requests can currently placed against the
+ * transaction of this object.
+ *
+ * Note that this is independent from the state of the backend
+ * connection.
+ */
+ _confirmActiveTransaction(): void {
+ if (!this._transaction._active) {
+ throw new TransactionInactiveError();
+ }
+ if (this._transaction._aborted) {
+ throw new TransactionInactiveError();
+ }
+ }
+
// http://w3c.github.io/IndexedDB/#dom-idbobjectstore-name
set name(newName: any) {
const transaction = this._transaction;
@@ -1396,7 +1396,7 @@ export class BridgeIDBObjectStore implements IDBObjectStore {
throw new InvalidStateError();
}
- let { btx } = this._confirmActiveTransaction();
+ let { btx } = this._confirmStartedBackendTransaction();
newName = String(newName);
@@ -1407,12 +1407,11 @@ export class BridgeIDBObjectStore implements IDBObjectStore {
}
this._backend.renameObjectStore(btx, oldName, newName);
- this._transaction._db._schema = this._backend.getSchema(
- this._backendConnection,
+ this._transaction._db._schema = this._backend.getCurrentTransactionSchema(
+ btx,
);
}
-
public _store(value: any, key: IDBValidKey | undefined, overwrite: boolean) {
if (BridgeIDBFactory.enableTracing) {
console.log(`TRACE: IDBObjectStore._store`);
@@ -1428,16 +1427,10 @@ export class BridgeIDBObjectStore implements IDBObjectStore {
}
// We only call this to synchronously verify the request.
- makeStoreKeyValue(
- value,
- key,
- 1,
- autoIncrement,
- keyPath,
- );
+ makeStoreKeyValue(value, key, 1, autoIncrement, keyPath);
const operation = async () => {
- const { btx } = this._confirmActiveTransaction();
+ const { btx } = this._confirmStartedBackendTransaction();
const result = await this._backend.storeRecord(btx, {
objectStoreName: this._name,
key: key,
@@ -1457,7 +1450,9 @@ export class BridgeIDBObjectStore implements IDBObjectStore {
throw new TypeError();
}
if (this._deleted) {
- throw new InvalidStateError("tried to call 'put' on a deleted object store");
+ throw new InvalidStateError(
+ "tried to call 'put' on a deleted object store",
+ );
}
return this._store(value, key, true);
}
@@ -1477,7 +1472,9 @@ export class BridgeIDBObjectStore implements IDBObjectStore {
throw new TypeError();
}
if (this._deleted) {
- throw new InvalidStateError("tried to call 'delete' on a deleted object store");
+ throw new InvalidStateError(
+ "tried to call 'delete' on a deleted object store",
+ );
}
if (this._transaction.mode === "readonly") {
throw new ReadOnlyError();
@@ -1492,7 +1489,7 @@ export class BridgeIDBObjectStore implements IDBObjectStore {
}
const operation = async () => {
- const { btx } = this._confirmActiveTransaction();
+ const { btx } = this._confirmStartedBackendTransaction();
return this._backend.deleteRecord(btx, this._name, keyRange);
};
@@ -1512,7 +1509,9 @@ export class BridgeIDBObjectStore implements IDBObjectStore {
}
if (this._deleted) {
- throw new InvalidStateError("tried to call 'delete' on a deleted object store");
+ throw new InvalidStateError(
+ "tried to call 'delete' on a deleted object store",
+ );
}
let keyRange: BridgeIDBKeyRange;
@@ -1544,7 +1543,7 @@ export class BridgeIDBObjectStore implements IDBObjectStore {
if (BridgeIDBFactory.enableTracing) {
console.log("running get operation:", recordRequest);
}
- const { btx } = this._confirmActiveTransaction();
+ const { btx } = this._confirmStartedBackendTransaction();
const result = await this._backend.getRecords(btx, recordRequest);
if (BridgeIDBFactory.enableTracing) {
@@ -1599,7 +1598,9 @@ export class BridgeIDBObjectStore implements IDBObjectStore {
direction: IDBCursorDirection = "next",
) {
if (this._deleted) {
- throw new InvalidStateError("tried to call 'openCursor' on a deleted object store");
+ throw new InvalidStateError(
+ "tried to call 'openCursor' on a deleted object store",
+ );
}
if (range === null) {
range = undefined;
@@ -1633,7 +1634,9 @@ export class BridgeIDBObjectStore implements IDBObjectStore {
direction?: IDBCursorDirection,
) {
if (this._deleted) {
- throw new InvalidStateError("tried to call 'openKeyCursor' on a deleted object store");
+ throw new InvalidStateError(
+ "tried to call 'openKeyCursor' on a deleted object store",
+ );
}
if (range === null) {
range = undefined;
@@ -1682,7 +1685,7 @@ export class BridgeIDBObjectStore implements IDBObjectStore {
throw new InvalidStateError();
}
- const { btx } = this._confirmActiveTransaction();
+ const { btx } = this._confirmStartedBackendTransaction();
const multiEntry =
optionalParameters.multiEntry !== undefined
@@ -1750,7 +1753,7 @@ export class BridgeIDBObjectStore implements IDBObjectStore {
throw new InvalidStateError();
}
- const { btx } = this._confirmActiveTransaction();
+ const { btx } = this._confirmStartedBackendTransaction();
const index = this._indexesCache.get(indexName);
if (index !== undefined) {
@@ -1781,7 +1784,7 @@ export class BridgeIDBObjectStore implements IDBObjectStore {
};
const operation = async () => {
- const { btx } = this._confirmActiveTransaction();
+ const { btx } = this._confirmStartedBackendTransaction();
const result = await this._backend.getRecords(btx, recordGetRequest);
return result.count;
};
@@ -2015,14 +2018,15 @@ export class BridgeIDBTransaction
this._db._upgradeTransaction = null;
}
- // Only roll back if we actually executed the scheduled operations.
const maybeBtx = this._backendTransaction;
if (maybeBtx) {
+ this._db._schema = this._backend.getInitialTransactionSchema(maybeBtx);
+ // Only roll back if we actually executed the scheduled operations.
await this._backend.rollback(maybeBtx);
+ } else {
+ this._db._schema = this._backend.getSchema(this._db._backendConnection);
}
- this._db._refreshSchema();
-
queueTask(() => {
const event = new FakeEvent("abort", {
bubbles: true,
diff --git a/packages/idb-bridge/src/idb-wpt-ported/abort-in-initial-upgradeneeded.test.ts b/packages/idb-bridge/src/idb-wpt-ported/abort-in-initial-upgradeneeded.test.ts
index da9ed2632..70f2f2b8a 100644
--- a/packages/idb-bridge/src/idb-wpt-ported/abort-in-initial-upgradeneeded.test.ts
+++ b/packages/idb-bridge/src/idb-wpt-ported/abort-in-initial-upgradeneeded.test.ts
@@ -9,7 +9,7 @@ test("WPT test abort-in-initial-upgradeneeded.htm", async (t) => {
open_rq.onupgradeneeded = function (e) {
const tgt = e.target as any;
db = tgt.result;
- t.assert(db.version === 2);
+ t.deepEqual(db.version, 2);
var transaction = tgt.transaction;
transaction.oncomplete = () => t.fail("unexpected transaction.complete");
transaction.onabort = function (e: any) {
diff --git a/packages/idb-bridge/src/idb-wpt-ported/idbindex_get.test.ts b/packages/idb-bridge/src/idb-wpt-ported/idbindex_get.test.ts
index 8a8cb3129..7601faada 100644
--- a/packages/idb-bridge/src/idb-wpt-ported/idbindex_get.test.ts
+++ b/packages/idb-bridge/src/idb-wpt-ported/idbindex_get.test.ts
@@ -175,3 +175,59 @@ test("WPT idbindex_get6.htm", async (t) => {
});
t.pass();
});
+
+// IDBIndex.get() - throw TransactionInactiveError on aborted transaction
+test("WPT idbindex_get7.htm", async (t) => {
+ await new Promise<void>((resolve, reject) => {
+ var db: any;
+
+ var open_rq = createdb(t);
+ open_rq.onupgradeneeded = function (e: any) {
+ const db = e.target.result as IDBDatabase;
+ var store = db.createObjectStore("store", { keyPath: "key" });
+ var index = store.createIndex("index", "indexedProperty");
+ store.add({ key: 1, indexedProperty: "data" });
+ };
+ open_rq.onsuccess = function (e: any) {
+ const db = e.target.result as IDBDatabase;
+ var tx = db.transaction("store");
+ var index = tx.objectStore("store").index("index");
+ tx.abort();
+
+ t.throws(
+ function () {
+ index.get("data");
+ },
+ { name: "TransactionInactiveError" },
+ );
+ resolve();
+ };
+ });
+ t.pass();
+});
+
+// IDBIndex.get() - throw InvalidStateError on index deleted by aborted upgrade
+test("WPT idbindex_get8.htm", async (t) => {
+ await new Promise<void>((resolve, reject) => {
+ var db: any;
+
+ var open_rq = createdb(t);
+ open_rq.onupgradeneeded = function (e: any) {
+ db = e.target.result;
+ var store = db.createObjectStore("store", { keyPath: "key" });
+ var index = store.createIndex("index", "indexedProperty");
+ store.add({ key: 1, indexedProperty: "data" });
+
+ e.target.transaction.abort();
+
+ t.throws(
+ function () {
+ index.get("data");
+ },
+ { name: "InvalidStateError" },
+ );
+ resolve();
+ };
+ });
+ t.pass();
+});