• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
14var base = {};
15base.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 */
22base.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 */
29base.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 */
46base.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  */
62base.Disposable = function() {};
63base.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 */
69base.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 */
81base.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 */
96base.extend = function(dest, src) {
97  base.mix(dest.prototype, src.prototype || src);
98};
99
100base.doNothing = function() {};
101
102/**
103 * Returns an array containing the values of |dict|.
104 * @param {!Object} dict
105 * @return {Array}
106 */
107base.values = function(dict) {
108  return Object.keys(dict).map(
109    /** @param {string} key */
110    function(key) {
111      return dict[key];
112    });
113};
114
115base.Promise = function() {};
116
117/**
118 * @param {number} delay
119 * @return {Promise} a Promise that will be fulfilled after |delay| ms.
120 */
121base.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  */
154base.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  */
164base.EventSource = function() {
165  /** @type {Object.<string, base.EventEntry>} */
166  this.eventMap_;
167};
168
169/**
170  * @param {base.EventSource} obj
171  * @param {string} type
172  */
173base.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
180base.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