// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /** * @fileoverview * A module that contains basic utility components and methods for the * chromoting project * */ 'use strict'; var base = {}; base.debug = function() {}; /** * Whether to break in debugger and alert when an assertion fails. * Set it to true for debugging. * @type {boolean} */ base.debug.breakOnAssert = false; /** * Assert that |expr| is true else print the |opt_msg|. * @param {boolean} expr * @param {string=} opt_msg */ base.debug.assert = function(expr, opt_msg) { if (!expr) { var msg = 'Assertion Failed.'; if (opt_msg) { msg += ' ' + opt_msg; } console.error(msg); if (base.debug.breakOnAssert) { alert(msg); debugger; } } }; /** * @return {string} The callstack of the current method. */ base.debug.callstack = function() { try { throw new Error(); } catch (e) { var error = /** @type {Error} */ e; var callstack = error.stack .replace(/^\s+(at eval )?at\s+/gm, '') // Remove 'at' and indentation. .split('\n'); callstack.splice(0,2); // Remove the stack of the current function. } return callstack.join('\n'); }; /** * @interface */ base.Disposable = function() {}; base.Disposable.prototype.dispose = function() {}; /** * A utility function to invoke |obj|.dispose without a null check on |obj|. * @param {base.Disposable} obj */ base.dispose = function(obj) { if (obj) { base.debug.assert(typeof obj.dispose == 'function'); obj.dispose(); } }; /** * Copy all properties from src to dest. * @param {Object} dest * @param {Object} src */ base.mix = function(dest, src) { for (var prop in src) { if (src.hasOwnProperty(prop)) { base.debug.assert(!dest.hasOwnProperty(prop),"Don't override properties"); dest[prop] = src[prop]; } } }; /** * Adds a mixin to a class. * @param {Object} dest * @param {Object} src * @suppress {checkTypes} */ base.extend = function(dest, src) { base.mix(dest.prototype, src.prototype || src); }; base.doNothing = function() {}; /** * Returns an array containing the values of |dict|. * @param {!Object} dict * @return {Array} */ base.values = function(dict) { return Object.keys(dict).map( /** @param {string} key */ function(key) { return dict[key]; }); }; /** * @type {boolean|undefined} * @private */ base.isAppsV2_ = undefined; /** * @return {boolean} True if this is a v2 app; false if it is a legacy app. */ base.isAppsV2 = function() { if (base.isAppsV2_ === undefined) { var manifest = chrome.runtime.getManifest(); base.isAppsV2_ = Boolean(manifest && manifest.app && manifest.app.background); } return base.isAppsV2_; }; /** * Joins the |url| with optional query parameters defined in |opt_params| * See unit test for usage. * @param {string} url * @param {Object.=} opt_params * @return {string} */ base.urlJoin = function(url, opt_params) { if (!opt_params) { return url; } var queryParameters = []; for (var key in opt_params) { queryParameters.push(encodeURIComponent(key) + "=" + encodeURIComponent(opt_params[key])); } return url + '?' + queryParameters.join('&'); }; /** * Convert special characters (e.g. &, < and >) to HTML entities. * * @param {string} str * @return {string} */ base.escapeHTML = function(str) { var div = document.createElement('div'); div.appendChild(document.createTextNode(str)); return div.innerHTML; }; /** * Promise is a great tool for writing asynchronous code. However, the construct * var p = new promise(function init(resolve, reject) { * ... // code that fulfills the Promise. * }); * forces the Promise-resolving logic to reside in the |init| function * of the constructor. This is problematic when you need to resolve the * Promise in a member function(which is quite common for event callbacks). * * base.Deferred comes to the rescue. It encapsulates a Promise * object and exposes member methods (resolve/reject) to fulfill it. * * Here are the recommended steps to follow when implementing an asynchronous * function that returns a Promise: * 1. Create a deferred object by calling * var deferred = new base.Deferred(); * 2. Call deferred.resolve() when the asynchronous operation finishes. * 3. Call deferred.reject() when the asynchronous operation fails. * 4. Return deferred.promise() to the caller so that it can subscribe * to status changes using the |then| handler. * * Sample Usage: * function myAsyncAPI() { * var deferred = new base.Deferred(); * window.setTimeout(function() { * deferred.resolve(); * }, 100); * return deferred.promise(); * }; * * @constructor */ base.Deferred = function() { /** * @type {?function(?=)} * @private */ this.resolve_ = null; /** * @type {?function(?)} * @private */ this.reject_ = null; /** * @type {Promise} * @private */ this.promise_ = new Promise( /** * @param {function(?=):void} resolve * @param {function(?):void} reject * @this {base.Deferred} */ function(resolve, reject) { this.resolve_ = resolve; this.reject_ = reject; }.bind(this) ); }; /** @param {*} reason */ base.Deferred.prototype.reject = function(reason) { this.reject_(reason); }; /** @param {*=} opt_value */ base.Deferred.prototype.resolve = function(opt_value) { this.resolve_(opt_value); }; /** @return {Promise} */ base.Deferred.prototype.promise = function() { return this.promise_; }; base.Promise = function() {}; /** * @param {number} delay * @return {Promise} a Promise that will be fulfilled after |delay| ms. */ base.Promise.sleep = function(delay) { return new Promise( /** @param {function():void} fulfill */ function(fulfill) { window.setTimeout(fulfill, delay); }); }; /** * @param {Promise} promise * @return {Promise} a Promise that will be fulfilled iff the specified Promise * is rejected. */ base.Promise.negate = function(promise) { return promise.then( /** @return {Promise} */ function() { return Promise.reject(); }, /** @return {Promise} */ function() { return Promise.resolve(); }); }; /** * A mixin for classes with events. * * For example, to create an alarm event for SmokeDetector: * functionSmokeDetector() { * this.defineEvents(['alarm']); * }; * base.extend(SmokeDetector, base.EventSource); * * To fire an event: * SmokeDetector.prototype.onCarbonMonoxideDetected = function() { * var param = {} // optional parameters * this.raiseEvent('alarm', param); * } * * To listen to an event: * var smokeDetector = new SmokeDetector(); * smokeDetector.addEventListener('alarm', listenerObj.someCallback) * */ /** * Helper interface for the EventSource. * @constructor */ base.EventEntry = function() { /** @type {Array.} */ this.listeners = []; }; /** * @constructor * Since this class is implemented as a mixin, the constructor may not be * called. All initializations should be done in defineEvents. */ base.EventSource = function() { /** @type {Object.} */ this.eventMap_; }; /** * @param {base.EventSource} obj * @param {string} type */ base.EventSource.isDefined = function(obj, type) { base.debug.assert(Boolean(obj.eventMap_), "The object doesn't support events"); base.debug.assert(Boolean(obj.eventMap_[type]), 'Event <' + type + '> is undefined for the current object'); }; base.EventSource.prototype = { /** * Define |events| for this event source. * @param {Array.} events */ defineEvents: function(events) { base.debug.assert(!Boolean(this.eventMap_), 'defineEvents can only be called once.'); this.eventMap_ = {}; events.forEach( /** * @this {base.EventSource} * @param {string} type */ function(type) { base.debug.assert(typeof type == 'string'); this.eventMap_[type] = new base.EventEntry(); }, this); }, /** * Add a listener |fn| to listen to |type| event. * @param {string} type * @param {function(?=):void} fn */ addEventListener: function(type, fn) { base.debug.assert(typeof fn == 'function'); base.EventSource.isDefined(this, type); var listeners = this.eventMap_[type].listeners; listeners.push(fn); }, /** * Remove the listener |fn| from the event source. * @param {string} type * @param {function(?=):void} fn */ removeEventListener: function(type, fn) { base.debug.assert(typeof fn == 'function'); base.EventSource.isDefined(this, type); var listeners = this.eventMap_[type].listeners; // find the listener to remove. for (var i = 0; i < listeners.length; i++) { var listener = listeners[i]; if (listener == fn) { listeners.splice(i, 1); break; } } }, /** * Fire an event of a particular type on this object. * @param {string} type * @param {*=} opt_details The type of |opt_details| should be ?= to * match what is defined in add(remove)EventListener. However, JSCompile * cannot handle invoking an unknown type as an argument to |listener| * As a hack, we set the type to *=. */ raiseEvent: function(type, opt_details) { base.EventSource.isDefined(this, type); var entry = this.eventMap_[type]; var listeners = entry.listeners.slice(0); // Make a copy of the listeners. listeners.forEach( /** @param {function(*=):void} listener */ function(listener){ if (listener) { listener(opt_details); } }); } }; /** * Converts UTF-8 string to ArrayBuffer. * * @param {string} string * @return {ArrayBuffer} */ base.encodeUtf8 = function(string) { var utf8String = unescape(encodeURIComponent(string)); var result = new Uint8Array(utf8String.length); for (var i = 0; i < utf8String.length; i++) result[i] = utf8String.charCodeAt(i); return result.buffer; } /** * Decodes UTF-8 string from ArrayBuffer. * * @param {ArrayBuffer} buffer * @return {string} */ base.decodeUtf8 = function(buffer) { return decodeURIComponent( escape(String.fromCharCode.apply(null, new Uint8Array(buffer)))); }