Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / remoting / webapp / base.js
blobc2bf1a2b54b28a67c9f7422f10887a5dae5bd2e9
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.
5 /**
6  * @fileoverview
7  * A module that contains basic utility components and methods for the
8  * chromoting project
9  *
10  */
12 'use strict';
14 var base = {};
15 base.debug = function() {};
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;
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   }
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');
59 /**
60   * @interface
61   */
62 base.Disposable = function() {};
63 base.Disposable.prototype.dispose = function() {};
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   }
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   }
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);
100 base.doNothing = function() {};
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     });
116  * @type {boolean|undefined}
117  * @private
118  */
119 base.isAppsV2_ = undefined;
122  * @return {boolean} True if this is a v2 app; false if it is a legacy app.
123  */
124 base.isAppsV2 = function() {
125   if (base.isAppsV2_ === undefined) {
126     var manifest = chrome.runtime.getManifest();
127     base.isAppsV2_ =
128         Boolean(manifest && manifest.app && manifest.app.background);
129   }
130   return base.isAppsV2_;
134  * Joins the |url| with optional query parameters defined in |opt_params|
135  * See unit test for usage.
136  * @param {string} url
137  * @param {Object.<string>=} opt_params
138  * @return {string}
139  */
140 base.urlJoin = function(url, opt_params) {
141   if (!opt_params) {
142     return url;
143   }
144   var queryParameters = [];
145   for (var key in opt_params) {
146     queryParameters.push(encodeURIComponent(key) + "=" +
147                          encodeURIComponent(opt_params[key]));
148   }
149   return url + '?' + queryParameters.join('&');
153  * Promise is a great tool for writing asynchronous code. However, the construct
154  *   var p = new promise(function init(resolve, reject) {
155  *     ... // code that fulfills the Promise.
156  *   });
157  * forces the Promise-resolving logic to reside in the |init| function
158  * of the constructor.  This is problematic when you need to resolve the
159  * Promise in a member function(which is quite common for event callbacks).
161  * base.Deferred comes to the rescue.  It encapsulates a Promise
162  * object and exposes member methods (resolve/reject) to fulfill it.
164  * Here are the recommended steps to follow when implementing an asynchronous
165  * function that returns a Promise:
166  * 1. Create a deferred object by calling
167  *      var deferred = new base.Deferred();
168  * 2. Call deferred.resolve() when the asynchronous operation finishes.
169  * 3. Call deferred.reject() when the asynchronous operation fails.
170  * 4. Return deferred.promise() to the caller so that it can subscribe
171  *    to status changes using the |then| handler.
173  * Sample Usage:
174  *  function myAsyncAPI() {
175  *    var deferred = new base.Deferred();
176  *    window.setTimeout(function() {
177  *      deferred.resolve();
178  *    }, 100);
179  *    return deferred.promise();
180  *  };
182  * @constructor
183  */
184 base.Deferred = function() {
185   /**
186    * @type {?function(?=)}
187    * @private
188    */
189   this.resolve_ = null;
191   /**
192    * @type {?function(?)}
193    * @private
194    */
195   this.reject_ = null;
197   /**
198    * @type {Promise}
199    * @private
200    */
201   this.promise_ = new Promise(
202     /**
203      * @param {function(?=):void} resolve
204      * @param {function(?):void} reject
205      * @this {base.Deferred}
206      */
207     function(resolve, reject) {
208       this.resolve_ = resolve;
209       this.reject_ = reject;
210     }.bind(this)
211   );
214 /** @param {*} reason */
215 base.Deferred.prototype.reject = function(reason) {
216   this.reject_(reason);
219 /** @param {*=} opt_value */
220 base.Deferred.prototype.resolve = function(opt_value) {
221   this.resolve_(opt_value);
224 /** @return {Promise} */
225 base.Deferred.prototype.promise = function() {
226   return this.promise_;
229 base.Promise = function() {};
232  * @param {number} delay
233  * @return {Promise} a Promise that will be fulfilled after |delay| ms.
234  */
235 base.Promise.sleep = function(delay) {
236   return new Promise(
237     /** @param {function():void} fulfill */
238     function(fulfill) {
239       window.setTimeout(fulfill, delay);
240     });
245  * @param {Promise} promise
246  * @return {Promise} a Promise that will be fulfilled iff the specified Promise
247  *     is rejected.
248  */
249 base.Promise.negate = function(promise) {
250   return promise.then(
251       /** @return {Promise} */
252       function() {
253         return Promise.reject();
254       },
255       /** @return {Promise} */
256       function() {
257         return Promise.resolve();
258       });
262  * A mixin for classes with events.
264  * For example, to create an alarm event for SmokeDetector:
265  * functionSmokeDetector() {
266  *    this.defineEvents(['alarm']);
267  * };
268  * base.extend(SmokeDetector, base.EventSource);
270  * To fire an event:
271  * SmokeDetector.prototype.onCarbonMonoxideDetected = function() {
272  *   var param = {} // optional parameters
273  *   this.raiseEvent('alarm', param);
274  * }
276  * To listen to an event:
277  * var smokeDetector = new SmokeDetector();
278  * smokeDetector.addEventListener('alarm', listenerObj.someCallback)
280  */
283   * Helper interface for the EventSource.
284   * @constructor
285   */
286 base.EventEntry = function() {
287   /** @type {Array.<function():void>} */
288   this.listeners = [];
292   * @constructor
293   * Since this class is implemented as a mixin, the constructor may not be
294   * called.  All initializations should be done in defineEvents.
295   */
296 base.EventSource = function() {
297   /** @type {Object.<string, base.EventEntry>} */
298   this.eventMap_;
302   * @param {base.EventSource} obj
303   * @param {string} type
304   */
305 base.EventSource.isDefined = function(obj, type) {
306   base.debug.assert(Boolean(obj.eventMap_),
307                    "The object doesn't support events");
308   base.debug.assert(Boolean(obj.eventMap_[type]), 'Event <' + type +
309     '> is undefined for the current object');
312 base.EventSource.prototype = {
313   /**
314     * Define |events| for this event source.
315     * @param {Array.<string>} events
316     */
317   defineEvents: function(events) {
318     base.debug.assert(!Boolean(this.eventMap_),
319                      'defineEvents can only be called once.');
320     this.eventMap_ = {};
321     events.forEach(
322       /**
323         * @this {base.EventSource}
324         * @param {string} type
325         */
326       function(type) {
327         base.debug.assert(typeof type == 'string');
328         this.eventMap_[type] = new base.EventEntry();
329     }, this);
330   },
332   /**
333     * Add a listener |fn| to listen to |type| event.
334     * @param {string} type
335     * @param {function(?=):void} fn
336     */
337   addEventListener: function(type, fn) {
338     base.debug.assert(typeof fn == 'function');
339     base.EventSource.isDefined(this, type);
341     var listeners = this.eventMap_[type].listeners;
342     listeners.push(fn);
343   },
345   /**
346     * Remove the listener |fn| from the event source.
347     * @param {string} type
348     * @param {function(?=):void} fn
349     */
350   removeEventListener: function(type, fn) {
351     base.debug.assert(typeof fn == 'function');
352     base.EventSource.isDefined(this, type);
354     var listeners = this.eventMap_[type].listeners;
355     // find the listener to remove.
356     for (var i = 0; i < listeners.length; i++) {
357       var listener = listeners[i];
358       if (listener == fn) {
359         listeners.splice(i, 1);
360         break;
361       }
362     }
363   },
365   /**
366     * Fire an event of a particular type on this object.
367     * @param {string} type
368     * @param {*=} opt_details The type of |opt_details| should be ?= to
369     *     match what is defined in add(remove)EventListener.  However, JSCompile
370     *     cannot handle invoking an unknown type as an argument to |listener|
371     *     As a hack, we set the type to *=.
372     */
373   raiseEvent: function(type, opt_details) {
374     base.EventSource.isDefined(this, type);
376     var entry = this.eventMap_[type];
377     var listeners = entry.listeners.slice(0); // Make a copy of the listeners.
379     listeners.forEach(
380       /** @param {function(*=):void} listener */
381       function(listener){
382         if (listener) {
383           listener(opt_details);
384         }
385     });
386   }