ext/Observers.js

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
};