1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/services/common/observers.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,154 @@ 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 +#ifndef MERGED_COMPARTMENT 1.9 + 1.10 +this.EXPORTED_SYMBOLS = ["Observers"]; 1.11 + 1.12 +const Cc = Components.classes; 1.13 +const Ci = Components.interfaces; 1.14 +const Cr = Components.results; 1.15 +const Cu = Components.utils; 1.16 + 1.17 +#endif 1.18 + 1.19 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.20 + 1.21 +/** 1.22 + * A service for adding, removing and notifying observers of notifications. 1.23 + * Wraps the nsIObserverService interface. 1.24 + * 1.25 + * @version 0.2 1.26 + */ 1.27 +this.Observers = { 1.28 + /** 1.29 + * Register the given callback as an observer of the given topic. 1.30 + * 1.31 + * @param topic {String} 1.32 + * the topic to observe 1.33 + * 1.34 + * @param callback {Object} 1.35 + * the callback; an Object that implements nsIObserver or a Function 1.36 + * that gets called when the notification occurs 1.37 + * 1.38 + * @param thisObject {Object} [optional] 1.39 + * the object to use as |this| when calling a Function callback 1.40 + * 1.41 + * @returns the observer 1.42 + */ 1.43 + add: function(topic, callback, thisObject) { 1.44 + let observer = new Observer(topic, callback, thisObject); 1.45 + this._cache.push(observer); 1.46 + this._service.addObserver(observer, topic, true); 1.47 + 1.48 + return observer; 1.49 + }, 1.50 + 1.51 + /** 1.52 + * Unregister the given callback as an observer of the given topic. 1.53 + * 1.54 + * @param topic {String} 1.55 + * the topic being observed 1.56 + * 1.57 + * @param callback {Object} 1.58 + * the callback doing the observing 1.59 + * 1.60 + * @param thisObject {Object} [optional] 1.61 + * the object being used as |this| when calling a Function callback 1.62 + */ 1.63 + remove: function(topic, callback, thisObject) { 1.64 + // This seems fairly inefficient, but I'm not sure how much better 1.65 + // we can make it. We could index by topic, but we can't index by callback 1.66 + // or thisObject, as far as I know, since the keys to JavaScript hashes 1.67 + // (a.k.a. objects) can apparently only be primitive values. 1.68 + let [observer] = this._cache.filter(function(v) v.topic == topic && 1.69 + v.callback == callback && 1.70 + v.thisObject == thisObject); 1.71 + if (observer) { 1.72 + this._service.removeObserver(observer, topic); 1.73 + this._cache.splice(this._cache.indexOf(observer), 1); 1.74 + } 1.75 + }, 1.76 + 1.77 + /** 1.78 + * Notify observers about something. 1.79 + * 1.80 + * @param topic {String} 1.81 + * the topic to notify observers about 1.82 + * 1.83 + * @param subject {Object} [optional] 1.84 + * some information about the topic; can be any JS object or primitive 1.85 + * 1.86 + * @param data {String} [optional] [deprecated] 1.87 + * some more information about the topic; deprecated as the subject 1.88 + * is sufficient to pass all needed information to the JS observers 1.89 + * that this module targets; if you have multiple values to pass to 1.90 + * the observer, wrap them in an object and pass them via the subject 1.91 + * parameter (i.e.: { foo: 1, bar: "some string", baz: myObject }) 1.92 + */ 1.93 + notify: function(topic, subject, data) { 1.94 + subject = (typeof subject == "undefined") ? null : new Subject(subject); 1.95 + data = (typeof data == "undefined") ? null : data; 1.96 + this._service.notifyObservers(subject, topic, data); 1.97 + }, 1.98 + 1.99 + _service: Cc["@mozilla.org/observer-service;1"]. 1.100 + getService(Ci.nsIObserverService), 1.101 + 1.102 + /** 1.103 + * A cache of observers that have been added. 1.104 + * 1.105 + * We use this to remove observers when a caller calls |remove|. 1.106 + * 1.107 + * XXX This might result in reference cycles, causing memory leaks, 1.108 + * if we hold a reference to an observer that holds a reference to us. 1.109 + * Could we fix that by making this an independent top-level object 1.110 + * rather than a property of this object? 1.111 + */ 1.112 + _cache: [] 1.113 +}; 1.114 + 1.115 + 1.116 +function Observer(topic, callback, thisObject) { 1.117 + this.topic = topic; 1.118 + this.callback = callback; 1.119 + this.thisObject = thisObject; 1.120 +} 1.121 + 1.122 +Observer.prototype = { 1.123 + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]), 1.124 + observe: function(subject, topic, data) { 1.125 + // Extract the wrapped object for subjects that are one of our wrappers 1.126 + // around a JS object. This way we support both wrapped subjects created 1.127 + // using this module and those that are real XPCOM components. 1.128 + if (subject && typeof subject == "object" && 1.129 + ("wrappedJSObject" in subject) && 1.130 + ("observersModuleSubjectWrapper" in subject.wrappedJSObject)) 1.131 + subject = subject.wrappedJSObject.object; 1.132 + 1.133 + if (typeof this.callback == "function") { 1.134 + if (this.thisObject) 1.135 + this.callback.call(this.thisObject, subject, data); 1.136 + else 1.137 + this.callback(subject, data); 1.138 + } 1.139 + else // typeof this.callback == "object" (nsIObserver) 1.140 + this.callback.observe(subject, topic, data); 1.141 + } 1.142 +} 1.143 + 1.144 + 1.145 +function Subject(object) { 1.146 + // Double-wrap the object and set a property identifying the wrappedJSObject 1.147 + // as one of our wrappers to distinguish between subjects that are one of our 1.148 + // wrappers (which we should unwrap when notifying our observers) and those 1.149 + // that are real JS XPCOM components (which we should pass through unaltered). 1.150 + this.wrappedJSObject = { observersModuleSubjectWrapper: true, object: object }; 1.151 +} 1.152 + 1.153 +Subject.prototype = { 1.154 + QueryInterface: XPCOMUtils.generateQI([]), 1.155 + getHelperForLanguage: function() {}, 1.156 + getInterfaces: function() {} 1.157 +};