Fix #228: Uncaught [Filer Error] Your browser doesn't support IndexedDB or WebSQL.

This commit is contained in:
David Humphrey (:humph) david.humphrey@senecacollege.ca 2014-06-23 13:09:02 -04:00
parent 31b062152e
commit 98cd3e6da7
4 changed files with 334 additions and 347 deletions

View File

@ -1,130 +1,127 @@
(function(global) { var FILE_SYSTEM_NAME = require('../constants.js').FILE_SYSTEM_NAME;
var FILE_SYSTEM_NAME = require('../constants.js').FILE_SYSTEM_NAME; var FILE_STORE_NAME = require('../constants.js').FILE_STORE_NAME;
var FILE_STORE_NAME = require('../constants.js').FILE_STORE_NAME; var IDB_RW = require('../constants.js').IDB_RW;
var IDB_RW = require('../constants.js').IDB_RW; var IDB_RO = require('../constants.js').IDB_RO;
var IDB_RO = require('../constants.js').IDB_RO; var Errors = require('../errors.js');
var Errors = require('../errors.js');
var indexedDB = global.indexedDB || var indexedDB = global.indexedDB ||
global.mozIndexedDB || global.mozIndexedDB ||
global.webkitIndexedDB || global.webkitIndexedDB ||
global.msIndexedDB; global.msIndexedDB;
function IndexedDBContext(db, mode) { function IndexedDBContext(db, mode) {
var transaction = db.transaction(FILE_STORE_NAME, mode); var transaction = db.transaction(FILE_STORE_NAME, mode);
this.objectStore = transaction.objectStore(FILE_STORE_NAME); this.objectStore = transaction.objectStore(FILE_STORE_NAME);
}
IndexedDBContext.prototype.clear = function(callback) {
try {
var request = this.objectStore.clear();
request.onsuccess = function(event) {
callback();
};
request.onerror = function(error) {
callback(error);
};
} catch(e) {
callback(e);
} }
IndexedDBContext.prototype.clear = function(callback) { };
try { IndexedDBContext.prototype.get = function(key, callback) {
var request = this.objectStore.clear(); try {
request.onsuccess = function(event) { var request = this.objectStore.get(key);
callback(); request.onsuccess = function onsuccess(event) {
}; var result = event.target.result;
request.onerror = function(error) { callback(null, result);
callback(error); };
}; request.onerror = function onerror(error) {
} catch(e) { callback(error);
callback(e); };
} } catch(e) {
}; callback(e);
IndexedDBContext.prototype.get = function(key, callback) {
try {
var request = this.objectStore.get(key);
request.onsuccess = function onsuccess(event) {
var result = event.target.result;
callback(null, result);
};
request.onerror = function onerror(error) {
callback(error);
};
} catch(e) {
callback(e);
}
};
IndexedDBContext.prototype.put = function(key, value, callback) {
try {
var request = this.objectStore.put(value, key);
request.onsuccess = function onsuccess(event) {
var result = event.target.result;
callback(null, result);
};
request.onerror = function onerror(error) {
callback(error);
};
} catch(e) {
callback(e);
}
};
IndexedDBContext.prototype.delete = function(key, callback) {
try {
var request = this.objectStore.delete(key);
request.onsuccess = function onsuccess(event) {
var result = event.target.result;
callback(null, result);
};
request.onerror = function(error) {
callback(error);
};
} catch(e) {
callback(e);
}
};
function IndexedDB(name) {
this.name = name || FILE_SYSTEM_NAME;
this.db = null;
} }
IndexedDB.isSupported = function() { };
return !!indexedDB; IndexedDBContext.prototype.put = function(key, value, callback) {
}; try {
var request = this.objectStore.put(value, key);
request.onsuccess = function onsuccess(event) {
var result = event.target.result;
callback(null, result);
};
request.onerror = function onerror(error) {
callback(error);
};
} catch(e) {
callback(e);
}
};
IndexedDBContext.prototype.delete = function(key, callback) {
try {
var request = this.objectStore.delete(key);
request.onsuccess = function onsuccess(event) {
var result = event.target.result;
callback(null, result);
};
request.onerror = function(error) {
callback(error);
};
} catch(e) {
callback(e);
}
};
IndexedDB.prototype.open = function(callback) {
var that = this;
// Bail if we already have a db open function IndexedDB(name) {
if( that.db ) { this.name = name || FILE_SYSTEM_NAME;
callback(null, false); this.db = null;
return; }
IndexedDB.isSupported = function() {
return !!indexedDB;
};
IndexedDB.prototype.open = function(callback) {
var that = this;
// Bail if we already have a db open
if( that.db ) {
callback(null, false);
return;
}
// Keep track of whether we're accessing this db for the first time
// and therefore needs to get formatted.
var firstAccess = false;
// NOTE: we're not using versioned databases.
var openRequest = indexedDB.open(that.name);
// If the db doesn't exist, we'll create it
openRequest.onupgradeneeded = function onupgradeneeded(event) {
var db = event.target.result;
if(db.objectStoreNames.contains(FILE_STORE_NAME)) {
db.deleteObjectStore(FILE_STORE_NAME);
} }
db.createObjectStore(FILE_STORE_NAME);
// Keep track of whether we're accessing this db for the first time firstAccess = true;
// and therefore needs to get formatted.
var firstAccess = false;
// NOTE: we're not using versioned databases.
var openRequest = indexedDB.open(that.name);
// If the db doesn't exist, we'll create it
openRequest.onupgradeneeded = function onupgradeneeded(event) {
var db = event.target.result;
if(db.objectStoreNames.contains(FILE_STORE_NAME)) {
db.deleteObjectStore(FILE_STORE_NAME);
}
db.createObjectStore(FILE_STORE_NAME);
firstAccess = true;
};
openRequest.onsuccess = function onsuccess(event) {
that.db = event.target.result;
callback(null, firstAccess);
};
openRequest.onerror = function onerror(error) {
callback(new Errors.EINVAL('IndexedDB cannot be accessed. If private browsing is enabled, disable it.'));
};
};
IndexedDB.prototype.getReadOnlyContext = function() {
// Due to timing issues in Chrome with readwrite vs. readonly indexeddb transactions
// always use readwrite so we can make sure pending commits finish before callbacks.
// See https://github.com/js-platform/filer/issues/128
return new IndexedDBContext(this.db, IDB_RW);
};
IndexedDB.prototype.getReadWriteContext = function() {
return new IndexedDBContext(this.db, IDB_RW);
}; };
module.exports = IndexedDB; openRequest.onsuccess = function onsuccess(event) {
that.db = event.target.result;
callback(null, firstAccess);
};
openRequest.onerror = function onerror(error) {
callback(new Errors.EINVAL('IndexedDB cannot be accessed. If private browsing is enabled, disable it.'));
};
};
IndexedDB.prototype.getReadOnlyContext = function() {
// Due to timing issues in Chrome with readwrite vs. readonly indexeddb transactions
// always use readwrite so we can make sure pending commits finish before callbacks.
// See https://github.com/js-platform/filer/issues/128
return new IndexedDBContext(this.db, IDB_RW);
};
IndexedDB.prototype.getReadWriteContext = function() {
return new IndexedDBContext(this.db, IDB_RW);
};
}(this)); module.exports = IndexedDB;

View File

@ -1,161 +1,158 @@
(function(global) { var FILE_SYSTEM_NAME = require('../constants.js').FILE_SYSTEM_NAME;
var FILE_SYSTEM_NAME = require('../constants.js').FILE_SYSTEM_NAME; var FILE_STORE_NAME = require('../constants.js').FILE_STORE_NAME;
var FILE_STORE_NAME = require('../constants.js').FILE_STORE_NAME; var WSQL_VERSION = require('../constants.js').WSQL_VERSION;
var WSQL_VERSION = require('../constants.js').WSQL_VERSION; var WSQL_SIZE = require('../constants.js').WSQL_SIZE;
var WSQL_SIZE = require('../constants.js').WSQL_SIZE; var WSQL_DESC = require('../constants.js').WSQL_DESC;
var WSQL_DESC = require('../constants.js').WSQL_DESC; var u8toArray = require('../shared.js').u8toArray;
var u8toArray = require('../shared.js').u8toArray; var Errors = require('../errors.js');
var Errors = require('../errors.js');
function WebSQLContext(db, isReadOnly) { function WebSQLContext(db, isReadOnly) {
var that = this; var that = this;
this.getTransaction = function(callback) { this.getTransaction = function(callback) {
if(that.transaction) { if(that.transaction) {
callback(that.transaction); callback(that.transaction);
return; return;
}
// Either do readTransaction() (read-only) or transaction() (read/write)
db[isReadOnly ? 'readTransaction' : 'transaction'](function(transaction) {
that.transaction = transaction;
callback(transaction);
});
};
}
WebSQLContext.prototype.clear = function(callback) {
function onError(transaction, error) {
callback(error);
}
function onSuccess(transaction, result) {
callback(null);
}
this.getTransaction(function(transaction) {
transaction.executeSql("DELETE FROM " + FILE_STORE_NAME + ";",
[], onSuccess, onError);
});
};
WebSQLContext.prototype.get = function(key, callback) {
function onSuccess(transaction, result) {
// If the key isn't found, return null
var value = result.rows.length === 0 ? null : result.rows.item(0).data;
try {
if(value) {
value = JSON.parse(value);
// Deal with special-cased flattened typed arrays in WebSQL (see put() below)
if(value.__isUint8Array) {
value = new Uint8Array(value.__array);
}
} }
// Either do readTransaction() (read-only) or transaction() (read/write) callback(null, value);
db[isReadOnly ? 'readTransaction' : 'transaction'](function(transaction) { } catch(e) {
that.transaction = transaction; callback(e);
callback(transaction); }
}); }
function onError(transaction, error) {
callback(error);
}
this.getTransaction(function(transaction) {
transaction.executeSql("SELECT data FROM " + FILE_STORE_NAME + " WHERE id = ?;",
[key], onSuccess, onError);
});
};
WebSQLContext.prototype.put = function(key, value, callback) {
// We do extra work to make sure typed arrays survive
// being stored in the db and still get the right prototype later.
if(Object.prototype.toString.call(value) === "[object Uint8Array]") {
value = {
__isUint8Array: true,
__array: u8toArray(value)
}; };
} }
WebSQLContext.prototype.clear = function(callback) { value = JSON.stringify(value);
function onError(transaction, error) { function onSuccess(transaction, result) {
callback(error); callback(null);
}
function onSuccess(transaction, result) {
callback(null);
}
this.getTransaction(function(transaction) {
transaction.executeSql("DELETE FROM " + FILE_STORE_NAME + ";",
[], onSuccess, onError);
});
};
WebSQLContext.prototype.get = function(key, callback) {
function onSuccess(transaction, result) {
// If the key isn't found, return null
var value = result.rows.length === 0 ? null : result.rows.item(0).data;
try {
if(value) {
value = JSON.parse(value);
// Deal with special-cased flattened typed arrays in WebSQL (see put() below)
if(value.__isUint8Array) {
value = new Uint8Array(value.__array);
}
}
callback(null, value);
} catch(e) {
callback(e);
}
}
function onError(transaction, error) {
callback(error);
}
this.getTransaction(function(transaction) {
transaction.executeSql("SELECT data FROM " + FILE_STORE_NAME + " WHERE id = ?;",
[key], onSuccess, onError);
});
};
WebSQLContext.prototype.put = function(key, value, callback) {
// We do extra work to make sure typed arrays survive
// being stored in the db and still get the right prototype later.
if(Object.prototype.toString.call(value) === "[object Uint8Array]") {
value = {
__isUint8Array: true,
__array: u8toArray(value)
};
}
value = JSON.stringify(value);
function onSuccess(transaction, result) {
callback(null);
}
function onError(transaction, error) {
callback(error);
}
this.getTransaction(function(transaction) {
transaction.executeSql("INSERT OR REPLACE INTO " + FILE_STORE_NAME + " (id, data) VALUES (?, ?);",
[key, value], onSuccess, onError);
});
};
WebSQLContext.prototype.delete = function(key, callback) {
function onSuccess(transaction, result) {
callback(null);
}
function onError(transaction, error) {
callback(error);
}
this.getTransaction(function(transaction) {
transaction.executeSql("DELETE FROM " + FILE_STORE_NAME + " WHERE id = ?;",
[key], onSuccess, onError);
});
};
function WebSQL(name) {
this.name = name || FILE_SYSTEM_NAME;
this.db = null;
} }
WebSQL.isSupported = function() { function onError(transaction, error) {
return !!global.openDatabase; callback(error);
}; }
this.getTransaction(function(transaction) {
transaction.executeSql("INSERT OR REPLACE INTO " + FILE_STORE_NAME + " (id, data) VALUES (?, ?);",
[key, value], onSuccess, onError);
});
};
WebSQLContext.prototype.delete = function(key, callback) {
function onSuccess(transaction, result) {
callback(null);
}
function onError(transaction, error) {
callback(error);
}
this.getTransaction(function(transaction) {
transaction.executeSql("DELETE FROM " + FILE_STORE_NAME + " WHERE id = ?;",
[key], onSuccess, onError);
});
};
WebSQL.prototype.open = function(callback) {
var that = this;
// Bail if we already have a db open function WebSQL(name) {
if(that.db) { this.name = name || FILE_SYSTEM_NAME;
callback(null, false); this.db = null;
return; }
WebSQL.isSupported = function() {
return !!global.openDatabase;
};
WebSQL.prototype.open = function(callback) {
var that = this;
// Bail if we already have a db open
if(that.db) {
callback(null, false);
return;
}
var db = global.openDatabase(that.name, WSQL_VERSION, WSQL_DESC, WSQL_SIZE);
if(!db) {
callback("[WebSQL] Unable to open database.");
return;
}
function onError(transaction, error) {
if (error.code === 5) {
callback(new Errors.EINVAL('WebSQL cannot be accessed. If private browsing is enabled, disable it.'));
} }
callback(error);
}
function onSuccess(transaction, result) {
that.db = db;
var db = global.openDatabase(that.name, WSQL_VERSION, WSQL_DESC, WSQL_SIZE); function gotCount(transaction, result) {
if(!db) { var firstAccess = result.rows.item(0).count === 0;
callback("[WebSQL] Unable to open database."); callback(null, firstAccess);
return;
} }
function onError(transaction, error) { function onError(transaction, error) {
if (error.code === 5) {
callback(new Errors.EINVAL('WebSQL cannot be accessed. If private browsing is enabled, disable it.'));
}
callback(error); callback(error);
} }
function onSuccess(transaction, result) { // Keep track of whether we're accessing this db for the first time
that.db = db; // and therefore needs to get formatted.
transaction.executeSql("SELECT COUNT(id) AS count FROM " + FILE_STORE_NAME + ";",
[], gotCount, onError);
}
function gotCount(transaction, result) { // Create the table and index we'll need to store the fs data.
var firstAccess = result.rows.item(0).count === 0; db.transaction(function(transaction) {
callback(null, firstAccess); function createIndex(transaction) {
} transaction.executeSql("CREATE INDEX IF NOT EXISTS idx_" + FILE_STORE_NAME + "_id" +
function onError(transaction, error) { " on " + FILE_STORE_NAME + " (id);",
callback(error); [], onSuccess, onError);
}
// Keep track of whether we're accessing this db for the first time
// and therefore needs to get formatted.
transaction.executeSql("SELECT COUNT(id) AS count FROM " + FILE_STORE_NAME + ";",
[], gotCount, onError);
} }
transaction.executeSql("CREATE TABLE IF NOT EXISTS " + FILE_STORE_NAME + " (id unique, data TEXT);",
[], createIndex, onError);
});
};
WebSQL.prototype.getReadOnlyContext = function() {
return new WebSQLContext(this.db, true);
};
WebSQL.prototype.getReadWriteContext = function() {
return new WebSQLContext(this.db, false);
};
// Create the table and index we'll need to store the fs data. module.exports = WebSQL;
db.transaction(function(transaction) {
function createIndex(transaction) {
transaction.executeSql("CREATE INDEX IF NOT EXISTS idx_" + FILE_STORE_NAME + "_id" +
" on " + FILE_STORE_NAME + " (id);",
[], onSuccess, onError);
}
transaction.executeSql("CREATE TABLE IF NOT EXISTS " + FILE_STORE_NAME + " (id unique, data TEXT);",
[], createIndex, onError);
});
};
WebSQL.prototype.getReadOnlyContext = function() {
return new WebSQLContext(this.db, true);
};
WebSQL.prototype.getReadWriteContext = function() {
return new WebSQLContext(this.db, false);
};
module.exports = WebSQL;
}(this));

View File

@ -1,56 +1,53 @@
(function(global) { var Filer = require("../..");
var Filer = require("../..");
var indexedDB = global.indexedDB || var indexedDB = global.indexedDB ||
global.mozIndexedDB || global.mozIndexedDB ||
global.webkitIndexedDB || global.webkitIndexedDB ||
global.msIndexedDB; global.msIndexedDB;
var needsCleanup = []; var needsCleanup = [];
if(global.addEventListener) { if(global.addEventListener) {
global.addEventListener('beforeunload', function() { global.addEventListener('beforeunload', function() {
needsCleanup.forEach(function(f) { f(); }); needsCleanup.forEach(function(f) { f(); });
}); });
} }
function IndexedDBTestProvider(name) { function IndexedDBTestProvider(name) {
var _done = false; var _done = false;
var that = this; var that = this;
function cleanup(callback) { function cleanup(callback) {
if(!that.provider || _done) { if(!that.provider || _done) {
return; return;
}
// We have to force any other connections to close
// before we can delete a db.
if(that.provider.db) {
that.provider.db.close();
}
callback = callback || function(){};
var request = indexedDB.deleteDatabase(name);
function finished() {
that.provider = null;
_done = true;
callback();
}
request.onsuccess = finished;
request.onerror = finished;
} }
function init() { // We have to force any other connections to close
if(that.provider) { // before we can delete a db.
return; if(that.provider.db) {
} that.provider.db.close();
that.provider = new Filer.FileSystem.providers.IndexedDB(name);
needsCleanup.push(cleanup);
} }
this.init = init; callback = callback || function(){};
this.cleanup = cleanup; var request = indexedDB.deleteDatabase(name);
function finished() {
that.provider = null;
_done = true;
callback();
}
request.onsuccess = finished;
request.onerror = finished;
} }
module.exports = IndexedDBTestProvider; function init() {
if(that.provider) {
return;
}
that.provider = new Filer.FileSystem.providers.IndexedDB(name);
needsCleanup.push(cleanup);
}
}(this)); this.init = init;
this.cleanup = cleanup;
}
module.exports = IndexedDBTestProvider;

View File

@ -1,47 +1,43 @@
(function(global) { var Filer = require('../..');
var Filer = require('../..'); var needsCleanup = [];
if(global.addEventListener) {
window.addEventListener('beforeunload', function() {
needsCleanup.forEach(function(f) { f(); });
});
}
var needsCleanup = []; function WebSQLTestProvider(name) {
if(global.addEventListener) { var _done = false;
window.addEventListener('beforeunload', function() { var that = this;
needsCleanup.forEach(function(f) { f(); });
function cleanup(callback) {
if(!that.provider || _done) {
return;
}
// Provider is there, but db was never touched
if(!that.provider.db) {
return;
}
var context = that.provider.getReadWriteContext();
context.clear(function() {
that.provider = null;
_done = true;
callback();
}); });
} }
function WebSQLTestProvider(name) { function init() {
var _done = false; if(that.provider) {
var that = this; return;
function cleanup(callback) {
if(!that.provider || _done) {
return;
}
// Provider is there, but db was never touched
if(!that.provider.db) {
return;
}
var context = that.provider.getReadWriteContext();
context.clear(function() {
that.provider = null;
_done = true;
callback();
});
} }
that.provider = new Filer.FileSystem.providers.WebSQL(name);
function init() { needsCleanup.push(cleanup);
if(that.provider) {
return;
}
that.provider = new Filer.FileSystem.providers.WebSQL(name);
needsCleanup.push(cleanup);
}
this.init = init;
this.cleanup = cleanup;
} }
module.exports = WebSQLTestProvider; this.init = init;
this.cleanup = cleanup;
}
}(this)); module.exports = WebSQLTestProvider;