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