110 lines
4.3 KiB
JavaScript
110 lines
4.3 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/. */
|
|
"use strict";
|
|
|
|
const { Cc, Ci, Cr, Cu } = require("chrome");
|
|
const { Input, start, stop, end, receive, outputs } = require("../event/utils");
|
|
const { once, off } = require("../event/core");
|
|
const { id: addonID } = require("../self");
|
|
|
|
const unloadMessage = require("@loader/unload");
|
|
const { addObserver, removeObserver } = Cc['@mozilla.org/observer-service;1'].
|
|
getService(Ci.nsIObserverService);
|
|
|
|
const addonUnloadTopic = "sdk:loader:destroy";
|
|
|
|
const isXrayWrapper = Cu.isXrayWrapper;
|
|
// In the past SDK used to double-wrap notifications dispatched, which
|
|
// made them awkward to use outside of SDK. At present they no longer
|
|
// do that, although we still supported for legacy reasons.
|
|
const isLegacyWrapper = x =>
|
|
x && x.wrappedJSObject &&
|
|
"observersModuleSubjectWrapper" in x.wrappedJSObject;
|
|
|
|
const unwrapLegacy = x => x.wrappedJSObject.object;
|
|
|
|
// `InputPort` provides a way to create a signal out of the observer
|
|
// notification subject's for the given `topic`. If `options.initial`
|
|
// is provided it is used as initial value otherwise `null` is used.
|
|
// Constructor can be given `options.id` that will be used to create
|
|
// a `topic` which is namespaced to an add-on (this avoids conflicts
|
|
// when multiple add-on are used, although in a future host probably
|
|
// should just be shared across add-ons). It is also possible to
|
|
// specify a specific `topic` via `options.topic` which is used as
|
|
// without namespacing. Created signal ends whenever add-on is
|
|
// unloaded.
|
|
const InputPort = function InputPort({id, topic, initial}) {
|
|
this.id = id || topic;
|
|
this.topic = topic || "sdk:" + addonID + ":" + id;
|
|
this.value = initial === void(0) ? null : initial;
|
|
this.observing = false;
|
|
this[outputs] = [];
|
|
};
|
|
|
|
// InputPort type implements `Input` signal interface.
|
|
InputPort.prototype = new Input();
|
|
InputPort.prototype.constructor = InputPort;
|
|
|
|
// When port is started (which is when it's subgraph get's
|
|
// first subscriber) actual observer is registered.
|
|
InputPort.start = input => {
|
|
input.addListener(input);
|
|
// Also register add-on unload observer to end this signal
|
|
// when that happens.
|
|
addObserver(input, addonUnloadTopic, false);
|
|
};
|
|
InputPort.prototype[start] = InputPort.start;
|
|
|
|
InputPort.addListener = input => addObserver(input, input.topic, false);
|
|
InputPort.prototype.addListener = InputPort.addListener;
|
|
|
|
// When port is stopped (which is when it's subgraph has no
|
|
// no subcribers left) an actual observer unregistered.
|
|
// Note that port stopped once it ends as well (which is when
|
|
// add-on is unloaded).
|
|
InputPort.stop = input => {
|
|
input.removeListener(input);
|
|
removeObserver(input, addonUnloadTopic);
|
|
};
|
|
InputPort.prototype[stop] = InputPort.stop;
|
|
|
|
InputPort.removeListener = input => removeObserver(input, input.topic);
|
|
InputPort.prototype.removeListener = InputPort.removeListener;
|
|
|
|
// `InputPort` also implements `nsIObserver` interface and
|
|
// `nsISupportsWeakReference` interfaces as it's going to be used as such.
|
|
InputPort.prototype.QueryInterface = function(iid) {
|
|
if (!iid.equals(Ci.nsIObserver) && !iid.equals(Ci.nsISupportsWeakReference))
|
|
throw Cr.NS_ERROR_NO_INTERFACE;
|
|
|
|
return this;
|
|
};
|
|
|
|
// `InputPort` instances implement `observe` method, which is invoked when
|
|
// observer notifications are dispatched. The `subject` of that notification
|
|
// are received on this signal.
|
|
InputPort.prototype.observe = function(subject, topic, data) {
|
|
// Unwrap message from the subject. SDK used to have it's own version of
|
|
// wrappedJSObjects which take precedence, if subject has `wrappedJSObject`
|
|
// and it's not an XrayWrapper use it as message. Otherwise use subject as
|
|
// is.
|
|
const message = subject === null ? null :
|
|
isLegacyWrapper(subject) ? unwrapLegacy(subject) :
|
|
isXrayWrapper(subject) ? subject :
|
|
subject.wrappedJSObject ? subject.wrappedJSObject :
|
|
subject;
|
|
|
|
// If observer topic matches topic of the input port receive a message.
|
|
if (topic === this.topic) {
|
|
receive(this, message);
|
|
}
|
|
|
|
// If observe topic is add-on unload topic we create an end message.
|
|
if (topic === addonUnloadTopic && message === unloadMessage) {
|
|
end(this);
|
|
}
|
|
};
|
|
|
|
exports.InputPort = InputPort;
|