Home | History | Annotate | Download | only in src
      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.
      4 
      5 'use strict';
      6 
      7 
      8 /**
      9  * The global object.
     10  * @type {!Object}
     11  * @const
     12  */
     13 var global = this;
     14 
     15 
     16 /** Platform, package, object property, and Event support. */
     17 this.base = (function() {
     18 
     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   }
     28 
     29 
     30   function mLog(text, opt_indentLevel) {
     31     if (true)
     32       return;
     33 
     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   }
     40 
     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;
     55 
     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   };
     68 
     69   var didLoadModules = false;
     70   var moduleDependencies = {};
     71   var moduleStylesheets = {};
     72   var moduleRawScripts = {};
     73 
     74   function addModuleDependency(moduleName, dependentModuleName) {
     75     if (!moduleDependencies[moduleName])
     76       moduleDependencies[moduleName] = [];
     77 
     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   }
     86 
     87   function addModuleRawScriptDependency(moduleName, rawScriptName) {
     88     if (!moduleRawScripts[moduleName])
     89       moduleRawScripts[moduleName] = [];
     90 
     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   }
     99 
    100   function addModuleStylesheet(moduleName, stylesheetName) {
    101     if (!moduleStylesheets[moduleName])
    102       moduleStylesheets[moduleName] = [];
    103 
    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   }
    112 
    113   function ensureDepsLoaded() {
    114     if (didLoadModules)
    115       return;
    116     didLoadModules = true;
    117 
    118     var req = new XMLHttpRequest();
    119     var src = moduleBasePath + '/' + '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.');
    125 
    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;
    138 
    139   }
    140 
    141   var moduleLoadStatus = {};
    142   var rawScriptLoadStatus = {};
    143   function require(dependentModuleName, opt_indentLevel) {
    144     var indentLevel = opt_indentLevel || 0;
    145 
    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();
    155 
    156     mLog('require(' + dependentModuleName + ')', indentLevel);
    157 
    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';
    163 
    164     // Load the module stylesheet first.
    165     var stylesheets = moduleStylesheets[dependentModuleName] || [];
    166     for (var i = 0; i < stylesheets.length; i++)
    167       requireStylesheet(stylesheets[i]);
    168 
    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;
    175 
    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     }
    183 
    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);
    189 
    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   }
    199 
    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     }
    215 
    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   }
    221 
    222   var stylesheetLoadStatus = {};
    223   function requireStylesheet(dependentStylesheetName) {
    224     if (window.FLATTENED)
    225       return;
    226 
    227     if (stylesheetLoadStatus[dependentStylesheetName])
    228       return;
    229     stylesheetLoadStatus[dependentStylesheetName] = true;
    230     var localPath = dependentStylesheetName.replace(/\./g, '/') + '.css';
    231     var stylesheetPath = moduleBasePath + '/' + localPath;
    232 
    233     var linkEl = document.createElement('link');
    234     linkEl.setAttribute('rel', 'stylesheet');
    235     linkEl.setAttribute('href', stylesheetPath);
    236     base.doc.head.appendChild(linkEl);
    237   }
    238 
    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     }
    248 
    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   };
    261 
    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   }
    276 
    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   }
    286 
    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',
    298 
    299     /**
    300      * The property backing data is stored as an attribute on an element.
    301      */
    302     ATTR: 'attr',
    303 
    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   };
    310 
    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   }
    337 
    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         };
    362 
    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         };
    377 
    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   }
    394 
    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;
    408 
    409     var kind = opt_kind || PropertyKind.JS;
    410 
    411     if (!obj.__lookupGetter__(name))
    412       obj.__defineGetter__(name, getGetter(name, kind));
    413 
    414     if (!obj.__lookupSetter__(name))
    415       obj.__defineSetter__(name, getSetter(name, kind, opt_setHook));
    416   }
    417 
    418   /**
    419    * Counter for use with createUid
    420    */
    421   var uidCounter = 1;
    422 
    423   /**
    424    * @return {number} A new unique ID.
    425    */
    426   function createUid() {
    427     return uidCounter++;
    428   }
    429 
    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   }
    441 
    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   }
    456 
    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   }
    468 
    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   };
    486 
    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;
    495 
    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       });
    504 
    505       return;
    506     }
    507 
    508     Event.prototype = {__proto__: global.Event.prototype};
    509 
    510     base.doc = document;
    511 
    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);
    518 
    519     setModuleBasePath('/src');
    520   }
    521 
    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   }
    528 
    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   }
    538 
    539   function dictionaryKeys(dict) {
    540     var keys = [];
    541     for (var key in dict)
    542       keys.push(key);
    543     return keys;
    544   }
    545 
    546   function dictionaryValues(dict) {
    547     var values = [];
    548     for (var key in dict)
    549       values.push(dict[key]);
    550     return values;
    551   }
    552 
    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,
    563 
    564     add: function(type, value) {
    565       this.types.push(type);
    566       this.values.push(value);
    567     },
    568 
    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   };
    577 
    578   return {
    579     set moduleBasePath(path) {
    580       setModuleBasePath(path);
    581     },
    582 
    583     get moduleBasePath() {
    584       return moduleBasePath;
    585     },
    586 
    587     require: require,
    588     requireStylesheet: requireStylesheet,
    589     requireRawScript: requireRawScript,
    590     exportTo: exportTo,
    591 
    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 })();
    608 
    609 
    610 /**
    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();
    615