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.
7 * A module that contains basic utility components and methods for the
15 base.debug = function() {};
18 * Whether to break in debugger and alert when an assertion fails.
19 * Set it to true for debugging.
22 base.debug.breakOnAssert = false;
25 * Assert that |expr| is true else print the |opt_msg|.
26 * @param {boolean} expr
27 * @param {string=} opt_msg
29 base.debug.assert = function(expr, opt_msg) {
31 var msg = 'Assertion Failed.';
36 if (base.debug.breakOnAssert) {
44 * @return {string} The callstack of the current method.
46 base.debug.callstack = function() {
50 var error = /** @type {Error} */ e;
51 var callstack = error.stack
52 .replace(/^\s+(at eval )?at\s+/gm, '') // Remove 'at' and indentation.
54 callstack.splice(0,2); // Remove the stack of the current function.
56 return callstack.join('\n');
62 base.Disposable = function() {};
63 base.Disposable.prototype.dispose = function() {};
66 * A utility function to invoke |obj|.dispose without a null check on |obj|.
67 * @param {base.Disposable} obj
69 base.dispose = function(obj) {
71 base.debug.assert(typeof obj.dispose == 'function');
77 * Copy all properties from src to dest.
78 * @param {Object} dest
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];
91 * Adds a mixin to a class.
92 * @param {Object} dest
94 * @suppress {checkTypes}
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
107 base.values = function(dict) {
108 return Object.keys(dict).map(
109 /** @param {string} key */
116 * @type {boolean|undefined}
119 base.isAppsV2_ = undefined;
122 * @return {boolean} True if this is a v2 app; false if it is a legacy app.
124 base.isAppsV2 = function() {
125 if (base.isAppsV2_ === undefined) {
126 var manifest = chrome.runtime.getManifest();
128 Boolean(manifest && manifest.app && manifest.app.background);
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
140 base.urlJoin = function(url, opt_params) {
144 var queryParameters = [];
145 for (var key in opt_params) {
146 queryParameters.push(encodeURIComponent(key) + "=" +
147 encodeURIComponent(opt_params[key]));
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.
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.
174 * function myAsyncAPI() {
175 * var deferred = new base.Deferred();
176 * window.setTimeout(function() {
177 * deferred.resolve();
179 * return deferred.promise();
184 base.Deferred = function() {
186 * @type {?function(?=)}
189 this.resolve_ = null;
192 * @type {?function(?)}
201 this.promise_ = new Promise(
203 * @param {function(?=):void} resolve
204 * @param {function(?):void} reject
205 * @this {base.Deferred}
207 function(resolve, reject) {
208 this.resolve_ = resolve;
209 this.reject_ = reject;
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.
235 base.Promise.sleep = function(delay) {
237 /** @param {function():void} fulfill */
239 window.setTimeout(fulfill, delay);
245 * @param {Promise} promise
246 * @return {Promise} a Promise that will be fulfilled iff the specified Promise
249 base.Promise.negate = function(promise) {
251 /** @return {Promise} */
253 return Promise.reject();
255 /** @return {Promise} */
257 return Promise.resolve();
262 * A mixin for classes with events.
264 * For example, to create an alarm event for SmokeDetector:
265 * functionSmokeDetector() {
266 * this.defineEvents(['alarm']);
268 * base.extend(SmokeDetector, base.EventSource);
271 * SmokeDetector.prototype.onCarbonMonoxideDetected = function() {
272 * var param = {} // optional parameters
273 * this.raiseEvent('alarm', param);
276 * To listen to an event:
277 * var smokeDetector = new SmokeDetector();
278 * smokeDetector.addEventListener('alarm', listenerObj.someCallback)
283 * Helper interface for the EventSource.
286 base.EventEntry = function() {
287 /** @type {Array.<function():void>} */
293 * Since this class is implemented as a mixin, the constructor may not be
294 * called. All initializations should be done in defineEvents.
296 base.EventSource = function() {
297 /** @type {Object.<string, base.EventEntry>} */
302 * @param {base.EventSource} obj
303 * @param {string} type
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 = {
314 * Define |events| for this event source.
315 * @param {Array.<string>} events
317 defineEvents: function(events) {
318 base.debug.assert(!Boolean(this.eventMap_),
319 'defineEvents can only be called once.');
323 * @this {base.EventSource}
324 * @param {string} type
327 base.debug.assert(typeof type == 'string');
328 this.eventMap_[type] = new base.EventEntry();
333 * Add a listener |fn| to listen to |type| event.
334 * @param {string} type
335 * @param {function(?=):void} fn
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;
346 * Remove the listener |fn| from the event source.
347 * @param {string} type
348 * @param {function(?=):void} fn
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);
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 *=.
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.
380 /** @param {function(*=):void} listener */
383 listener(opt_details);