more tests, fix event ordering issue
This commit is contained in:
parent
e6946694f2
commit
3eced74a88
@ -1609,6 +1609,10 @@ export class BridgeIDBObjectStore implements IDBObjectStore {
|
||||
throw new TypeError();
|
||||
}
|
||||
|
||||
if (!this._transaction._active) {
|
||||
throw new TransactionInactiveError();
|
||||
}
|
||||
|
||||
if (this._deleted) {
|
||||
throw new InvalidStateError(
|
||||
"tried to call 'delete' on a deleted object store",
|
||||
@ -1918,6 +1922,8 @@ export class BridgeIDBRequest extends FakeEventTarget implements IDBRequest {
|
||||
onsuccess: EventListener | null = null;
|
||||
onerror: EventListener | null = null;
|
||||
|
||||
_debugName: string | undefined;
|
||||
|
||||
get error() {
|
||||
if (this.readyState === "pending") {
|
||||
throw new InvalidStateError();
|
||||
@ -1998,6 +2004,25 @@ export class BridgeIDBOpenDBRequest
|
||||
}
|
||||
}
|
||||
|
||||
function waitMacroQueue(): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
let immCalled = false;
|
||||
let timeoutCalled = false;
|
||||
setImmediate(() => {
|
||||
immCalled = true;
|
||||
if (immCalled && timeoutCalled) {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
setTimeout(() => {
|
||||
timeoutCalled = true;
|
||||
if (immCalled && timeoutCalled) {
|
||||
resolve();
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
|
||||
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#transaction
|
||||
/** @public */
|
||||
export class BridgeIDBTransaction
|
||||
@ -2182,7 +2207,7 @@ export class BridgeIDBTransaction
|
||||
// http://w3c.github.io/IndexedDB/#dom-idbtransaction-objectstore
|
||||
public objectStore(name: string): BridgeIDBObjectStore {
|
||||
if (!this._active) {
|
||||
throw new InvalidStateError();
|
||||
throw new TransactionInactiveError();
|
||||
}
|
||||
|
||||
if (!this._db._schema.objectStores[name]) {
|
||||
@ -2279,6 +2304,8 @@ export class BridgeIDBTransaction
|
||||
}
|
||||
}
|
||||
|
||||
await waitMacroQueue();
|
||||
|
||||
if (!request._source) {
|
||||
// Special requests like indexes that just need to run some code,
|
||||
// with error handling already built into operation
|
||||
@ -2289,9 +2316,12 @@ export class BridgeIDBTransaction
|
||||
BridgeIDBFactory.enableTracing &&
|
||||
console.log("TRACE: running operation in transaction");
|
||||
const result = await operation();
|
||||
// Wait until setTimeout/setImmediate tasks are run
|
||||
BridgeIDBFactory.enableTracing &&
|
||||
console.log(
|
||||
"TRACE: operation in transaction finished with success",
|
||||
`TRACE: request (${
|
||||
request._debugName ?? "??"
|
||||
}) in transaction finished with success`,
|
||||
);
|
||||
request.readyState = "done";
|
||||
request.result = result;
|
||||
@ -2304,6 +2334,10 @@ export class BridgeIDBTransaction
|
||||
cancelable: false,
|
||||
});
|
||||
|
||||
queueTask(() => {
|
||||
this._active = false;
|
||||
});
|
||||
|
||||
try {
|
||||
event.eventPath = [this._db, this];
|
||||
request.dispatchEvent(event);
|
||||
@ -2372,7 +2406,11 @@ export class BridgeIDBTransaction
|
||||
this._committed = true;
|
||||
if (!this._error) {
|
||||
if (BridgeIDBFactory.enableTracing) {
|
||||
console.log("dispatching 'complete' event on transaction");
|
||||
console.log(
|
||||
`dispatching 'complete' event on transaction (${
|
||||
this._debugName ?? "??"
|
||||
})`,
|
||||
);
|
||||
}
|
||||
const event = new FakeEvent("complete");
|
||||
event.eventPath = [this._db, this];
|
||||
|
@ -0,0 +1,57 @@
|
||||
import test from "ava";
|
||||
import { BridgeIDBRequest } from "..";
|
||||
import {
|
||||
createdb,
|
||||
indexeddb_test,
|
||||
is_transaction_active,
|
||||
keep_alive,
|
||||
} from "./wptsupport";
|
||||
|
||||
test("WPT test abort-in-initial-upgradeneeded.htm", async (t) => {
|
||||
// Transactions are active during success handlers
|
||||
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.assert(
|
||||
is_transaction_active(t, tx, "store"),
|
||||
"Transaction should be active after creation",
|
||||
);
|
||||
|
||||
const request = tx.objectStore("store").get(4242);
|
||||
(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",
|
||||
);
|
||||
|
||||
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);
|
||||
};
|
||||
},
|
||||
);
|
||||
});
|
@ -40,7 +40,6 @@ async function t2(t: ExecutionContext, method: string): Promise<void> {
|
||||
const store = db.createObjectStore("s");
|
||||
},
|
||||
(done, db) => {
|
||||
(db as any)._debugName = method;
|
||||
const tx = db.transaction("s", "readonly");
|
||||
const store = tx.objectStore("s");
|
||||
|
||||
|
@ -0,0 +1,49 @@
|
||||
import test from "ava";
|
||||
import { createdb } from "./wptsupport";
|
||||
|
||||
// IDBTransaction - complete event
|
||||
test("WPT idbtransaction-oncomplete.htm", async (t) => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
var db: any;
|
||||
var store: any;
|
||||
let open_rq = createdb(t);
|
||||
let stages: any[] = [];
|
||||
|
||||
open_rq.onupgradeneeded = function (e: any) {
|
||||
stages.push("upgradeneeded");
|
||||
|
||||
db = e.target.result;
|
||||
store = db.createObjectStore("store");
|
||||
|
||||
e.target.transaction.oncomplete = function () {
|
||||
stages.push("complete");
|
||||
};
|
||||
};
|
||||
|
||||
open_rq.onsuccess = function (e) {
|
||||
stages.push("success");
|
||||
|
||||
// Making a totally new transaction to check
|
||||
db
|
||||
.transaction("store")
|
||||
.objectStore("store")
|
||||
.count().onsuccess = function (e: any) {
|
||||
t.deepEqual(stages, ["upgradeneeded", "complete", "success"]);
|
||||
resolve();
|
||||
};
|
||||
// XXX: Make one with real transactions, not only open() versionchange one
|
||||
|
||||
/*db.transaction.objectStore('store').openCursor().onsuccess = function(e) {
|
||||
stages.push("opencursor1");
|
||||
}
|
||||
store.openCursor().onsuccess = function(e) {
|
||||
stages.push("opencursor2");
|
||||
}
|
||||
e.target.transaction.objectStore('store').openCursor().onsuccess = function(e) {
|
||||
stages.push("opencursor3");
|
||||
}
|
||||
*/
|
||||
};
|
||||
});
|
||||
t.pass();
|
||||
});
|
@ -1,5 +1,5 @@
|
||||
import test, { ExecutionContext } from "ava";
|
||||
import { BridgeIDBFactory } from "..";
|
||||
import { BridgeIDBFactory, BridgeIDBRequest } from "..";
|
||||
import {
|
||||
IDBDatabase,
|
||||
IDBIndex,
|
||||
@ -480,3 +480,65 @@ export function indexeddb_test(
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps the passed transaction alive indefinitely (by making requests
|
||||
* against the named store). Returns a function that asserts that the
|
||||
* transaction has not already completed and then ends the request loop so that
|
||||
* the transaction may autocommit and complete.
|
||||
*/
|
||||
export function keep_alive(
|
||||
t: ExecutionContext,
|
||||
tx: IDBTransaction,
|
||||
store_name: string,
|
||||
) {
|
||||
let completed = false;
|
||||
tx.addEventListener("complete", () => {
|
||||
completed = true;
|
||||
});
|
||||
|
||||
let keepSpinning = true;
|
||||
let spinCount = 0;
|
||||
|
||||
function spin() {
|
||||
console.log("spinning");
|
||||
if (!keepSpinning) return;
|
||||
const request = tx.objectStore(store_name).get(0);
|
||||
(request as BridgeIDBRequest)._debugName = `req-spin-${spinCount}`;
|
||||
spinCount++;
|
||||
request.onsuccess = spin;
|
||||
}
|
||||
spin();
|
||||
|
||||
return () => {
|
||||
t.log("stopping spin");
|
||||
t.false(completed, "Transaction completed while kept alive");
|
||||
keepSpinning = false;
|
||||
};
|
||||
}
|
||||
|
||||
// Checks to see if the passed transaction is active (by making
|
||||
// requests against the named store).
|
||||
export function is_transaction_active(
|
||||
t: ExecutionContext,
|
||||
tx: IDBTransaction,
|
||||
store_name: string,
|
||||
) {
|
||||
try {
|
||||
const request = tx.objectStore(store_name).get(0);
|
||||
request.onerror = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
return true;
|
||||
} catch (ex) {
|
||||
console.log(ex.stack);
|
||||
t.deepEqual(
|
||||
ex.name,
|
||||
"TransactionInactiveError",
|
||||
"Active check should either not throw anything, or throw " +
|
||||
"TransactionInactiveError",
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,20 @@
|
||||
*/
|
||||
|
||||
export function queueTask(fn: () => void) {
|
||||
setImmediate(fn);
|
||||
let called = false;
|
||||
const callFirst = () => {
|
||||
if (called) {
|
||||
return;
|
||||
}
|
||||
called = true;
|
||||
fn();
|
||||
};
|
||||
// We must schedule both of these,
|
||||
// since on node, there is no guarantee
|
||||
// that a setImmediate function that is registered
|
||||
// before a setTimeout function is called first.
|
||||
setImmediate(callFirst);
|
||||
setTimeout(callFirst, 0);
|
||||
}
|
||||
|
||||
export default queueTask;
|
||||
|
Loading…
Reference in New Issue
Block a user