Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
var DEBUG = 0;
var debug;
if (DEBUG) {
debug = function (s) {
dump("-*- IndexedDBHelper: " + s + "\n");
};
} else {
debug = function () {};
}
function getErrorName(err) {
return (err && err.name) || "UnknownError";
}
export function IndexedDBHelper() {}
IndexedDBHelper.prototype = {
// Close the database
close: function close() {
if (this._db) {
this._db.close();
this._db = null;
}
},
/**
* Open a new database.
* User has to provide upgradeSchema.
*
* @param successCb
* Success callback to call once database is open.
* @param failureCb
* Error callback to call when an error is encountered.
*/
open: function open(aCallback) {
if (aCallback && !this._waitForOpenCallbacks.has(aCallback)) {
this._waitForOpenCallbacks.add(aCallback);
if (this._waitForOpenCallbacks.size !== 1) {
return;
}
}
let self = this;
let invokeCallbacks = err => {
for (let callback of self._waitForOpenCallbacks) {
callback(err);
}
self._waitForOpenCallbacks.clear();
};
if (DEBUG) {
debug("Try to open database:" + self.dbName + " " + self.dbVersion);
}
let req;
try {
req = indexedDB.open(this.dbName, this.dbVersion);
} catch (e) {
if (DEBUG) {
debug("Error opening database: " + self.dbName);
}
Services.tm.dispatchToMainThread(() => invokeCallbacks(getErrorName(e)));
return;
}
req.onsuccess = function (event) {
if (DEBUG) {
debug("Opened database:" + self.dbName + " " + self.dbVersion);
}
self._db = event.target.result;
self._db.onversionchange = function () {
if (DEBUG) {
debug("WARNING: DB modified from a different window.");
}
};
invokeCallbacks();
};
req.onupgradeneeded = function (aEvent) {
if (DEBUG) {
debug(
"Database needs upgrade:" +
self.dbName +
aEvent.oldVersion +
aEvent.newVersion
);
debug(
"Correct new database version:" +
(aEvent.newVersion == this.dbVersion)
);
}
let _db = aEvent.target.result;
self.upgradeSchema(
req.transaction,
_db,
aEvent.oldVersion,
aEvent.newVersion
);
};
req.onerror = function (aEvent) {
if (DEBUG) {
debug("Failed to open database: " + self.dbName);
}
invokeCallbacks(getErrorName(aEvent.target.error));
};
req.onblocked = function () {
if (DEBUG) {
debug("Opening database request is blocked.");
}
};
},
/**
* Use the cached DB or open a new one.
*
* @param successCb
* Success callback to call.
* @param failureCb
* Error callback to call when an error is encountered.
*/
ensureDB: function ensureDB(aSuccessCb, aFailureCb) {
if (this._db) {
if (DEBUG) {
debug("ensureDB: already have a database, returning early.");
}
if (aSuccessCb) {
Services.tm.dispatchToMainThread(aSuccessCb);
}
return;
}
this.open(aError => {
if (aError) {
aFailureCb && aFailureCb(aError);
} else {
aSuccessCb && aSuccessCb();
}
});
},
/**
* Start a new transaction.
*
* @param txn_type
* Type of transaction (e.g. "readwrite")
* @param store_name
* The object store you want to be passed to the callback
* @param callback
* Function to call when the transaction is available. It will
* be invoked with the transaction and the `store' object store.
* @param successCb
* Success callback to call on a successful transaction commit.
* The result is stored in txn.result (in the callback function).
* @param failureCb
* Error callback to call when an error is encountered.
*/
newTxn: function newTxn(
txn_type,
store_name,
callback,
successCb,
failureCb
) {
this.ensureDB(() => {
if (DEBUG) {
debug("Starting new transaction" + txn_type);
}
let txn;
try {
txn = this._db.transaction(
Array.isArray(store_name) ? store_name : this.dbStoreNames,
txn_type
);
} catch (e) {
if (DEBUG) {
debug("Error starting transaction: " + this.dbName);
}
failureCb(getErrorName(e));
return;
}
if (DEBUG) {
debug("Retrieving object store: " + this.dbName);
}
let stores;
if (Array.isArray(store_name)) {
stores = [];
for (let i = 0; i < store_name.length; ++i) {
stores.push(txn.objectStore(store_name[i]));
}
} else {
stores = txn.objectStore(store_name);
}
txn.oncomplete = function () {
if (DEBUG) {
debug("Transaction complete. Returning to callback.");
}
/*
* txn.result property is not part of the transaction object returned
* by this._db.transaction method called above.
* The property is expected to be set in the callback function.
* However, it can happen that the property is not set for some reason,
* so we have to check if the property exists before calling the
* success callback.
*/
if (successCb) {
if ("result" in txn) {
successCb(txn.result);
} else {
successCb();
}
}
};
txn.onabort = function () {
if (DEBUG) {
debug("Caught error on transaction");
}
/*
* txn.error property is part of the transaction object returned by
* this._db.transaction method called above.
* The attribute is defined in IDBTranscation WebIDL interface.
* It may be null.
*/
if (failureCb) {
failureCb(getErrorName(txn.error));
}
};
callback(txn, stores);
}, failureCb);
},
/**
* Initialize the DB. Does not call open.
*
* @param aDBName
* DB name for the open call.
* @param aDBVersion
* Current DB version. User has to implement upgradeSchema.
* @param aDBStoreName
* ObjectStore that is used.
*/
initDBHelper: function initDBHelper(aDBName, aDBVersion, aDBStoreNames) {
this.dbName = aDBName;
this.dbVersion = aDBVersion;
this.dbStoreNames = aDBStoreNames;
// Cache the database.
this._db = null;
this._waitForOpenCallbacks = new Set();
},
};