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