|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 "use strict"; |
|
6 |
|
7 module.metadata = { |
|
8 "stability": "deprecated" |
|
9 }; |
|
10 |
|
11 const ERROR_TYPE = 'error', |
|
12 UNCAUGHT_ERROR = 'An error event was dispatched for which there was' |
|
13 + ' no listener.', |
|
14 BAD_LISTENER = 'The event listener must be a function.'; |
|
15 /** |
|
16 * This object is used to create an `EventEmitter` that, useful for composing |
|
17 * objects that emit events. It implements an interface like `EventTarget` from |
|
18 * DOM Level 2, which is implemented by Node objects in implementations that |
|
19 * support the DOM Event Model. |
|
20 * @see http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget |
|
21 * @see http://nodejs.org/api.html#EventEmitter |
|
22 * @see http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/events/EventDispatcher.html |
|
23 */ |
|
24 const eventEmitter = { |
|
25 /** |
|
26 * Registers an event `listener` that is called every time events of |
|
27 * specified `type` are emitted. |
|
28 * @param {String} type |
|
29 * The type of event. |
|
30 * @param {Function} listener |
|
31 * The listener function that processes the event. |
|
32 * @example |
|
33 * worker.on('message', function (data) { |
|
34 * console.log('data received: ' + data) |
|
35 * }) |
|
36 */ |
|
37 on: function on(type, listener) { |
|
38 if ('function' !== typeof listener) |
|
39 throw new Error(BAD_LISTENER); |
|
40 let listeners = this._listeners(type); |
|
41 if (0 > listeners.indexOf(listener)) |
|
42 listeners.push(listener); |
|
43 // Use of `_public` is required by the legacy traits code that will go away |
|
44 // once bug-637633 is fixed. |
|
45 return this._public || this; |
|
46 }, |
|
47 |
|
48 /** |
|
49 * Registers an event `listener` that is called once the next time an event |
|
50 * of the specified `type` is emitted. |
|
51 * @param {String} type |
|
52 * The type of the event. |
|
53 * @param {Function} listener |
|
54 * The listener function that processes the event. |
|
55 */ |
|
56 once: function once(type, listener) { |
|
57 this.on(type, function selfRemovableListener() { |
|
58 this.removeListener(type, selfRemovableListener); |
|
59 listener.apply(this, arguments); |
|
60 }); |
|
61 }, |
|
62 |
|
63 /** |
|
64 * Unregister `listener` for the specified event type. |
|
65 * @param {String} type |
|
66 * The type of event. |
|
67 * @param {Function} listener |
|
68 * The listener function that processes the event. |
|
69 */ |
|
70 removeListener: function removeListener(type, listener) { |
|
71 if ('function' !== typeof listener) |
|
72 throw new Error(BAD_LISTENER); |
|
73 let listeners = this._listeners(type), |
|
74 index = listeners.indexOf(listener); |
|
75 if (0 <= index) |
|
76 listeners.splice(index, 1); |
|
77 // Use of `_public` is required by the legacy traits code, that will go away |
|
78 // once bug-637633 is fixed. |
|
79 return this._public || this; |
|
80 }, |
|
81 |
|
82 /** |
|
83 * Hash of listeners on this EventEmitter. |
|
84 */ |
|
85 _events: null, |
|
86 |
|
87 /** |
|
88 * Returns an array of listeners for the specified event `type`. This array |
|
89 * can be manipulated, e.g. to remove listeners. |
|
90 * @param {String} type |
|
91 * The type of event. |
|
92 */ |
|
93 _listeners: function listeners(type) { |
|
94 let events = this._events || (this._events = {}); |
|
95 return (events.hasOwnProperty(type) && events[type]) || (events[type] = []); |
|
96 }, |
|
97 |
|
98 /** |
|
99 * Execute each of the listeners in order with the supplied arguments. |
|
100 * Returns `true` if listener for this event was called, `false` if there are |
|
101 * no listeners for this event `type`. |
|
102 * |
|
103 * All the exceptions that are thrown by listeners during the emit |
|
104 * are caught and can be handled by listeners of 'error' event. Thrown |
|
105 * exceptions are passed as an argument to an 'error' event listener. |
|
106 * If no 'error' listener is registered exception will propagate to a |
|
107 * caller of this method. |
|
108 * |
|
109 * **It's recommended to have a default 'error' listener in all the complete |
|
110 * composition that in worst case may dump errors to the console.** |
|
111 * |
|
112 * @param {String} type |
|
113 * The type of event. |
|
114 * @params {Object|Number|String|Boolean} |
|
115 * Arguments that will be passed to listeners. |
|
116 * @returns {Boolean} |
|
117 */ |
|
118 _emit: function _emit(type, event) { |
|
119 let args = Array.slice(arguments); |
|
120 // Use of `_public` is required by the legacy traits code that will go away |
|
121 // once bug-637633 is fixed. |
|
122 args.unshift(this._public || this); |
|
123 return this._emitOnObject.apply(this, args); |
|
124 }, |
|
125 |
|
126 /** |
|
127 * A version of _emit that lets you specify the object on which listeners are |
|
128 * called. This is a hack that is sometimes necessary when such an object |
|
129 * (exports, for example) cannot be an EventEmitter for some reason, but other |
|
130 * object(s) managing events for the object are EventEmitters. Once bug |
|
131 * 577782 is fixed, this method shouldn't be necessary. |
|
132 * |
|
133 * @param {object} targetObj |
|
134 * The object on which listeners will be called. |
|
135 * @param {string} type |
|
136 * The event name. |
|
137 * @param {value} event |
|
138 * The first argument to pass to listeners. |
|
139 * @param {value} ... |
|
140 * More arguments to pass to listeners. |
|
141 * @returns {boolean} |
|
142 */ |
|
143 _emitOnObject: function _emitOnObject(targetObj, type, event /* , ... */) { |
|
144 let listeners = this._listeners(type).slice(0); |
|
145 // If there is no 'error' event listener then throw. |
|
146 if (type === ERROR_TYPE && !listeners.length) |
|
147 console.exception(event); |
|
148 if (!listeners.length) |
|
149 return false; |
|
150 let params = Array.slice(arguments, 2); |
|
151 for each (let listener in listeners) { |
|
152 try { |
|
153 listener.apply(targetObj, params); |
|
154 } catch(e) { |
|
155 // Bug 726967: Ignore exceptions being throws while notifying the error |
|
156 // in order to avoid infinite loops. |
|
157 if (type !== ERROR_TYPE) |
|
158 this._emit(ERROR_TYPE, e); |
|
159 else |
|
160 console.exception("Exception in error event listener " + e); |
|
161 } |
|
162 } |
|
163 return true; |
|
164 }, |
|
165 |
|
166 /** |
|
167 * Removes all the event listeners for the specified event `type`. |
|
168 * @param {String} type |
|
169 * The type of event. |
|
170 */ |
|
171 _removeAllListeners: function _removeAllListeners(type) { |
|
172 if (typeof type == "undefined") { |
|
173 this._events = null; |
|
174 return this; |
|
175 } |
|
176 |
|
177 this._listeners(type).splice(0); |
|
178 return this; |
|
179 } |
|
180 }; |
|
181 exports.EventEmitter = require("./traits").Trait.compose(eventEmitter); |
|
182 exports.EventEmitterTrait = require('./light-traits').Trait(eventEmitter); |