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