Re-enable index-basics-workers test to see if still times
[chromium-blink-merge.git] / tools / cc-frame-viewer / src / base.js
blobd661495e51a89fc387a2c50d19d85ce460a312a9
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 'use strict';
8 /**
9  * The global object.
10  * @type {!Object}
11  * @const
12  */
13 var global = this;
16 /** Platform, package, object property, and Event support. */
17 this.base = (function() {
19   /**
20    * Base path for modules. Used to form URLs for module 'require' requests.
21    */
22   var moduleBasePath = '.';
23   function setModuleBasePath(path) {
24     if (path[path.length - 1] == '/')
25       path = path.substring(0, path.length - 1);
26     moduleBasePath = path;
27   }
30   function mLog(text, opt_indentLevel) {
31     if (true)
32       return;
34     var spacing = '';
35     var indentLevel = opt_indentLevel || 0;
36     for (var i = 0; i < indentLevel; i++)
37       spacing += ' ';
38     console.log(spacing + text);
39   }
41   /**
42    * Builds an object structure for the provided namespace path,
43    * ensuring that names that already exist are not overwritten. For
44    * example:
45    * 'a.b.c' -> a = {};a.b={};a.b.c={};
46    * @param {string} name Name of the object that this file defines.
47    * @param {*=} opt_object The object to expose at the end of the path.
48    * @param {Object=} opt_objectToExportTo The object to add the path to;
49    *     default is {@code global}.
50    * @private
51    */
52   function exportPath(name, opt_object, opt_objectToExportTo) {
53     var parts = name.split('.');
54     var cur = opt_objectToExportTo || global;
56     for (var part; parts.length && (part = parts.shift());) {
57       if (!parts.length && opt_object !== undefined) {
58         // last part and we have an object; use it
59         cur[part] = opt_object;
60       } else if (part in cur) {
61         cur = cur[part];
62       } else {
63         cur = cur[part] = {};
64       }
65     }
66     return cur;
67   };
69   var didLoadModules = false;
70   var moduleDependencies = {};
71   var moduleStylesheets = {};
72   var moduleRawScripts = {};
74   function addModuleDependency(moduleName, dependentModuleName) {
75     if (!moduleDependencies[moduleName])
76       moduleDependencies[moduleName] = [];
78     var dependentModules = moduleDependencies[moduleName];
79     var found = false;
80     for (var i = 0; i < dependentModules.length; i++)
81       if (dependentModules[i] == dependentModuleName)
82         found = true;
83     if (!found)
84       dependentModules.push(dependentModuleName);
85   }
87   function addModuleRawScriptDependency(moduleName, rawScriptName) {
88     if (!moduleRawScripts[moduleName])
89       moduleRawScripts[moduleName] = [];
91     var dependentRawScripts = moduleRawScripts[moduleName];
92     var found = false;
93     for (var i = 0; i < moduleRawScripts.length; i++)
94       if (dependentRawScripts[i] == rawScriptName)
95         found = true;
96     if (!found)
97       dependentRawScripts.push(rawScriptName);
98   }
100   function addModuleStylesheet(moduleName, stylesheetName) {
101     if (!moduleStylesheets[moduleName])
102       moduleStylesheets[moduleName] = [];
104     var stylesheets = moduleStylesheets[moduleName];
105     var found = false;
106     for (var i = 0; i < stylesheets.length; i++)
107       if (stylesheets[i] == stylesheetName)
108         found = true;
109       if (!found)
110         stylesheets.push(stylesheetName);
111   }
113   function ensureDepsLoaded() {
114     if (didLoadModules)
115       return;
116     didLoadModules = true;
118     var req = new XMLHttpRequest();
119     var src = moduleBasePath + '/base/' + 'deps.js';
120     req.open('GET', src, false);
121     req.send(null);
122     if (req.status != 200)
123       throw new Error('Could not find ' + src +
124                       '. Run calcdeps.py and try again.');
126     base.addModuleDependency = addModuleDependency;
127     base.addModuleRawScriptDependency = addModuleRawScriptDependency;
128     base.addModuleStylesheet = addModuleStylesheet;
129     try {
130       // By construction, the deps file should call addModuleDependency.
131       eval(req.responseText);
132     } catch (e) {
133       throw new Error('When loading deps, got ' + e.stack ? e.stack : e);
134     }
135     delete base.addModuleStylesheet;
136     delete base.addModuleRawScriptDependency;
137     delete base.addModuleDependency;
139   }
141   var moduleLoadStatus = {};
142   var rawScriptLoadStatus = {};
143   function require(dependentModuleName, opt_indentLevel) {
144     var indentLevel = opt_indentLevel || 0;
146     if (window.FLATTENED) {
147       if (!window.FLATTENED[dependentModuleName]) {
148         throw new Error('Somehow, module ' + dependentModuleName +
149                         ' didn\'t get stored in the flattened js file! ' +
150                         'You may need to rerun build/calcdeps.py');
151       }
152       return;
153     }
154     ensureDepsLoaded();
156     mLog('require(' + dependentModuleName + ')', indentLevel);
158     if (moduleLoadStatus[dependentModuleName] == 'APPENDED')
159       return;
160     if (moduleLoadStatus[dependentModuleName] == 'RESOLVING')
161       throw new Error('Circular dependency betwen modules. Cannot continue!');
162     moduleLoadStatus[dependentModuleName] = 'RESOLVING';
164     // Load the module stylesheet first.
165     var stylesheets = moduleStylesheets[dependentModuleName] || [];
166     for (var i = 0; i < stylesheets.length; i++)
167       requireStylesheet(stylesheets[i]);
169     // Load the module raw scripts next
170     var rawScripts = moduleRawScripts[dependentModuleName] || [];
171     for (var i = 0; i < rawScripts.length; i++) {
172       var rawScriptName = rawScripts[i];
173       if (rawScriptLoadStatus[rawScriptName])
174         continue;
176       mLog('load(' + rawScriptName + ')', indentLevel);
177       var src = moduleBasePath + '/' + rawScriptName;
178       var text = '<script type="text/javascript" src="' + src +
179         '"></' + 'script>';
180       base.doc.write(text);
181       rawScriptLoadStatus[rawScriptName] = 'APPENDED';
182     }
184     // Load the module's dependent scripts after.
185     var dependentModules =
186         moduleDependencies[dependentModuleName] || [];
187     for (var i = 0; i < dependentModules.length; i++)
188       require(dependentModules[i], indentLevel + 1);
190     mLog('load(' + dependentModuleName + ')', indentLevel);
191     // Load the module itself.
192     var localPath = dependentModuleName.replace(/\./g, '/') + '.js';
193     var src = moduleBasePath + '/' + localPath;
194     var text = '<script type="text/javascript" src="' + src +
195         '"></' + 'script>';
196     base.doc.write(text);
197     moduleLoadStatus[dependentModuleName] = 'APPENDED';
198   }
200   /**
201    * Adds a dependency on a raw javascript file, e.g. a third party
202    * library.
203    * @param {String} rawScriptName The path to the script file, relative to
204    * moduleBasePath.
205    */
206   function requireRawScript(rawScriptPath) {
207     if (window.FLATTENED_RAW_SCRIPTS) {
208       if (!window.FLATTENED_RAW_SCRIPTS[rawScriptPath]) {
209         throw new Error('Somehow, ' + rawScriptPath +
210                         ' didn\'t get stored in the flattened js file! ' +
211                         'You may need to rerun build/calcdeps.py');
212       }
213       return;
214     }
216     if (rawScriptLoadStatus[rawScriptPath])
217       return;
218     throw new Error(rawScriptPath + ' should already have been loaded.' +
219                     ' Did you forget to run calcdeps.py?');
220   }
222   var stylesheetLoadStatus = {};
223   function requireStylesheet(dependentStylesheetName) {
224     if (window.FLATTENED)
225       return;
227     if (stylesheetLoadStatus[dependentStylesheetName])
228       return;
229     stylesheetLoadStatus[dependentStylesheetName] = true;
230     var localPath = dependentStylesheetName.replace(/\./g, '/') + '.css';
231     var stylesheetPath = moduleBasePath + '/' + localPath;
233     var linkEl = document.createElement('link');
234     linkEl.setAttribute('rel', 'stylesheet');
235     linkEl.setAttribute('href', stylesheetPath);
236     base.doc.head.appendChild(linkEl);
237   }
239   function exportTo(namespace, fn) {
240     var obj = exportPath(namespace);
241     try {
242       var exports = fn();
243     } catch (e) {
244       console.log('While running exports for ', name, ':');
245       console.log(e.stack || e);
246       return;
247     }
249     for (var propertyName in exports) {
250       // Maybe we should check the prototype chain here? The current usage
251       // pattern is always using an object literal so we only care about own
252       // properties.
253       var propertyDescriptor = Object.getOwnPropertyDescriptor(exports,
254                                                                propertyName);
255       if (propertyDescriptor) {
256         Object.defineProperty(obj, propertyName, propertyDescriptor);
257         mLog('  +' + propertyName);
258       }
259     }
260   };
262   /**
263    * Fires a property change event on the target.
264    * @param {EventTarget} target The target to dispatch the event on.
265    * @param {string} propertyName The name of the property that changed.
266    * @param {*} newValue The new value for the property.
267    * @param {*} oldValue The old value for the property.
268    */
269   function dispatchPropertyChange(target, propertyName, newValue, oldValue) {
270     var e = new base.Event(propertyName + 'Change');
271     e.propertyName = propertyName;
272     e.newValue = newValue;
273     e.oldValue = oldValue;
274     target.dispatchEvent(e);
275   }
277   /**
278    * Converts a camelCase javascript property name to a hyphenated-lower-case
279    * attribute name.
280    * @param {string} jsName The javascript camelCase property name.
281    * @return {string} The equivalent hyphenated-lower-case attribute name.
282    */
283   function getAttributeName(jsName) {
284     return jsName.replace(/([A-Z])/g, '-$1').toLowerCase();
285   }
287   /**
288    * The kind of property to define in {@code defineProperty}.
289    * @enum {number}
290    * @const
291    */
292   var PropertyKind = {
293     /**
294      * Plain old JS property where the backing data is stored as a 'private'
295      * field on the object.
296      */
297     JS: 'js',
299     /**
300      * The property backing data is stored as an attribute on an element.
301      */
302     ATTR: 'attr',
304     /**
305      * The property backing data is stored as an attribute on an element. If the
306      * element has the attribute then the value is true.
307      */
308     BOOL_ATTR: 'boolAttr'
309   };
311   /**
312    * Helper function for defineProperty that returns the getter to use for the
313    * property.
314    * @param {string} name The name of the property.
315    * @param {base.PropertyKind} kind The kind of the property.
316    * @return {function():*} The getter for the property.
317    */
318   function getGetter(name, kind) {
319     switch (kind) {
320       case PropertyKind.JS:
321         var privateName = name + '_';
322         return function() {
323           return this[privateName];
324         };
325       case PropertyKind.ATTR:
326         var attributeName = getAttributeName(name);
327         return function() {
328           return this.getAttribute(attributeName);
329         };
330       case PropertyKind.BOOL_ATTR:
331         var attributeName = getAttributeName(name);
332         return function() {
333           return this.hasAttribute(attributeName);
334         };
335     }
336   }
338   /**
339    * Helper function for defineProperty that returns the setter of the right
340    * kind.
341    * @param {string} name The name of the property we are defining the setter
342    *     for.
343    * @param {base.PropertyKind} kind The kind of property we are getting the
344    *     setter for.
345    * @param {function(*):void} opt_setHook A function to run after the property
346    *     is set, but before the propertyChange event is fired.
347    * @return {function(*):void} The function to use as a setter.
348    */
349   function getSetter(name, kind, opt_setHook) {
350     switch (kind) {
351       case PropertyKind.JS:
352         var privateName = name + '_';
353         return function(value) {
354           var oldValue = this[privateName];
355           if (value !== oldValue) {
356             this[privateName] = value;
357             if (opt_setHook)
358               opt_setHook.call(this, value, oldValue);
359             dispatchPropertyChange(this, name, value, oldValue);
360           }
361         };
363       case PropertyKind.ATTR:
364         var attributeName = getAttributeName(name);
365         return function(value) {
366           var oldValue = this[attributeName];
367           if (value !== oldValue) {
368             if (value == undefined)
369               this.removeAttribute(attributeName);
370             else
371               this.setAttribute(attributeName, value);
372             if (opt_setHook)
373               opt_setHook.call(this, value, oldValue);
374             dispatchPropertyChange(this, name, value, oldValue);
375           }
376         };
378       case PropertyKind.BOOL_ATTR:
379         var attributeName = getAttributeName(name);
380         return function(value) {
381           var oldValue = this[attributeName];
382           if (value !== oldValue) {
383             if (value)
384               this.setAttribute(attributeName, name);
385             else
386               this.removeAttribute(attributeName);
387             if (opt_setHook)
388               opt_setHook.call(this, value, oldValue);
389             dispatchPropertyChange(this, name, value, oldValue);
390           }
391         };
392     }
393   }
395   /**
396    * Defines a property on an object. When the setter changes the value a
397    * property change event with the type {@code name + 'Change'} is fired.
398    * @param {!Object} obj The object to define the property for.
399    * @param {string} name The name of the property.
400    * @param {base.PropertyKind=} opt_kind What kind of underlying storage to
401    * use.
402    * @param {function(*):void} opt_setHook A function to run after the
403    *     property is set, but before the propertyChange event is fired.
404    */
405   function defineProperty(obj, name, opt_kind, opt_setHook) {
406     if (typeof obj == 'function')
407       obj = obj.prototype;
409     var kind = opt_kind || PropertyKind.JS;
411     if (!obj.__lookupGetter__(name))
412       obj.__defineGetter__(name, getGetter(name, kind));
414     if (!obj.__lookupSetter__(name))
415       obj.__defineSetter__(name, getSetter(name, kind, opt_setHook));
416   }
418   /**
419    * Counter for use with createUid
420    */
421   var uidCounter = 1;
423   /**
424    * @return {number} A new unique ID.
425    */
426   function createUid() {
427     return uidCounter++;
428   }
430   /**
431    * Returns a unique ID for the item. This mutates the item so it needs to be
432    * an object
433    * @param {!Object} item The item to get the unique ID for.
434    * @return {number} The unique ID for the item.
435    */
436   function getUid(item) {
437     if (item.hasOwnProperty('uid'))
438       return item.uid;
439     return item.uid = createUid();
440   }
442   /**
443    * Dispatches a simple event on an event target.
444    * @param {!EventTarget} target The event target to dispatch the event on.
445    * @param {string} type The type of the event.
446    * @param {boolean=} opt_bubbles Whether the event bubbles or not.
447    * @param {boolean=} opt_cancelable Whether the default action of the event
448    *     can be prevented.
449    * @return {boolean} If any of the listeners called {@code preventDefault}
450    *     during the dispatch this will return false.
451    */
452   function dispatchSimpleEvent(target, type, opt_bubbles, opt_cancelable) {
453     var e = new base.Event(type, opt_bubbles, opt_cancelable);
454     return target.dispatchEvent(e);
455   }
457   /**
458    * Adds a {@code getInstance} static method that always return the same
459    * instance object.
460    * @param {!Function} ctor The constructor for the class to add the static
461    *     method to.
462    */
463   function addSingletonGetter(ctor) {
464     ctor.getInstance = function() {
465       return ctor.instance_ || (ctor.instance_ = new ctor());
466     };
467   }
469   /**
470    * Creates a new event to be used with base.EventTarget or DOM EventTarget
471    * objects.
472    * @param {string} type The name of the event.
473    * @param {boolean=} opt_bubbles Whether the event bubbles.
474    *     Default is false.
475    * @param {boolean=} opt_preventable Whether the default action of the event
476    *     can be prevented.
477    * @constructor
478    * @extends {Event}
479    */
480   function Event(type, opt_bubbles, opt_preventable) {
481     var e = base.doc.createEvent('Event');
482     e.initEvent(type, !!opt_bubbles, !!opt_preventable);
483     e.__proto__ = global.Event.prototype;
484     return e;
485   };
487   /**
488    * Initialization which must be deferred until run-time.
489    */
490   function initialize() {
491     // If 'document' isn't defined, then we must be being pre-compiled,
492     // so set a trap so that we're initialized on first access at run-time.
493     if (!global.document) {
494       var originalCr = cr;
496       Object.defineProperty(global, 'cr', {
497         get: function() {
498           Object.defineProperty(global, 'cr', {value: originalCr});
499           originalBase.initialize();
500           return originalCr;
501         },
502         configurable: true
503       });
505       return;
506     }
508     Event.prototype = {__proto__: global.Event.prototype};
510     base.doc = document;
512     base.isMac = /Mac/.test(navigator.platform);
513     base.isWindows = /Win/.test(navigator.platform);
514     base.isChromeOS = /CrOS/.test(navigator.userAgent);
515     base.isLinux = /Linux/.test(navigator.userAgent);
516     base.isGTK = /GTK/.test(chrome.toolkit);
517     base.isViews = /views/.test(chrome.toolkit);
519     setModuleBasePath('/src');
520   }
522   function asArray(arrayish) {
523     var values = [];
524     for (var i = 0; i < arrayish.length; i++)
525       values.push(arrayish[i]);
526     return values;
527   }
529   function concatenateArrays(/*arguments*/) {
530     var values = [];
531     for (var i = 0; i < arguments.length; i++) {
532       if(!(arguments[i] instanceof Array))
533         throw new Error('Arguments ' + i + 'is not an array');
534       values.push.apply(values, arguments[i]);
535     }
536     return values;
537   }
539   function dictionaryKeys(dict) {
540     var keys = [];
541     for (var key in dict)
542       keys.push(key);
543     return keys;
544   }
546   function dictionaryValues(dict) {
547     var values = [];
548     for (var key in dict)
549       values.push(dict[key]);
550     return values;
551   }
553   /**
554    * Maps types to a given value.
555    * @constructor
556    */
557   function TypeMap() {
558     this.types = [];
559     this.values = [];
560   }
561   TypeMap.prototype = {
562     __proto__: Object.prototype,
564     add: function(type, value) {
565       this.types.push(type);
566       this.values.push(value);
567     },
569     get: function(instance) {
570       for (var i = 0; i < this.types.length; i++) {
571         if (instance instanceof this.types[i])
572           return this.values[i];
573       }
574       return undefined;
575     }
576   };
578   return {
579     set moduleBasePath(path) {
580       setModuleBasePath(path);
581     },
583     get moduleBasePath() {
584       return moduleBasePath;
585     },
587     require: require,
588     requireStylesheet: requireStylesheet,
589     requireRawScript: requireRawScript,
590     exportTo: exportTo,
592     addSingletonGetter: addSingletonGetter,
593     createUid: createUid,
594     defineProperty: defineProperty,
595     dispatchPropertyChange: dispatchPropertyChange,
596     dispatchSimpleEvent: dispatchSimpleEvent,
597     Event: Event,
598     getUid: getUid,
599     initialize: initialize,
600     PropertyKind: PropertyKind,
601     asArray: asArray,
602     concatenateArrays: concatenateArrays,
603     dictionaryKeys: dictionaryKeys,
604     dictionaryValues: dictionaryValues,
605     TypeMap: TypeMap,
606   };
607 })();
611  * TODO(kgr): Move this to another file which is to be loaded last.
612  * This will be done as part of future work to make this code pre-compilable.
613  */
614 base.initialize();