diff options
Diffstat (limited to 'dom/base/IndexedDBHelper.sys.mjs')
-rw-r--r-- | dom/base/IndexedDBHelper.sys.mjs | 253 |
1 files changed, 253 insertions, 0 deletions
diff --git a/dom/base/IndexedDBHelper.sys.mjs b/dom/base/IndexedDBHelper.sys.mjs new file mode 100644 index 0000000000..a36b211c0a --- /dev/null +++ b/dom/base/IndexedDBHelper.sys.mjs @@ -0,0 +1,253 @@ +/* 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 (s) {}; +} + +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 (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.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(); + }, +}; |