services/common/observers.js

changeset 0
6474c204b198
     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 +};

mercurial