|
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/. */ |
|
4 |
|
5 #ifndef MERGED_COMPARTMENT |
|
6 |
|
7 this.EXPORTED_SYMBOLS = ["Observers"]; |
|
8 |
|
9 const Cc = Components.classes; |
|
10 const Ci = Components.interfaces; |
|
11 const Cr = Components.results; |
|
12 const Cu = Components.utils; |
|
13 |
|
14 #endif |
|
15 |
|
16 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
17 |
|
18 /** |
|
19 * A service for adding, removing and notifying observers of notifications. |
|
20 * Wraps the nsIObserverService interface. |
|
21 * |
|
22 * @version 0.2 |
|
23 */ |
|
24 this.Observers = { |
|
25 /** |
|
26 * Register the given callback as an observer of the given topic. |
|
27 * |
|
28 * @param topic {String} |
|
29 * the topic to observe |
|
30 * |
|
31 * @param callback {Object} |
|
32 * the callback; an Object that implements nsIObserver or a Function |
|
33 * that gets called when the notification occurs |
|
34 * |
|
35 * @param thisObject {Object} [optional] |
|
36 * the object to use as |this| when calling a Function callback |
|
37 * |
|
38 * @returns the observer |
|
39 */ |
|
40 add: function(topic, callback, thisObject) { |
|
41 let observer = new Observer(topic, callback, thisObject); |
|
42 this._cache.push(observer); |
|
43 this._service.addObserver(observer, topic, true); |
|
44 |
|
45 return observer; |
|
46 }, |
|
47 |
|
48 /** |
|
49 * Unregister the given callback as an observer of the given topic. |
|
50 * |
|
51 * @param topic {String} |
|
52 * the topic being observed |
|
53 * |
|
54 * @param callback {Object} |
|
55 * the callback doing the observing |
|
56 * |
|
57 * @param thisObject {Object} [optional] |
|
58 * the object being used as |this| when calling a Function callback |
|
59 */ |
|
60 remove: function(topic, callback, thisObject) { |
|
61 // This seems fairly inefficient, but I'm not sure how much better |
|
62 // we can make it. We could index by topic, but we can't index by callback |
|
63 // or thisObject, as far as I know, since the keys to JavaScript hashes |
|
64 // (a.k.a. objects) can apparently only be primitive values. |
|
65 let [observer] = this._cache.filter(function(v) v.topic == topic && |
|
66 v.callback == callback && |
|
67 v.thisObject == thisObject); |
|
68 if (observer) { |
|
69 this._service.removeObserver(observer, topic); |
|
70 this._cache.splice(this._cache.indexOf(observer), 1); |
|
71 } |
|
72 }, |
|
73 |
|
74 /** |
|
75 * Notify observers about something. |
|
76 * |
|
77 * @param topic {String} |
|
78 * the topic to notify observers about |
|
79 * |
|
80 * @param subject {Object} [optional] |
|
81 * some information about the topic; can be any JS object or primitive |
|
82 * |
|
83 * @param data {String} [optional] [deprecated] |
|
84 * some more information about the topic; deprecated as the subject |
|
85 * is sufficient to pass all needed information to the JS observers |
|
86 * that this module targets; if you have multiple values to pass to |
|
87 * the observer, wrap them in an object and pass them via the subject |
|
88 * parameter (i.e.: { foo: 1, bar: "some string", baz: myObject }) |
|
89 */ |
|
90 notify: function(topic, subject, data) { |
|
91 subject = (typeof subject == "undefined") ? null : new Subject(subject); |
|
92 data = (typeof data == "undefined") ? null : data; |
|
93 this._service.notifyObservers(subject, topic, data); |
|
94 }, |
|
95 |
|
96 _service: Cc["@mozilla.org/observer-service;1"]. |
|
97 getService(Ci.nsIObserverService), |
|
98 |
|
99 /** |
|
100 * A cache of observers that have been added. |
|
101 * |
|
102 * We use this to remove observers when a caller calls |remove|. |
|
103 * |
|
104 * XXX This might result in reference cycles, causing memory leaks, |
|
105 * if we hold a reference to an observer that holds a reference to us. |
|
106 * Could we fix that by making this an independent top-level object |
|
107 * rather than a property of this object? |
|
108 */ |
|
109 _cache: [] |
|
110 }; |
|
111 |
|
112 |
|
113 function Observer(topic, callback, thisObject) { |
|
114 this.topic = topic; |
|
115 this.callback = callback; |
|
116 this.thisObject = thisObject; |
|
117 } |
|
118 |
|
119 Observer.prototype = { |
|
120 QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]), |
|
121 observe: function(subject, topic, data) { |
|
122 // Extract the wrapped object for subjects that are one of our wrappers |
|
123 // around a JS object. This way we support both wrapped subjects created |
|
124 // using this module and those that are real XPCOM components. |
|
125 if (subject && typeof subject == "object" && |
|
126 ("wrappedJSObject" in subject) && |
|
127 ("observersModuleSubjectWrapper" in subject.wrappedJSObject)) |
|
128 subject = subject.wrappedJSObject.object; |
|
129 |
|
130 if (typeof this.callback == "function") { |
|
131 if (this.thisObject) |
|
132 this.callback.call(this.thisObject, subject, data); |
|
133 else |
|
134 this.callback(subject, data); |
|
135 } |
|
136 else // typeof this.callback == "object" (nsIObserver) |
|
137 this.callback.observe(subject, topic, data); |
|
138 } |
|
139 } |
|
140 |
|
141 |
|
142 function Subject(object) { |
|
143 // Double-wrap the object and set a property identifying the wrappedJSObject |
|
144 // as one of our wrappers to distinguish between subjects that are one of our |
|
145 // wrappers (which we should unwrap when notifying our observers) and those |
|
146 // that are real JS XPCOM components (which we should pass through unaltered). |
|
147 this.wrappedJSObject = { observersModuleSubjectWrapper: true, object: object }; |
|
148 } |
|
149 |
|
150 Subject.prototype = { |
|
151 QueryInterface: XPCOMUtils.generateQI([]), |
|
152 getHelperForLanguage: function() {}, |
|
153 getInterfaces: function() {} |
|
154 }; |