1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/addon-sdk/source/lib/sdk/system/events.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,155 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +'use strict'; 1.9 + 1.10 +module.metadata = { 1.11 + 'stability': 'unstable' 1.12 +}; 1.13 + 1.14 +const { Cc, Ci, Cu } = require('chrome'); 1.15 +const { Unknown } = require('../platform/xpcom'); 1.16 +const { Class } = require('../core/heritage'); 1.17 +const { ns } = require('../core/namespace'); 1.18 +const { addObserver, removeObserver, notifyObservers } = 1.19 + Cc['@mozilla.org/observer-service;1'].getService(Ci.nsIObserverService); 1.20 +const unloadSubject = require('@loader/unload'); 1.21 + 1.22 +const Subject = Class({ 1.23 + extends: Unknown, 1.24 + initialize: function initialize(object) { 1.25 + // Double-wrap the object and set a property identifying the 1.26 + // wrappedJSObject as one of our wrappers to distinguish between 1.27 + // subjects that are one of our wrappers (which we should unwrap 1.28 + // when notifying our observers) and those that are real JS XPCOM 1.29 + // components (which we should pass through unaltered). 1.30 + this.wrappedJSObject = { 1.31 + observersModuleSubjectWrapper: true, 1.32 + object: object 1.33 + }; 1.34 + }, 1.35 + getHelperForLanguage: function() {}, 1.36 + getInterfaces: function() {} 1.37 +}); 1.38 + 1.39 +function emit(type, event) { 1.40 + // From bug 910599 1.41 + // We must test to see if 'subject' or 'data' is a defined property 1.42 + // of the event object, but also allow primitives to be passed in, 1.43 + // which the `in` operator breaks, yet `null` is an object, hence 1.44 + // the long conditional 1.45 + let subject = event && typeof event === 'object' && 'subject' in event ? 1.46 + Subject(event.subject) : 1.47 + null; 1.48 + let data = event && typeof event === 'object' ? 1.49 + // An object either returns its `data` property or null 1.50 + ('data' in event ? event.data : null) : 1.51 + // All other types return themselves (and cast to strings/null 1.52 + // via observer service) 1.53 + event; 1.54 + notifyObservers(subject, type, data); 1.55 +} 1.56 +exports.emit = emit; 1.57 + 1.58 +const Observer = Class({ 1.59 + extends: Unknown, 1.60 + initialize: function initialize(listener) { 1.61 + this.listener = listener; 1.62 + }, 1.63 + interfaces: [ 'nsIObserver', 'nsISupportsWeakReference' ], 1.64 + observe: function(subject, topic, data) { 1.65 + // Extract the wrapped object for subjects that are one of our 1.66 + // wrappers around a JS object. This way we support both wrapped 1.67 + // subjects created using this module and those that are real 1.68 + // XPCOM components. 1.69 + if (subject && typeof(subject) == 'object' && 1.70 + ('wrappedJSObject' in subject) && 1.71 + ('observersModuleSubjectWrapper' in subject.wrappedJSObject)) 1.72 + subject = subject.wrappedJSObject.object; 1.73 + 1.74 + try { 1.75 + this.listener({ 1.76 + type: topic, 1.77 + subject: subject, 1.78 + data: data 1.79 + }); 1.80 + } 1.81 + catch (error) { 1.82 + console.exception(error); 1.83 + } 1.84 + } 1.85 +}); 1.86 + 1.87 +const subscribers = ns(); 1.88 + 1.89 +function on(type, listener, strong) { 1.90 + // Unless last optional argument is `true` we use a weak reference to a 1.91 + // listener. 1.92 + let weak = !strong; 1.93 + // Take list of observers associated with given `listener` function. 1.94 + let observers = subscribers(listener); 1.95 + // If `observer` for the given `type` is not registered yet, then 1.96 + // associate an `observer` and register it. 1.97 + if (!(type in observers)) { 1.98 + let observer = Observer(listener); 1.99 + observers[type] = observer; 1.100 + addObserver(observer, type, weak); 1.101 + // WeakRef gymnastics to remove all alive observers on unload 1.102 + let ref = Cu.getWeakReference(observer); 1.103 + weakRefs.set(observer, ref); 1.104 + stillAlive.set(ref, type); 1.105 + } 1.106 +} 1.107 +exports.on = on; 1.108 + 1.109 +function once(type, listener) { 1.110 + // Note: this code assumes order in which listeners are called, which is fine 1.111 + // as long as dispatch happens in same order as listener registration which 1.112 + // is the case now. That being said we should be aware that this may break 1.113 + // in a future if order will change. 1.114 + on(type, listener); 1.115 + on(type, function cleanup() { 1.116 + off(type, listener); 1.117 + off(type, cleanup); 1.118 + }, true); 1.119 +} 1.120 +exports.once = once; 1.121 + 1.122 +function off(type, listener) { 1.123 + // Take list of observers as with the given `listener`. 1.124 + let observers = subscribers(listener); 1.125 + // If `observer` for the given `type` is registered, then 1.126 + // remove it & unregister. 1.127 + if (type in observers) { 1.128 + let observer = observers[type]; 1.129 + delete observers[type]; 1.130 + removeObserver(observer, type); 1.131 + stillAlive.delete(weakRefs.get(observer)); 1.132 + } 1.133 +} 1.134 +exports.off = off; 1.135 + 1.136 +// must use WeakMap to keep reference to all the WeakRefs (!), see bug 986115 1.137 +let weakRefs = new WeakMap(); 1.138 + 1.139 +// and we're out of beta, we're releasing on time! 1.140 +let stillAlive = new Map(); 1.141 + 1.142 +on('sdk:loader:destroy', function onunload({ subject, data: reason }) { 1.143 + // using logic from ./unload, to avoid a circular module reference 1.144 + if (subject.wrappedJSObject === unloadSubject) { 1.145 + off('sdk:loader:destroy', onunload); 1.146 + 1.147 + // don't bother 1.148 + if (reason === 'shutdown') 1.149 + return; 1.150 + 1.151 + stillAlive.forEach( (type, ref) => { 1.152 + let observer = ref.get(); 1.153 + if (observer) 1.154 + removeObserver(observer, type); 1.155 + }) 1.156 + } 1.157 + // a strong reference 1.158 +}, true);