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: michael@0: 'use strict'; michael@0: michael@0: module.metadata = { michael@0: 'stability': 'unstable' michael@0: }; michael@0: michael@0: const { Cc, Ci, Cu } = require('chrome'); michael@0: const { Unknown } = require('../platform/xpcom'); michael@0: const { Class } = require('../core/heritage'); michael@0: const { ns } = require('../core/namespace'); michael@0: const { addObserver, removeObserver, notifyObservers } = michael@0: Cc['@mozilla.org/observer-service;1'].getService(Ci.nsIObserverService); michael@0: const unloadSubject = require('@loader/unload'); michael@0: michael@0: const Subject = Class({ michael@0: extends: Unknown, michael@0: initialize: function initialize(object) { michael@0: // Double-wrap the object and set a property identifying the michael@0: // wrappedJSObject as one of our wrappers to distinguish between michael@0: // subjects that are one of our wrappers (which we should unwrap michael@0: // when notifying our observers) and those that are real JS XPCOM michael@0: // components (which we should pass through unaltered). michael@0: this.wrappedJSObject = { michael@0: observersModuleSubjectWrapper: true, michael@0: object: object michael@0: }; michael@0: }, michael@0: getHelperForLanguage: function() {}, michael@0: getInterfaces: function() {} michael@0: }); michael@0: michael@0: function emit(type, event) { michael@0: // From bug 910599 michael@0: // We must test to see if 'subject' or 'data' is a defined property michael@0: // of the event object, but also allow primitives to be passed in, michael@0: // which the `in` operator breaks, yet `null` is an object, hence michael@0: // the long conditional michael@0: let subject = event && typeof event === 'object' && 'subject' in event ? michael@0: Subject(event.subject) : michael@0: null; michael@0: let data = event && typeof event === 'object' ? michael@0: // An object either returns its `data` property or null michael@0: ('data' in event ? event.data : null) : michael@0: // All other types return themselves (and cast to strings/null michael@0: // via observer service) michael@0: event; michael@0: notifyObservers(subject, type, data); michael@0: } michael@0: exports.emit = emit; michael@0: michael@0: const Observer = Class({ michael@0: extends: Unknown, michael@0: initialize: function initialize(listener) { michael@0: this.listener = listener; michael@0: }, michael@0: interfaces: [ 'nsIObserver', 'nsISupportsWeakReference' ], michael@0: observe: function(subject, topic, data) { michael@0: // Extract the wrapped object for subjects that are one of our michael@0: // wrappers around a JS object. This way we support both wrapped michael@0: // subjects created using this module and those that are real michael@0: // XPCOM components. michael@0: if (subject && typeof(subject) == 'object' && michael@0: ('wrappedJSObject' in subject) && michael@0: ('observersModuleSubjectWrapper' in subject.wrappedJSObject)) michael@0: subject = subject.wrappedJSObject.object; michael@0: michael@0: try { michael@0: this.listener({ michael@0: type: topic, michael@0: subject: subject, michael@0: data: data michael@0: }); michael@0: } michael@0: catch (error) { michael@0: console.exception(error); michael@0: } michael@0: } michael@0: }); michael@0: michael@0: const subscribers = ns(); michael@0: michael@0: function on(type, listener, strong) { michael@0: // Unless last optional argument is `true` we use a weak reference to a michael@0: // listener. michael@0: let weak = !strong; michael@0: // Take list of observers associated with given `listener` function. michael@0: let observers = subscribers(listener); michael@0: // If `observer` for the given `type` is not registered yet, then michael@0: // associate an `observer` and register it. michael@0: if (!(type in observers)) { michael@0: let observer = Observer(listener); michael@0: observers[type] = observer; michael@0: addObserver(observer, type, weak); michael@0: // WeakRef gymnastics to remove all alive observers on unload michael@0: let ref = Cu.getWeakReference(observer); michael@0: weakRefs.set(observer, ref); michael@0: stillAlive.set(ref, type); michael@0: } michael@0: } michael@0: exports.on = on; michael@0: michael@0: function once(type, listener) { michael@0: // Note: this code assumes order in which listeners are called, which is fine michael@0: // as long as dispatch happens in same order as listener registration which michael@0: // is the case now. That being said we should be aware that this may break michael@0: // in a future if order will change. michael@0: on(type, listener); michael@0: on(type, function cleanup() { michael@0: off(type, listener); michael@0: off(type, cleanup); michael@0: }, true); michael@0: } michael@0: exports.once = once; michael@0: michael@0: function off(type, listener) { michael@0: // Take list of observers as with the given `listener`. michael@0: let observers = subscribers(listener); michael@0: // If `observer` for the given `type` is registered, then michael@0: // remove it & unregister. michael@0: if (type in observers) { michael@0: let observer = observers[type]; michael@0: delete observers[type]; michael@0: removeObserver(observer, type); michael@0: stillAlive.delete(weakRefs.get(observer)); michael@0: } michael@0: } michael@0: exports.off = off; michael@0: michael@0: // must use WeakMap to keep reference to all the WeakRefs (!), see bug 986115 michael@0: let weakRefs = new WeakMap(); michael@0: michael@0: // and we're out of beta, we're releasing on time! michael@0: let stillAlive = new Map(); michael@0: michael@0: on('sdk:loader:destroy', function onunload({ subject, data: reason }) { michael@0: // using logic from ./unload, to avoid a circular module reference michael@0: if (subject.wrappedJSObject === unloadSubject) { michael@0: off('sdk:loader:destroy', onunload); michael@0: michael@0: // don't bother michael@0: if (reason === 'shutdown') michael@0: return; michael@0: michael@0: stillAlive.forEach( (type, ref) => { michael@0: let observer = ref.get(); michael@0: if (observer) michael@0: removeObserver(observer, type); michael@0: }) michael@0: } michael@0: // a strong reference michael@0: }, true);