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/. */
6 * A service for adding, removing and notifying observers of notifications.
7 * Wraps the nsIObserverService interface.
11 export var Observers = {
13 * Register the given callback as an observer of the given topic.
15 * @param topic {String}
16 * the topic to observe
18 * @param callback {Object}
19 * the callback; an Object that implements nsIObserver or a Function
20 * that gets called when the notification occurs
22 * @param thisObject {Object} [optional]
23 * the object to use as |this| when calling a Function callback
25 * @returns the observer
27 add(topic, callback, thisObject) {
28 let observer = new Observer(topic, callback, thisObject);
29 this._cache.push(observer);
30 Services.obs.addObserver(observer, topic, true);
36 * Unregister the given callback as an observer of the given topic.
38 * @param topic {String}
39 * the topic being observed
41 * @param callback {Object}
42 * the callback doing the observing
44 * @param thisObject {Object} [optional]
45 * the object being used as |this| when calling a Function callback
47 remove(topic, callback, thisObject) {
48 // This seems fairly inefficient, but I'm not sure how much better
49 // we can make it. We could index by topic, but we can't index by callback
50 // or thisObject, as far as I know, since the keys to JavaScript hashes
51 // (a.k.a. objects) can apparently only be primitive values.
52 let [observer] = this._cache.filter(
54 v.topic == topic && v.callback == callback && v.thisObject == thisObject
57 Services.obs.removeObserver(observer, topic);
58 this._cache.splice(this._cache.indexOf(observer), 1);
60 throw new Error("Attempt to remove non-existing observer");
65 * Notify observers about something.
67 * @param topic {String}
68 * the topic to notify observers about
70 * @param subject {Object} [optional]
71 * some information about the topic; can be any JS object or primitive
73 * @param data {String} [optional] [deprecated]
74 * some more information about the topic; deprecated as the subject
75 * is sufficient to pass all needed information to the JS observers
76 * that this module targets; if you have multiple values to pass to
77 * the observer, wrap them in an object and pass them via the subject
78 * parameter (i.e.: { foo: 1, bar: "some string", baz: myObject })
80 notify(topic, subject, data) {
81 subject = typeof subject == "undefined" ? null : new Subject(subject);
82 data = typeof data == "undefined" ? null : data;
83 Services.obs.notifyObservers(subject, topic, data);
87 * A cache of observers that have been added.
89 * We use this to remove observers when a caller calls |remove|.
91 * XXX This might result in reference cycles, causing memory leaks,
92 * if we hold a reference to an observer that holds a reference to us.
93 * Could we fix that by making this an independent top-level object
94 * rather than a property of this object?
99 function Observer(topic, callback, thisObject) {
101 this.callback = callback;
102 this.thisObject = thisObject;
105 Observer.prototype = {
106 QueryInterface: ChromeUtils.generateQI([
108 "nsISupportsWeakReference",
110 observe(subject, topic, data) {
111 // Extract the wrapped object for subjects that are one of our wrappers
112 // around a JS object. This way we support both wrapped subjects created
113 // using this module and those that are real XPCOM components.
116 typeof subject == "object" &&
117 "wrappedJSObject" in subject &&
118 "observersModuleSubjectWrapper" in subject.wrappedJSObject
120 subject = subject.wrappedJSObject.object;
123 if (typeof this.callback == "function") {
124 if (this.thisObject) {
125 this.callback.call(this.thisObject, subject, data);
127 this.callback(subject, data);
130 // typeof this.callback == "object" (nsIObserver)
131 this.callback.observe(subject, topic, data);
136 function Subject(object) {
137 // Double-wrap the object and set a property identifying the wrappedJSObject
138 // as one of our wrappers to distinguish between subjects that are one of our
139 // wrappers (which we should unwrap when notifying our observers) and those
140 // that are real JS XPCOM components (which we should pass through unaltered).
141 this.wrappedJSObject = { observersModuleSubjectWrapper: true, object };
144 Subject.prototype = {
145 QueryInterface: ChromeUtils.generateQI([]),
146 getScriptableHelper() {},