Bug 1842773 - Part 5: Add ArrayBuffer.prototype.{maxByteLength,resizable} getters...
[gecko.git] / dom / base / IndexedDBHelper.sys.mjs
bloba36b211c0ad9596aff6163d136d126a26f5bee26
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/. */
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 (s) {};
15 function getErrorName(err) {
16   return (err && err.name) || "UnknownError";
19 export function IndexedDBHelper() {}
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   },
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     }
47     let self = this;
48     let invokeCallbacks = err => {
49       for (let callback of self._waitForOpenCallbacks) {
50         callback(err);
51       }
52       self._waitForOpenCallbacks.clear();
53     };
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 (event) {
74         if (DEBUG) {
75           debug("WARNING: DB modified from a different window.");
76         }
77       };
78       invokeCallbacks();
79     };
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       }
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 (aEvent) {
110       if (DEBUG) {
111         debug("Opening database request is blocked.");
112       }
113     };
114   },
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   },
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       }
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       };
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   },
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   },