Index: remoting/webapp/base.js |
diff --git a/remoting/webapp/base.js b/remoting/webapp/base.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..19d574d71536a09d91f79610d0ce12d193bdeb44 |
--- /dev/null |
+++ b/remoting/webapp/base.js |
@@ -0,0 +1,228 @@ |
+// 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') |
+ .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() {}; |
+ |
+/** |
+ * 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.<function():void>} */ |
+ 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.<string, base.EventEntry>} */ |
+ 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.<string>} 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); |
+ } |
+ }); |
+ } |
+}; |