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 base.endsWith = function(string, suffix) 31 { 32 if (suffix.length > string.length) 33 return false; 34 var expectedIndex = string.length - suffix.length; 35 return string.lastIndexOf(suffix) == expectedIndex; 36 }; 37 38 base.joinPath = function(parent, child) 39 { 40 if (parent.length == 0) 41 return child; 42 return parent + '/' + child; 43 }; 44 45 base.dirName = function(path) 46 { 47 var directoryIndex = path.lastIndexOf('/'); 48 if (directoryIndex == -1) 49 return path; 50 return path.substr(0, directoryIndex); 51 }; 52 53 base.trimExtension = function(url) 54 { 55 var index = url.lastIndexOf('.'); 56 if (index == -1) 57 return url; 58 return url.substr(0, index); 59 } 60 61 base.uniquifyArray = function(array) 62 { 63 var seen = {}; 64 var result = []; 65 $.each(array, function(index, value) { 66 if (seen[value]) 67 return; 68 seen[value] = true; 69 result.push(value); 70 }); 71 return result; 72 }; 73 74 base.flattenArray = function(arrayOfArrays) 75 { 76 if (!arrayOfArrays.length) 77 return []; 78 return arrayOfArrays.reduce(function(left, right) { 79 return left.concat(right); 80 }); 81 }; 82 83 base.filterDictionary = function(dictionary, predicate) 84 { 85 var result = {}; 86 87 for (var key in dictionary) { 88 if (predicate(key)) 89 result[key] = dictionary[key]; 90 } 91 92 return result; 93 }; 94 95 base.mapDictionary = function(dictionary, functor) 96 { 97 var result = {}; 98 99 for (var key in dictionary) { 100 var value = functor(dictionary[key]); 101 if (typeof value !== 'undefined') 102 result[key] = value; 103 } 104 105 return result; 106 }; 107 108 base.filterTree = function(tree, isLeaf, predicate) 109 { 110 var filteredTree = {}; 111 112 function walkSubtree(subtree, directory) 113 { 114 for (var childName in subtree) { 115 var child = subtree[childName]; 116 var childPath = base.joinPath(directory, childName); 117 if (isLeaf(child)) { 118 if (predicate(child)) 119 filteredTree[childPath] = child; 120 continue; 121 } 122 walkSubtree(child, childPath); 123 } 124 } 125 126 walkSubtree(tree, ''); 127 return filteredTree; 128 }; 129 130 base.forEachDirectory = function(pathList, callback) 131 { 132 var pathsByDirectory = {}; 133 pathList.forEach(function(path) { 134 var directory = base.dirName(path); 135 pathsByDirectory[directory] = pathsByDirectory[directory] || []; 136 pathsByDirectory[directory].push(path); 137 }); 138 Object.keys(pathsByDirectory).sort().forEach(function(directory) { 139 var paths = pathsByDirectory[directory]; 140 callback(directory + ' (' + paths.length + ' tests)', paths); 141 }); 142 }; 143 144 base.parseJSONP = function(jsonp) 145 { 146 if (!jsonp) 147 return {}; 148 149 if (!jsonp.match(/^[^{[]*\(/)) 150 return JSON.parse(jsonp); 151 152 var startIndex = jsonp.indexOf('(') + 1; 153 var endIndex = jsonp.lastIndexOf(')'); 154 if (startIndex == 0 || endIndex == -1) 155 return {}; 156 return JSON.parse(jsonp.substr(startIndex, endIndex - startIndex)); 157 }; 158 159 // This is effectively a cache of possibly-resolved promises. 160 base.AsynchronousCache = function(fetch) 161 { 162 this._fetch = fetch; 163 this._promiseCache = {}; 164 }; 165 166 base.AsynchronousCache._sentinel = new Object(); 167 base.AsynchronousCache.prototype.get = function(key) 168 { 169 if (!(key in this._promiseCache)) { 170 this._promiseCache[key] = base.AsynchronousCache._sentinel; 171 this._promiseCache[key] = this._fetch.call(null, key); 172 } 173 if (this._promiseCache[key] === base.AsynchronousCache._sentinel) 174 return Promise.reject(Error("Reentrant request for ", key)); 175 176 return this._promiseCache[key]; 177 }; 178 179 base.AsynchronousCache.prototype.clear = function() 180 { 181 this._promiseCache = {}; 182 }; 183 184 /* 185 Maintains a dictionary of items, tracking their updates and removing items that haven't been updated. 186 An "update" is a call to the "update" method. 187 To remove stale items, call the "remove" method. It will remove all 188 items that have not been been updated since the last call of "remove". 189 */ 190 base.UpdateTracker = function() 191 { 192 this._items = {}; 193 this._updated = {}; 194 } 195 196 base.UpdateTracker.prototype = { 197 /* 198 Update an {key}/{item} pair. You can make the dictionary act as a set and 199 skip the {item}, in which case the {key} is also the {item}. 200 */ 201 update: function(key, object) 202 { 203 object = object || key; 204 this._items[key] = object; 205 this._updated[key] = 1; 206 }, 207 exists: function(key) 208 { 209 return !!this.get(key); 210 }, 211 get: function(key) 212 { 213 return this._items[key]; 214 }, 215 length: function() 216 { 217 return Object.keys(this._items).length; 218 }, 219 /* 220 Callback parameters are: 221 - item 222 - key 223 - updated, which is true if the item was updated after last purge() call. 224 */ 225 forEach: function(callback, thisObject) 226 { 227 if (!callback) 228 return; 229 230 Object.keys(this._items).sort().forEach(function(key) { 231 var item = this._items[key]; 232 callback.call(thisObject || item, item, key, !!this._updated[key]); 233 }, this); 234 }, 235 purge: function(removeCallback, thisObject) { 236 removeCallback = removeCallback || function() {}; 237 this.forEach(function(item, key, updated) { 238 if (updated) 239 return; 240 removeCallback.call(thisObject || item, item); 241 delete this._items[key]; 242 }, this); 243 this._updated = {}; 244 } 245 } 246 247 // Based on http://src.chromium.org/viewvc/chrome/trunk/src/chrome/browser/resources/shared/js/cr/ui.js 248 base.extends = function(base, prototype) 249 { 250 var extended = function() { 251 var element = typeof base == 'string' ? document.createElement(base) : base.call(this); 252 extended.prototype.__proto__ = element.__proto__; 253 element.__proto__ = extended.prototype; 254 var singleton = element.init && element.init.apply(element, arguments); 255 if (singleton) 256 return singleton; 257 return element; 258 } 259 260 extended.prototype = prototype; 261 return extended; 262 } 263 264 base.getURLParameter = function(name) 265 { 266 var match = RegExp(name + '=' + '(.+?)(&|$)').exec(location.search); 267 if (!match) 268 return null; 269 return decodeURI(match[1]) 270 } 271 272 base.underscoredBuilderName = function(builderName) 273 { 274 return builderName.replace(/[ .()]/g, '_'); 275 } 276 277 })(); 278