michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef MERGED_COMPARTMENT michael@0: michael@0: this.EXPORTED_SYMBOLS = ["Observers"]; michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cr = Components.results; michael@0: const Cu = Components.utils; michael@0: michael@0: #endif michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: michael@0: /** michael@0: * A service for adding, removing and notifying observers of notifications. michael@0: * Wraps the nsIObserverService interface. michael@0: * michael@0: * @version 0.2 michael@0: */ michael@0: this.Observers = { michael@0: /** michael@0: * Register the given callback as an observer of the given topic. michael@0: * michael@0: * @param topic {String} michael@0: * the topic to observe michael@0: * michael@0: * @param callback {Object} michael@0: * the callback; an Object that implements nsIObserver or a Function michael@0: * that gets called when the notification occurs michael@0: * michael@0: * @param thisObject {Object} [optional] michael@0: * the object to use as |this| when calling a Function callback michael@0: * michael@0: * @returns the observer michael@0: */ michael@0: add: function(topic, callback, thisObject) { michael@0: let observer = new Observer(topic, callback, thisObject); michael@0: this._cache.push(observer); michael@0: this._service.addObserver(observer, topic, true); michael@0: michael@0: return observer; michael@0: }, michael@0: michael@0: /** michael@0: * Unregister the given callback as an observer of the given topic. michael@0: * michael@0: * @param topic {String} michael@0: * the topic being observed michael@0: * michael@0: * @param callback {Object} michael@0: * the callback doing the observing michael@0: * michael@0: * @param thisObject {Object} [optional] michael@0: * the object being used as |this| when calling a Function callback michael@0: */ michael@0: remove: function(topic, callback, thisObject) { michael@0: // This seems fairly inefficient, but I'm not sure how much better michael@0: // we can make it. We could index by topic, but we can't index by callback michael@0: // or thisObject, as far as I know, since the keys to JavaScript hashes michael@0: // (a.k.a. objects) can apparently only be primitive values. michael@0: let [observer] = this._cache.filter(function(v) v.topic == topic && michael@0: v.callback == callback && michael@0: v.thisObject == thisObject); michael@0: if (observer) { michael@0: this._service.removeObserver(observer, topic); michael@0: this._cache.splice(this._cache.indexOf(observer), 1); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Notify observers about something. michael@0: * michael@0: * @param topic {String} michael@0: * the topic to notify observers about michael@0: * michael@0: * @param subject {Object} [optional] michael@0: * some information about the topic; can be any JS object or primitive michael@0: * michael@0: * @param data {String} [optional] [deprecated] michael@0: * some more information about the topic; deprecated as the subject michael@0: * is sufficient to pass all needed information to the JS observers michael@0: * that this module targets; if you have multiple values to pass to michael@0: * the observer, wrap them in an object and pass them via the subject michael@0: * parameter (i.e.: { foo: 1, bar: "some string", baz: myObject }) michael@0: */ michael@0: notify: function(topic, subject, data) { michael@0: subject = (typeof subject == "undefined") ? null : new Subject(subject); michael@0: data = (typeof data == "undefined") ? null : data; michael@0: this._service.notifyObservers(subject, topic, data); michael@0: }, michael@0: michael@0: _service: Cc["@mozilla.org/observer-service;1"]. michael@0: getService(Ci.nsIObserverService), michael@0: michael@0: /** michael@0: * A cache of observers that have been added. michael@0: * michael@0: * We use this to remove observers when a caller calls |remove|. michael@0: * michael@0: * XXX This might result in reference cycles, causing memory leaks, michael@0: * if we hold a reference to an observer that holds a reference to us. michael@0: * Could we fix that by making this an independent top-level object michael@0: * rather than a property of this object? michael@0: */ michael@0: _cache: [] michael@0: }; michael@0: michael@0: michael@0: function Observer(topic, callback, thisObject) { michael@0: this.topic = topic; michael@0: this.callback = callback; michael@0: this.thisObject = thisObject; michael@0: } michael@0: michael@0: Observer.prototype = { michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]), michael@0: observe: function(subject, topic, data) { michael@0: // Extract the wrapped object for subjects that are one of our wrappers michael@0: // around a JS object. This way we support both wrapped subjects created michael@0: // using this module and those that are real XPCOM components. michael@0: if (subject && typeof subject == "object" && michael@0: ("wrappedJSObject" in subject) && michael@0: ("observersModuleSubjectWrapper" in subject.wrappedJSObject)) michael@0: subject = subject.wrappedJSObject.object; michael@0: michael@0: if (typeof this.callback == "function") { michael@0: if (this.thisObject) michael@0: this.callback.call(this.thisObject, subject, data); michael@0: else michael@0: this.callback(subject, data); michael@0: } michael@0: else // typeof this.callback == "object" (nsIObserver) michael@0: this.callback.observe(subject, topic, data); michael@0: } michael@0: } michael@0: michael@0: michael@0: function Subject(object) { michael@0: // Double-wrap the object and set a property identifying the wrappedJSObject michael@0: // as one of our wrappers to distinguish between subjects that are one of our michael@0: // wrappers (which we should unwrap when notifying our observers) and those michael@0: // that are real JS XPCOM components (which we should pass through unaltered). michael@0: this.wrappedJSObject = { observersModuleSubjectWrapper: true, object: object }; michael@0: } michael@0: michael@0: Subject.prototype = { michael@0: QueryInterface: XPCOMUtils.generateQI([]), michael@0: getHelperForLanguage: function() {}, michael@0: getInterfaces: function() {} michael@0: };