mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 21:01:08 +00:00
c60ae74d84
Differential Revision: https://phabricator.services.mozilla.com/D171054
149 lines
5.0 KiB
JavaScript
149 lines
5.0 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/**
|
|
* A service for adding, removing and notifying observers of notifications.
|
|
* Wraps the nsIObserverService interface.
|
|
*
|
|
* @version 0.2
|
|
*/
|
|
export var Observers = {
|
|
/**
|
|
* Register the given callback as an observer of the given topic.
|
|
*
|
|
* @param topic {String}
|
|
* the topic to observe
|
|
*
|
|
* @param callback {Object}
|
|
* the callback; an Object that implements nsIObserver or a Function
|
|
* that gets called when the notification occurs
|
|
*
|
|
* @param thisObject {Object} [optional]
|
|
* the object to use as |this| when calling a Function callback
|
|
*
|
|
* @returns the observer
|
|
*/
|
|
add(topic, callback, thisObject) {
|
|
let observer = new Observer(topic, callback, thisObject);
|
|
this._cache.push(observer);
|
|
Services.obs.addObserver(observer, topic, true);
|
|
|
|
return observer;
|
|
},
|
|
|
|
/**
|
|
* Unregister the given callback as an observer of the given topic.
|
|
*
|
|
* @param topic {String}
|
|
* the topic being observed
|
|
*
|
|
* @param callback {Object}
|
|
* the callback doing the observing
|
|
*
|
|
* @param thisObject {Object} [optional]
|
|
* the object being used as |this| when calling a Function callback
|
|
*/
|
|
remove(topic, callback, thisObject) {
|
|
// This seems fairly inefficient, but I'm not sure how much better
|
|
// we can make it. We could index by topic, but we can't index by callback
|
|
// or thisObject, as far as I know, since the keys to JavaScript hashes
|
|
// (a.k.a. objects) can apparently only be primitive values.
|
|
let [observer] = this._cache.filter(
|
|
v =>
|
|
v.topic == topic && v.callback == callback && v.thisObject == thisObject
|
|
);
|
|
if (observer) {
|
|
Services.obs.removeObserver(observer, topic);
|
|
this._cache.splice(this._cache.indexOf(observer), 1);
|
|
} else {
|
|
throw new Error("Attempt to remove non-existing observer");
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Notify observers about something.
|
|
*
|
|
* @param topic {String}
|
|
* the topic to notify observers about
|
|
*
|
|
* @param subject {Object} [optional]
|
|
* some information about the topic; can be any JS object or primitive
|
|
*
|
|
* @param data {String} [optional] [deprecated]
|
|
* some more information about the topic; deprecated as the subject
|
|
* is sufficient to pass all needed information to the JS observers
|
|
* that this module targets; if you have multiple values to pass to
|
|
* the observer, wrap them in an object and pass them via the subject
|
|
* parameter (i.e.: { foo: 1, bar: "some string", baz: myObject })
|
|
*/
|
|
notify(topic, subject, data) {
|
|
subject = typeof subject == "undefined" ? null : new Subject(subject);
|
|
data = typeof data == "undefined" ? null : data;
|
|
Services.obs.notifyObservers(subject, topic, data);
|
|
},
|
|
|
|
/**
|
|
* A cache of observers that have been added.
|
|
*
|
|
* We use this to remove observers when a caller calls |remove|.
|
|
*
|
|
* XXX This might result in reference cycles, causing memory leaks,
|
|
* if we hold a reference to an observer that holds a reference to us.
|
|
* Could we fix that by making this an independent top-level object
|
|
* rather than a property of this object?
|
|
*/
|
|
_cache: [],
|
|
};
|
|
|
|
function Observer(topic, callback, thisObject) {
|
|
this.topic = topic;
|
|
this.callback = callback;
|
|
this.thisObject = thisObject;
|
|
}
|
|
|
|
Observer.prototype = {
|
|
QueryInterface: ChromeUtils.generateQI([
|
|
"nsIObserver",
|
|
"nsISupportsWeakReference",
|
|
]),
|
|
observe(subject, topic, data) {
|
|
// Extract the wrapped object for subjects that are one of our wrappers
|
|
// around a JS object. This way we support both wrapped subjects created
|
|
// using this module and those that are real XPCOM components.
|
|
if (
|
|
subject &&
|
|
typeof subject == "object" &&
|
|
"wrappedJSObject" in subject &&
|
|
"observersModuleSubjectWrapper" in subject.wrappedJSObject
|
|
) {
|
|
subject = subject.wrappedJSObject.object;
|
|
}
|
|
|
|
if (typeof this.callback == "function") {
|
|
if (this.thisObject) {
|
|
this.callback.call(this.thisObject, subject, data);
|
|
} else {
|
|
this.callback(subject, data);
|
|
}
|
|
} else {
|
|
// typeof this.callback == "object" (nsIObserver)
|
|
this.callback.observe(subject, topic, data);
|
|
}
|
|
},
|
|
};
|
|
|
|
function Subject(object) {
|
|
// Double-wrap the object and set a property identifying the wrappedJSObject
|
|
// as one of our wrappers to distinguish between subjects that are one of our
|
|
// wrappers (which we should unwrap when notifying our observers) and those
|
|
// that are real JS XPCOM components (which we should pass through unaltered).
|
|
this.wrappedJSObject = { observersModuleSubjectWrapper: true, object };
|
|
}
|
|
|
|
Subject.prototype = {
|
|
QueryInterface: ChromeUtils.generateQI([]),
|
|
getScriptableHelper() {},
|
|
getInterfaces() {},
|
|
};
|