IndexedDBHelper.sys.mjs (6843B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 var DEBUG = 0; 6 var debug; 7 if (DEBUG) { 8 debug = function (s) { 9 dump("-*- IndexedDBHelper: " + s + "\n"); 10 }; 11 } else { 12 debug = function () {}; 13 } 14 15 function getErrorName(err) { 16 return (err && err.name) || "UnknownError"; 17 } 18 19 export function IndexedDBHelper() {} 20 21 IndexedDBHelper.prototype = { 22 // Close the database 23 close: function close() { 24 if (this._db) { 25 this._db.close(); 26 this._db = null; 27 } 28 }, 29 30 /** 31 * Open a new database. 32 * User has to provide upgradeSchema. 33 * 34 * @param successCb 35 * Success callback to call once database is open. 36 * @param failureCb 37 * Error callback to call when an error is encountered. 38 */ 39 open: function open(aCallback) { 40 if (aCallback && !this._waitForOpenCallbacks.has(aCallback)) { 41 this._waitForOpenCallbacks.add(aCallback); 42 if (this._waitForOpenCallbacks.size !== 1) { 43 return; 44 } 45 } 46 47 let self = this; 48 let invokeCallbacks = err => { 49 for (let callback of self._waitForOpenCallbacks) { 50 callback(err); 51 } 52 self._waitForOpenCallbacks.clear(); 53 }; 54 55 if (DEBUG) { 56 debug("Try to open database:" + self.dbName + " " + self.dbVersion); 57 } 58 let req; 59 try { 60 req = indexedDB.open(this.dbName, this.dbVersion); 61 } catch (e) { 62 if (DEBUG) { 63 debug("Error opening database: " + self.dbName); 64 } 65 Services.tm.dispatchToMainThread(() => invokeCallbacks(getErrorName(e))); 66 return; 67 } 68 req.onsuccess = function (event) { 69 if (DEBUG) { 70 debug("Opened database:" + self.dbName + " " + self.dbVersion); 71 } 72 self._db = event.target.result; 73 self._db.onversionchange = function () { 74 if (DEBUG) { 75 debug("WARNING: DB modified from a different window."); 76 } 77 }; 78 invokeCallbacks(); 79 }; 80 81 req.onupgradeneeded = function (aEvent) { 82 if (DEBUG) { 83 debug( 84 "Database needs upgrade:" + 85 self.dbName + 86 aEvent.oldVersion + 87 aEvent.newVersion 88 ); 89 debug( 90 "Correct new database version:" + 91 (aEvent.newVersion == this.dbVersion) 92 ); 93 } 94 95 let _db = aEvent.target.result; 96 self.upgradeSchema( 97 req.transaction, 98 _db, 99 aEvent.oldVersion, 100 aEvent.newVersion 101 ); 102 }; 103 req.onerror = function (aEvent) { 104 if (DEBUG) { 105 debug("Failed to open database: " + self.dbName); 106 } 107 invokeCallbacks(getErrorName(aEvent.target.error)); 108 }; 109 req.onblocked = function () { 110 if (DEBUG) { 111 debug("Opening database request is blocked."); 112 } 113 }; 114 }, 115 116 /** 117 * Use the cached DB or open a new one. 118 * 119 * @param successCb 120 * Success callback to call. 121 * @param failureCb 122 * Error callback to call when an error is encountered. 123 */ 124 ensureDB: function ensureDB(aSuccessCb, aFailureCb) { 125 if (this._db) { 126 if (DEBUG) { 127 debug("ensureDB: already have a database, returning early."); 128 } 129 if (aSuccessCb) { 130 Services.tm.dispatchToMainThread(aSuccessCb); 131 } 132 return; 133 } 134 this.open(aError => { 135 if (aError) { 136 aFailureCb && aFailureCb(aError); 137 } else { 138 aSuccessCb && aSuccessCb(); 139 } 140 }); 141 }, 142 143 /** 144 * Start a new transaction. 145 * 146 * @param txn_type 147 * Type of transaction (e.g. "readwrite") 148 * @param store_name 149 * The object store you want to be passed to the callback 150 * @param callback 151 * Function to call when the transaction is available. It will 152 * be invoked with the transaction and the `store' object store. 153 * @param successCb 154 * Success callback to call on a successful transaction commit. 155 * The result is stored in txn.result (in the callback function). 156 * @param failureCb 157 * Error callback to call when an error is encountered. 158 */ 159 newTxn: function newTxn( 160 txn_type, 161 store_name, 162 callback, 163 successCb, 164 failureCb 165 ) { 166 this.ensureDB(() => { 167 if (DEBUG) { 168 debug("Starting new transaction" + txn_type); 169 } 170 let txn; 171 try { 172 txn = this._db.transaction( 173 Array.isArray(store_name) ? store_name : this.dbStoreNames, 174 txn_type 175 ); 176 } catch (e) { 177 if (DEBUG) { 178 debug("Error starting transaction: " + this.dbName); 179 } 180 failureCb(getErrorName(e)); 181 return; 182 } 183 if (DEBUG) { 184 debug("Retrieving object store: " + this.dbName); 185 } 186 let stores; 187 if (Array.isArray(store_name)) { 188 stores = []; 189 for (let i = 0; i < store_name.length; ++i) { 190 stores.push(txn.objectStore(store_name[i])); 191 } 192 } else { 193 stores = txn.objectStore(store_name); 194 } 195 196 txn.oncomplete = function () { 197 if (DEBUG) { 198 debug("Transaction complete. Returning to callback."); 199 } 200 /* 201 * txn.result property is not part of the transaction object returned 202 * by this._db.transaction method called above. 203 * The property is expected to be set in the callback function. 204 * However, it can happen that the property is not set for some reason, 205 * so we have to check if the property exists before calling the 206 * success callback. 207 */ 208 if (successCb) { 209 if ("result" in txn) { 210 successCb(txn.result); 211 } else { 212 successCb(); 213 } 214 } 215 }; 216 217 txn.onabort = function () { 218 if (DEBUG) { 219 debug("Caught error on transaction"); 220 } 221 /* 222 * txn.error property is part of the transaction object returned by 223 * this._db.transaction method called above. 224 * The attribute is defined in IDBTranscation WebIDL interface. 225 * It may be null. 226 */ 227 if (failureCb) { 228 failureCb(getErrorName(txn.error)); 229 } 230 }; 231 callback(txn, stores); 232 }, failureCb); 233 }, 234 235 /** 236 * Initialize the DB. Does not call open. 237 * 238 * @param aDBName 239 * DB name for the open call. 240 * @param aDBVersion 241 * Current DB version. User has to implement upgradeSchema. 242 * @param aDBStoreName 243 * ObjectStore that is used. 244 */ 245 initDBHelper: function initDBHelper(aDBName, aDBVersion, aDBStoreNames) { 246 this.dbName = aDBName; 247 this.dbVersion = aDBVersion; 248 this.dbStoreNames = aDBStoreNames; 249 // Cache the database. 250 this._db = null; 251 this._waitForOpenCallbacks = new Set(); 252 }, 253 };