michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: module.metadata = { michael@0: "stability": "deprecated" michael@0: }; michael@0: michael@0: const ERROR_TYPE = 'error', michael@0: UNCAUGHT_ERROR = 'An error event was dispatched for which there was' michael@0: + ' no listener.', michael@0: BAD_LISTENER = 'The event listener must be a function.'; michael@0: /** michael@0: * This object is used to create an `EventEmitter` that, useful for composing michael@0: * objects that emit events. It implements an interface like `EventTarget` from michael@0: * DOM Level 2, which is implemented by Node objects in implementations that michael@0: * support the DOM Event Model. michael@0: * @see http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget michael@0: * @see http://nodejs.org/api.html#EventEmitter michael@0: * @see http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/events/EventDispatcher.html michael@0: */ michael@0: const eventEmitter = { michael@0: /** michael@0: * Registers an event `listener` that is called every time events of michael@0: * specified `type` are emitted. michael@0: * @param {String} type michael@0: * The type of event. michael@0: * @param {Function} listener michael@0: * The listener function that processes the event. michael@0: * @example michael@0: * worker.on('message', function (data) { michael@0: * console.log('data received: ' + data) michael@0: * }) michael@0: */ michael@0: on: function on(type, listener) { michael@0: if ('function' !== typeof listener) michael@0: throw new Error(BAD_LISTENER); michael@0: let listeners = this._listeners(type); michael@0: if (0 > listeners.indexOf(listener)) michael@0: listeners.push(listener); michael@0: // Use of `_public` is required by the legacy traits code that will go away michael@0: // once bug-637633 is fixed. michael@0: return this._public || this; michael@0: }, michael@0: michael@0: /** michael@0: * Registers an event `listener` that is called once the next time an event michael@0: * of the specified `type` is emitted. michael@0: * @param {String} type michael@0: * The type of the event. michael@0: * @param {Function} listener michael@0: * The listener function that processes the event. michael@0: */ michael@0: once: function once(type, listener) { michael@0: this.on(type, function selfRemovableListener() { michael@0: this.removeListener(type, selfRemovableListener); michael@0: listener.apply(this, arguments); michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Unregister `listener` for the specified event type. michael@0: * @param {String} type michael@0: * The type of event. michael@0: * @param {Function} listener michael@0: * The listener function that processes the event. michael@0: */ michael@0: removeListener: function removeListener(type, listener) { michael@0: if ('function' !== typeof listener) michael@0: throw new Error(BAD_LISTENER); michael@0: let listeners = this._listeners(type), michael@0: index = listeners.indexOf(listener); michael@0: if (0 <= index) michael@0: listeners.splice(index, 1); michael@0: // Use of `_public` is required by the legacy traits code, that will go away michael@0: // once bug-637633 is fixed. michael@0: return this._public || this; michael@0: }, michael@0: michael@0: /** michael@0: * Hash of listeners on this EventEmitter. michael@0: */ michael@0: _events: null, michael@0: michael@0: /** michael@0: * Returns an array of listeners for the specified event `type`. This array michael@0: * can be manipulated, e.g. to remove listeners. michael@0: * @param {String} type michael@0: * The type of event. michael@0: */ michael@0: _listeners: function listeners(type) { michael@0: let events = this._events || (this._events = {}); michael@0: return (events.hasOwnProperty(type) && events[type]) || (events[type] = []); michael@0: }, michael@0: michael@0: /** michael@0: * Execute each of the listeners in order with the supplied arguments. michael@0: * Returns `true` if listener for this event was called, `false` if there are michael@0: * no listeners for this event `type`. michael@0: * michael@0: * All the exceptions that are thrown by listeners during the emit michael@0: * are caught and can be handled by listeners of 'error' event. Thrown michael@0: * exceptions are passed as an argument to an 'error' event listener. michael@0: * If no 'error' listener is registered exception will propagate to a michael@0: * caller of this method. michael@0: * michael@0: * **It's recommended to have a default 'error' listener in all the complete michael@0: * composition that in worst case may dump errors to the console.** michael@0: * michael@0: * @param {String} type michael@0: * The type of event. michael@0: * @params {Object|Number|String|Boolean} michael@0: * Arguments that will be passed to listeners. michael@0: * @returns {Boolean} michael@0: */ michael@0: _emit: function _emit(type, event) { michael@0: let args = Array.slice(arguments); michael@0: // Use of `_public` is required by the legacy traits code that will go away michael@0: // once bug-637633 is fixed. michael@0: args.unshift(this._public || this); michael@0: return this._emitOnObject.apply(this, args); michael@0: }, michael@0: michael@0: /** michael@0: * A version of _emit that lets you specify the object on which listeners are michael@0: * called. This is a hack that is sometimes necessary when such an object michael@0: * (exports, for example) cannot be an EventEmitter for some reason, but other michael@0: * object(s) managing events for the object are EventEmitters. Once bug michael@0: * 577782 is fixed, this method shouldn't be necessary. michael@0: * michael@0: * @param {object} targetObj michael@0: * The object on which listeners will be called. michael@0: * @param {string} type michael@0: * The event name. michael@0: * @param {value} event michael@0: * The first argument to pass to listeners. michael@0: * @param {value} ... michael@0: * More arguments to pass to listeners. michael@0: * @returns {boolean} michael@0: */ michael@0: _emitOnObject: function _emitOnObject(targetObj, type, event /* , ... */) { michael@0: let listeners = this._listeners(type).slice(0); michael@0: // If there is no 'error' event listener then throw. michael@0: if (type === ERROR_TYPE && !listeners.length) michael@0: console.exception(event); michael@0: if (!listeners.length) michael@0: return false; michael@0: let params = Array.slice(arguments, 2); michael@0: for each (let listener in listeners) { michael@0: try { michael@0: listener.apply(targetObj, params); michael@0: } catch(e) { michael@0: // Bug 726967: Ignore exceptions being throws while notifying the error michael@0: // in order to avoid infinite loops. michael@0: if (type !== ERROR_TYPE) michael@0: this._emit(ERROR_TYPE, e); michael@0: else michael@0: console.exception("Exception in error event listener " + e); michael@0: } michael@0: } michael@0: return true; michael@0: }, michael@0: michael@0: /** michael@0: * Removes all the event listeners for the specified event `type`. michael@0: * @param {String} type michael@0: * The type of event. michael@0: */ michael@0: _removeAllListeners: function _removeAllListeners(type) { michael@0: if (typeof type == "undefined") { michael@0: this._events = null; michael@0: return this; michael@0: } michael@0: michael@0: this._listeners(type).splice(0); michael@0: return this; michael@0: } michael@0: }; michael@0: exports.EventEmitter = require("./traits").Trait.compose(eventEmitter); michael@0: exports.EventEmitterTrait = require('./light-traits').Trait(eventEmitter);