diff options
Diffstat (limited to 'toolkit/jetpack/sdk/system/events.js')
-rw-r--r-- | toolkit/jetpack/sdk/system/events.js | 181 |
1 files changed, 181 insertions, 0 deletions
diff --git a/toolkit/jetpack/sdk/system/events.js b/toolkit/jetpack/sdk/system/events.js new file mode 100644 index 000000000..0cf525aa1 --- /dev/null +++ b/toolkit/jetpack/sdk/system/events.js @@ -0,0 +1,181 @@ +/* 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 observerService = + Cc['@mozilla.org/observer-service;1'].getService(Ci.nsIObserverService); +const { addObserver, removeObserver, notifyObservers } = observerService; +const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm"); +const addObserverNoShim = ShimWaiver.getProperty(observerService, "addObserver"); +const removeObserverNoShim = ShimWaiver.getProperty(observerService, "removeObserver"); +const notifyObserversNoShim = ShimWaiver.getProperty(observerService, "notifyObservers"); +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 + }; + }, + getScriptableHelper: function() {}, + getInterfaces: function() {} +}); + +function emit(type, event, shimmed = false) { + // 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; + if (shimmed) { + notifyObservers(subject, type, data); + } else { + notifyObserversNoShim(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, shimmed = false) { + // 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; + if (shimmed) { + addObserver(observer, type, weak); + } else { + addObserverNoShim(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); + wasShimmed.set(ref, shimmed); + } +} +exports.on = on; + +function once(type, listener, shimmed = false) { + // 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, shimmed); + on(type, function cleanup() { + off(type, listener, shimmed); + off(type, cleanup, shimmed); + }, true, shimmed); +} +exports.once = once; + +function off(type, listener, shimmed = false) { + // 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]; + if (shimmed) { + removeObserver(observer, type); + } else { + removeObserverNoShim(observer, type); + } + stillAlive.delete(weakRefs.get(observer)); + wasShimmed.delete(weakRefs.get(observer)); + } +} +exports.off = off; + +// must use WeakMap to keep reference to all the WeakRefs (!), see bug 986115 +var weakRefs = new WeakMap(); + +// and we're out of beta, we're releasing on time! +var stillAlive = new Map(); + +var wasShimmed = 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, false); + + // don't bother + if (reason === 'shutdown') + return; + + stillAlive.forEach( (type, ref) => { + let observer = ref.get(); + if (observer) { + if (wasShimmed.get(ref)) { + removeObserver(observer, type); + } else { + removeObserverNoShim(observer, type); + } + } + }) + } + // a strong reference +}, true, false); |