|
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 /** |
|
6 * EventEmitter. |
|
7 */ |
|
8 |
|
9 this.EventEmitter = function EventEmitter() {}; |
|
10 |
|
11 if (typeof(require) === "function") { |
|
12 module.exports = EventEmitter; |
|
13 var {Cu, components} = require("chrome"); |
|
14 } else { |
|
15 var EXPORTED_SYMBOLS = ["EventEmitter"]; |
|
16 var Cu = this["Components"].utils; |
|
17 var components = Components; |
|
18 } |
|
19 |
|
20 const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {}); |
|
21 const { Services } = Cu.import("resource://gre/modules/Services.jsm"); |
|
22 |
|
23 /** |
|
24 * Decorate an object with event emitter functionality. |
|
25 * |
|
26 * @param Object aObjectToDecorate |
|
27 * Bind all public methods of EventEmitter to |
|
28 * the aObjectToDecorate object. |
|
29 */ |
|
30 EventEmitter.decorate = function EventEmitter_decorate (aObjectToDecorate) { |
|
31 let emitter = new EventEmitter(); |
|
32 aObjectToDecorate.on = emitter.on.bind(emitter); |
|
33 aObjectToDecorate.off = emitter.off.bind(emitter); |
|
34 aObjectToDecorate.once = emitter.once.bind(emitter); |
|
35 aObjectToDecorate.emit = emitter.emit.bind(emitter); |
|
36 }; |
|
37 |
|
38 EventEmitter.prototype = { |
|
39 /** |
|
40 * Connect a listener. |
|
41 * |
|
42 * @param string aEvent |
|
43 * The event name to which we're connecting. |
|
44 * @param function aListener |
|
45 * Called when the event is fired. |
|
46 */ |
|
47 on: function EventEmitter_on(aEvent, aListener) { |
|
48 if (!this._eventEmitterListeners) |
|
49 this._eventEmitterListeners = new Map(); |
|
50 if (!this._eventEmitterListeners.has(aEvent)) { |
|
51 this._eventEmitterListeners.set(aEvent, []); |
|
52 } |
|
53 this._eventEmitterListeners.get(aEvent).push(aListener); |
|
54 }, |
|
55 |
|
56 /** |
|
57 * Listen for the next time an event is fired. |
|
58 * |
|
59 * @param string aEvent |
|
60 * The event name to which we're connecting. |
|
61 * @param function aListener |
|
62 * (Optional) Called when the event is fired. Will be called at most |
|
63 * one time. |
|
64 * @return promise |
|
65 * A promise which is resolved when the event next happens. The |
|
66 * resolution value of the promise is the first event argument. If |
|
67 * you need access to second or subsequent event arguments (it's rare |
|
68 * that this is needed) then use aListener |
|
69 */ |
|
70 once: function EventEmitter_once(aEvent, aListener) { |
|
71 let deferred = promise.defer(); |
|
72 |
|
73 let handler = function(aEvent, aFirstArg) { |
|
74 this.off(aEvent, handler); |
|
75 if (aListener) { |
|
76 aListener.apply(null, arguments); |
|
77 } |
|
78 deferred.resolve(aFirstArg); |
|
79 }.bind(this); |
|
80 |
|
81 handler._originalListener = aListener; |
|
82 this.on(aEvent, handler); |
|
83 |
|
84 return deferred.promise; |
|
85 }, |
|
86 |
|
87 /** |
|
88 * Remove a previously-registered event listener. Works for events |
|
89 * registered with either on or once. |
|
90 * |
|
91 * @param string aEvent |
|
92 * The event name whose listener we're disconnecting. |
|
93 * @param function aListener |
|
94 * The listener to remove. |
|
95 */ |
|
96 off: function EventEmitter_off(aEvent, aListener) { |
|
97 if (!this._eventEmitterListeners) |
|
98 return; |
|
99 let listeners = this._eventEmitterListeners.get(aEvent); |
|
100 if (listeners) { |
|
101 this._eventEmitterListeners.set(aEvent, listeners.filter(l => { |
|
102 return l !== aListener && l._originalListener !== aListener; |
|
103 })); |
|
104 } |
|
105 }, |
|
106 |
|
107 /** |
|
108 * Emit an event. All arguments to this method will |
|
109 * be sent to listner functions. |
|
110 */ |
|
111 emit: function EventEmitter_emit(aEvent) { |
|
112 this.logEvent(aEvent, arguments); |
|
113 |
|
114 if (!this._eventEmitterListeners || !this._eventEmitterListeners.has(aEvent)) { |
|
115 return; |
|
116 } |
|
117 |
|
118 let originalListeners = this._eventEmitterListeners.get(aEvent); |
|
119 for (let listener of this._eventEmitterListeners.get(aEvent)) { |
|
120 // If the object was destroyed during event emission, stop |
|
121 // emitting. |
|
122 if (!this._eventEmitterListeners) { |
|
123 break; |
|
124 } |
|
125 |
|
126 // If listeners were removed during emission, make sure the |
|
127 // event handler we're going to fire wasn't removed. |
|
128 if (originalListeners === this._eventEmitterListeners.get(aEvent) || |
|
129 this._eventEmitterListeners.get(aEvent).some(function(l) l === listener)) { |
|
130 try { |
|
131 listener.apply(null, arguments); |
|
132 } |
|
133 catch (ex) { |
|
134 // Prevent a bad listener from interfering with the others. |
|
135 let msg = ex + ": " + ex.stack; |
|
136 Cu.reportError(msg); |
|
137 dump(msg + "\n"); |
|
138 } |
|
139 } |
|
140 } |
|
141 }, |
|
142 |
|
143 logEvent: function(aEvent, args) { |
|
144 let logging = Services.prefs.getBoolPref("devtools.dump.emit"); |
|
145 |
|
146 if (logging) { |
|
147 let caller = components.stack.caller.caller; |
|
148 let func = caller.name; |
|
149 let path = caller.filename.split(/ -> /)[1] + ":" + caller.lineNumber; |
|
150 |
|
151 let argOut = "("; |
|
152 if (args.length === 1) { |
|
153 argOut += aEvent; |
|
154 } |
|
155 |
|
156 let out = "EMITTING: "; |
|
157 |
|
158 // We need this try / catch to prevent any dead object errors. |
|
159 try { |
|
160 for (let i = 1; i < args.length; i++) { |
|
161 if (i === 1) { |
|
162 argOut = "(" + aEvent + ", "; |
|
163 } else { |
|
164 argOut += ", "; |
|
165 } |
|
166 |
|
167 let arg = args[i]; |
|
168 argOut += arg; |
|
169 |
|
170 if (arg && arg.nodeName) { |
|
171 argOut += " (" + arg.nodeName; |
|
172 if (arg.id) { |
|
173 argOut += "#" + arg.id; |
|
174 } |
|
175 if (arg.className) { |
|
176 argOut += "." + arg.className; |
|
177 } |
|
178 argOut += ")"; |
|
179 } |
|
180 } |
|
181 } catch(e) { |
|
182 // Object is dead so the toolbox is most likely shutting down, |
|
183 // do nothing. |
|
184 } |
|
185 |
|
186 argOut += ")"; |
|
187 out += "emit" + argOut + " from " + func + "() -> " + path + "\n"; |
|
188 |
|
189 dump(out); |
|
190 } |
|
191 }, |
|
192 }; |