Add WebSQL Blob support

This commit is contained in:
Matthew Riley MacPherson 2014-02-20 20:58:30 -05:00
parent 896bdb55a8
commit a21987ef16
7 changed files with 899 additions and 290 deletions

467
dist/localforage.js vendored
View File

@ -1074,9 +1074,23 @@ requireModule('promise/polyfill').polyfill();
return Promise.resolve();
}
var SERIALIZED_MARKER = '__lfsc__';
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;
// Remove all keys from the datastore, effectively destroying all data in
// the app's key/value store!
function clear(callback) {
@ -1094,49 +1108,6 @@ requireModule('promise/polyfill').polyfill();
});
}
function deserializeValue(value) {
if (value.substring(0, SERIALIZED_MARKER_LENGTH) !== SERIALIZED_MARKER) {
return JSON.parse(value);
}
var type = value.substring(SERIALIZED_MARKER_LENGTH,
SERIALIZED_MARKER_LENGTH + 4);
var str = value.substring(SERIALIZED_MARKER_LENGTH + 5 /* type + colon */);
// Fill the string into a ArrayBuffer.
var buf = new ArrayBuffer(str.length * 2); // 2 bytes for each char
var bufView = new Uint16Array(buf);
for (var i = str.length - 1; i >= 0; i--) {
bufView[i] = str.charCodeAt(i);
}
switch (type) {
case 'arbf':
return buf;
case 'blob':
return new Blob([buf]);
case 'si08':
return new Int8Array(buf);
case 'ui08':
return new Uint8Array(buf);
case 'uic8':
return new Uint8ClampedArray(buf);
case 'si16':
return new Int16Array(buf);
case 'ur16':
return new Uint16Array(buf);
case 'ui32':
return new Uint32Array(buf);
case 'fl32':
return new Float32Array(buf);
case 'flt64':
return new Float64Array(buf);
default:
throw new Error('Unkown type: ' + type);
}
}
// Retrieve an item from the store. Unlike the original async_storage
// library in Gaia, we don't modify return values at all. If a key's value
// is `undefined`, we pass that value to the callback function.
@ -1152,11 +1123,7 @@ requireModule('promise/polyfill').polyfill();
// is likely undefined and we'll pass it straight to the
// callback.
if (result) {
result = deserializeValue(result);
}
if (callback) {
callback(result);
result = _deserialize(result);
}
resolve(result);
@ -1219,51 +1186,117 @@ requireModule('promise/polyfill').polyfill();
});
}
function serializeValue(value, callback) {
// 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 buffer = new ArrayBuffer(serializedString.length * 2); // 2 bytes for each char
var bufferView = new Uint16Array(buffer);
for (var i = serializedString.length - 1; i >= 0; i--) {
bufferView[i] = serializedString.charCodeAt(i);
}
// 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...
if (value &&
(value.toString() === '[object ArrayBuffer]' ||
value.buffer && value.buffer.toString() === '[object ArrayBuffer]'))
{
//
// 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 buf;
var buffer;
var marker = SERIALIZED_MARKER;
if (value instanceof ArrayBuffer) {
buf = value;
marker += 'arbf:';
buffer = value;
marker += TYPE_ARRAYBUFFER;
} else {
buf = value.buffer;
buffer = value.buffer;
if (valueString === '[object Int8Array]') {
marker += 'si08:';
marker += TYPE_INT8ARRAY;
} else if (valueString === '[object Uint8Array]') {
marker += 'ui08:';
} else if (valueString === '[object Uint8ClampedArray]') {
marker += 'uic8:';
} else if (valueString === '[object Int16Array]') {
marker += 'si16:';
} else if (valueString === '[object Uint16Array]') {
marker += 'ur16:';
} else if (valueString === '[object Int32Array]') {
marker += 'si32:';
} else if (valueString === '[object Uint32Array]') {
marker += 'ui32:';
} else if (valueString === '[object Float32Array]') {
marker += 'fl32:';
} else if (valueString === '[object Float64Array]') {
marker += 'fl64:';
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"));
}
}
var str = '';
var uint16Array = new Uint16Array(buf);
var uint16Array = new Uint16Array(buffer);
try {
str = String.fromCharCode.apply(null, uint16Array);
} catch (e) {
@ -1273,15 +1306,29 @@ requireModule('promise/polyfill').polyfill();
str += String.fromCharCode(uint16Array[i]);
}
}
callback(null, marker + str);
} else if (valueString === "[object Blob]") {
// Conver the blob to a binaryArray and then to a string.
var fileReader = new FileReader();
fileReader.onload = function() {
var resializedString = String.fromCharCode.apply(
null, new Uint16Array(this.result));
callback(null, SERIALIZED_MARKER + 'blob:' + resializedString);
var str = '';
var uint16Array = new Uint16Array(this.result);
try {
str = String.fromCharCode.apply(null, uint16Array);
} catch (e) {
// This is a fallback implementation in case the first one does
// not work. This is required to get the phantomjs passing...
for (var i = 0; i < uint16Array.length; i++) {
str += String.fromCharCode(uint16Array[i]);
}
}
callback(null, SERIALIZED_MARKER + TYPE_BLOB + str);
};
fileReader.readAsArrayBuffer(value);
} else {
try {
@ -1311,7 +1358,7 @@ requireModule('promise/polyfill').polyfill();
// Save the original value to pass to the callback.
var originalValue = value;
serializeValue(value, function setSerialized(error, value) {
_serialize(value, function setSerialized(error, value) {
if (error) {
reject(error);
} else {
@ -1350,16 +1397,29 @@ requireModule('promise/polyfill').polyfill();
this.localStorageWrapper = localStorageWrapper;
}
}).call(this);
/*
* 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+/';
// Default DB size is _JUST UNDER_ 5MB, as it's the highest size we can use
// without a prompt.
//
// TODO: Add a way to increase this size programmatically?
var DB_SIZE = 4980736;
var SERIALIZED_MARKER = '__lfsc__:';
var SERIALIZED_MARKER_LENGTH = SERIALIZED_MARKER.length;
var Promise = window.Promise;
var db = null;
var dbInfo = {
@ -1369,6 +1429,23 @@ requireModule('promise/polyfill').polyfill();
version: '1.0'
};
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 (!window.openDatabase) {
return;
@ -1410,12 +1487,8 @@ requireModule('promise/polyfill').polyfill();
// Check to see if this is serialized content we need to
// unpack.
if (result && result.substr(0, SERIALIZED_MARKER_LENGTH) === SERIALIZED_MARKER) {
try {
result = JSON.parse(result.slice(SERIALIZED_MARKER_LENGTH));
} catch (e) {
reject(e);
}
if (result) {
result = _deserialize(result);
}
if (callback) {
@ -1440,27 +1513,23 @@ requireModule('promise/polyfill').polyfill();
value = null;
}
// We need to serialize certain types of objects using WebSQL;
// otherwise they'll get stored as strings as be useless when we
// use getItem() later.
var valueToSave;
if (typeof(value) === 'boolean' || typeof(value) === 'number' || typeof(value) === 'object') {
// Mark the content as "localForage serialized content" so we
// know to run JSON.parse() on it when we get it back out from
// the database.
valueToSave = SERIALIZED_MARKER + JSON.stringify(value);
} else {
valueToSave = value;
}
// Save the original value to pass to the callback.
var originalValue = value;
db.transaction(function (t) {
t.executeSql('INSERT OR REPLACE INTO ' + dbInfo.storeName + ' (key, value) VALUES (?, ?)', [key, valueToSave], function() {
if (callback) {
callback(value);
}
_serialize(value, function setItemserializeValueCallback(error, value) {
if (error) {
reject(error);
} else {
db.transaction(function (t) {
t.executeSql('INSERT OR REPLACE INTO localforage (key, value) VALUES (?, ?)', [key, value], function() {
if (callback) {
callback(originalValue);
}
resolve(value);
}, null);
resolve(originalValue);
}, null);
});
}
});
});
});
@ -1550,6 +1619,192 @@ requireModule('promise/polyfill').polyfill();
});
}
// 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]);
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"));
}
}
// base64-arraybuffer
var bytes = new Uint8Array(buffer);
var i;
var base64String = '';
for (i = 0; i < bytes.length; i += 3) {
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) + "==";
}
callback(null, marker + base64String);
} else if (valueString === "[object Blob]") {
// Conver the blob to a binaryArray and then to a string.
var fileReader = new FileReader();
fileReader.onload = function() {
// base64-arraybuffer
var bytes = new Uint8Array(this.result);
var i;
var base64String = '';
for (i = 0; i < bytes.length; i += 3) {
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) + "==";
}
callback(null, SERIALIZED_MARKER + TYPE_BLOB + base64String);
};
fileReader.readAsArrayBuffer(value);
} else {
try {
callback(null, JSON.stringify(value));
} catch (e) {
console.error("Couldn't convert value into a JSON string: ",
value);
callback(e);
}
}
}
var webSQLStorage = {
_driver: 'webSQLStorage',
_initStorage: _initStorage,

File diff suppressed because one or more lines are too long

View File

@ -41,9 +41,23 @@
return Promise.resolve();
}
var SERIALIZED_MARKER = '__lfsc__';
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;
// Remove all keys from the datastore, effectively destroying all data in
// the app's key/value store!
function clear(callback) {
@ -61,49 +75,6 @@
});
}
function deserializeValue(value) {
if (value.substring(0, SERIALIZED_MARKER_LENGTH) !== SERIALIZED_MARKER) {
return JSON.parse(value);
}
var type = value.substring(SERIALIZED_MARKER_LENGTH,
SERIALIZED_MARKER_LENGTH + 4);
var str = value.substring(SERIALIZED_MARKER_LENGTH + 5 /* type + colon */);
// Fill the string into a ArrayBuffer.
var buf = new ArrayBuffer(str.length * 2); // 2 bytes for each char
var bufView = new Uint16Array(buf);
for (var i = str.length - 1; i >= 0; i--) {
bufView[i] = str.charCodeAt(i);
}
switch (type) {
case 'arbf':
return buf;
case 'blob':
return new Blob([buf]);
case 'si08':
return new Int8Array(buf);
case 'ui08':
return new Uint8Array(buf);
case 'uic8':
return new Uint8ClampedArray(buf);
case 'si16':
return new Int16Array(buf);
case 'ur16':
return new Uint16Array(buf);
case 'ui32':
return new Uint32Array(buf);
case 'fl32':
return new Float32Array(buf);
case 'flt64':
return new Float64Array(buf);
default:
throw new Error('Unkown type: ' + type);
}
}
// Retrieve an item from the store. Unlike the original async_storage
// library in Gaia, we don't modify return values at all. If a key's value
// is `undefined`, we pass that value to the callback function.
@ -119,11 +90,7 @@
// is likely undefined and we'll pass it straight to the
// callback.
if (result) {
result = deserializeValue(result);
}
if (callback) {
callback(result);
result = _deserialize(result);
}
resolve(result);
@ -186,51 +153,117 @@
});
}
function serializeValue(value, callback) {
// 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 buffer = new ArrayBuffer(serializedString.length * 2); // 2 bytes for each char
var bufferView = new Uint16Array(buffer);
for (var i = serializedString.length - 1; i >= 0; i--) {
bufferView[i] = serializedString.charCodeAt(i);
}
// 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...
if (value &&
(value.toString() === '[object ArrayBuffer]' ||
value.buffer && value.buffer.toString() === '[object ArrayBuffer]'))
{
//
// 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 buf;
var buffer;
var marker = SERIALIZED_MARKER;
if (value instanceof ArrayBuffer) {
buf = value;
marker += 'arbf:';
buffer = value;
marker += TYPE_ARRAYBUFFER;
} else {
buf = value.buffer;
buffer = value.buffer;
if (valueString === '[object Int8Array]') {
marker += 'si08:';
marker += TYPE_INT8ARRAY;
} else if (valueString === '[object Uint8Array]') {
marker += 'ui08:';
} else if (valueString === '[object Uint8ClampedArray]') {
marker += 'uic8:';
} else if (valueString === '[object Int16Array]') {
marker += 'si16:';
} else if (valueString === '[object Uint16Array]') {
marker += 'ur16:';
} else if (valueString === '[object Int32Array]') {
marker += 'si32:';
} else if (valueString === '[object Uint32Array]') {
marker += 'ui32:';
} else if (valueString === '[object Float32Array]') {
marker += 'fl32:';
} else if (valueString === '[object Float64Array]') {
marker += 'fl64:';
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"));
}
}
var str = '';
var uint16Array = new Uint16Array(buf);
var uint16Array = new Uint16Array(buffer);
try {
str = String.fromCharCode.apply(null, uint16Array);
} catch (e) {
@ -240,15 +273,29 @@
str += String.fromCharCode(uint16Array[i]);
}
}
callback(null, marker + str);
} else if (valueString === "[object Blob]") {
// Conver the blob to a binaryArray and then to a string.
var fileReader = new FileReader();
fileReader.onload = function() {
var resializedString = String.fromCharCode.apply(
null, new Uint16Array(this.result));
callback(null, SERIALIZED_MARKER + 'blob:' + resializedString);
var str = '';
var uint16Array = new Uint16Array(this.result);
try {
str = String.fromCharCode.apply(null, uint16Array);
} catch (e) {
// This is a fallback implementation in case the first one does
// not work. This is required to get the phantomjs passing...
for (var i = 0; i < uint16Array.length; i++) {
str += String.fromCharCode(uint16Array[i]);
}
}
callback(null, SERIALIZED_MARKER + TYPE_BLOB + str);
};
fileReader.readAsArrayBuffer(value);
} else {
try {
@ -278,7 +325,7 @@
// Save the original value to pass to the callback.
var originalValue = value;
serializeValue(value, function setSerialized(error, value) {
_serialize(value, function setSerialized(error, value) {
if (error) {
reject(error);
} else {

View File

@ -1,13 +1,26 @@
/*
* 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+/';
// Default DB size is _JUST UNDER_ 5MB, as it's the highest size we can use
// without a prompt.
//
// TODO: Add a way to increase this size programmatically?
var DB_SIZE = 4980736;
var SERIALIZED_MARKER = '__lfsc__:';
var SERIALIZED_MARKER_LENGTH = SERIALIZED_MARKER.length;
var Promise = window.Promise;
var db = null;
var dbInfo = {
@ -17,6 +30,23 @@
version: '1.0'
};
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 (!window.openDatabase) {
return;
@ -58,12 +88,8 @@
// Check to see if this is serialized content we need to
// unpack.
if (result && result.substr(0, SERIALIZED_MARKER_LENGTH) === SERIALIZED_MARKER) {
try {
result = JSON.parse(result.slice(SERIALIZED_MARKER_LENGTH));
} catch (e) {
reject(e);
}
if (result) {
result = _deserialize(result);
}
if (callback) {
@ -88,27 +114,23 @@
value = null;
}
// We need to serialize certain types of objects using WebSQL;
// otherwise they'll get stored as strings as be useless when we
// use getItem() later.
var valueToSave;
if (typeof(value) === 'boolean' || typeof(value) === 'number' || typeof(value) === 'object') {
// Mark the content as "localForage serialized content" so we
// know to run JSON.parse() on it when we get it back out from
// the database.
valueToSave = SERIALIZED_MARKER + JSON.stringify(value);
} else {
valueToSave = value;
}
// Save the original value to pass to the callback.
var originalValue = value;
db.transaction(function (t) {
t.executeSql('INSERT OR REPLACE INTO ' + dbInfo.storeName + ' (key, value) VALUES (?, ?)', [key, valueToSave], function() {
if (callback) {
callback(value);
}
_serialize(value, function setItemserializeValueCallback(error, value) {
if (error) {
reject(error);
} else {
db.transaction(function (t) {
t.executeSql('INSERT OR REPLACE INTO localforage (key, value) VALUES (?, ?)', [key, value], function() {
if (callback) {
callback(originalValue);
}
resolve(value);
}, null);
resolve(originalValue);
}, null);
});
}
});
});
});
@ -198,6 +220,192 @@
});
}
// 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]);
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"));
}
}
// base64-arraybuffer
var bytes = new Uint8Array(buffer);
var i;
var base64String = '';
for (i = 0; i < bytes.length; i += 3) {
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) + "==";
}
callback(null, marker + base64String);
} else if (valueString === "[object Blob]") {
// Conver the blob to a binaryArray and then to a string.
var fileReader = new FileReader();
fileReader.onload = function() {
// base64-arraybuffer
var bytes = new Uint8Array(this.result);
var i;
var base64String = '';
for (i = 0; i < bytes.length; i += 3) {
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) + "==";
}
callback(null, SERIALIZED_MARKER + TYPE_BLOB + base64String);
};
fileReader.readAsArrayBuffer(value);
} else {
try {
callback(null, JSON.stringify(value));
} catch (e) {
console.error("Couldn't convert value into a JSON string: ",
value);
callback(e);
}
}
}
var webSQLStorage = {
_driver: 'webSQLStorage',
_initStorage: _initStorage,

View File

@ -1,61 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf8" />
<title>Simple localForage example</title>
</head>
<body>
<img id="image">
<script src="/localforage.js"></script>
<script>
var request = new XMLHttpRequest();
// Let's get the first user's photo.
request.open('GET', "/photo.jpg", true);
request.responseType = 'arraybuffer';
// When the AJAX state changes, save the photo locally.
request.addEventListener('readystatechange', function() {
if (request.readyState === 4) { // readyState DONE
// console.log(request);
// We store the binary data as-is; this wouldn't work with localStorage.
localforage.setItem('user_1_photo', request.response, function(value) {
// Photo has been saved, do whatever happens next!
// var blob = new Blob([value]);
// console.log(window.URL.createObjectURL(blob), value, typeof(value), blob);
// document.getElementById('image').src = window.URL.createObjectURL(blob);
doEeet();
});
}
});
localforage.setDriver('webSQLStorage').then(function() {
request.send(null);
});
// var array = new Uint8ClampedArray(3)
// array[0] = -17
// array[1] = 93
// array[2] = 350
// localforage.setDriver('localStorageWrapper')
// doEeet();
function doEeet() {
localforage.getItem('user_1_photo', function(value) {
console.log('message');
// Photo has been saved, do whatever happens next!
var blob_ = new Blob([value], {type: 'image/jpeg'});
console.log(window.URL.createObjectURL(blob_), value, typeof(value), blob_);
document.getElementById('image').src = window.URL.createObjectURL(blob_);
});
}
// localforage.setDriver('webSQLStorage').then(function() {
// console.log('message');
// // doEeet()
// });
</script>
</body>
</html>

View File

@ -8,6 +8,7 @@
# We run the same test suite for multiple drivers, so we'll set them here.
casper.DRIVER = casper.cli.get('driver') or 'localStorageWrapper'
casper.DRIVER_NAME = casper.cli.get('driver-name') or 'localStorage'
casper.ENGINE = casper.cli.get('engine') or 'casperjs'
casper.URL = casper.cli.get('url') or 'localstorage'
# Oh boy, this is naughty:

View File

@ -347,18 +347,89 @@ casper.test.begin "Testing #{casper.DRIVER_NAME} driver", (test) ->
window._testValue isnt undefined
, 'setItem() returns null for undefined'
# Test for binary data support.
# ArrayBuffer
casper.then ->
# Test that all types of binary data are saved and retrieved properly.
test.info "Testing binary data types"
@evaluate ->
array = new ArrayBuffer(16)
localforage.setItem('ArrayBuffer', array).then (writeValue) ->
localforage.getItem('ArrayBuffer').then (readValue) ->
window._testValue = readValue
__utils__.findOne('.status').id = 'ArrayBuffer'
@waitForSelector '#ArrayBuffer', ->
test.assertEval ->
window._testValue.toString() is '[object ArrayBuffer]'
, 'setItem() and getItem() for ArrayBuffer returns value of type ArrayBuffer'
test.assertEval ->
window._testValue.byteLength is 16
, 'ArrayBuffer can be saved and retrieved properly'
# Blob Data
casper.then ->
@evaluate ->
arr = new Uint8Array(8)
arr[0] = 65
arr[4] = 0
localforage.setItem('Uint8Array', arr).then (writeValue) ->
request = new XMLHttpRequest()
# Let's get the first user's photo.
request.open "GET", "/photo.jpg", true
request.responseType = "arraybuffer"
# When the AJAX state changes, save the photo locally.
request.addEventListener "readystatechange", ->
if (request.readyState == 4) # readyState DONE
# Store Blob data.
blob = new Blob([request.response])
localforage.setItem "blobData", blob, (writeValue) ->
__utils__.echo readValue
localforage.getItem "blobData", (readValue) ->
# Photo has been saved, do whatever happens next!
window._testValue = readValue
__utils__.echo readValue
__utils__.findOne('.status').id = 'Blob'
request.send()
@waitForSelector '#Blob', ->
test.assertEval ->
window._testValue.toString() is '[object Blob]'
, 'setItem() and getItem() for Blob returns value of type Blob'
# Int8Array
casper.then ->
@evaluate ->
array = new Int8Array(8)
array[2] = 65
array[4] = 0
localforage.setItem('Int8Array', array).then (writeValue) ->
localforage.getItem('Int8Array').then (readValue) ->
window._testValue = readValue
__utils__.findOne('.status').id = 'Int8Array'
@waitForSelector '#Int8Array', ->
test.assertEval ->
window._testValue.toString() is '[object Int8Array]'
, 'setItem() and getItem() for Int8Array returns value of type Int8Array'
test.assertEval ->
window._testValue[2] is 65 and
window._testValue[4] is 0
, 'Int8Array can be saved and retrieved properly'
# Uint8Array
casper.then ->
@evaluate ->
array = new Uint8Array(8)
array[0] = 65
array[4] = 0
localforage.setItem('Uint8Array', array).then (writeValue) ->
localforage.getItem('Uint8Array').then (readValue) ->
window._testValue = readValue
__utils__.findOne('.status').id = 'Uint8Array-test'
__utils__.findOne('.status').id = 'Uint8Array'
@waitForSelector '#Uint8Array-test', ->
@waitForSelector '#Uint8Array', ->
test.assertEval ->
window._testValue.toString() is '[object Uint8Array]'
, 'setItem() and getItem() for Uint8Array returns value of type Uint8Array'
@ -366,19 +437,66 @@ casper.test.begin "Testing #{casper.DRIVER_NAME} driver", (test) ->
test.assertEval ->
window._testValue[0] is 65 and
window._testValue[4] is 0
, 'setItem() and getItem() for Uint8Array returns same values again'
, 'Uinit8Array can be saved and retrieved properly'
# Uint8ClampedArray
# phantomjs/casperjs seems to see the Uint8ClampedArray as an Uint8Array,
# not sure why.
# casper.then ->
# @evaluate ->
# array = new Uint8ClampedArray(3)
# array[0] = -17
# array[1] = 93
# array[2] = 350
# localforage.setItem('Uint8ClampedArray', array).then (writeValue) ->
# localforage.getItem('Uint8ClampedArray').then (readValue) ->
# window._testValue = readValue
# __utils__.findOne('.status').id = 'Uint8ClampedArray'
# casper.then ->
# test.assertEval ->
# window._testValue.toString() is '[object Uint8ClampedArray]'
# , 'setItem() and getItem() for Uint8ClampedArray returns value of type Uint8ClampedArray'
# test.assertEval ->
# window._testValue[0] is 0 and
# window._testValue[1] is 93 and
# window._testValue[2] is 255
# , 'Uinit8Array can be saved and retrieved properly'
# Int16Array
casper.then ->
@evaluate ->
arr = new Uint8Array(8)
arr[0] = 65
arr[4] = 0
localforage.setItem 'Uint8Array', arr, (writeValue) ->
localforage.getItem 'Uint8Array', (readValue) ->
array = new Int16Array(8)
array[0] = 65
array[4] = 0
localforage.setItem('Int16Array', array).then (writeValue) ->
localforage.getItem('Int16Array').then (readValue) ->
window._testValue = readValue
__utils__.findOne('.status').id = 'Uint8Array-test-callback'
__utils__.findOne('.status').id = 'Int16Array'
@waitForSelector '#Uint8Array-test-callback', ->
@waitForSelector '#Int16Array', ->
test.assertEval ->
window._testValue.toString() is '[object Int16Array]'
, 'setItem() and getItem() for Int16Array returns value of type Int16Array'
test.assertEval ->
window._testValue[0] is 65 and
window._testValue[4] is 0
, 'Int16Array can be saved and retrieved properly'
# Uint8Array
casper.then ->
@evaluate ->
array = new Uint8Array(8)
array[0] = 65
array[4] = 0
localforage.setItem('Uint8Array', array).then (writeValue) ->
localforage.getItem('Uint8Array').then (readValue) ->
window._testValue = readValue
__utils__.findOne('.status').id = 'Uint8Array'
@waitForSelector '#Uint8Array', ->
test.assertEval ->
window._testValue.toString() is '[object Uint8Array]'
, 'setItem() and getItem() for Uint8Array returns value of type Uint8Array'
@ -386,7 +504,48 @@ casper.test.begin "Testing #{casper.DRIVER_NAME} driver", (test) ->
test.assertEval ->
window._testValue[0] is 65 and
window._testValue[4] is 0
, 'setItem() and getItem() for Uint8Array returns same values again'
, 'Uinit8Array can be saved and retrieved properly'
# Uint16Array
casper.then ->
@evaluate ->
array = new Uint16Array(8)
array[0] = 65
array[4] = 0
localforage.setItem('Uint16Array', array).then (writeValue) ->
localforage.getItem('Uint16Array').then (readValue) ->
window._testValue = readValue
__utils__.findOne('.status').id = 'Uint16Array'
@waitForSelector '#Uint16Array', ->
test.assertEval ->
window._testValue.toString() is '[object Uint16Array]'
, 'setItem() and getItem() for Uint16Array returns value of type Uint16Array'
test.assertEval ->
window._testValue[0] is 65 and
window._testValue[4] is 0
, 'Uint16Array can be saved and retrieved properly'
# casper.then ->
# @evaluate ->
# array = new Uint8Array(8)
# array[0] = 65
# array[4] = 0
# localforage.setItem 'Uint8Array', array, (writeValue) ->
# localforage.getItem 'Uint8Array', (readValue) ->
# window._testValue = readValue
# __utils__.findOne('.status').id = 'Uint8Array-test-callback'
# @waitForSelector '#Uint8Array-test-callback', ->
# test.assertEval ->
# window._testValue.toString() is '[object Uint8Array]'
# , 'setItem() and getItem() for Uint8Array returns value of type Uint8Array'
# test.assertEval ->
# window._testValue[0] is 65 and
# window._testValue[4] is 0
# , 'setItem() and getItem() for Uint8Array returns same values again'
casper.thenOpen "#{casper.TEST_URL}test.min.html", ->
test.info "Test minified version"