text: /*
   * Includes code from:
   *
   * base64-arraybuffer
   * https://github.com/niklasvh/base64-arraybuffer
   *
   * Copyright (c) 2012 Niklas von Hertzen
   * Licensed under the MIT license.
   */
  (function() {
      'use strict';
  
      // Sadly, the best way to save binary data in WebSQL is Base64 serializing
      // it, so this is how we store it to prevent very strange errors with less
      // verbose ways of binary <-> string data storage.
      var BASE_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
  
      // Promises!
      var Promise = (typeof module !== 'undefined' && module.exports) ?
                    require('promise') : this.Promise;
  
      var openDatabase = this.openDatabase;
      var db = null;
      var dbInfo = {};
  
      var SERIALIZED_MARKER = '__lfsc__:';
      var SERIALIZED_MARKER_LENGTH = SERIALIZED_MARKER.length;
  
      // OMG the serializations!
      var TYPE_ARRAYBUFFER = 'arbf';
      var TYPE_BLOB = 'blob';
      var TYPE_INT8ARRAY = 'si08';
      var TYPE_UINT8ARRAY = 'ui08';
      var TYPE_UINT8CLAMPEDARRAY = 'uic8';
      var TYPE_INT16ARRAY = 'si16';
      var TYPE_INT32ARRAY = 'si32';
      var TYPE_UINT16ARRAY = 'ur16';
      var TYPE_UINT32ARRAY = 'ui32';
      var TYPE_FLOAT32ARRAY = 'fl32';
      var TYPE_FLOAT64ARRAY = 'fl64';
      var TYPE_SERIALIZED_MARKER_LENGTH = SERIALIZED_MARKER_LENGTH + TYPE_ARRAYBUFFER.length;
  
      // If WebSQL methods aren't available, we can stop now.
      if (!openDatabase) {
          return;
      }
  
      // Open the WebSQL database (automatically creates one if one didn't
      // previously exist), using any options set in the config.
      function _initStorage(options) {
          var _this = this;
  
          if (options) {
              for (var i in options) {
                  dbInfo[i] = typeof(options[i]) !== 'string' ? options[i].toString() : options[i];
              }
          }
  
          return new Promise(function(resolve) {
              // Open the database; the openDatabase API will automatically
              // create it for us if it doesn't exist.
              try {
                  db = openDatabase(dbInfo.name, dbInfo.version,
                                    dbInfo.description, dbInfo.size);
              } catch (e) {
                  return _this.setDriver('localStorageWrapper').then(resolve);
              }
  
              // Create our key/value table if it doesn't exist.
              db.transaction(function(t) {
                  t.executeSql('CREATE TABLE IF NOT EXISTS ' + dbInfo.storeName + 
                               ' (id INTEGER PRIMARY KEY, key unique, value)', [], function() {
                      resolve();
                  }, null);
              });
          });
      }
  
      function getItem(key, callback) {
          var _this = this;
          return new Promise(function(resolve, reject) {
              _this.ready().then(function() {
                  db.transaction(function(t) {
                      t.executeSql('SELECT * FROM ' + dbInfo.storeName + 
                                   ' WHERE key = ? LIMIT 1', [key], function(t, results) {
                          var result = results.rows.length ? results.rows.item(0).value : null;
  
                          // Check to see if this is serialized content we need to
                          // unpack.
                          if (result) {
                              result = _deserialize(result);
                          }
  
                          if (callback) {
                              callback(result);
                          }
  
                          resolve(result);
                      }, function(t, error) {
                          if (callback) {
                              callback(null, error);
                          }
  
                          reject(error);
                      });
                  });
              });
          });
      }
  
      function setItem(key, value, callback) {
          var _this = this;
          return new Promise(function(resolve, reject) {
              _this.ready().then(function() {
                  // The localStorage API doesn't return undefined values in an
                  // "expected" way, so undefined is always cast to null in all
                  // drivers. See: https://github.com/mozilla/localForage/pull/42
                  if (value === undefined) {
                      value = null;
                  }
  
                  // Save the original value to pass to the callback.
                  var originalValue = value;
  
                  _serialize(value, function(value, error) {
                      if (error) {
                          reject(error);
                      } else {
                          db.transaction(function(t) {
                              t.executeSql('INSERT OR REPLACE INTO ' + dbInfo.storeName + 
                                           ' (key, value) VALUES (?, ?)', [key, value], function() {
                                  if (callback) {
                                      callback(originalValue);
                                  }
  
                                  resolve(originalValue);
                              }, function(t, error) {
                                  if (callback) {
                                      callback(null, error);
                                  }
  
                                  reject(error);
                              });
                          }, function(sqlError) { // The transaction failed; check
                                                  // to see if it's a quota error.
                              if (sqlError.code === sqlError.QUOTA_ERR) {
                                  // We reject the callback outright for now, but
                                  // it's worth trying to re-run the transaction.
                                  // Even if the user accepts the prompt to use
                                  // more storage on Safari, this error will
                                  // be called.
                                  //
                                  // TODO: Try to re-run the transaction.
                                  if (callback) {
                                      callback(null, sqlError);
                                  }
  
                                  reject(sqlError);
                              }
                          });
                      }
                  });
              });
          });
      }
  
      function removeItem(key, callback) {
          var _this = this;
          return new Promise(function(resolve, reject) {
              _this.ready().then(function() {
                  db.transaction(function(t) {
                      t.executeSql('DELETE FROM ' + dbInfo.storeName + 
                                   ' WHERE key = ?', [key], function() {
                          if (callback) {
                              callback();
                          }
  
                          resolve();
                      }, function(t, error) {
                          if (callback) {
                              callback(error);
                          }
  
                          reject(error);
                      });
                  });
              });
          });
      }
  
      // Deletes every item in the table.
      // TODO: Find out if this resets the AUTO_INCREMENT number.
      function clear(callback) {
          var _this = this;
          return new Promise(function(resolve, reject) {
              _this.ready().then(function() {
                  db.transaction(function(t) {
                      t.executeSql('DELETE FROM ' + dbInfo.storeName, [], function() {
                          if (callback) {
                              callback();
                          }
  
                          resolve();
                      }, function(t, error) {
                          if (callback) {
                              callback(error);
                          }
  
                          reject(error);
                      });
                  });
              });
          });
      }
  
      // Does a simple `COUNT(key)` to get the number of items stored in
      // localForage.
      function length(callback) {
          var _this = this;
          return new Promise(function(resolve, reject) {
              _this.ready().then(function() {
                  db.transaction(function(t) {
                      // Ahhh, SQL makes this one soooooo easy.
                      t.executeSql('SELECT COUNT(key) as c FROM ' + 
                                   dbInfo.storeName, [], function(t, results) {
                          var result = results.rows.item(0).c;
  
                          if (callback) {
                              callback(result);
                          }
  
                          resolve(result);
                      }, function(t, error) {
                          if (callback) {
                              callback(null, error);
                          }
  
                          reject(error);
                      });
                  });
              });
          });
      }
  
      // Return the key located at key index X; essentially gets the key from a
      // `WHERE id = ?`. This is the most efficient way I can think to implement
      // this rarely-used (in my experience) part of the API, but it can seem
      // inconsistent, because we do `INSERT OR REPLACE INTO` on `setItem()`, so
      // the ID of each key will change every time it's updated. Perhaps a stored
      // procedure for the `setItem()` SQL would solve this problem?
      // TODO: Don't change ID on `setItem()`.
      function key(n, callback) {
          var _this = this;
          return new Promise(function(resolve, reject) {
              _this.ready().then(function() {
                  db.transaction(function(t) {
                      t.executeSql('SELECT key FROM ' + dbInfo.storeName +
                                   ' WHERE id = ? LIMIT 1', [n + 1], function(t, results) {
                          var result = results.rows.length ? results.rows.item(0).key : null;
  
                          if (callback) {
                              callback(result);
                          }
  
                          resolve(result);
                      }, function(t, error) {
                          if (callback) {
                              callback(null, error);
                          }
  
                          reject(error);
                      });
                  });
              });
          });
      }
  
      // Converts a buffer to a string to store, serialized, in the backend
      // storage library.
      function _bufferToString(buffer) {
          // base64-arraybuffer
          var bytes = new Uint8Array(buffer);
          var i;
          var base64String = '';
  
          for (i = 0; i < bytes.length; i += 3) {
              /*jslint bitwise: true */
              base64String += BASE_CHARS[bytes[i] >> 2];
              base64String += BASE_CHARS[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
              base64String += BASE_CHARS[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
              base64String += BASE_CHARS[bytes[i + 2] & 63];
          }
  
          if ((bytes.length % 3) === 2) {
              base64String = base64String.substring(0, base64String.length - 1) + "=";
          } else if (bytes.length % 3 === 1) {
              base64String = base64String.substring(0, base64String.length - 2) + "==";
          }
  
          return base64String;
      }
  
      // Deserialize data we've inserted into a value column/field. We place
      // special markers into our strings to mark them as encoded; this isn't
      // as nice as a meta field, but it's the only sane thing we can do whilst
      // keeping localStorage support intact.
      //
      // Oftentimes this will just deserialize JSON content, but if we have a
      // special marker (SERIALIZED_MARKER, defined above), we will extract
      // some kind of arraybuffer/binary data/typed array out of the string.
      function _deserialize(value) {
          // If we haven't marked this string as being specially serialized (i.e.
          // something other than serialized JSON), we can just return it and be
          // done with it.
          if (value.substring(0, SERIALIZED_MARKER_LENGTH) !== SERIALIZED_MARKER) {
              return JSON.parse(value);
          }
  
          // The following code deals with deserializing some kind of Blob or
          // TypedArray. First we separate out the type of data we're dealing
          // with from the data itself.
          var serializedString = value.substring(TYPE_SERIALIZED_MARKER_LENGTH);
          var type = value.substring(SERIALIZED_MARKER_LENGTH, TYPE_SERIALIZED_MARKER_LENGTH);
  
          // Fill the string into a ArrayBuffer.
          var bufferLength = serializedString.length * 0.75;
          var len = serializedString.length;
          var i;
          var p = 0;
          var encoded1, encoded2, encoded3, encoded4;
  
          if (serializedString[serializedString.length - 1] === "=") {
              bufferLength--;
              if (serializedString[serializedString.length - 2] === "=") {
                  bufferLength--;
              }
          }
  
          var buffer = new ArrayBuffer(bufferLength);
          var bytes = new Uint8Array(buffer);
  
          for (i = 0; i < len; i+=4) {
              encoded1 = BASE_CHARS.indexOf(serializedString[i]);
              encoded2 = BASE_CHARS.indexOf(serializedString[i+1]);
              encoded3 = BASE_CHARS.indexOf(serializedString[i+2]);
              encoded4 = BASE_CHARS.indexOf(serializedString[i+3]);
  
              /*jslint bitwise: true */
              bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
              bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
              bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
          }
  
          // Return the right type based on the code/type set during
          // serialization.
          switch (type) {
              case TYPE_ARRAYBUFFER:
                  return buffer;
              case TYPE_BLOB:
                  return new Blob([buffer]);
              case TYPE_INT8ARRAY:
                  return new Int8Array(buffer);
              case TYPE_UINT8ARRAY:
                  return new Uint8Array(buffer);
              case TYPE_UINT8CLAMPEDARRAY:
                  return new Uint8ClampedArray(buffer);
              case TYPE_INT16ARRAY:
                  return new Int16Array(buffer);
              case TYPE_UINT16ARRAY:
                  return new Uint16Array(buffer);
              case TYPE_INT32ARRAY:
                  return new Int32Array(buffer);
              case TYPE_UINT32ARRAY:
                  return new Uint32Array(buffer);
              case TYPE_FLOAT32ARRAY:
                  return new Float32Array(buffer);
              case TYPE_FLOAT64ARRAY:
                  return new Float64Array(buffer);
              default:
                  throw new Error('Unkown type: ' + type);
          }
      }
  
      // Serialize a value, afterwards executing a callback (which usually
      // instructs the `setItem()` callback/promise to be executed). This is how
      // we store binary data with localStorage.
      function _serialize(value, callback) {
          var valueString = '';
          if (value) {
              valueString = value.toString();
          }
  
          // Cannot use `value instanceof ArrayBuffer` or such here, as these
          // checks fail when running the tests using casper.js...
          //
          // TODO: See why those tests fail and use a better solution.
          if (value && (value.toString() === '[object ArrayBuffer]' ||
                        value.buffer && value.buffer.toString() === '[object ArrayBuffer]')) {
              // Convert binary arrays to a string and prefix the string with
              // a special marker.
              var buffer;
              var marker = SERIALIZED_MARKER;
  
              if (value instanceof ArrayBuffer) {
                  buffer = value;
                  marker += TYPE_ARRAYBUFFER;
              } else {
                  buffer = value.buffer;
  
                  if (valueString === '[object Int8Array]') {
                      marker += TYPE_INT8ARRAY;
                  } else if (valueString === '[object Uint8Array]') {
                      marker += TYPE_UINT8ARRAY;
                  } else if (valueString === '[object Uint8ClampedArray]') {
                      marker += TYPE_UINT8CLAMPEDARRAY;
                  } else if (valueString === '[object Int16Array]') {
                      marker += TYPE_INT16ARRAY;
                  } else if (valueString === '[object Uint16Array]') {
                      marker += TYPE_UINT16ARRAY;
                  } else if (valueString === '[object Int32Array]') {
                      marker += TYPE_INT32ARRAY;
                  } else if (valueString === '[object Uint32Array]') {
                      marker += TYPE_UINT32ARRAY;
                  } else if (valueString === '[object Float32Array]') {
                      marker += TYPE_FLOAT32ARRAY;
                  } else if (valueString === '[object Float64Array]') {
                      marker += TYPE_FLOAT64ARRAY;
                  } else {
                      callback(new Error("Failed to get type for BinaryArray"));
                  }
              }
  
              callback(marker + _bufferToString(buffer));
          } else if (valueString === "[object Blob]") {
              // Conver the blob to a binaryArray and then to a string.
              var fileReader = new FileReader();
  
              fileReader.onload = function() {
                  var str = _bufferToString(this.result);
  
                  callback(SERIALIZED_MARKER + TYPE_BLOB + str);
              };
  
              fileReader.readAsArrayBuffer(value);
          } else {
              try {
                  callback(JSON.stringify(value));
              } catch (e) {
                  if (this.console && this.console.error) {
                      this.console.error("Couldn't convert value into a JSON string: ", value);
                  }
  
                  callback(null, e);
              }
          }
      }
  
      var webSQLStorage = {
          _driver: 'webSQLStorage',
          _initStorage: _initStorage,
          getItem: getItem,
          setItem: setItem,
          removeItem: removeItem,
          clear: clear,
          length: length,
          key: key
      };
  
      if (typeof define === 'function' && define.amd) {
          define('webSQLStorage', function() {
              return webSQLStorage;
          });
      } else if (typeof module !== 'undefined' && module.exports) {
          module.exports = webSQLStorage;
      } else {
          this.webSQLStorage = webSQLStorage;
      }
  }).call(this);
  