1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/devtools/event-emitter.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,192 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +/** 1.9 + * EventEmitter. 1.10 + */ 1.11 + 1.12 +this.EventEmitter = function EventEmitter() {}; 1.13 + 1.14 +if (typeof(require) === "function") { 1.15 + module.exports = EventEmitter; 1.16 + var {Cu, components} = require("chrome"); 1.17 +} else { 1.18 + var EXPORTED_SYMBOLS = ["EventEmitter"]; 1.19 + var Cu = this["Components"].utils; 1.20 + var components = Components; 1.21 +} 1.22 + 1.23 +const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {}); 1.24 +const { Services } = Cu.import("resource://gre/modules/Services.jsm"); 1.25 + 1.26 +/** 1.27 + * Decorate an object with event emitter functionality. 1.28 + * 1.29 + * @param Object aObjectToDecorate 1.30 + * Bind all public methods of EventEmitter to 1.31 + * the aObjectToDecorate object. 1.32 + */ 1.33 +EventEmitter.decorate = function EventEmitter_decorate (aObjectToDecorate) { 1.34 + let emitter = new EventEmitter(); 1.35 + aObjectToDecorate.on = emitter.on.bind(emitter); 1.36 + aObjectToDecorate.off = emitter.off.bind(emitter); 1.37 + aObjectToDecorate.once = emitter.once.bind(emitter); 1.38 + aObjectToDecorate.emit = emitter.emit.bind(emitter); 1.39 +}; 1.40 + 1.41 +EventEmitter.prototype = { 1.42 + /** 1.43 + * Connect a listener. 1.44 + * 1.45 + * @param string aEvent 1.46 + * The event name to which we're connecting. 1.47 + * @param function aListener 1.48 + * Called when the event is fired. 1.49 + */ 1.50 + on: function EventEmitter_on(aEvent, aListener) { 1.51 + if (!this._eventEmitterListeners) 1.52 + this._eventEmitterListeners = new Map(); 1.53 + if (!this._eventEmitterListeners.has(aEvent)) { 1.54 + this._eventEmitterListeners.set(aEvent, []); 1.55 + } 1.56 + this._eventEmitterListeners.get(aEvent).push(aListener); 1.57 + }, 1.58 + 1.59 + /** 1.60 + * Listen for the next time an event is fired. 1.61 + * 1.62 + * @param string aEvent 1.63 + * The event name to which we're connecting. 1.64 + * @param function aListener 1.65 + * (Optional) Called when the event is fired. Will be called at most 1.66 + * one time. 1.67 + * @return promise 1.68 + * A promise which is resolved when the event next happens. The 1.69 + * resolution value of the promise is the first event argument. If 1.70 + * you need access to second or subsequent event arguments (it's rare 1.71 + * that this is needed) then use aListener 1.72 + */ 1.73 + once: function EventEmitter_once(aEvent, aListener) { 1.74 + let deferred = promise.defer(); 1.75 + 1.76 + let handler = function(aEvent, aFirstArg) { 1.77 + this.off(aEvent, handler); 1.78 + if (aListener) { 1.79 + aListener.apply(null, arguments); 1.80 + } 1.81 + deferred.resolve(aFirstArg); 1.82 + }.bind(this); 1.83 + 1.84 + handler._originalListener = aListener; 1.85 + this.on(aEvent, handler); 1.86 + 1.87 + return deferred.promise; 1.88 + }, 1.89 + 1.90 + /** 1.91 + * Remove a previously-registered event listener. Works for events 1.92 + * registered with either on or once. 1.93 + * 1.94 + * @param string aEvent 1.95 + * The event name whose listener we're disconnecting. 1.96 + * @param function aListener 1.97 + * The listener to remove. 1.98 + */ 1.99 + off: function EventEmitter_off(aEvent, aListener) { 1.100 + if (!this._eventEmitterListeners) 1.101 + return; 1.102 + let listeners = this._eventEmitterListeners.get(aEvent); 1.103 + if (listeners) { 1.104 + this._eventEmitterListeners.set(aEvent, listeners.filter(l => { 1.105 + return l !== aListener && l._originalListener !== aListener; 1.106 + })); 1.107 + } 1.108 + }, 1.109 + 1.110 + /** 1.111 + * Emit an event. All arguments to this method will 1.112 + * be sent to listner functions. 1.113 + */ 1.114 + emit: function EventEmitter_emit(aEvent) { 1.115 + this.logEvent(aEvent, arguments); 1.116 + 1.117 + if (!this._eventEmitterListeners || !this._eventEmitterListeners.has(aEvent)) { 1.118 + return; 1.119 + } 1.120 + 1.121 + let originalListeners = this._eventEmitterListeners.get(aEvent); 1.122 + for (let listener of this._eventEmitterListeners.get(aEvent)) { 1.123 + // If the object was destroyed during event emission, stop 1.124 + // emitting. 1.125 + if (!this._eventEmitterListeners) { 1.126 + break; 1.127 + } 1.128 + 1.129 + // If listeners were removed during emission, make sure the 1.130 + // event handler we're going to fire wasn't removed. 1.131 + if (originalListeners === this._eventEmitterListeners.get(aEvent) || 1.132 + this._eventEmitterListeners.get(aEvent).some(function(l) l === listener)) { 1.133 + try { 1.134 + listener.apply(null, arguments); 1.135 + } 1.136 + catch (ex) { 1.137 + // Prevent a bad listener from interfering with the others. 1.138 + let msg = ex + ": " + ex.stack; 1.139 + Cu.reportError(msg); 1.140 + dump(msg + "\n"); 1.141 + } 1.142 + } 1.143 + } 1.144 + }, 1.145 + 1.146 + logEvent: function(aEvent, args) { 1.147 + let logging = Services.prefs.getBoolPref("devtools.dump.emit"); 1.148 + 1.149 + if (logging) { 1.150 + let caller = components.stack.caller.caller; 1.151 + let func = caller.name; 1.152 + let path = caller.filename.split(/ -> /)[1] + ":" + caller.lineNumber; 1.153 + 1.154 + let argOut = "("; 1.155 + if (args.length === 1) { 1.156 + argOut += aEvent; 1.157 + } 1.158 + 1.159 + let out = "EMITTING: "; 1.160 + 1.161 + // We need this try / catch to prevent any dead object errors. 1.162 + try { 1.163 + for (let i = 1; i < args.length; i++) { 1.164 + if (i === 1) { 1.165 + argOut = "(" + aEvent + ", "; 1.166 + } else { 1.167 + argOut += ", "; 1.168 + } 1.169 + 1.170 + let arg = args[i]; 1.171 + argOut += arg; 1.172 + 1.173 + if (arg && arg.nodeName) { 1.174 + argOut += " (" + arg.nodeName; 1.175 + if (arg.id) { 1.176 + argOut += "#" + arg.id; 1.177 + } 1.178 + if (arg.className) { 1.179 + argOut += "." + arg.className; 1.180 + } 1.181 + argOut += ")"; 1.182 + } 1.183 + } 1.184 + } catch(e) { 1.185 + // Object is dead so the toolbox is most likely shutting down, 1.186 + // do nothing. 1.187 + } 1.188 + 1.189 + argOut += ")"; 1.190 + out += "emit" + argOut + " from " + func + "() -> " + path + "\n"; 1.191 + 1.192 + dump(out); 1.193 + } 1.194 + }, 1.195 +};