Files
tubestation/addon-sdk/source/lib/sdk/system/events.js
2014-04-14 22:56:11 -07:00

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