|
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 'use strict'; |
|
6 |
|
7 module.metadata = { |
|
8 'stability': 'unstable' |
|
9 }; |
|
10 |
|
11 const { Cc, Ci, Cu } = require('chrome'); |
|
12 const { Unknown } = require('../platform/xpcom'); |
|
13 const { Class } = require('../core/heritage'); |
|
14 const { ns } = require('../core/namespace'); |
|
15 const { addObserver, removeObserver, notifyObservers } = |
|
16 Cc['@mozilla.org/observer-service;1'].getService(Ci.nsIObserverService); |
|
17 const unloadSubject = require('@loader/unload'); |
|
18 |
|
19 const Subject = Class({ |
|
20 extends: Unknown, |
|
21 initialize: function initialize(object) { |
|
22 // Double-wrap the object and set a property identifying the |
|
23 // wrappedJSObject as one of our wrappers to distinguish between |
|
24 // subjects that are one of our wrappers (which we should unwrap |
|
25 // when notifying our observers) and those that are real JS XPCOM |
|
26 // components (which we should pass through unaltered). |
|
27 this.wrappedJSObject = { |
|
28 observersModuleSubjectWrapper: true, |
|
29 object: object |
|
30 }; |
|
31 }, |
|
32 getHelperForLanguage: function() {}, |
|
33 getInterfaces: function() {} |
|
34 }); |
|
35 |
|
36 function emit(type, event) { |
|
37 // From bug 910599 |
|
38 // We must test to see if 'subject' or 'data' is a defined property |
|
39 // of the event object, but also allow primitives to be passed in, |
|
40 // which the `in` operator breaks, yet `null` is an object, hence |
|
41 // the long conditional |
|
42 let subject = event && typeof event === 'object' && 'subject' in event ? |
|
43 Subject(event.subject) : |
|
44 null; |
|
45 let data = event && typeof event === 'object' ? |
|
46 // An object either returns its `data` property or null |
|
47 ('data' in event ? event.data : null) : |
|
48 // All other types return themselves (and cast to strings/null |
|
49 // via observer service) |
|
50 event; |
|
51 notifyObservers(subject, type, data); |
|
52 } |
|
53 exports.emit = emit; |
|
54 |
|
55 const Observer = Class({ |
|
56 extends: Unknown, |
|
57 initialize: function initialize(listener) { |
|
58 this.listener = listener; |
|
59 }, |
|
60 interfaces: [ 'nsIObserver', 'nsISupportsWeakReference' ], |
|
61 observe: function(subject, topic, data) { |
|
62 // Extract the wrapped object for subjects that are one of our |
|
63 // wrappers around a JS object. This way we support both wrapped |
|
64 // subjects created using this module and those that are real |
|
65 // XPCOM components. |
|
66 if (subject && typeof(subject) == 'object' && |
|
67 ('wrappedJSObject' in subject) && |
|
68 ('observersModuleSubjectWrapper' in subject.wrappedJSObject)) |
|
69 subject = subject.wrappedJSObject.object; |
|
70 |
|
71 try { |
|
72 this.listener({ |
|
73 type: topic, |
|
74 subject: subject, |
|
75 data: data |
|
76 }); |
|
77 } |
|
78 catch (error) { |
|
79 console.exception(error); |
|
80 } |
|
81 } |
|
82 }); |
|
83 |
|
84 const subscribers = ns(); |
|
85 |
|
86 function on(type, listener, strong) { |
|
87 // Unless last optional argument is `true` we use a weak reference to a |
|
88 // listener. |
|
89 let weak = !strong; |
|
90 // Take list of observers associated with given `listener` function. |
|
91 let observers = subscribers(listener); |
|
92 // If `observer` for the given `type` is not registered yet, then |
|
93 // associate an `observer` and register it. |
|
94 if (!(type in observers)) { |
|
95 let observer = Observer(listener); |
|
96 observers[type] = observer; |
|
97 addObserver(observer, type, weak); |
|
98 // WeakRef gymnastics to remove all alive observers on unload |
|
99 let ref = Cu.getWeakReference(observer); |
|
100 weakRefs.set(observer, ref); |
|
101 stillAlive.set(ref, type); |
|
102 } |
|
103 } |
|
104 exports.on = on; |
|
105 |
|
106 function once(type, listener) { |
|
107 // Note: this code assumes order in which listeners are called, which is fine |
|
108 // as long as dispatch happens in same order as listener registration which |
|
109 // is the case now. That being said we should be aware that this may break |
|
110 // in a future if order will change. |
|
111 on(type, listener); |
|
112 on(type, function cleanup() { |
|
113 off(type, listener); |
|
114 off(type, cleanup); |
|
115 }, true); |
|
116 } |
|
117 exports.once = once; |
|
118 |
|
119 function off(type, listener) { |
|
120 // Take list of observers as with the given `listener`. |
|
121 let observers = subscribers(listener); |
|
122 // If `observer` for the given `type` is registered, then |
|
123 // remove it & unregister. |
|
124 if (type in observers) { |
|
125 let observer = observers[type]; |
|
126 delete observers[type]; |
|
127 removeObserver(observer, type); |
|
128 stillAlive.delete(weakRefs.get(observer)); |
|
129 } |
|
130 } |
|
131 exports.off = off; |
|
132 |
|
133 // must use WeakMap to keep reference to all the WeakRefs (!), see bug 986115 |
|
134 let weakRefs = new WeakMap(); |
|
135 |
|
136 // and we're out of beta, we're releasing on time! |
|
137 let stillAlive = new Map(); |
|
138 |
|
139 on('sdk:loader:destroy', function onunload({ subject, data: reason }) { |
|
140 // using logic from ./unload, to avoid a circular module reference |
|
141 if (subject.wrappedJSObject === unloadSubject) { |
|
142 off('sdk:loader:destroy', onunload); |
|
143 |
|
144 // don't bother |
|
145 if (reason === 'shutdown') |
|
146 return; |
|
147 |
|
148 stillAlive.forEach( (type, ref) => { |
|
149 let observer = ref.get(); |
|
150 if (observer) |
|
151 removeObserver(observer, type); |
|
152 }) |
|
153 } |
|
154 // a strong reference |
|
155 }, true); |