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();
|
throw new TypeError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this._transaction._active) {
|
||||||
|
throw new TransactionInactiveError();
|
||||||
|
}
|
||||||
|
|
||||||
if (this._deleted) {
|
if (this._deleted) {
|
||||||
throw new InvalidStateError(
|
throw new InvalidStateError(
|
||||||
"tried to call 'delete' on a deleted object store",
|
"tried to call 'delete' on a deleted object store",
|
||||||
@ -1918,6 +1922,8 @@ export class BridgeIDBRequest extends FakeEventTarget implements IDBRequest {
|
|||||||
onsuccess: EventListener | null = null;
|
onsuccess: EventListener | null = null;
|
||||||
onerror: EventListener | null = null;
|
onerror: EventListener | null = null;
|
||||||
|
|
||||||
|
_debugName: string | undefined;
|
||||||
|
|
||||||
get error() {
|
get error() {
|
||||||
if (this.readyState === "pending") {
|
if (this.readyState === "pending") {
|
||||||
throw new InvalidStateError();
|
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
|
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#transaction
|
||||||
/** @public */
|
/** @public */
|
||||||
export class BridgeIDBTransaction
|
export class BridgeIDBTransaction
|
||||||
@ -2182,7 +2207,7 @@ export class BridgeIDBTransaction
|
|||||||
// http://w3c.github.io/IndexedDB/#dom-idbtransaction-objectstore
|
// http://w3c.github.io/IndexedDB/#dom-idbtransaction-objectstore
|
||||||
public objectStore(name: string): BridgeIDBObjectStore {
|
public objectStore(name: string): BridgeIDBObjectStore {
|
||||||
if (!this._active) {
|
if (!this._active) {
|
||||||
throw new InvalidStateError();
|
throw new TransactionInactiveError();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._db._schema.objectStores[name]) {
|
if (!this._db._schema.objectStores[name]) {
|
||||||
@ -2279,6 +2304,8 @@ export class BridgeIDBTransaction
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await waitMacroQueue();
|
||||||
|
|
||||||
if (!request._source) {
|
if (!request._source) {
|
||||||
// Special requests like indexes that just need to run some code,
|
// Special requests like indexes that just need to run some code,
|
||||||
// with error handling already built into operation
|
// with error handling already built into operation
|
||||||
@ -2289,9 +2316,12 @@ export class BridgeIDBTransaction
|
|||||||
BridgeIDBFactory.enableTracing &&
|
BridgeIDBFactory.enableTracing &&
|
||||||
console.log("TRACE: running operation in transaction");
|
console.log("TRACE: running operation in transaction");
|
||||||
const result = await operation();
|
const result = await operation();
|
||||||
|
// Wait until setTimeout/setImmediate tasks are run
|
||||||
BridgeIDBFactory.enableTracing &&
|
BridgeIDBFactory.enableTracing &&
|
||||||
console.log(
|
console.log(
|
||||||
"TRACE: operation in transaction finished with success",
|
`TRACE: request (${
|
||||||
|
request._debugName ?? "??"
|
||||||
|
}) in transaction finished with success`,
|
||||||
);
|
);
|
||||||
request.readyState = "done";
|
request.readyState = "done";
|
||||||
request.result = result;
|
request.result = result;
|
||||||
@ -2304,6 +2334,10 @@ export class BridgeIDBTransaction
|
|||||||
cancelable: false,
|
cancelable: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
queueTask(() => {
|
||||||
|
this._active = false;
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
event.eventPath = [this._db, this];
|
event.eventPath = [this._db, this];
|
||||||
request.dispatchEvent(event);
|
request.dispatchEvent(event);
|
||||||
@ -2372,7 +2406,11 @@ export class BridgeIDBTransaction
|
|||||||
this._committed = true;
|
this._committed = true;
|
||||||
if (!this._error) {
|
if (!this._error) {
|
||||||
if (BridgeIDBFactory.enableTracing) {
|
if (BridgeIDBFactory.enableTracing) {
|
||||||
console.log("dispatching 'complete' event on transaction");
|
console.log(
|
||||||
|
`dispatching 'complete' event on transaction (${
|
||||||
|
this._debugName ?? "??"
|
||||||
|
})`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const event = new FakeEvent("complete");
|
const event = new FakeEvent("complete");
|
||||||
event.eventPath = [this._db, this];
|
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");
|
const store = db.createObjectStore("s");
|
||||||
},
|
},
|
||||||
(done, db) => {
|
(done, db) => {
|
||||||
(db as any)._debugName = method;
|
|
||||||
const tx = db.transaction("s", "readonly");
|
const tx = db.transaction("s", "readonly");
|
||||||
const store = tx.objectStore("s");
|
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 test, { ExecutionContext } from "ava";
|
||||||
import { BridgeIDBFactory } from "..";
|
import { BridgeIDBFactory, BridgeIDBRequest } from "..";
|
||||||
import {
|
import {
|
||||||
IDBDatabase,
|
IDBDatabase,
|
||||||
IDBIndex,
|
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) {
|
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;
|
export default queueTask;
|
||||||
|
Loading…
Reference in New Issue
Block a user