2
* Copyright (C) 2011 Google Inc. All rights reserved.
4
* Redistribution and use in source and binary forms, with or without
5
* modification, are permitted provided that the following conditions
7
* 1. Redistributions of source code must retain the above copyright
8
* notice, this list of conditions and the following disclaimer.
9
* 2. Redistributions in binary form must reproduce the above copyright
10
* notice, this list of conditions and the following disclaimer in the
11
* documentation and/or other materials provided with the distribution.
13
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23
* THE POSSIBILITY OF SUCH DAMAGE.
26
var base = base || {};
30
// Safari 5.1 lacks Function.prototype.bind.
31
if (!('bind' in Function.prototype)) {
32
Function.prototype.bind = function(thisObject) {
34
var boundArguments = [];
35
for (var i = 1; i < arguments.length; ++i) {
36
boundArguments.push(arguments[i]);
39
var actualParameters = [];
40
for (var i = 0; i < boundArguments.length; ++i) {
41
actualParameters.push(boundArguments[i]);
43
for (var i = 0; i < arguments.length; ++i) {
44
actualParameters.push(arguments[i]);
46
return method.apply(thisObject, actualParameters);
51
base.asInteger = function(stringOrInteger)
53
if (typeof stringOrInteger == 'string')
54
return parseInt(stringOrInteger);
55
return stringOrInteger;
58
base.endsWith = function(string, suffix)
60
if (suffix.length > string.length)
62
var expectedIndex = string.length - suffix.length;
63
return string.lastIndexOf(suffix) == expectedIndex;
66
base.repeatString = function(string, count)
68
return new Array(count + 1).join(string);
71
base.joinPath = function(parent, child)
73
if (parent.length == 0)
75
return parent + '/' + child;
78
base.dirName = function(path)
80
var directoryIndex = path.lastIndexOf('/');
81
if (directoryIndex == -1)
83
return path.substr(0, directoryIndex);
86
base.trimExtension = function(url)
88
var index = url.lastIndexOf('.');
91
return url.substr(0, index);
94
base.uniquifyArray = function(array)
98
$.each(array, function(index, value) {
107
base.flattenArray = function(arrayOfArrays)
109
if (!arrayOfArrays.length)
111
return arrayOfArrays.reduce(function(left, right) {
112
return left.concat(right);
116
base.filterDictionary = function(dictionary, predicate)
120
for (var key in dictionary) {
122
result[key] = dictionary[key];
128
base.mapDictionary = function(dictionary, functor)
132
for (var key in dictionary) {
133
var value = functor(dictionary[key]);
134
if (typeof value !== 'undefined')
141
base.filterTree = function(tree, isLeaf, predicate)
143
var filteredTree = {};
145
function walkSubtree(subtree, directory)
147
for (var childName in subtree) {
148
var child = subtree[childName];
149
var childPath = base.joinPath(directory, childName);
151
if (predicate(child))
152
filteredTree[childPath] = child;
155
walkSubtree(child, childPath);
159
walkSubtree(tree, '');
163
base.forEachDirectory = function(pathList, callback)
165
var pathsByDirectory = {};
166
pathList.forEach(function(path) {
167
var directory = base.dirName(path);
168
pathsByDirectory[directory] = pathsByDirectory[directory] || [];
169
pathsByDirectory[directory].push(path);
171
Object.keys(pathsByDirectory).sort().forEach(function(directory) {
172
var paths = pathsByDirectory[directory];
173
callback(directory + ' (' + paths.length + ' tests)', paths);
177
base.parseJSONP = function(jsonp)
179
var startIndex = jsonp.indexOf('(') + 1;
180
var endIndex = jsonp.lastIndexOf(')');
181
return JSON.parse(jsonp.substr(startIndex, endIndex - startIndex));
184
base.RequestTracker = function(requestsInFlight, callback, args)
186
this._requestsInFlight = requestsInFlight;
187
this._callback = callback;
188
this._args = args || [];
192
base.RequestTracker.prototype = {
193
_tryCallback: function()
195
if (!this._requestsInFlight && this._callback)
196
this._callback.apply(null, this._args);
198
requestComplete: function()
200
--this._requestsInFlight;
205
base.callInParallel = function(functionList, callback)
207
var requestTracker = new base.RequestTracker(functionList.length, callback);
209
$.each(functionList, function(index, func) {
211
requestTracker.requestComplete();
216
base.callInSequence = function(func, argumentList, callback)
222
if (nextIndex >= argumentList.length) {
227
func(argumentList[nextIndex++], callNext);
233
base.CallbackIterator = function(callback, listOfArgumentArrays)
235
this._callback = callback;
237
this._listOfArgumentArrays = listOfArgumentArrays;
240
base.CallbackIterator.prototype.hasNext = function()
242
return this._nextIndex < this._listOfArgumentArrays.length;
245
base.CallbackIterator.prototype.hasPrevious = function()
247
return this._nextIndex - 2 >= 0;
250
base.CallbackIterator.prototype.callNext = function()
254
var args = this._listOfArgumentArrays[this._nextIndex];
256
this._callback.apply(null, args);
259
base.CallbackIterator.prototype.callPrevious = function()
261
if (!this.hasPrevious())
263
var args = this._listOfArgumentArrays[this._nextIndex - 2];
265
this._callback.apply(null, args);
268
base.AsynchronousCache = function(fetch)
271
this._dataCache = {};
272
this._callbackCache = {};
275
base.AsynchronousCache.prototype.get = function(key, callback)
279
if (self._dataCache[key]) {
280
// FIXME: Consider always calling callback asynchronously.
281
callback(self._dataCache[key]);
285
if (key in self._callbackCache) {
286
self._callbackCache[key].push(callback);
290
self._callbackCache[key] = [callback];
292
self._fetch.call(null, key, function(data) {
293
self._dataCache[key] = data;
295
var callbackList = self._callbackCache[key];
296
delete self._callbackCache[key];
298
callbackList.forEach(function(cachedCallback) {
299
cachedCallback(data);
304
base.AsynchronousCache.prototype.clear = function()
306
this._dataCache = {};
307
this._callbackCache = {};
311
Maintains a dictionary of items, tracking their updates and removing items that haven't been updated.
312
An "update" is a call to the "update" method.
313
To remove stale items, call the "remove" method. It will remove all
314
items that have not been been updated since the last call of "remove".
316
base.UpdateTracker = function()
322
base.UpdateTracker.prototype = {
324
Update an {key}/{item} pair. You can make the dictionary act as a set and
325
skip the {item}, in which case the {key} is also the {item}.
327
update: function(key, object)
329
object = object || key;
330
this._items[key] = object;
331
this._updated[key] = 1;
333
exists: function(key)
335
return !!this.get(key);
339
return this._items[key];
343
return Object.keys(this._items).length;
346
Callback parameters are:
349
- updated, which is true if the item was updated after last purge() call.
351
forEach: function(callback, thisObject)
356
Object.keys(this._items).sort().forEach(function(key) {
357
var item = this._items[key];
358
callback.call(thisObject || item, item, key, !!this._updated[key]);
361
purge: function(removeCallback, thisObject) {
362
removeCallback = removeCallback || function() {};
363
this.forEach(function(item, key, updated) {
366
removeCallback.call(thisObject || item, item);
367
delete this._items[key];
373
// Based on http://src.chromium.org/viewvc/chrome/trunk/src/chrome/browser/resources/shared/js/cr/ui.js
374
base.extends = function(base, prototype)
376
var extended = function() {
377
var element = typeof base == 'string' ? document.createElement(base) : base.call(this);
378
extended.prototype.__proto__ = element.__proto__;
379
element.__proto__ = extended.prototype;
380
var singleton = element.init && element.init.apply(element, arguments);
386
extended.prototype = prototype;
390
function createRelativeTimeDescriptor(divisorInMilliseconds, unit)
392
return function(delta) {
393
var deltaInUnits = delta / divisorInMilliseconds;
394
return (deltaInUnits).toFixed(0) + ' ' + unit + (deltaInUnits >= 1.5 ? 's' : '') + ' ago';
398
var kMinuteInMilliseconds = 60 * 1000;
399
var kRelativeTimeSlots = [
401
maxMilliseconds: kMinuteInMilliseconds,
402
describe: function(delta) { return 'Just now'; }
405
maxMilliseconds: 60 * kMinuteInMilliseconds,
406
describe: createRelativeTimeDescriptor(kMinuteInMilliseconds, 'minute')
409
maxMilliseconds: 24 * 60 * kMinuteInMilliseconds,
410
describe: createRelativeTimeDescriptor(60 * kMinuteInMilliseconds, 'hour')
413
maxMilliseconds: Number.MAX_VALUE,
414
describe: createRelativeTimeDescriptor(24 * 60 * kMinuteInMilliseconds, 'day')
419
Represent time as descriptive text, relative to now and gradually decreasing in precision:
420
delta < 1 minutes => Just Now
421
delta < 60 minutes => X minute[s] ago
422
delta < 24 hours => X hour[s] ago
423
delta < inf => X day[s] ago
425
base.relativizeTime = function(time)
428
var delta = new Date().getTime() - time;
429
kRelativeTimeSlots.some(function(slot) {
430
if (delta >= slot.maxMilliseconds)
433
result = slot.describe(delta);
439
base.getURLParameter = function(name)
441
var match = RegExp(name + '=' + '(.+?)(&|$)').exec(location.search);
444
return decodeURI(match[1])
447
base.underscoredBuilderName = function(builderName)
449
return builderName.replace(/[ .()]/g, '_');
452
base.createLinkNode = function(url, textContent, opt_target)
454
var link = document.createElement('a');
457
link.target = opt_target;
458
link.appendChild(document.createTextNode(textContent));