| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 * Copyright (C) 2011 Google Inc. All rights reserved. | |
| 3 * | |
| 4 * Redistribution and use in source and binary forms, with or without | |
| 5 * modification, are permitted provided that the following conditions | |
| 6 * are met: | |
| 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. | |
| 12 * | |
| 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. | |
| 24 */ | |
| 25 | |
| 26 var base = base || {}; | |
| 27 | |
| 28 (function(){ | |
| 29 | |
| 30 // Safari 5.1 lacks Function.prototype.bind. | |
| 31 if (!('bind' in Function.prototype)) { | |
| 32 Function.prototype.bind = function(thisObject) { | |
| 33 var method = this; | |
| 34 var boundArguments = []; | |
| 35 for (var i = 1; i < arguments.length; ++i) { | |
| 36 boundArguments.push(arguments[i]); | |
| 37 } | |
| 38 return function() { | |
| 39 var actualParameters = []; | |
| 40 for (var i = 0; i < boundArguments.length; ++i) { | |
| 41 actualParameters.push(boundArguments[i]); | |
| 42 } | |
| 43 for (var i = 0; i < arguments.length; ++i) { | |
| 44 actualParameters.push(arguments[i]); | |
| 45 } | |
| 46 return method.apply(thisObject, actualParameters); | |
| 47 } | |
| 48 } | |
| 49 } | |
| 50 | |
| 51 base.asInteger = function(stringOrInteger) | |
| 52 { | |
| 53 if (typeof stringOrInteger == 'string') | |
| 54 return parseInt(stringOrInteger); | |
| 55 return stringOrInteger; | |
| 56 }; | |
| 57 | |
| 58 base.endsWith = function(string, suffix) | |
| 59 { | |
| 60 if (suffix.length > string.length) | |
| 61 return false; | |
| 62 var expectedIndex = string.length - suffix.length; | |
| 63 return string.lastIndexOf(suffix) == expectedIndex; | |
| 64 }; | |
| 65 | |
| 66 base.repeatString = function(string, count) | |
| 67 { | |
| 68 return new Array(count + 1).join(string); | |
| 69 }; | |
| 70 | |
| 71 base.joinPath = function(parent, child) | |
| 72 { | |
| 73 if (parent.length == 0) | |
| 74 return child; | |
| 75 return parent + '/' + child; | |
| 76 }; | |
| 77 | |
| 78 base.dirName = function(path) | |
| 79 { | |
| 80 var directoryIndex = path.lastIndexOf('/'); | |
| 81 if (directoryIndex == -1) | |
| 82 return path; | |
| 83 return path.substr(0, directoryIndex); | |
| 84 }; | |
| 85 | |
| 86 base.trimExtension = function(url) | |
| 87 { | |
| 88 var index = url.lastIndexOf('.'); | |
| 89 if (index == -1) | |
| 90 return url; | |
| 91 return url.substr(0, index); | |
| 92 } | |
| 93 | |
| 94 base.uniquifyArray = function(array) | |
| 95 { | |
| 96 var seen = {}; | |
| 97 var result = []; | |
| 98 $.each(array, function(index, value) { | |
| 99 if (seen[value]) | |
| 100 return; | |
| 101 seen[value] = true; | |
| 102 result.push(value); | |
| 103 }); | |
| 104 return result; | |
| 105 }; | |
| 106 | |
| 107 base.flattenArray = function(arrayOfArrays) | |
| 108 { | |
| 109 if (!arrayOfArrays.length) | |
| 110 return []; | |
| 111 return arrayOfArrays.reduce(function(left, right) { | |
| 112 return left.concat(right); | |
| 113 }); | |
| 114 }; | |
| 115 | |
| 116 base.filterDictionary = function(dictionary, predicate) | |
| 117 { | |
| 118 var result = {}; | |
| 119 | |
| 120 for (var key in dictionary) { | |
| 121 if (predicate(key)) | |
| 122 result[key] = dictionary[key]; | |
| 123 } | |
| 124 | |
| 125 return result; | |
| 126 }; | |
| 127 | |
| 128 base.mapDictionary = function(dictionary, functor) | |
| 129 { | |
| 130 var result = {}; | |
| 131 | |
| 132 for (var key in dictionary) { | |
| 133 var value = functor(dictionary[key]); | |
| 134 if (typeof value !== 'undefined') | |
| 135 result[key] = value; | |
| 136 } | |
| 137 | |
| 138 return result; | |
| 139 }; | |
| 140 | |
| 141 base.filterTree = function(tree, isLeaf, predicate) | |
| 142 { | |
| 143 var filteredTree = {}; | |
| 144 | |
| 145 function walkSubtree(subtree, directory) | |
| 146 { | |
| 147 for (var childName in subtree) { | |
| 148 var child = subtree[childName]; | |
| 149 var childPath = base.joinPath(directory, childName); | |
| 150 if (isLeaf(child)) { | |
| 151 if (predicate(child)) | |
| 152 filteredTree[childPath] = child; | |
| 153 continue; | |
| 154 } | |
| 155 walkSubtree(child, childPath); | |
| 156 } | |
| 157 } | |
| 158 | |
| 159 walkSubtree(tree, ''); | |
| 160 return filteredTree; | |
| 161 }; | |
| 162 | |
| 163 base.forEachDirectory = function(pathList, callback) | |
| 164 { | |
| 165 var pathsByDirectory = {}; | |
| 166 pathList.forEach(function(path) { | |
| 167 var directory = base.dirName(path); | |
| 168 pathsByDirectory[directory] = pathsByDirectory[directory] || []; | |
| 169 pathsByDirectory[directory].push(path); | |
| 170 }); | |
| 171 Object.keys(pathsByDirectory).sort().forEach(function(directory) { | |
| 172 var paths = pathsByDirectory[directory]; | |
| 173 callback(directory + ' (' + paths.length + ' tests)', paths); | |
| 174 }); | |
| 175 }; | |
| 176 | |
| 177 base.parseJSONP = function(jsonp) | |
| 178 { | |
| 179 var startIndex = jsonp.indexOf('(') + 1; | |
| 180 var endIndex = jsonp.lastIndexOf(')'); | |
| 181 if (startIndex == 0 || endIndex == -1) | |
| 182 return {}; | |
| 183 return JSON.parse(jsonp.substr(startIndex, endIndex - startIndex)); | |
| 184 }; | |
| 185 | |
| 186 base.RequestTracker = function(requestsInFlight, callback, args) | |
| 187 { | |
| 188 this._requestsInFlight = requestsInFlight; | |
| 189 this._callback = callback; | |
| 190 this._args = args || []; | |
| 191 this._tryCallback(); | |
| 192 }; | |
| 193 | |
| 194 base.RequestTracker.prototype = { | |
| 195 _tryCallback: function() | |
| 196 { | |
| 197 if (!this._requestsInFlight && this._callback) | |
| 198 this._callback.apply(null, this._args); | |
| 199 }, | |
| 200 requestComplete: function() | |
| 201 { | |
| 202 --this._requestsInFlight; | |
| 203 this._tryCallback(); | |
| 204 } | |
| 205 } | |
| 206 | |
| 207 base.callInParallel = function(functionList, callback) | |
| 208 { | |
| 209 var requestTracker = new base.RequestTracker(functionList.length, callback); | |
| 210 | |
| 211 $.each(functionList, function(index, func) { | |
| 212 func(function() { | |
| 213 requestTracker.requestComplete(); | |
| 214 }); | |
| 215 }); | |
| 216 }; | |
| 217 | |
| 218 base.callInSequence = function(func, argumentList, callback) | |
| 219 { | |
| 220 var nextIndex = 0; | |
| 221 | |
| 222 function callNext() | |
| 223 { | |
| 224 if (nextIndex >= argumentList.length) { | |
| 225 callback(); | |
| 226 return; | |
| 227 } | |
| 228 | |
| 229 func(argumentList[nextIndex++], callNext); | |
| 230 } | |
| 231 | |
| 232 callNext(); | |
| 233 }; | |
| 234 | |
| 235 base.CallbackIterator = function(callback, listOfArgumentArrays) | |
| 236 { | |
| 237 this._callback = callback; | |
| 238 this._nextIndex = 0; | |
| 239 this._listOfArgumentArrays = listOfArgumentArrays; | |
| 240 }; | |
| 241 | |
| 242 base.CallbackIterator.prototype.hasNext = function() | |
| 243 { | |
| 244 return this._nextIndex < this._listOfArgumentArrays.length; | |
| 245 }; | |
| 246 | |
| 247 base.CallbackIterator.prototype.hasPrevious = function() | |
| 248 { | |
| 249 return this._nextIndex - 2 >= 0; | |
| 250 }; | |
| 251 | |
| 252 base.CallbackIterator.prototype.callNext = function() | |
| 253 { | |
| 254 if (!this.hasNext()) | |
| 255 return; | |
| 256 var args = this._listOfArgumentArrays[this._nextIndex]; | |
| 257 this._nextIndex++; | |
| 258 this._callback.apply(null, args); | |
| 259 }; | |
| 260 | |
| 261 base.CallbackIterator.prototype.callPrevious = function() | |
| 262 { | |
| 263 if (!this.hasPrevious()) | |
| 264 return; | |
| 265 var args = this._listOfArgumentArrays[this._nextIndex - 2]; | |
| 266 this._nextIndex--; | |
| 267 this._callback.apply(null, args); | |
| 268 }; | |
| 269 | |
| 270 base.AsynchronousCache = function(fetch) | |
| 271 { | |
| 272 this._fetch = fetch; | |
| 273 this._dataCache = {}; | |
| 274 this._callbackCache = {}; | |
| 275 }; | |
| 276 | |
| 277 base.AsynchronousCache.prototype.get = function(key, callback) | |
| 278 { | |
| 279 var self = this; | |
| 280 | |
| 281 if (self._dataCache[key]) { | |
| 282 // FIXME: Consider always calling callback asynchronously. | |
| 283 callback(self._dataCache[key]); | |
| 284 return; | |
| 285 } | |
| 286 | |
| 287 if (key in self._callbackCache) { | |
| 288 self._callbackCache[key].push(callback); | |
| 289 return; | |
| 290 } | |
| 291 | |
| 292 self._callbackCache[key] = [callback]; | |
| 293 | |
| 294 self._fetch.call(null, key, function(data) { | |
| 295 self._dataCache[key] = data; | |
| 296 | |
| 297 var callbackList = self._callbackCache[key]; | |
| 298 delete self._callbackCache[key]; | |
| 299 | |
| 300 callbackList.forEach(function(cachedCallback) { | |
| 301 cachedCallback(data); | |
| 302 }); | |
| 303 }); | |
| 304 }; | |
| 305 | |
| 306 base.AsynchronousCache.prototype.clear = function() | |
| 307 { | |
| 308 this._dataCache = {}; | |
| 309 this._callbackCache = {}; | |
| 310 } | |
| 311 | |
| 312 /* | |
| 313 Maintains a dictionary of items, tracking their updates and removing items t
hat haven't been updated. | |
| 314 An "update" is a call to the "update" method. | |
| 315 To remove stale items, call the "remove" method. It will remove all | |
| 316 items that have not been been updated since the last call of "remove". | |
| 317 */ | |
| 318 base.UpdateTracker = function() | |
| 319 { | |
| 320 this._items = {}; | |
| 321 this._updated = {}; | |
| 322 } | |
| 323 | |
| 324 base.UpdateTracker.prototype = { | |
| 325 /* | |
| 326 Update an {key}/{item} pair. You can make the dictionary act as a set an
d | |
| 327 skip the {item}, in which case the {key} is also the {item}. | |
| 328 */ | |
| 329 update: function(key, object) | |
| 330 { | |
| 331 object = object || key; | |
| 332 this._items[key] = object; | |
| 333 this._updated[key] = 1; | |
| 334 }, | |
| 335 exists: function(key) | |
| 336 { | |
| 337 return !!this.get(key); | |
| 338 }, | |
| 339 get: function(key) | |
| 340 { | |
| 341 return this._items[key]; | |
| 342 }, | |
| 343 length: function() | |
| 344 { | |
| 345 return Object.keys(this._items).length; | |
| 346 }, | |
| 347 /* | |
| 348 Callback parameters are: | |
| 349 - item | |
| 350 - key | |
| 351 - updated, which is true if the item was updated after last purge() call
. | |
| 352 */ | |
| 353 forEach: function(callback, thisObject) | |
| 354 { | |
| 355 if (!callback) | |
| 356 return; | |
| 357 | |
| 358 Object.keys(this._items).sort().forEach(function(key) { | |
| 359 var item = this._items[key]; | |
| 360 callback.call(thisObject || item, item, key, !!this._updated[key]); | |
| 361 }, this); | |
| 362 }, | |
| 363 purge: function(removeCallback, thisObject) { | |
| 364 removeCallback = removeCallback || function() {}; | |
| 365 this.forEach(function(item, key, updated) { | |
| 366 if (updated) | |
| 367 return; | |
| 368 removeCallback.call(thisObject || item, item); | |
| 369 delete this._items[key]; | |
| 370 }, this); | |
| 371 this._updated = {}; | |
| 372 } | |
| 373 } | |
| 374 | |
| 375 // Based on http://src.chromium.org/viewvc/chrome/trunk/src/chrome/browser/resou
rces/shared/js/cr/ui.js | |
| 376 base.extends = function(base, prototype) | |
| 377 { | |
| 378 var extended = function() { | |
| 379 var element = typeof base == 'string' ? document.createElement(base) : b
ase.call(this); | |
| 380 extended.prototype.__proto__ = element.__proto__; | |
| 381 element.__proto__ = extended.prototype; | |
| 382 var singleton = element.init && element.init.apply(element, arguments); | |
| 383 if (singleton) | |
| 384 return singleton; | |
| 385 return element; | |
| 386 } | |
| 387 | |
| 388 extended.prototype = prototype; | |
| 389 return extended; | |
| 390 } | |
| 391 | |
| 392 function createRelativeTimeDescriptor(divisorInMilliseconds, unit) | |
| 393 { | |
| 394 return function(delta) { | |
| 395 var deltaInUnits = delta / divisorInMilliseconds; | |
| 396 return (deltaInUnits).toFixed(0) + ' ' + unit + (deltaInUnits >= 1.5 ? '
s' : '') + ' ago'; | |
| 397 } | |
| 398 } | |
| 399 | |
| 400 var kMinuteInMilliseconds = 60 * 1000; | |
| 401 var kRelativeTimeSlots = [ | |
| 402 { | |
| 403 maxMilliseconds: kMinuteInMilliseconds, | |
| 404 describe: function(delta) { return 'Just now'; } | |
| 405 }, | |
| 406 { | |
| 407 maxMilliseconds: 60 * kMinuteInMilliseconds, | |
| 408 describe: createRelativeTimeDescriptor(kMinuteInMilliseconds, 'minute') | |
| 409 }, | |
| 410 { | |
| 411 maxMilliseconds: 24 * 60 * kMinuteInMilliseconds, | |
| 412 describe: createRelativeTimeDescriptor(60 * kMinuteInMilliseconds, 'hour
') | |
| 413 }, | |
| 414 { | |
| 415 maxMilliseconds: Number.MAX_VALUE, | |
| 416 describe: createRelativeTimeDescriptor(24 * 60 * kMinuteInMilliseconds,
'day') | |
| 417 } | |
| 418 ]; | |
| 419 | |
| 420 /* | |
| 421 Represent time as descriptive text, relative to now and gradually decreasing
in precision: | |
| 422 delta < 1 minutes => Just Now | |
| 423 delta < 60 minutes => X minute[s] ago | |
| 424 delta < 24 hours => X hour[s] ago | |
| 425 delta < inf => X day[s] ago | |
| 426 */ | |
| 427 base.relativizeTime = function(time) | |
| 428 { | |
| 429 var result; | |
| 430 var delta = new Date().getTime() - time; | |
| 431 kRelativeTimeSlots.some(function(slot) { | |
| 432 if (delta >= slot.maxMilliseconds) | |
| 433 return false; | |
| 434 | |
| 435 result = slot.describe(delta); | |
| 436 return true; | |
| 437 }); | |
| 438 return result; | |
| 439 } | |
| 440 | |
| 441 base.getURLParameter = function(name) | |
| 442 { | |
| 443 var match = RegExp(name + '=' + '(.+?)(&|$)').exec(location.search); | |
| 444 if (!match) | |
| 445 return null; | |
| 446 return decodeURI(match[1]) | |
| 447 } | |
| 448 | |
| 449 base.underscoredBuilderName = function(builderName) | |
| 450 { | |
| 451 return builderName.replace(/[ .()]/g, '_'); | |
| 452 } | |
| 453 | |
| 454 base.createLinkNode = function(url, textContent, opt_target) | |
| 455 { | |
| 456 var link = document.createElement('a'); | |
| 457 link.href = url; | |
| 458 if (opt_target) | |
| 459 link.target = opt_target; | |
| 460 link.appendChild(document.createTextNode(textContent)); | |
| 461 return link; | |
| 462 } | |
| 463 | |
| 464 })(); | |
| OLD | NEW |