183 lines, 52 LOC, 43 covered (82%)
38 | 1 | /* ***** BEGIN LICENSE BLOCK ***** |
2 | * Version: MPL 1.1/GPL 2.0/LGPL 2.1 |
|
3 | * |
|
4 | * The contents of this file are subject to the Mozilla Public License Version |
|
5 | * 1.1 (the "License"); you may not use this file except in compliance with |
|
6 | * the License. You may obtain a copy of the License at |
|
7 | * http://www.mozilla.org/MPL/ |
|
8 | * |
|
9 | * Software distributed under the License is distributed on an "AS IS" basis, |
|
10 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License |
|
11 | * for the specific language governing rights and limitations under the |
|
12 | * License. |
|
13 | * |
|
14 | * The Original Code is Observers. |
|
15 | * |
|
16 | * The Initial Developer of the Original Code is Daniel Aquino. |
|
17 | * Portions created by the Initial Developer are Copyright (C) 2008 |
|
18 | * the Initial Developer. All Rights Reserved. |
|
19 | * |
|
20 | * Contributor(s): |
|
21 | * Daniel Aquino <mr.danielaquino@gmail.com> |
|
22 | * Myk Melez <myk@mozilla.org> |
|
23 | * |
|
24 | * Alternatively, the contents of this file may be used under the terms of |
|
25 | * either the GNU General Public License Version 2 or later (the "GPL"), or |
|
26 | * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), |
|
27 | * in which case the provisions of the GPL or the LGPL are applicable instead |
|
28 | * of those above. If you wish to allow use of your version of this file only |
|
29 | * under the terms of either the GPL or the LGPL, and not to allow others to |
|
30 | * use your version of this file under the terms of the MPL, indicate your |
|
31 | * decision by deleting the provisions above and replace them with the notice |
|
32 | * and other provisions required by the GPL or the LGPL. If you do not delete |
|
33 | * the provisions above, a recipient may use your version of this file under |
|
34 | * the terms of any one of the MPL, the GPL or the LGPL. |
|
35 | * |
|
36 | * ***** END LICENSE BLOCK ***** */ |
|
37 | ||
266 | 38 | let EXPORTED_SYMBOLS = ["Observers"]; |
39 | ||
152 | 40 | const Cc = Components.classes; |
152 | 41 | const Ci = Components.interfaces; |
152 | 42 | const Cr = Components.results; |
152 | 43 | const Cu = Components.utils; |
44 | ||
190 | 45 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
46 | ||
47 | /** |
|
48 | * A service for adding, removing and notifying observers of notifications. |
|
49 | * Wraps the nsIObserverService interface. |
|
50 | * |
|
51 | * @version 0.2 |
|
52 | */ |
|
76 | 53 | let Observers = { |
54 | /** |
|
55 | * Register the given callback as an observer of the given topic. |
|
56 | * |
|
57 | * @param topic {String} |
|
58 | * the topic to observe |
|
59 | * |
|
60 | * @param callback {Object} |
|
61 | * the callback; an Object that implements nsIObserver or a Function |
|
62 | * that gets called when the notification occurs |
|
63 | * |
|
64 | * @param thisObject {Object} [optional] |
|
65 | * the object to use as |this| when calling a Function callback |
|
66 | * |
|
67 | * @returns the observer |
|
68 | */ |
|
122 | 69 | add: function(topic, callback, thisObject) { |
276 | 70 | let observer = new Observer(topic, callback, thisObject); |
276 | 71 | this._cache.push(observer); |
368 | 72 | this._service.addObserver(observer, topic, true); |
73 | ||
92 | 74 | return observer; |
75 | }, |
|
76 | ||
77 | /** |
|
78 | * Unregister the given callback as an observer of the given topic. |
|
79 | * |
|
80 | * @param topic {String} |
|
81 | * the topic being observed |
|
82 | * |
|
83 | * @param callback {Object} |
|
84 | * the callback doing the observing |
|
85 | * |
|
86 | * @param thisObject {Object} [optional] |
|
87 | * the object being used as |this| when calling a Function callback |
|
88 | */ |
|
76 | 89 | remove: function(topic, callback, thisObject) { |
90 | // This seems fairly inefficient, but I'm not sure how much better |
|
91 | // we can make it. We could index by topic, but we can't index by callback |
|
92 | // or thisObject, as far as I know, since the keys to JavaScript hashes |
|
93 | // (a.k.a. objects) can apparently only be primitive values. |
|
94 | let [observer] = this._cache.filter(function(v) v.topic == topic && |
|
95 | v.callback == callback && |
|
96 | v.thisObject == thisObject); |
|
97 | if (observer) { |
|
98 | this._service.removeObserver(observer, topic); |
|
99 | this._cache.splice(this._cache.indexOf(observer), 1); |
|
100 | } |
|
101 | }, |
|
102 | ||
103 | /** |
|
104 | * Notify observers about something. |
|
105 | * |
|
106 | * @param topic {String} |
|
107 | * the topic to notify observers about |
|
108 | * |
|
109 | * @param subject {Object} [optional] |
|
110 | * some information about the topic; can be any JS object or primitive |
|
111 | * |
|
112 | * @param data {String} [optional] [deprecated] |
|
113 | * some more information about the topic; deprecated as the subject |
|
114 | * is sufficient to pass all needed information to the JS observers |
|
115 | * that this module targets; if you have multiple values to pass to |
|
116 | * the observer, wrap them in an object and pass them via the subject |
|
117 | * parameter (i.e.: { foo: 1, bar: "some string", baz: myObject }) |
|
118 | */ |
|
297 | 119 | notify: function(topic, subject, data) { |
1766 | 120 | subject = (typeof subject == "undefined") ? null : new Subject(subject); |
1547 | 121 | data = (typeof data == "undefined") ? null : data; |
1989 | 122 | this._service.notifyObservers(subject, topic, data); |
123 | }, |
|
124 | ||
114 | 125 | _service: Cc["@mozilla.org/observer-service;1"]. |
152 | 126 | getService(Ci.nsIObserverService), |
127 | ||
128 | /** |
|
129 | * A cache of observers that have been added. |
|
130 | * |
|
131 | * We use this to remove observers when a caller calls |remove|. |
|
132 | * |
|
133 | * XXX This might result in reference cycles, causing memory leaks, |
|
134 | * if we hold a reference to an observer that holds a reference to us. |
|
135 | * Could we fix that by making this an independent top-level object |
|
136 | * rather than a property of this object? |
|
137 | */ |
|
190 | 138 | _cache: [] |
139 | }; |
|
140 | ||
141 | ||
122 | 142 | function Observer(topic, callback, thisObject) { |
138 | 143 | this.topic = topic; |
138 | 144 | this.callback = callback; |
184 | 145 | this.thisObject = thisObject; |
146 | } |
|
147 | ||
76 | 148 | Observer.prototype = { |
532 | 149 | QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]), |
204 | 150 | observe: function(subject, topic, data) { |
151 | // Extract the wrapped object for subjects that are one of our wrappers |
|
152 | // around a JS object. This way we support both wrapped subjects created |
|
153 | // using this module and those that are real XPCOM components. |
|
98 | 154 | if (subject && typeof subject == "object" && |
56 | 155 | ("wrappedJSObject" in subject) && |
42 | 156 | ("observersModuleSubjectWrapper" in subject.wrappedJSObject)) |
56 | 157 | subject = subject.wrappedJSObject.object; |
158 | ||
56 | 159 | if (typeof this.callback == "function") { |
2 | 160 | if (this.thisObject) |
161 | this.callback.call(this.thisObject, subject, data); |
|
162 | else |
|
7 | 163 | this.callback(subject, data); |
164 | } |
|
165 | else // typeof this.callback == "object" (nsIObserver) |
|
118 | 166 | this.callback.observe(subject, topic, data); |
167 | } |
|
168 | } |
|
169 | ||
170 | ||
295 | 171 | function Subject(object) { |
172 | // Double-wrap the object and set a property identifying the wrappedJSObject |
|
173 | // as one of our wrappers to distinguish between subjects that are one of our |
|
174 | // wrappers (which we should unwrap when notifying our observers) and those |
|
175 | // that are real JS XPCOM components (which we should pass through unaltered). |
|
1971 | 176 | this.wrappedJSObject = { observersModuleSubjectWrapper: true, object: object }; |
177 | } |
|
178 | ||
76 | 179 | Subject.prototype = { |
228 | 180 | QueryInterface: XPCOMUtils.generateQI([]), |
76 | 181 | getHelperForLanguage: function() {}, |
190 | 182 | getInterfaces: function() {} |
38 | 183 | }; |