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