summaryrefslogtreecommitdiff
path: root/dom/base/IndexedDBHelper.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'dom/base/IndexedDBHelper.jsm')
-rw-r--r--dom/base/IndexedDBHelper.jsm208
1 files changed, 208 insertions, 0 deletions
diff --git a/dom/base/IndexedDBHelper.jsm b/dom/base/IndexedDBHelper.jsm
new file mode 100644
index 0000000000..587fb901f9
--- /dev/null
+++ b/dom/base/IndexedDBHelper.jsm
@@ -0,0 +1,208 @@
+/* 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/. */
+
+"use strict";
+
+var DEBUG = 0;
+var debug;
+if (DEBUG) {
+ debug = function (s) { dump("-*- IndexedDBHelper: " + s + "\n"); }
+} else {
+ debug = function (s) {}
+}
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+this.EXPORTED_SYMBOLS = ["IndexedDBHelper"];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.importGlobalProperties(["indexedDB"]);
+
+XPCOMUtils.defineLazyModuleGetter(this, 'Services',
+ 'resource://gre/modules/Services.jsm');
+
+function getErrorName(err) {
+ return err && err.name || "UnknownError";
+}
+
+this.IndexedDBHelper = 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.currentThread.dispatch(() => invokeCallbacks(getErrorName(e)),
+ Ci.nsIThread.DISPATCH_NORMAL);
+ return;
+ }
+ req.onsuccess = function (event) {
+ if (DEBUG) debug("Opened database:" + self.dbName + " " + self.dbVersion);
+ self._db = event.target.result;
+ self._db.onversionchange = function(event) {
+ 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 (aEvent) {
+ 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.currentThread.dispatch(aSuccessCb,
+ Ci.nsIThread.DISPATCH_NORMAL);
+ }
+ 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.
+ * @param failureCb
+ * Error callback to call when an error is encountered.
+ */
+ newTxn: function newTxn(txn_type, store_name, callback, successCb, failureCb) {
+ this.ensureDB(function () {
+ 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 (event) {
+ if (DEBUG) debug("Transaction complete. Returning to callback.");
+ if (successCb) {
+ successCb(txn.result);
+ }
+ };
+
+ txn.onabort = function (event) {
+ if (DEBUG) debug("Caught error on transaction");
+ /*
+ * event.target.error may be null
+ * if txn was aborted by calling txn.abort()
+ */
+ if (failureCb) {
+ failureCb(getErrorName(event.target.error));
+ }
+ };
+ callback(txn, stores);
+ }.bind(this), 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();
+ }
+}