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