1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 window
.indexedDB
= window
.indexedDB
|| window
.webkitIndexedDB
||
6 window
.mozIndexedDB
|| window
.msIndexedDB
;
7 window
.IDBKeyRange
= window
.IDBKeyRange
|| window
.webkitIDBKeyRange
;
13 automation
.setDone = function() {
14 this.setStatus("Test complete.");
15 document
.cookie
= '__done=1; path=/';
18 automation
.addResult = function(name
, result
) {
20 this.results
[name
] = result
;
21 var elt
= document
.getElementById('results');
22 var div
= document
.createElement('div');
23 div
.textContent
= name
+ ": " + result
;
27 automation
.getResults = function() {
31 automation
.setStatus = function(s
) {
32 document
.getElementById('status').textContent
= s
;
37 var e
= new Error("Assertion failed!");
44 var s
= "Caught error.";
45 if (e
.target
&& e
.target
.webkitErrorMessage
)
46 s
+= "\n" + e
.target
.webkitErrorMessage
;
47 else if (e
.target
&& e
.target
.error
)
48 s
+= "\n" + e
.target
.error
.name
;
50 automation
.setStatus(s
);
55 var baseVersion
= 2; // The version with our object stores.
58 // Valid options fields:
59 // indexName: the name of an index to create on each object store
60 // indexKeyPath: the key path for that index
61 // indexIsUnique: the "unique" option for IDBIndexParameters
62 // indexIsMultiEntry: the "multiEntry" option for IDBIndexParameters
64 function createDatabase(
65 name
, objectStoreNames
, handler
, errorHandler
, optionSets
) {
66 var openRequest
= indexedDB
.open(name
, baseVersion
);
67 openRequest
.onblocked
= errorHandler
;
68 openRequest
.onerror
= errorHandler
;
69 function createObjectStores(db
) {
70 for (var store
in objectStoreNames
) {
71 var name
= objectStoreNames
[store
];
72 assert(!db
.objectStoreNames
.contains(name
));
73 var os
= db
.createObjectStore(name
);
75 for (o
in optionSets
) {
76 var options
= optionSets
[o
];
77 assert(options
.indexName
);
78 assert('indexKeyPath' in options
);
79 os
.createIndex(options
.indexName
, options
.indexKeyPath
,
80 { unique
: options
.indexIsUnique
,
81 multiEntry
: options
.indexIsMultiEntry
});
86 openRequest
.onupgradeneeded = function(ev
) {
87 // This is the spec-compliant path, which doesn't yet run in Chrome, but
89 assert(openRequest
== ev
.target
);
90 var db
= openRequest
.result
;
91 db
.onerror
= errorHandler
;
92 createObjectStores(db
);
93 // onsuccess will get called after this exits.
95 openRequest
.onsuccess = function(ev
) {
96 assert(openRequest
== ev
.target
);
97 var db
= openRequest
.result
;
98 curVersion
= db
.version
;
99 db
.onerror = function(ev
) {
100 console
.log("db error", arguments
, openRequest
.webkitErrorMessage
);
103 if (curVersion
!= baseVersion
) {
104 // This is the legacy path, which runs only in Chrome.
105 var setVersionRequest
= db
.setVersion(baseVersion
);
106 setVersionRequest
.onerror
= errorHandler
;
107 setVersionRequest
.onsuccess = function(e
) {
108 assert(setVersionRequest
== e
.target
);
109 createObjectStores(db
);
110 var versionTransaction
= setVersionRequest
.result
;
111 versionTransaction
.oncomplete = function() { handler(db
); };
112 versionTransaction
.onerror
= onError
;
120 // You must close all database connections before calling this.
121 function alterObjectStores(
122 name
, objectStoreNames
, func
, handler
, errorHandler
) {
123 var version
= curVersion
+ 1;
124 var openRequest
= indexedDB
.open(name
, version
);
125 openRequest
.onblocked
= errorHandler
;
126 openRequest
.onupgradeneeded = function(ev
) {
127 doAlteration(ev
.target
.transaction
);
128 // onsuccess will get called after this exits.
130 openRequest
.onsuccess = function(ev
) {
131 assert(openRequest
== ev
.target
);
132 var db
= openRequest
.result
;
133 db
.onerror = function(ev
) {
134 console
.log("error altering db", arguments
,
135 openRequest
.webkitErrorMessage
);
138 if (db
.version
!= version
) {
139 // This is the legacy path, which runs only in Chrome before M23.
140 var setVersionRequest
= db
.setVersion(version
);
141 setVersionRequest
.onerror
= errorHandler
;
142 setVersionRequest
.onsuccess
=
144 curVersion
= db
.version
;
145 assert(setVersionRequest
== e
.target
);
146 var versionTransaction
= setVersionRequest
.result
;
147 versionTransaction
.oncomplete = function() { handler(db
); };
148 versionTransaction
.onerror
= onError
;
149 doAlteration(versionTransaction
);
155 function doAlteration(target
) {
156 for (var store
in objectStoreNames
) {
157 func(target
.objectStore(objectStoreNames
[store
]));
162 function getTransaction(db
, objectStoreNames
, mode
, opt_handler
) {
163 var transaction
= db
.transaction(objectStoreNames
, mode
);
164 transaction
.onerror
= onError
;
165 transaction
.onabort
= onError
;
167 transaction
.oncomplete
= opt_handler
;
172 function deleteDatabase(name
, opt_handler
) {
173 var deleteRequest
= indexedDB
.deleteDatabase(name
);
174 deleteRequest
.onerror
= onError
;
175 deleteRequest
.onblocked
= onError
;
177 deleteRequest
.onsuccess
= opt_handler
;
181 function getCompletionFunc(db
, testName
, startTime
, onTestComplete
) {
182 function onDeleted() {
183 automation
.setStatus("Deleted database.");
187 var duration
= window
.performance
.now() - startTime
;
188 // Ignore the cleanup time for this test.
189 automation
.addResult(testName
, duration
);
190 automation
.setStatus("Deleting database.");
192 deleteDatabase(testName
, onDeleted
);
196 function getDisplayName(args
) {
197 function functionName(f
) {
198 // Function.prototype.name is nonstandard, and not implemented in IE10-
199 return f
.name
|| f
.toString().match(/^function\s*([^(\s]*)/)[1];
201 // The last arg is the completion callback the test runner tacks on.
202 // TODO(ericu): Make test errors delete the database automatically.
203 return functionName(getDisplayName
.caller
) + (args
.length
> 1 ? "_" : "") +
204 Array
.prototype.slice
.call(args
, 0, args
.length
- 1).join("_");
207 // Pad a string [or object convertible to a string] to a fixed width; use this
208 // to have numeric strings sort properly.
209 function padToWidth(s
, width
) {
211 assert(s
.length
<= width
);
212 if (s
.length
< width
) {
213 s
= stringOfLength(width
- s
.length
, '0') + s
;
218 function stringOfLength(n
, c
) {
222 assert(n
== Math
.floor(n
));
223 return new Array(n
+ 1).join(c
);
226 function getSimpleKey(i
) {
227 return "key " + padToWidth(i
, 10);
230 function getSimpleValue(i
) {
231 return "value " + padToWidth(i
, 10);
234 function getIndexableValue(i
) {
235 return { id
: getSimpleValue(i
) };
238 function getForwardIndexKey(i
) {
242 function getBackwardIndexKey(i
) {
246 // This is useful for indexing by keypath; the two names should be ordered in
247 // opposite directions for all i in uint32 range.
248 function getObjectValue(i
) {
250 firstName
: getForwardIndexKey(i
),
251 lastName
: getBackwardIndexKey(i
)
255 function getNFieldName(k
) {
259 function getNFieldObjectValue(i
, n
) {
260 assert(Math
.floor(n
) == n
);
264 // The value varies per field, each object will tend to be unique,
265 // and thanks to the modulus, indexing on different fields will give you
266 // different ordering for large-enough data sets.
267 o
[getNFieldName(n
- 1)] = Math
.pow(i
+ 0.5, n
+ 0.5) % 65536;
272 function putLinearValues(
273 transaction
, objectStoreNames
, numKeys
, getKey
, getValue
) {
275 getKey
= getSimpleKey
;
277 getValue
= getSimpleValue
;
278 for (var i
in objectStoreNames
) {
279 var os
= transaction
.objectStore(objectStoreNames
[i
]);
280 for (var j
= 0; j
< numKeys
; ++j
) {
281 var request
= os
.put(getValue(j
), getKey(j
));
282 request
.onerror
= onError
;
287 function verifyResultNonNull(result
) {
288 assert(result
!= null);
291 function getRandomValues(
292 transaction
, objectStoreNames
, numReads
, numKeys
, indexName
, getKey
) {
294 getKey
= getSimpleKey
;
295 for (var i
in objectStoreNames
) {
296 var os
= transaction
.objectStore(objectStoreNames
[i
]);
299 source
= source
.index(indexName
);
300 for (var j
= 0; j
< numReads
; ++j
) {
301 var rand
= Math
.floor(random() * numKeys
);
302 var request
= source
.get(getKey(rand
));
303 request
.onerror
= onError
;
304 request
.onsuccess
= verifyResultNonNull
;
309 function putRandomValues(
310 transaction
, objectStoreNames
, numPuts
, numKeys
, getKey
, getValue
) {
312 getKey
= getSimpleKey
;
314 getValue
= getSimpleValue
;
315 for (var i
in objectStoreNames
) {
316 var os
= transaction
.objectStore(objectStoreNames
[i
]);
317 for (var j
= 0; j
< numPuts
; ++j
) {
318 var rand
= Math
.floor(random() * numKeys
);
319 var request
= os
.put(getValue(rand
), getKey(rand
));
320 request
.onerror
= onError
;
325 function getSpecificValues(transaction
, objectStoreNames
, indexName
, keys
) {
326 for (var i
in objectStoreNames
) {
327 var os
= transaction
.objectStore(objectStoreNames
[i
]);
330 source
= source
.index(indexName
);
331 for (var j
= 0; j
< keys
.length
; ++j
) {
332 var request
= source
.get(keys
[j
]);
333 request
.onerror
= onError
;
334 request
.onsuccess
= verifyResultNonNull
;
339 // getKey should be deterministic, as we assume that a cursor that starts at
340 // getKey(X) and runs through getKey(X + K) has exactly K values available.
341 // This is annoying to guarantee generally when using an index, so we avoid both
342 // ends of the key space just in case and use simple indices.
343 // TODO(ericu): Figure out if this can be simplified and we can remove uses of
344 // getObjectValue in favor of getNFieldObjectValue.
345 function getValuesFromCursor(
346 transaction
, inputObjectStoreName
, numReads
, numKeys
, indexName
, getKey
,
347 readKeysOnly
, outputObjectStoreName
) {
348 assert(2 * numReads
< numKeys
);
350 getKey
= getSimpleKey
;
351 var rand
= Math
.floor(random() * (numKeys
- 2 * numReads
)) + numReads
;
353 var queryObject
= transaction
.objectStore(inputObjectStoreName
);
356 queryObject
= queryObject
.index(indexName
);
357 var keyRange
= IDBKeyRange
.bound(
358 getKey(rand
), getKey(rand
+ numReads
), false, true);
361 request
= queryObject
.openKeyCursor(keyRange
);
363 request
= queryObject
.openCursor(keyRange
);
366 if (outputObjectStoreName
)
367 oos
= transaction
.objectStore(outputObjectStoreName
);
368 var numReadsLeft
= numReads
;
369 request
.onsuccess = function(event
) {
370 var cursor
= event
.target
.result
;
372 assert(numReadsLeft
);
375 // Put in random order for maximum difficulty. We add in numKeys just
376 // in case we're writing back to the same store; this way we won't
377 // affect the number of keys available to the cursor, since we're always
378 // outside its range.
379 oos
.put(cursor
.value
, numKeys
+ random());
380 values
.push({key
: cursor
.key
, value
: cursor
.value
});
383 assert(!numReadsLeft
);
386 request
.onerror
= onError
;
389 function runTransactionBatch(db
, count
, batchFunc
, objectStoreNames
, mode
,
391 var numTransactionsRunning
= 0;
395 function runOneBatch(db
) {
400 ++numTransactionsRunning
;
401 var transaction
= getTransaction(db
, objectStoreNames
, mode
,
403 assert(!--numTransactionsRunning
);
411 batchFunc(transaction
);
415 // Use random() instead of Math.random() so runs are repeatable.
416 var random
= (function(seed
) {
418 // Implementation of: http://www.burtleburtle.net/bob/rand/smallprng.html
419 function uint32(x
) { return x
>>> 0; }
420 function rot(x
, k
) { return (x
<< k
) | (x
>> (32 - k
)); }
422 function SmallPRNG(seed
) {
425 this.b
= this.c
= this.d
= seed
;
426 for (var i
= 0; i
< 20; ++i
)
430 SmallPRNG
.prototype.ranval = function() {
431 var e
= uint32(this.a
- rot(this.b
, 27));
432 this.a
= this.b
^ rot(this.c
, 17);
433 this.b
= uint32(this.c
+ this.d
);
434 this.c
= uint32(this.d
+ e
);
435 this.d
= uint32(e
+ this.a
);
439 var prng
= new SmallPRNG(seed
);
440 return function() { return prng
.ranval() / 0x100000000; };