Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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/. */
5 "use strict";
7 module.metadata = {
8 "stability": "unstable"
9 };
11 const UNCAUGHT_ERROR = 'An error event was emitted for which there was no listener.';
12 const BAD_LISTENER = 'The event listener must be a function.';
14 const { ns } = require('../core/namespace');
16 const event = ns();
18 const EVENT_TYPE_PATTERN = /^on([A-Z]\w+$)/;
19 exports.EVENT_TYPE_PATTERN = EVENT_TYPE_PATTERN;
21 // Utility function to access given event `target` object's event listeners for
22 // the specific event `type`. If listeners for this type does not exists they
23 // will be created.
24 const observers = function observers(target, type) {
25 if (!target) throw TypeError("Event target must be an object");
26 let listeners = event(target);
27 return type in listeners ? listeners[type] : listeners[type] = [];
28 };
30 /**
31 * Registers an event `listener` that is called every time events of
32 * specified `type` is emitted on the given event `target`.
33 * @param {Object} target
34 * Event target object.
35 * @param {String} type
36 * The type of event.
37 * @param {Function} listener
38 * The listener function that processes the event.
39 */
40 function on(target, type, listener) {
41 if (typeof(listener) !== 'function')
42 throw new Error(BAD_LISTENER);
44 let listeners = observers(target, type);
45 if (!~listeners.indexOf(listener))
46 listeners.push(listener);
47 }
48 exports.on = on;
50 /**
51 * Registers an event `listener` that is called only the next time an event
52 * of the specified `type` is emitted on the given event `target`.
53 * @param {Object} target
54 * Event target object.
55 * @param {String} type
56 * The type of the event.
57 * @param {Function} listener
58 * The listener function that processes the event.
59 */
60 function once(target, type, listener) {
61 on(target, type, function observer(...args) {
62 off(target, type, observer);
63 listener.apply(target, args);
64 });
65 }
66 exports.once = once;
68 /**
69 * Execute each of the listeners in order with the supplied arguments.
70 * All the exceptions that are thrown by listeners during the emit
71 * are caught and can be handled by listeners of 'error' event. Thrown
72 * exceptions are passed as an argument to an 'error' event listener.
73 * If no 'error' listener is registered exception will be logged into an
74 * error console.
75 * @param {Object} target
76 * Event target object.
77 * @param {String} type
78 * The type of event.
79 * @params {Object|Number|String|Boolean} args
80 * Arguments that will be passed to listeners.
81 */
82 function emit (target, type, ...args) {
83 let state = observers(target, type);
84 let listeners = state.slice();
85 let count = listeners.length;
86 let index = 0;
88 // If error event and there are no handlers then print error message
89 // into a console.
90 if (count === 0 && type === 'error') console.exception(args[0]);
91 while (index < count) {
92 try {
93 let listener = listeners[index];
94 // Dispatch only if listener is still registered.
95 if (~state.indexOf(listener))
96 listener.apply(target, args);
97 }
98 catch (error) {
99 // If exception is not thrown by a error listener and error listener is
100 // registered emit `error` event. Otherwise dump exception to the console.
101 if (type !== 'error') emit(target, 'error', error);
102 else console.exception(error);
103 }
104 index++;
105 }
106 // Also emit on `"*"` so that one could listen for all events.
107 if (type !== '*') emit(target, '*', type, ...args);
108 }
109 exports.emit = emit;
111 /**
112 * Removes an event `listener` for the given event `type` on the given event
113 * `target`. If no `listener` is passed removes all listeners of the given
114 * `type`. If `type` is not passed removes all the listeners of the given
115 * event `target`.
116 * @param {Object} target
117 * The event target object.
118 * @param {String} type
119 * The type of event.
120 * @param {Function} listener
121 * The listener function that processes the event.
122 */
123 function off(target, type, listener) {
124 let length = arguments.length;
125 if (length === 3) {
126 let listeners = observers(target, type);
127 let index = listeners.indexOf(listener);
128 if (~index)
129 listeners.splice(index, 1);
130 }
131 else if (length === 2) {
132 observers(target, type).splice(0);
133 }
134 else if (length === 1) {
135 let listeners = event(target);
136 Object.keys(listeners).forEach(type => delete listeners[type]);
137 }
138 }
139 exports.off = off;
141 /**
142 * Returns a number of event listeners registered for the given event `type`
143 * on the given event `target`.
144 */
145 function count(target, type) {
146 return observers(target, type).length;
147 }
148 exports.count = count;
150 /**
151 * Registers listeners on the given event `target` from the given `listeners`
152 * dictionary. Iterates over the listeners and if property name matches name
153 * pattern `onEventType` and property is a function, then registers it as
154 * an `eventType` listener on `target`.
155 *
156 * @param {Object} target
157 * The type of event.
158 * @param {Object} listeners
159 * Dictionary of listeners.
160 */
161 function setListeners(target, listeners) {
162 Object.keys(listeners || {}).forEach(key => {
163 let match = EVENT_TYPE_PATTERN.exec(key);
164 let type = match && match[1].toLowerCase();
165 if (!type) return;
167 let listener = listeners[key];
168 if (typeof(listener) === 'function')
169 on(target, type, listener);
170 });
171 }
172 exports.setListeners = setListeners;