Chromium Code Reviews| Index: remoting/webapp/base.js |
| diff --git a/remoting/webapp/base.js b/remoting/webapp/base.js |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..83b7be623a6fb9833446b28a2a602e8eddc8f3c1 |
| --- /dev/null |
| +++ b/remoting/webapp/base.js |
| @@ -0,0 +1,225 @@ |
| +// 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. |
| + * @interface |
| + */ |
| +base.EventEntry = function() { |
| + /** @type {Array.<Function>} */ |
|
Jamie
2014/04/24 20:38:34
Is "Function" a synonym for "function()"?
kelvinp
2014/04/24 21:38:32
Done.
|
| + this.listeners; |
| +}; |
| + |
| +/** @constructor */ |
| +base.EventSource = function() { |
| + this.eventMap_ = {}; |
|
Jamie
2014/04/24 20:38:34
Add type? I think it should be Object.<string, Eve
kelvinp
2014/04/24 21:38:32
Done.
|
| +}; |
| + |
| +/** |
| + * @param {base.EventSource} obj |
| + * @param {string} type |
| + */ |
| +base.EventSource.checkType = function(obj, type) { |
|
Jamie
2014/04/24 20:38:34
Why not make this a regular member function? Also,
kelvinp
2014/04/24 21:38:32
Renamed to isDefined. Don't want to make it a reg
|
| + 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] = { |
|
Jamie
2014/04/24 20:38:34
I think you want "new EventEntry" here.
kelvinp
2014/04/24 21:38:32
Don't know how I miss that. Good catch
|
| + recursionCount: 0, |
| + sweepRequired: false, |
| + listeners: [] |
| + }; |
| + }, this); |
| + }, |
| + /** |
|
Wez
2014/04/24 20:45:15
nit: I prefer a blank line between the }, and the
kelvinp
2014/04/24 21:38:32
Done.
|
| + * Add a listener |fn| to listen to |type| event. The listener |fn| will be |
| + * invoked with |thisObj| as the this pointer. |
|
Jamie
2014/04/24 20:38:34
thisObj no longer exists.
kelvinp
2014/04/24 21:38:32
Done.
|
| + * @param {string} type |
| + * @param {function(?=)} fn |
|
Jamie
2014/04/24 20:38:34
No need for "?=" in the type, here and below.
kelvinp
2014/04/24 21:38:32
Spoke offline. I want to indicate that the parame
|
| + */ |
| + addEventListener: function(type, fn) { |
| + base.debug.assert(typeof fn == 'function'); |
| + base.EventSource.checkType(this, type); |
| + |
| + var listeners = /** @type {Array} */ this.eventMap_[type].listeners; |
|
Jamie
2014/04/24 20:38:34
This cast should not be necessary if you add @type
kelvinp
2014/04/24 21:38:32
Done.
|
| + listeners.push(fn); |
| + }, |
| + /** |
| + * Remove the listener |fn| from the event source. |
| + * @param {string} type |
| + * @param {function(?=)} fn |
| + */ |
| + removeEventListener: function(type, fn) { |
| + base.debug.assert(typeof fn == 'function'); |
| + base.EventSource.checkType(this, type); |
| + |
| + /** @type {base.EventEntry} */ |
| + var entry = this.eventMap_[type]; |
| + |
| + var listeners = entry.listeners; |
| + // find the listener to remove. |
| + for (var i = 0; i < listeners.length; i++) { |
| + var listener = listeners[i]; |
| + if (listener && listener == fn) { |
| + listeners.splice(i, 1); |
| + break; |
| + } |
| + } |
| + }, |
| + /** |
| + * Fire an event of a particular type on this object. |
| + * @param {string} type |
| + * @param {*} details |
| + */ |
| + raiseEvent: function(type, details) { |
| + base.EventSource.checkType(this, type); |
| + |
| + /** @type {base.EventEntry} */ |
| + var entry = this.eventMap_[type]; |
| + var listeners = entry.listeners.slice(0); // Make a copy of the listeners. |
| + |
| + listeners.forEach( |
| + /** @param {Function} listener */ |
| + function(listener){ |
| + if (listener) { |
| + listener(details); |
| + } |
| + }); |
| + } |
| +}; |