Skip to content

Instantly share code, notes, and snippets.

@bgrins
Created February 20, 2015 16:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bgrins/3b3db4c49e56d544bf9a to your computer and use it in GitHub Desktop.
Save bgrins/3b3db4c49e56d544bf9a to your computer and use it in GitHub Desktop.
const {Cc, Ci, Cu, Cr} = require("chrome");
Cu.importGlobalProperties(["indexedDB"]);
const {Promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
/*!
localForage -- Offline Storage, Improved
Version 1.2.2
https://mozilla.github.io/localForage
(c) 2013-2015 Mozilla, Apache License 2.0
*/
module.exports = (function() {
"use strict";
// Some code originally from async_storage.js in
// [Gaia](https://github.com/mozilla-b2g/gaia).
// Originally found in https://github.com/mozilla-b2g/gaia/blob/e8f624e4cc9ea945727278039b3bc9bcb9f8667a/shared/js/async_storage.js
// Open the IndexedDB database (automatically creates one if one didn't
// previously exist), using any options set in the config.
function _initStorage(options) {
var self = this;
var dbInfo = {
db: null
};
if (options) {
for (var i in options) {
dbInfo[i] = options[i];
}
}
return new Promise(function(resolve, reject) {
var openreq = indexedDB.open(dbInfo.name, dbInfo.version);
openreq.onerror = function() {
reject(openreq.error);
};
openreq.onupgradeneeded = function() {
// First time setup: create an empty object store
openreq.result.createObjectStore(dbInfo.storeName);
};
openreq.onsuccess = function() {
dbInfo.db = openreq.result;
self._dbInfo = dbInfo;
resolve();
};
});
}
function getItem(key, callback) {
var self = this;
// Cast the key to a string, as that's all we can set as a key.
if (typeof key !== 'string') {
window.console.warn(key +
' used as a key, but it is not a string.');
key = String(key);
}
var promise = new Promise(function(resolve, reject) {
self.ready().then(function() {
var dbInfo = self._dbInfo;
var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly')
.objectStore(dbInfo.storeName);
var req = store.get(key);
req.onsuccess = function() {
var value = req.result;
if (value === undefined) {
value = null;
}
resolve(value);
};
req.onerror = function() {
reject(req.error);
};
})["catch"](reject);
});
executeDeferedCallback(promise, callback);
return promise;
}
// Iterate over all items stored in database.
function iterate(iterator, callback) {
var self = this;
var promise = new Promise(function(resolve, reject) {
self.ready().then(function() {
var dbInfo = self._dbInfo;
var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly')
.objectStore(dbInfo.storeName);
var req = store.openCursor();
var iterationNumber = 1;
req.onsuccess = function() {
var cursor = req.result;
if (cursor) {
var result = iterator(cursor.value, cursor.key, iterationNumber++);
if (result !== void(0)) {
resolve(result);
} else {
cursor["continue"]();
}
} else {
resolve();
}
};
req.onerror = function() {
reject(req.error);
};
})["catch"](reject);
});
executeDeferedCallback(promise, callback);
return promise;
}
function setItem(key, value, callback) {
var self = this;
// Cast the key to a string, as that's all we can set as a key.
if (typeof key !== 'string') {
window.console.warn(key +
' used as a key, but it is not a string.');
key = String(key);
}
var promise = new Promise(function(resolve, reject) {
self.ready().then(function() {
var dbInfo = self._dbInfo;
var transaction = dbInfo.db.transaction(dbInfo.storeName, 'readwrite');
var store = transaction.objectStore(dbInfo.storeName);
// The reason we don't _save_ null is because IE 10 does
// not support saving the `null` type in IndexedDB. How
// ironic, given the bug below!
// See: https://github.com/mozilla/localForage/issues/161
if (value === null) {
value = undefined;
}
var req = store.put(value, key);
transaction.oncomplete = function() {
// Cast to undefined so the value passed to
// callback/promise is the same as what one would get out
// of `getItem()` later. This leads to some weirdness
// (setItem('foo', undefined) will return `null`), but
// it's not my fault localStorage is our baseline and that
// it's weird.
if (value === undefined) {
value = null;
}
resolve(value);
};
transaction.onabort = transaction.onerror = function() {
reject(req.error);
};
})["catch"](reject);
});
executeDeferedCallback(promise, callback);
return promise;
}
function removeItem(key, callback) {
var self = this;
// Cast the key to a string, as that's all we can set as a key.
if (typeof key !== 'string') {
window.console.warn(key +
' used as a key, but it is not a string.');
key = String(key);
}
var promise = new Promise(function(resolve, reject) {
self.ready().then(function() {
var dbInfo = self._dbInfo;
var transaction = dbInfo.db.transaction(dbInfo.storeName, 'readwrite');
var store = transaction.objectStore(dbInfo.storeName);
// We use a Grunt task to make this safe for IE and some
// versions of Android (including those used by Cordova).
// Normally IE won't like `.delete()` and will insist on
// using `['delete']()`, but we have a build step that
// fixes this for us now.
var req = store["delete"](key);
transaction.oncomplete = function() {
resolve();
};
transaction.onerror = function() {
reject(req.error);
};
// The request will be aborted if we've exceeded our storage
// space. In this case, we will reject with a specific
// "QuotaExceededError".
transaction.onabort = function(event) {
var error = event.target.error;
if (error === 'QuotaExceededError') {
reject(error);
}
};
})["catch"](reject);
});
executeDeferedCallback(promise, callback);
return promise;
}
function clear(callback) {
var self = this;
var promise = new Promise(function(resolve, reject) {
self.ready().then(function() {
var dbInfo = self._dbInfo;
var transaction = dbInfo.db.transaction(dbInfo.storeName, 'readwrite');
var store = transaction.objectStore(dbInfo.storeName);
var req = store.clear();
transaction.oncomplete = function() {
resolve();
};
transaction.onabort = transaction.onerror = function() {
reject(req.error);
};
})["catch"](reject);
});
executeDeferedCallback(promise, callback);
return promise;
}
function length(callback) {
var self = this;
var promise = new Promise(function(resolve, reject) {
self.ready().then(function() {
var dbInfo = self._dbInfo;
var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly')
.objectStore(dbInfo.storeName);
var req = store.count();
req.onsuccess = function() {
resolve(req.result);
};
req.onerror = function() {
reject(req.error);
};
})["catch"](reject);
});
executeCallback(promise, callback);
return promise;
}
function key(n, callback) {
var self = this;
var promise = new Promise(function(resolve, reject) {
if (n < 0) {
resolve(null);
return;
}
self.ready().then(function() {
var dbInfo = self._dbInfo;
var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly')
.objectStore(dbInfo.storeName);
var advanced = false;
var req = store.openCursor();
req.onsuccess = function() {
var cursor = req.result;
if (!cursor) {
// this means there weren't enough keys
resolve(null);
return;
}
if (n === 0) {
// We have the first key, return it if that's what they
// wanted.
resolve(cursor.key);
} else {
if (!advanced) {
// Otherwise, ask the cursor to skip ahead n
// records.
advanced = true;
cursor.advance(n);
} else {
// When we get here, we've got the nth key.
resolve(cursor.key);
}
}
};
req.onerror = function() {
reject(req.error);
};
})["catch"](reject);
});
executeCallback(promise, callback);
return promise;
}
function keys(callback) {
var self = this;
var promise = new Promise(function(resolve, reject) {
self.ready().then(function() {
var dbInfo = self._dbInfo;
var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly')
.objectStore(dbInfo.storeName);
var req = store.openCursor();
var keys = [];
req.onsuccess = function() {
var cursor = req.result;
if (!cursor) {
resolve(keys);
return;
}
keys.push(cursor.key);
cursor["continue"]();
};
req.onerror = function() {
reject(req.error);
};
})["catch"](reject);
});
executeCallback(promise, callback);
return promise;
}
function executeCallback(promise, callback) {
if (callback) {
promise.then(function(result) {
callback(null, result);
}, function(error) {
callback(error);
});
}
}
function executeDeferedCallback(promise, callback) {
if (callback) {
promise.then(function(result) {
deferCallback(callback, result);
}, function(error) {
callback(error);
});
}
}
// Under Chrome the callback is called before the changes (save, clear)
// are actually made. So we use a defer function which wait that the
// call stack to be empty.
// For more info : https://github.com/mozilla/localForage/issues/175
// Pull request : https://github.com/mozilla/localForage/pull/178
function deferCallback(callback, result) {
if (callback) {
return setTimeout(function() {
return callback(null, result);
}, 0);
}
}
var asyncStorage = {
_driver: 'asyncStorage',
_initStorage: _initStorage,
iterate: iterate,
getItem: getItem,
setItem: setItem,
removeItem: removeItem,
clear: clear,
length: length,
key: key,
keys: keys
};
// Create an object to interface similarly to localForage.
// We don't need the full-blown object, since we are only
// ever going to use the idb driver.
function localForageShim() {
}
localForageShim.prototype = {
_ready: null,
_dbInfo: null,
_driver: 'asyncStorage',
_initStorage: _initStorage,
iterate: iterate,
getItem: getItem,
setItem: setItem,
removeItem: removeItem,
clear: clear,
length: length,
key: key,
keys: keys
};
localForageShim.prototype.ready = function() {
var ready;;
if (!this._ready) {
ready = this._initStorage({
name: "devtools-async-storage",
version: 1,
storeName: "key-values",
});
console.log("Here", ready, this._initStorage);
}
return this._ready;
};
return new localForageShim();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment