ZeroOmega/omega-web/lib/fake-indexeddb/FDBFactory.js

256 lines
8.3 KiB
JavaScript

import FDBDatabase from "./FDBDatabase.js";
import FDBOpenDBRequest from "./FDBOpenDBRequest.js";
import FDBVersionChangeEvent from "./FDBVersionChangeEvent.js";
import cmp from "./lib/cmp.js";
import Database from "./lib/Database.js";
import enforceRange from "./lib/enforceRange.js";
import { AbortError, VersionError } from "./lib/errors.js";
import FakeEvent from "./lib/FakeEvent.js";
import { queueTask } from "./lib/scheduling.js";
const waitForOthersClosedDelete = (databases, name, openDatabases, cb) => {
const anyOpen = openDatabases.some(openDatabase2 => {
return !openDatabase2._closed && !openDatabase2._closePending;
});
if (anyOpen) {
queueTask(() => waitForOthersClosedDelete(databases, name, openDatabases, cb));
return;
}
databases.delete(name);
cb(null);
};
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-deleting-a-database
const deleteDatabase = (databases, name, request, cb) => {
try {
const db = databases.get(name);
if (db === undefined) {
cb(null);
return;
}
db.deletePending = true;
const openDatabases = db.connections.filter(connection => {
return !connection._closed && !connection._closePending;
});
for (const openDatabase2 of openDatabases) {
if (!openDatabase2._closePending) {
const event = new FDBVersionChangeEvent("versionchange", {
newVersion: null,
oldVersion: db.version
});
openDatabase2.dispatchEvent(event);
}
}
const anyOpen = openDatabases.some(openDatabase3 => {
return !openDatabase3._closed && !openDatabase3._closePending;
});
if (request && anyOpen) {
const event = new FDBVersionChangeEvent("blocked", {
newVersion: null,
oldVersion: db.version
});
request.dispatchEvent(event);
}
waitForOthersClosedDelete(databases, name, openDatabases, cb);
} catch (err) {
cb(err);
}
};
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-running-a-versionchange-transaction
const runVersionchangeTransaction = (connection, version, request, cb) => {
connection._runningVersionchangeTransaction = true;
const oldVersion = connection.version;
const openDatabases = connection._rawDatabase.connections.filter(otherDatabase => {
return connection !== otherDatabase;
});
for (const openDatabase2 of openDatabases) {
if (!openDatabase2._closed && !openDatabase2._closePending) {
const event = new FDBVersionChangeEvent("versionchange", {
newVersion: version,
oldVersion
});
openDatabase2.dispatchEvent(event);
}
}
const anyOpen = openDatabases.some(openDatabase3 => {
return !openDatabase3._closed && !openDatabase3._closePending;
});
if (anyOpen) {
const event = new FDBVersionChangeEvent("blocked", {
newVersion: version,
oldVersion
});
request.dispatchEvent(event);
}
const waitForOthersClosed = () => {
const anyOpen2 = openDatabases.some(openDatabase2 => {
return !openDatabase2._closed && !openDatabase2._closePending;
});
if (anyOpen2) {
queueTask(waitForOthersClosed);
return;
}
// Set the version of database to version. This change is considered part of the transaction, and so if the
// transaction is aborted, this change is reverted.
connection._rawDatabase.version = version;
connection.version = version;
// Get rid of this setImmediate?
const transaction = connection.transaction(connection.objectStoreNames, "versionchange");
request.result = connection;
request.readyState = "done";
request.transaction = transaction;
transaction._rollbackLog.push(() => {
connection._rawDatabase.version = oldVersion;
connection.version = oldVersion;
});
const event = new FDBVersionChangeEvent("upgradeneeded", {
newVersion: version,
oldVersion
});
request.dispatchEvent(event);
transaction.addEventListener("error", () => {
connection._runningVersionchangeTransaction = false;
// throw arguments[0].target.error;
// console.log("error in versionchange transaction - not sure if anything needs to be done here", e.target.error.name);
});
transaction.addEventListener("abort", () => {
connection._runningVersionchangeTransaction = false;
request.transaction = null;
queueTask(() => {
cb(new AbortError());
});
});
transaction.addEventListener("complete", () => {
connection._runningVersionchangeTransaction = false;
request.transaction = null;
// Let other complete event handlers run before continuing
queueTask(() => {
if (connection._closePending) {
cb(new AbortError());
} else {
cb(null);
}
});
});
};
waitForOthersClosed();
};
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-opening-a-database
const openDatabase = (databases, name, version, request, cb) => {
let db = databases.get(name);
if (db === undefined) {
db = new Database(name, 0);
databases.set(name, db);
}
if (version === undefined) {
version = db.version !== 0 ? db.version : 1;
}
if (db.version > version) {
return cb(new VersionError());
}
const connection = new FDBDatabase(db);
if (db.version < version) {
runVersionchangeTransaction(connection, version, request, err => {
if (err) {
// DO THIS HERE: ensure that connection is closed by running the steps for closing a database connection before these
// steps are aborted.
return cb(err);
}
cb(null, connection);
});
} else {
cb(null, connection);
}
};
class FDBFactory {
cmp = cmp;
_databases = new Map();
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBFactory-deleteDatabase-IDBOpenDBRequest-DOMString-name
deleteDatabase(name) {
const request = new FDBOpenDBRequest();
request.source = null;
queueTask(() => {
const db = this._databases.get(name);
const oldVersion = db !== undefined ? db.version : 0;
deleteDatabase(this._databases, name, request, err => {
if (err) {
request.error = new DOMException(err.message, err.name);
request.readyState = "done";
const event = new FakeEvent("error", {
bubbles: true,
cancelable: true
});
event.eventPath = [];
request.dispatchEvent(event);
return;
}
request.result = undefined;
request.readyState = "done";
const event2 = new FDBVersionChangeEvent("success", {
newVersion: null,
oldVersion
});
request.dispatchEvent(event2);
});
});
return request;
}
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBFactory-open-IDBOpenDBRequest-DOMString-name-unsigned-long-long-version
open(name, version) {
if (arguments.length > 1 && version !== undefined) {
// Based on spec, not sure why "MAX_SAFE_INTEGER" instead of "unsigned long long", but it's needed to pass
// tests
version = enforceRange(version, "MAX_SAFE_INTEGER");
}
if (version === 0) {
throw new TypeError();
}
const request = new FDBOpenDBRequest();
request.source = null;
queueTask(() => {
openDatabase(this._databases, name, version, request, (err, connection) => {
if (err) {
request.result = undefined;
request.readyState = "done";
request.error = new DOMException(err.message, err.name);
const event = new FakeEvent("error", {
bubbles: true,
cancelable: true
});
event.eventPath = [];
request.dispatchEvent(event);
return;
}
request.result = connection;
request.readyState = "done";
const event2 = new FakeEvent("success");
event2.eventPath = [];
request.dispatchEvent(event2);
});
});
return request;
}
// https://w3c.github.io/IndexedDB/#dom-idbfactory-databases
databases() {
return new Promise(resolve => {
const result = [];
for (const [name, database] of this._databases) {
result.push({
name,
version: database.version
});
}
resolve(result);
});
}
toString() {
return "[object IDBFactory]";
}
}
export default FDBFactory;