gecko-dev/dom/base/IndexedDBHelper.jsm
Kit Cambridge 7810d425a8 Bug 1264710 - Catch IDB exceptions in IndexedDBHelper. r=fabrice
MozReview-Commit-ID: 7L2lXkYvc1f

--HG--
extra : rebase_source : dabd3510266ae8445bc7d79507e56734ba879533
2016-04-14 10:00:42 -07:00

209 lines
6.2 KiB
JavaScript

/* 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();
}
}