Home | History | Annotate | Download | only in webapp
      1 // Copyright 2014 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 /**
      6  * @fileoverview
      7  * A module that contains basic utility components and methods for the
      8  * chromoting project
      9  *
     10  */
     11 
     12 'use strict';
     13 
     14 var base = {};
     15 base.debug = function() {};
     16 
     17 /**
     18  * Whether to break in debugger and alert when an assertion fails.
     19  * Set it to true for debugging.
     20  * @type {boolean}
     21  */
     22 base.debug.breakOnAssert = false;
     23 
     24 /**
     25  * Assert that |expr| is true else print the |opt_msg|.
     26  * @param {boolean} expr
     27  * @param {string=} opt_msg
     28  */
     29 base.debug.assert = function(expr, opt_msg) {
     30   if (!expr) {
     31     var msg = 'Assertion Failed.';
     32     if (opt_msg) {
     33       msg += ' ' + opt_msg;
     34     }
     35     console.error(msg);
     36     if (base.debug.breakOnAssert) {
     37       alert(msg);
     38       debugger;
     39     }
     40   }
     41 };
     42 
     43 /**
     44  * @return {string} The callstack of the current method.
     45  */
     46 base.debug.callstack = function() {
     47   try {
     48     throw new Error();
     49   } catch (e) {
     50     var error = /** @type {Error} */ e;
     51     var callstack = error.stack
     52       .replace(/^\s+(at eval )?at\s+/gm, '') // Remove 'at' and indentation.
     53       .split('\n');
     54     callstack.splice(0,2); // Remove the stack of the current function.
     55   }
     56   return callstack.join('\n');
     57 };
     58 
     59 /**
     60   * @interface
     61   */
     62 base.Disposable = function() {};
     63 base.Disposable.prototype.dispose = function() {};
     64 
     65 /**
     66  * A utility function to invoke |obj|.dispose without a null check on |obj|.
     67  * @param {base.Disposable} obj
     68  */
     69 base.dispose = function(obj) {
     70   if (obj) {
     71     base.debug.assert(typeof obj.dispose == 'function');
     72     obj.dispose();
     73   }
     74 };
     75 
     76 /**
     77  * Copy all properties from src to dest.
     78  * @param {Object} dest
     79  * @param {Object} src
     80  */
     81 base.mix = function(dest, src) {
     82   for (var prop in src) {
     83     if (src.hasOwnProperty(prop)) {
     84       base.debug.assert(!dest.hasOwnProperty(prop),"Don't override properties");
     85       dest[prop] = src[prop];
     86     }
     87   }
     88 };
     89 
     90 /**
     91  * Adds a mixin to a class.
     92  * @param {Object} dest
     93  * @param {Object} src
     94  * @suppress {checkTypes}
     95  */
     96 base.extend = function(dest, src) {
     97   base.mix(dest.prototype, src.prototype || src);
     98 };
     99 
    100 base.doNothing = function() {};
    101 
    102 /**
    103  * Returns an array containing the values of |dict|.
    104  * @param {!Object} dict
    105  * @return {Array}
    106  */
    107 base.values = function(dict) {
    108   return Object.keys(dict).map(
    109     /** @param {string} key */
    110     function(key) {
    111       return dict[key];
    112     });
    113 };
    114 
    115 base.Promise = function() {};
    116 
    117 /**
    118  * @param {number} delay
    119  * @return {Promise} a Promise that will be fulfilled after |delay| ms.
    120  */
    121 base.Promise.sleep = function(delay) {
    122   return new Promise(
    123     /** @param {function():void} fulfill */
    124     function(fulfill) {
    125       window.setTimeout(fulfill, delay);
    126     });
    127 };
    128 
    129 /**
    130  * A mixin for classes with events.
    131  *
    132  * For example, to create an alarm event for SmokeDetector:
    133  * functionSmokeDetector() {
    134  *    this.defineEvents(['alarm']);
    135  * };
    136  * base.extend(SmokeDetector, base.EventSource);
    137  *
    138  * To fire an event:
    139  * SmokeDetector.prototype.onCarbonMonoxideDetected = function() {
    140  *   var param = {} // optional parameters
    141  *   this.raiseEvent('alarm', param);
    142  * }
    143  *
    144  * To listen to an event:
    145  * var smokeDetector = new SmokeDetector();
    146  * smokeDetector.addEventListener('alarm', listenerObj.someCallback)
    147  *
    148  */
    149 
    150 /**
    151   * Helper interface for the EventSource.
    152   * @constructor
    153   */
    154 base.EventEntry = function() {
    155   /** @type {Array.<function():void>} */
    156   this.listeners = [];
    157 };
    158 
    159 /**
    160   * @constructor
    161   * Since this class is implemented as a mixin, the constructor may not be
    162   * called.  All initializations should be done in defineEvents.
    163   */
    164 base.EventSource = function() {
    165   /** @type {Object.<string, base.EventEntry>} */
    166   this.eventMap_;
    167 };
    168 
    169 /**
    170   * @param {base.EventSource} obj
    171   * @param {string} type
    172   */
    173 base.EventSource.isDefined = function(obj, type) {
    174   base.debug.assert(Boolean(obj.eventMap_),
    175                    "The object doesn't support events");
    176   base.debug.assert(Boolean(obj.eventMap_[type]), 'Event <' + type +
    177     '> is undefined for the current object');
    178 };
    179 
    180 base.EventSource.prototype = {
    181   /**
    182     * Define |events| for this event source.
    183     * @param {Array.<string>} events
    184     */
    185   defineEvents: function(events) {
    186     base.debug.assert(!Boolean(this.eventMap_),
    187                      'defineEvents can only be called once.');
    188     this.eventMap_ = {};
    189     events.forEach(
    190       /**
    191         * @this {base.EventSource}
    192         * @param {string} type
    193         */
    194       function(type) {
    195         base.debug.assert(typeof type == 'string');
    196         this.eventMap_[type] = new base.EventEntry();
    197     }, this);
    198   },
    199 
    200   /**
    201     * Add a listener |fn| to listen to |type| event.
    202     * @param {string} type
    203     * @param {function(?=):void} fn
    204     */
    205   addEventListener: function(type, fn) {
    206     base.debug.assert(typeof fn == 'function');
    207     base.EventSource.isDefined(this, type);
    208 
    209     var listeners = this.eventMap_[type].listeners;
    210     listeners.push(fn);
    211   },
    212 
    213   /**
    214     * Remove the listener |fn| from the event source.
    215     * @param {string} type
    216     * @param {function(?=):void} fn
    217     */
    218   removeEventListener: function(type, fn) {
    219     base.debug.assert(typeof fn == 'function');
    220     base.EventSource.isDefined(this, type);
    221 
    222     var listeners = this.eventMap_[type].listeners;
    223     // find the listener to remove.
    224     for (var i = 0; i < listeners.length; i++) {
    225       var listener = listeners[i];
    226       if (listener == fn) {
    227         listeners.splice(i, 1);
    228         break;
    229       }
    230     }
    231   },
    232 
    233   /**
    234     * Fire an event of a particular type on this object.
    235     * @param {string} type
    236     * @param {*=} opt_details The type of |opt_details| should be ?= to
    237     *     match what is defined in add(remove)EventListener.  However, JSCompile
    238     *     cannot handle invoking an unknown type as an argument to |listener|
    239     *     As a hack, we set the type to *=.
    240     */
    241   raiseEvent: function(type, opt_details) {
    242     base.EventSource.isDefined(this, type);
    243 
    244     var entry = this.eventMap_[type];
    245     var listeners = entry.listeners.slice(0); // Make a copy of the listeners.
    246 
    247     listeners.forEach(
    248       /** @param {function(*=):void} listener */
    249       function(listener){
    250         if (listener) {
    251           listener(opt_details);
    252         }
    253     });
    254   }
    255 };
    256