|
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 "use strict"; |
|
5 |
|
6 const { Cc, Ci, Cr, Cu } = require("chrome"); |
|
7 const { Input, start, stop, end, receive, outputs } = require("../event/utils"); |
|
8 const { once, off } = require("../event/core"); |
|
9 const { id: addonID } = require("../self"); |
|
10 |
|
11 const unloadMessage = require("@loader/unload"); |
|
12 const { addObserver, removeObserver } = Cc['@mozilla.org/observer-service;1']. |
|
13 getService(Ci.nsIObserverService); |
|
14 |
|
15 const addonUnloadTopic = "sdk:loader:destroy"; |
|
16 |
|
17 const isXrayWrapper = Cu.isXrayWrapper; |
|
18 // In the past SDK used to double-wrap notifications dispatched, which |
|
19 // made them awkward to use outside of SDK. At present they no longer |
|
20 // do that, although we still supported for legacy reasons. |
|
21 const isLegacyWrapper = x => |
|
22 x && x.wrappedJSObject && |
|
23 "observersModuleSubjectWrapper" in x.wrappedJSObject; |
|
24 |
|
25 const unwrapLegacy = x => x.wrappedJSObject.object; |
|
26 |
|
27 // `InputPort` provides a way to create a signal out of the observer |
|
28 // notification subject's for the given `topic`. If `options.initial` |
|
29 // is provided it is used as initial value otherwise `null` is used. |
|
30 // Constructor can be given `options.id` that will be used to create |
|
31 // a `topic` which is namespaced to an add-on (this avoids conflicts |
|
32 // when multiple add-on are used, although in a future host probably |
|
33 // should just be shared across add-ons). It is also possible to |
|
34 // specify a specific `topic` via `options.topic` which is used as |
|
35 // without namespacing. Created signal ends whenever add-on is |
|
36 // unloaded. |
|
37 const InputPort = function InputPort({id, topic, initial}) { |
|
38 this.id = id || topic; |
|
39 this.topic = topic || "sdk:" + addonID + ":" + id; |
|
40 this.value = initial === void(0) ? null : initial; |
|
41 this.observing = false; |
|
42 this[outputs] = []; |
|
43 }; |
|
44 |
|
45 // InputPort type implements `Input` signal interface. |
|
46 InputPort.prototype = new Input(); |
|
47 InputPort.prototype.constructor = InputPort; |
|
48 |
|
49 // When port is started (which is when it's subgraph get's |
|
50 // first subscriber) actual observer is registered. |
|
51 InputPort.start = input => { |
|
52 input.addListener(input); |
|
53 // Also register add-on unload observer to end this signal |
|
54 // when that happens. |
|
55 addObserver(input, addonUnloadTopic, false); |
|
56 }; |
|
57 InputPort.prototype[start] = InputPort.start; |
|
58 |
|
59 InputPort.addListener = input => addObserver(input, input.topic, false); |
|
60 InputPort.prototype.addListener = InputPort.addListener; |
|
61 |
|
62 // When port is stopped (which is when it's subgraph has no |
|
63 // no subcribers left) an actual observer unregistered. |
|
64 // Note that port stopped once it ends as well (which is when |
|
65 // add-on is unloaded). |
|
66 InputPort.stop = input => { |
|
67 input.removeListener(input); |
|
68 removeObserver(input, addonUnloadTopic); |
|
69 }; |
|
70 InputPort.prototype[stop] = InputPort.stop; |
|
71 |
|
72 InputPort.removeListener = input => removeObserver(input, input.topic); |
|
73 InputPort.prototype.removeListener = InputPort.removeListener; |
|
74 |
|
75 // `InputPort` also implements `nsIObserver` interface and |
|
76 // `nsISupportsWeakReference` interfaces as it's going to be used as such. |
|
77 InputPort.prototype.QueryInterface = function(iid) { |
|
78 if (!iid.equals(Ci.nsIObserver) && !iid.equals(Ci.nsISupportsWeakReference)) |
|
79 throw Cr.NS_ERROR_NO_INTERFACE; |
|
80 |
|
81 return this; |
|
82 }; |
|
83 |
|
84 // `InputPort` instances implement `observe` method, which is invoked when |
|
85 // observer notifications are dispatched. The `subject` of that notification |
|
86 // are received on this signal. |
|
87 InputPort.prototype.observe = function(subject, topic, data) { |
|
88 // Unwrap message from the subject. SDK used to have it's own version of |
|
89 // wrappedJSObjects which take precedence, if subject has `wrappedJSObject` |
|
90 // and it's not an XrayWrapper use it as message. Otherwise use subject as |
|
91 // is. |
|
92 const message = subject === null ? null : |
|
93 isLegacyWrapper(subject) ? unwrapLegacy(subject) : |
|
94 isXrayWrapper(subject) ? subject : |
|
95 subject.wrappedJSObject ? subject.wrappedJSObject : |
|
96 subject; |
|
97 |
|
98 // If observer topic matches topic of the input port receive a message. |
|
99 if (topic === this.topic) { |
|
100 receive(this, message); |
|
101 } |
|
102 |
|
103 // If observe topic is add-on unload topic we create an end message. |
|
104 if (topic === addonUnloadTopic && message === unloadMessage) { |
|
105 end(this); |
|
106 } |
|
107 }; |
|
108 |
|
109 exports.InputPort = InputPort; |