Preparation Code Preparation HTML (this will be inserted in the <body>
of a valid HTML5 document in standards mode) (useful when testing DOM operations or including libraries) <script >
(function (name, definition, global ) {
if (typeof define === 'function' ) {
define (definition);
} else if (typeof module !== 'undefined' && module .exports ) {
module .exports = definition ();
} else {
global [name] = definition ();
}
})('IDBStore' , function ( ) {
"use strict" ;
var defaults = {
storeName : 'Store' ,
storePrefix : 'IDBWrapper-' ,
dbVersion : 1 ,
keyPath : 'id' ,
autoIncrement : true ,
onStoreReady : function ( ) {
},
onError : function (error ){
throw error;
},
indexes : []
};
var IDBStore = function (kwArgs, onStoreReady ) {
for (var key in defaults){
this [key] = typeof kwArgs[key] != 'undefined' ? kwArgs[key] : defaults[key];
}
this .dbName = this .storePrefix + this .storeName ;
this .dbVersion = parseInt (this .dbVersion , 10 );
onStoreReady && (this .onStoreReady = onStoreReady);
this .idb = window .indexedDB || window .webkitIndexedDB || window .mozIndexedDB ;
this .keyRange = window .IDBKeyRange || window .webkitIDBKeyRange || window .mozIDBKeyRange ;
this .consts = {
'READ_ONLY' : 'readonly' ,
'READ_WRITE' : 'readwrite' ,
'VERSION_CHANGE' : 'versionchange' ,
'NEXT' : 'next' ,
'NEXT_NO_DUPLICATE' : 'nextunique' ,
'PREV' : 'prev' ,
'PREV_NO_DUPLICATE' : 'prevunique'
};
this .openDB ();
};
IDBStore.prototype = {
version : '1.1.0' ,
db : null ,
dbName : null ,
dbVersion : null ,
store : null ,
storeName : null ,
keyPath : null ,
autoIncrement : null ,
indexes : null ,
features : null ,
onStoreReady : null ,
onError : null ,
_insertIdCount : 0 ,
openDB : function ( ) {
var features = this .features = {};
features.hasAutoIncrement = !window .mozIndexedDB ;
var openRequest = this .idb .open (this .dbName , this .dbVersion );
var preventSuccessCallback = false ;
openRequest.onerror = function (error ) {
var gotVersionErr = false ;
if ('error' in error.target ) {
gotVersionErr = error.target .error .name == "VersionError" ;
} else if ('errorCode' in error.target ) {
gotVersionErr = error.target .errorCode == 12 ;
}
if (gotVersionErr) {
this .onError (new Error ('The version number provided is lower than the existing one.' ));
} else {
this .onError (error);
}
}.bind (this );
openRequest.onsuccess = function (event ) {
if (preventSuccessCallback) {
return ;
}
if (this .db ){
this .onStoreReady ();
return ;
}
this .db = event.target .result ;
if (typeof this .db .version == 'string' ){
this .onError (new Error ('The IndexedDB implementation in this browser is outdated. Please upgrade your browser.' ));
return ;
}
if (!this .db .objectStoreNames .contains (this .storeName )){
this .onError (new Error ('Something is wrong with the IndexedDB implementation in this browser. Please upgrade your browser.' ));
return ;
}
var emptyTransaction = this .db .transaction ([this .storeName ], this .consts .READ_ONLY );
this .store = emptyTransaction.objectStore (this .storeName );
this .indexes .forEach (function (indexData ){
var indexName = indexData.name ;
if (!indexName){
preventSuccessCallback = true ;
this .onError (new Error ('Cannot create index: No index name given.' ));
return ;
}
this .normalizeIndexData (indexData);
if (this .hasIndex (indexName)){
var actualIndex = this .store .index (indexName);
var complies = this .indexComplies (actualIndex, indexData);
if (!complies){
preventSuccessCallback = true ;
this .onError (new Error ('Cannot modify index "' + indexName + '" for current version. Please bump version number to ' + ( this .dbVersion + 1 ) + '.' ));
}
} else {
preventSuccessCallback = true ;
this .onError (new Error ('Cannot create new index "' + indexName + '" for current version. Please bump version number to ' + ( this .dbVersion + 1 ) + '.' ));
}
}, this );
preventSuccessCallback || this .onStoreReady ();
}.bind (this );
openRequest.onupgradeneeded = function ( event ){
this .db = event.target .result ;
if (this .db .objectStoreNames .contains (this .storeName )){
this .store = event.target .transaction .objectStore (this .storeName );
} else {
this .store = this .db .createObjectStore (this .storeName , { keyPath : this .keyPath , autoIncrement : this .autoIncrement });
}
this .indexes .forEach (function (indexData ){
var indexName = indexData.name ;
if (!indexName){
preventSuccessCallback = true ;
this .onError (new Error ('Cannot create index: No index name given.' ));
}
this .normalizeIndexData (indexData);
if (this .hasIndex (indexName)){
var actualIndex = this .store .index (indexName);
var complies = this .indexComplies (actualIndex, indexData);
if (!complies){
this .store .deleteIndex (indexName);
this .store .createIndex (indexName, indexData.keyPath , { unique : indexData.unique , multiEntry : indexData.multiEntry });
}
} else {
this .store .createIndex (indexName, indexData.keyPath , { unique : indexData.unique , multiEntry : indexData.multiEntry });
}
}, this );
}.bind (this );
},
deleteDatabase : function ( ) {
if (this .idb .deleteDatabase ) {
this .idb .deleteDatabase (this .dbName );
}
},
put : function (dataObj, onSuccess, onError ) {
onError || (onError = function (error ) {
console .error ('Could not write data.' , error);
});
onSuccess || (onSuccess = noop);
var hasSuccess = false ,
result = null ;
if (typeof dataObj[this .keyPath ] == 'undefined' && !this .features .hasAutoIncrement ) {
dataObj[this .keyPath ] = this ._getUID ();
}
var putTransaction = this .db .transaction ([this .storeName ], this .consts .READ_WRITE );
putTransaction.oncomplete = function ( ) {
var callback = hasSuccess ? onSuccess : onError;
callback (result);
};
putTransaction.onabort = onError;
putTransaction.onerror = onError;
var putRequest = putTransaction.objectStore (this .storeName ).put (dataObj);
putRequest.onsuccess = function (event ) {
hasSuccess = true ;
result = event.target .result ;
};
putRequest.onerror = onError;
},
get : function (key, onSuccess, onError ) {
onError || (onError = function (error ) {
console .error ('Could not read data.' , error);
});
onSuccess || (onSuccess = noop);
var hasSuccess = false ,
result = null ;
var getTransaction = this .db .transaction ([this .storeName ], this .consts .READ_ONLY );
getTransaction.oncomplete = function ( ) {
var callback = hasSuccess ? onSuccess : onError;
callback (result);
};
getTransaction.onabort = onError;
getTransaction.onerror = onError;
var getRequest = getTransaction.objectStore (this .storeName ).get (key);
getRequest.onsuccess = function (event ) {
hasSuccess = true ;
result = event.target .result ;
};
getRequest.onerror = onError;
},
remove : function (key, onSuccess, onError ) {
onError || (onError = function (error ) {
console .error ('Could not remove data.' , error);
});
onSuccess || (onSuccess = noop);
var hasSuccess = false ,
result = null ;
var removeTransaction = this .db .transaction ([this .storeName ], this .consts .READ_WRITE );
removeTransaction.oncomplete = function ( ) {
var callback = hasSuccess ? onSuccess : onError;
callback (result);
};
removeTransaction.onabort = onError;
removeTransaction.onerror = onError;
var deleteRequest = removeTransaction.objectStore (this .storeName )['delete' ](key);
deleteRequest.onsuccess = function (event ) {
hasSuccess = true ;
result = event.target .result ;
};
deleteRequest.onerror = onError;
},
batch : function (dataArray, onSuccess, onError ) {
onError || (onError = function (error ) {
console .error ('Could not apply batch.' , error);
});
onSuccess || (onSuccess = noop);
if (Object .prototype .toString .call (dataArray) != '[object Array]' ){
onError (new Error ('dataArray argument must be of type Array.' ));
}
var batchTransaction = this .db .transaction ([this .storeName ] , this .consts .READ_WRITE );
batchTransaction.oncomplete = function ( ) {
var callback = hasSuccess ? onSuccess : onError;
callback (hasSuccess);
};
batchTransaction.onabort = onError;
batchTransaction.onerror = onError;
var count = dataArray.length ;
var called = false ;
var hasSuccess = false ;
var onItemSuccess = function ( ) {
count--;
if (count === 0 && !called) {
called = true ;
hasSuccess = true ;
}
};
dataArray.forEach (function (operation ) {
var type = operation.type ;
var key = operation.key ;
var value = operation.value ;
var onItemError = function (err ) {
batchTransaction.abort ();
if (!called) {
called = true ;
onError (err, type, key);
}
};
if (type == "remove" ) {
var deleteRequest = batchTransaction.objectStore (this .storeName )['delete' ](key);
deleteRequest.onsuccess = onItemSuccess;
deleteRequest.onerror = onItemError;
} else if (type == "put" ) {
if (typeof value[this .keyPath ] == 'undefined' && !this .features .hasAutoIncrement ) {
value[this .keyPath ] = this ._getUID ();
}
var putRequest = batchTransaction.objectStore (this .storeName ).put (value);
putRequest.onsuccess = onItemSuccess;
putRequest.onerror = onItemError;
}
}, this );
},
getAll : function (onSuccess, onError ) {
onError || (onError = function (error ) {
console .error ('Could not read data.' , error);
});
onSuccess || (onSuccess = noop);
var getAllTransaction = this .db .transaction ([this .storeName ], this .consts .READ_ONLY );
var store = getAllTransaction.objectStore (this .storeName );
if (store.getAll ) {
this ._getAllNative (getAllTransaction, store, onSuccess, onError);
} else {
this ._getAllCursor (getAllTransaction, store, onSuccess, onError);
}
},
_getAllNative : function (getAllTransaction, store, onSuccess, onError ) {
var hasSuccess = false ,
result = null ;
getAllTransaction.oncomplete = function ( ) {
var callback = hasSuccess ? onSuccess : onError;
callback (result);
};
getAllTransaction.onabort = onError;
getAllTransaction.onerror = onError;
var getAllRequest = store.getAll ();
getAllRequest.onsuccess = function (event ) {
hasSuccess = true ;
result = event.target .result ;
};
getAllRequest.onerror = onError;
},
_getAllCursor : function (getAllTransaction, store, onSuccess, onError ) {
var all = [],
hasSuccess = false ,
result = null ;
getAllTransaction.oncomplete = function ( ) {
var callback = hasSuccess ? onSuccess : onError;
callback (result);
};
getAllTransaction.onabort = onError;
getAllTransaction.onerror = onError;
var cursorRequest = store.openCursor ();
cursorRequest.onsuccess = function (event ) {
var cursor = event.target .result ;
if (cursor) {
all.push (cursor.value );
cursor['continue' ]();
}
else {
hasSuccess = true ;
result = all;
}
};
cursorRequest.onError = onError;
},
clear : function (onSuccess, onError ) {
onError || (onError = function (error ) {
console .error ('Could not clear store.' , error);
});
onSuccess || (onSuccess = noop);
var hasSuccess = false ,
result = null ;
var clearTransaction = this .db .transaction ([this .storeName ], this .consts .READ_WRITE );
clearTransaction.oncomplete = function ( ) {
var callback = hasSuccess ? onSuccess : onError;
callback (result);
};
clearTransaction.onabort = onError;
clearTransaction.onerror = onError;
var clearRequest = clearTransaction.objectStore (this .storeName ).clear ();
clearRequest.onsuccess = function (event ) {
hasSuccess = true ;
result = event.target .result ;
};
clearRequest.onerror = onError;
},
_getUID : function ( ) {
return this ._insertIdCount ++ + Date .now ();
},
getIndexList : function ( ) {
return this .store .indexNames ;
},
hasIndex : function (indexName ) {
return this .store .indexNames .contains (indexName);
},
normalizeIndexData : function (indexData ) {
indexData.keyPath = indexData.keyPath || indexData.name ;
indexData.unique = !!indexData.unique ;
indexData.multiEntry = !!indexData.multiEntry ;
},
indexComplies : function (actual, expected ) {
var complies = ['keyPath' , 'unique' , 'multiEntry' ].every (function (key ) {
if (key == 'multiEntry' && actual[key] === undefined && expected[key] === false ) {
return true ;
}
return expected[key] == actual[key];
});
return complies;
},
iterate : function (onItem, options ) {
options = mixin ({
index : null ,
order : 'ASC' ,
filterDuplicates : false ,
keyRange : null ,
writeAccess : false ,
onEnd : null ,
onError : function (error ) {
console .error ('Could not open cursor.' , error);
}
}, options || {});
var directionType = options.order .toLowerCase () == 'desc' ? 'PREV' : 'NEXT' ;
if (options.filterDuplicates ) {
directionType += '_NO_DUPLICATE' ;
}
var hasSuccess = false ;
var cursorTransaction = this .db .transaction ([this .storeName ], this .consts [options.writeAccess ? 'READ_WRITE' : 'READ_ONLY' ]);
var cursorTarget = cursorTransaction.objectStore (this .storeName );
if (options.index ) {
cursorTarget = cursorTarget.index (options.index );
}
cursorTransaction.oncomplete = function ( ) {
if (!hasSuccess) {
options.onError (null );
return ;
}
if (options.onEnd ) {
options.onEnd ();
} else {
onItem (null );
}
};
cursorTransaction.onabort = options.onError ;
cursorTransaction.onerror = options.onError ;
var cursorRequest = cursorTarget.openCursor (options.keyRange , this .consts [directionType]);
cursorRequest.onerror = options.onError ;
cursorRequest.onsuccess = function (event ) {
var cursor = event.target .result ;
if (cursor) {
onItem (cursor.value , cursor, cursorTransaction);
cursor['continue' ]();
} else {
hasSuccess = true ;
}
};
},
query : function (onSuccess, options ) {
var result = [];
options = options || {};
options.onEnd = function ( ) {
onSuccess (result);
};
this .iterate (function (item ) {
result.push (item);
}, options);
},
count : function (onSuccess, options ) {
options = mixin ({
index : null ,
keyRange : null
}, options || {});
var onError = options.onError || function (error ) {
console .error ('Could not open cursor.' , error);
};
var hasSuccess = false ,
result = null ;
var cursorTransaction = this .db .transaction ([this .storeName ], this .consts .READ_ONLY );
cursorTransaction.oncomplete = function ( ) {
var callback = hasSuccess ? onSuccess : onError;
callback (result);
};
cursorTransaction.onabort = onError;
cursorTransaction.onerror = onError;
var cursorTarget = cursorTransaction.objectStore (this .storeName );
if (options.index ) {
cursorTarget = cursorTarget.index (options.index );
}
var countRequest = cursorTarget.count (options.keyRange );
countRequest.onsuccess = function (evt ) {
hasSuccess = true ;
result = evt.target .result ;
};
countRequest.onError = onError;
},
makeKeyRange : function (options ){
var keyRange,
hasLower = typeof options.lower != 'undefined' ,
hasUpper = typeof options.upper != 'undefined' ;
switch (true ){
case hasLower && hasUpper :
keyRange = this .keyRange .bound (options.lower , options.upper , options.excludeLower , options.excludeUpper );
break ;
case hasLower :
keyRange = this .keyRange .lowerBound (options.lower , options.excludeLower );
break ;
case hasUpper :
keyRange = this .keyRange .upperBound (options.upper , options.excludeUpper );
break ;
default :
throw new Error ('Cannot create KeyRange. Provide one or both of "lower" or "upper" value.' );
}
return keyRange;
}
};
var noop = function ( ) {
};
var empty = {};
var mixin = function (target, source ) {
var name, s;
for (name in source) {
s = source[name];
if (s !== empty[name] && s !== target[name]) {
target[name] = s;
}
}
return target;
};
IDBStore.version = IDBStore.prototype .version ;
return IDBStore;
}, this );
</script >
<script >
var scope = {};
var idb = new IDBStore ({
dbName : 'jsperftestdb' ,
storeName : 'jsperfteststore' ,
dbVersion : '1.0' ,
keyPath : 'id' ,
autoIncrement : true ,
onStoreReady : function ( ) {
idb.clear ();
}
});
var ls = window .localStorage ;
ls.clear ();
var websql = openDatabase ('benchmark' , '1.0' , '' , 1 * 1024 * 1024 );
websql.transaction (function (tx ) {
tx.executeSql ('DROP TABLE IF EXISTS benchmark;' );
tx.executeSql ('CREATE TABLE benchmark (id, value);' );
});
var i = 0 ,
lastnames = ['smith' , 'miller' , 'doe' , 'frankenstein' , 'furter' ],
firstnames = ['peter' , 'john' , 'frank' , 'james' ],
dummyData = {
"web-app" : {
"servlet" : [{
"servlet-name" : "cofaxCDS" ,
"servlet-class" : "org.cofax.cds.CDSServlet" ,
"init-param" : {
"configGlossary:installationAt" : "Philadelphia, PA" ,
"configGlossary:adminEmail" : "ksm@pobox.com" ,
"configGlossary:poweredBy" : "Cofax" ,
"configGlossary:poweredByIcon" : "/images/cofax.gif" ,
"configGlossary:staticPath" : "/content/static" ,
"templateProcessorClass" : "org.cofax.WysiwygTemplate" ,
"templateLoaderClass" : "org.cofax.FilesTemplateLoader" ,
"templatePath" : "templates" ,
"templateOverridePath" : "" ,
"defaultListTemplate" : "listTemplate.htm" ,
"defaultFileTemplate" : "articleTemplate.htm" ,
"useJSP" : false ,
"jspListTemplate" : "listTemplate.jsp" ,
"jspFileTemplate" : "articleTemplate.jsp" ,
"cachePackageTagsTrack" : 200 ,
"cachePackageTagsStore" : 200 ,
"cachePackageTagsRefresh" : 60 ,
"cacheTemplatesTrack" : 100 ,
"cacheTemplatesStore" : 50 ,
"cacheTemplatesRefresh" : 15 ,
"cachePagesTrack" : 200 ,
"cachePagesStore" : 100 ,
"cachePagesRefresh" : 10 ,
"cachePagesDirtyRead" : 10 ,
"searchEngineListTemplate" : "forSearchEnginesList.htm" ,
"searchEngineFileTemplate" : "forSearchEngines.htm" ,
"searchEngineRobotsDb" : "WEB-INF/robots.db" ,
"useDataStore" : true ,
"dataStoreClass" : "org.cofax.SqlDataStore" ,
"redirectionClass" : "org.cofax.SqlRedirection" ,
"dataStoreName" : "cofax" ,
"dataStoreDriver" : "com.microsoft.jdbc.sqlserver.SQLServerDriver" ,
"dataStoreUrl" : "jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon" ,
"dataStoreUser" : "sa" ,
"dataStorePassword" : "dataStoreTestQuery" ,
"dataStoreTestQuery" : "SET NOCOUNT ON;select test='test';" ,
"dataStoreLogFile" : "/usr/local/tomcat/logs/datastore.log" ,
"dataStoreInitConns" : 10 ,
"dataStoreMaxConns" : 100 ,
"dataStoreConnUsageLimit" : 100 ,
"dataStoreLogLevel" : "debug" ,
"maxUrlLength" : 500
}
}, {
"servlet-name" : "cofaxEmail" ,
"servlet-class" : "org.cofax.cds.EmailServlet" ,
"init-param" : {
"mailHost" : "mail1" ,
"mailHostOverride" : "mail2"
}
}, {
"servlet-name" : "cofaxAdmin" ,
"servlet-class" : "org.cofax.cds.AdminServlet"
},
{
"servlet-name" : "fileServlet" ,
"servlet-class" : "org.cofax.cds.FileServlet"
}, {
"servlet-name" : "cofaxTools" ,
"servlet-class" : "org.cofax.cms.CofaxToolsServlet" ,
"init-param" : {
"templatePath" : "toolstemplates/" ,
"log" : 1 ,
"logLocation" : "/usr/local/tomcat/logs/CofaxTools.log" ,
"logMaxSize" : "" ,
"dataLog" : 1 ,
"dataLogLocation" : "/usr/local/tomcat/logs/dataLog.log" ,
"dataLogMaxSize" : "" ,
"removePageCache" : "/content/admin/remove?cache=pages&id=" ,
"removeTemplateCache" : "/content/admin/remove?cache=templates&id=" ,
"fileTransferFolder" : "/usr/local/tomcat/webapps/content/fileTransferFolder" ,
"lookInContext" : 1 ,
"adminGroupID" : 4 ,
"betaServer" : true
}
}],
"servlet-mapping" : {
"cofaxCDS" : "/" ,
"cofaxEmail" : "/cofaxutil/aemail/*" ,
"cofaxAdmin" : "/admin/*" ,
"fileServlet" : "/static/*" ,
"cofaxTools" : "/tools/*"
},
"taglib" : {
"taglib-uri" : "cofax.tld" ,
"taglib-location" : "/WEB-INF/tlds/cofax.tld"
}
}
};
var oneMeg = "1234567890" ;
for (var j = 1 ; j < 50000 ; j++) {
oneMeg += "1234567890" ;
}
var fiveMeg = oneMeg + oneMeg + oneMeg + oneMeg + oneMeg;
var tenMeg = fiveMeg + fiveMeg;
var fiftyMeg = tenMeg + tenMeg + tenMeg + tenMeg + tenMeg;
</script >
Setup JS scope.i = 0 ;
scope.smallData = {
'lastname' : lastnames[Math .floor (Math .random () * 5 )],
'firstanme' : firstnames[Math .floor (Math .random () * 4 )],
'dummyData' : {}
};
scope.largeData = {
'lastname' : lastnames[Math .floor (Math .random () * 5 )],
'firstanme' : firstnames[Math .floor (Math .random () * 4 )],
'dummyData' : dummyData
};
Teardown JS