• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
26var base = base || {};
27
28(function(){
29
30base.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
38base.joinPath = function(parent, child)
39{
40    if (parent.length == 0)
41        return child;
42    return parent + '/' + child;
43};
44
45base.dirName = function(path)
46{
47    var directoryIndex = path.lastIndexOf('/');
48    if (directoryIndex == -1)
49        return path;
50    return path.substr(0, directoryIndex);
51};
52
53base.trimExtension = function(url)
54{
55    var index = url.lastIndexOf('.');
56    if (index == -1)
57        return url;
58    return url.substr(0, index);
59}
60
61base.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
74base.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
83base.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
95base.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
108base.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
130base.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
144base.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.
160base.AsynchronousCache = function(fetch)
161{
162    this._fetch = fetch;
163    this._promiseCache = {};
164};
165
166base.AsynchronousCache._sentinel = new Object();
167base.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
179base.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*/
190base.UpdateTracker = function()
191{
192    this._items = {};
193    this._updated = {};
194}
195
196base.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
248base.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
264base.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
272base.underscoredBuilderName = function(builderName)
273{
274    return builderName.replace(/[ .()]/g, '_');
275}
276
277})();
278