observers.sys.mjs (5042B)
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/. */ 4 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 {string} topic 16 * the topic to observe 17 * 18 * @param {object} callback 19 * the callback; an Object that implements nsIObserver or a Function 20 * that gets called when the notification occurs 21 * 22 * @param {object} [thisObject] 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); 31 32 return observer; 33 }, 34 35 /** 36 * Unregister the given callback as an observer of the given topic. 37 * 38 * @param {string} topic 39 * the topic being observed 40 * 41 * @param {object} callback 42 * the callback doing the observing 43 * 44 * @param {object} [thisObject] 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 }, 63 64 /** 65 * Notify observers about something. 66 * 67 * @param {string} topic 68 * the topic to notify observers about 69 * 70 * @param {object} [subject] 71 * some information about the topic; can be any JS object or primitive 72 * 73 * @param {string} [data] [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 }, 85 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: [], 97 }; 98 99 function Observer(topic, callback, thisObject) { 100 this.topic = topic; 101 this.callback = callback; 102 this.thisObject = thisObject; 103 } 104 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 } 122 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 }, 134 }; 135 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 }; 142 } 143 144 Subject.prototype = { 145 QueryInterface: ChromeUtils.generateQI([]), 146 getScriptableHelper() {}, 147 getInterfaces() {}, 148 };