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/. */
7 ChromeUtils.defineESModuleGetters(lazy, {
8 console: "resource://gre/modules/Console.sys.mjs",
11 export function EventEmitter() {}
13 let loggingEnabled = Services.prefs.getBoolPref("toolkit.dump.emit");
14 Services.prefs.addObserver("toolkit.dump.emit", {
16 loggingEnabled = Services.prefs.getBoolPref("toolkit.dump.emit");
21 * Decorate an object with event emitter functionality.
23 * @param Object objectToDecorate
24 * Bind all public methods of EventEmitter to
25 * the objectToDecorate object.
27 EventEmitter.decorate = function (objectToDecorate) {
28 let emitter = new EventEmitter();
29 objectToDecorate.on = emitter.on.bind(emitter);
30 objectToDecorate.off = emitter.off.bind(emitter);
31 objectToDecorate.once = emitter.once.bind(emitter);
32 objectToDecorate.emit = emitter.emit.bind(emitter);
35 function describeNthCaller(n) {
36 let caller = Components.stack;
37 // Do one extra iteration to skip this function.
40 caller = caller.caller;
43 let func = caller.name;
44 let file = caller.filename;
45 if (file.includes(" -> ")) {
46 file = caller.filename.split(/ -> /)[1];
48 let path = file + ":" + caller.lineNumber;
50 return func + "() -> " + path;
53 EventEmitter.prototype = {
58 * The event name to which we're connecting.
59 * @param function listener
60 * Called when the event is fired.
63 if (!this._eventEmitterListeners) {
64 this._eventEmitterListeners = new Map();
66 if (!this._eventEmitterListeners.has(event)) {
67 this._eventEmitterListeners.set(event, []);
69 this._eventEmitterListeners.get(event).push(listener);
73 * Listen for the next time an event is fired.
76 * The event name to which we're connecting.
77 * @param function listener
78 * (Optional) Called when the event is fired. Will be called at most
81 * A promise which is resolved when the event next happens. The
82 * resolution value of the promise is the first event argument. If
83 * you need access to second or subsequent event arguments (it's rare
84 * that this is needed) then use listener
86 once(event, listener) {
87 return new Promise(resolve => {
88 let handler = (_, first, ...rest) => {
89 this.off(event, handler);
91 listener(event, first, ...rest);
96 handler._originalListener = listener;
97 this.on(event, handler);
102 * Remove a previously-registered event listener. Works for events
103 * registered with either on or once.
105 * @param string event
106 * The event name whose listener we're disconnecting.
107 * @param function listener
108 * The listener to remove.
110 off(event, listener) {
111 if (!this._eventEmitterListeners) {
114 let listeners = this._eventEmitterListeners.get(event);
116 this._eventEmitterListeners.set(
118 listeners.filter(l => {
119 return l !== listener && l._originalListener !== listener;
126 * Emit an event. All arguments to this method will
127 * be sent to listener functions.
130 this.logEvent(event, arguments);
133 !this._eventEmitterListeners ||
134 !this._eventEmitterListeners.has(event)
139 let originalListeners = this._eventEmitterListeners.get(event);
140 for (let listener of this._eventEmitterListeners.get(event)) {
141 // If the object was destroyed during event emission, stop
143 if (!this._eventEmitterListeners) {
147 // If listeners were removed during emission, make sure the
148 // event handler we're going to fire wasn't removed.
150 originalListeners === this._eventEmitterListeners.get(event) ||
151 this._eventEmitterListeners.get(event).some(l => l === listener)
154 listener.apply(null, arguments);
156 // Prevent a bad listener from interfering with the others.
157 let msg = ex + ": " + ex.stack;
158 lazy.console.error(msg);
159 if (loggingEnabled) {
167 logEvent(event, args) {
168 if (!loggingEnabled) {
172 let description = describeNthCaller(2);
175 if (args.length === 1) {
179 let out = "EMITTING: ";
181 // We need this try / catch to prevent any dead object errors.
183 for (let i = 1; i < args.length; i++) {
185 argOut = "(" + event + ", ";
193 if (arg && arg.nodeName) {
194 argOut += " (" + arg.nodeName;
196 argOut += "#" + arg.id;
199 argOut += "." + arg.className;
205 // Object is dead so the toolbox is most likely shutting down,
210 out += "emit" + argOut + " from " + description + "\n";