Observers.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. /* ***** BEGIN LICENSE BLOCK *****
  2. * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3. *
  4. * The contents of this file are subject to the Mozilla Public License Version
  5. * 1.1 (the "License"); you may not use this file except in compliance with
  6. * the License. You may obtain a copy of the License at
  7. * http://www.mozilla.org/MPL/
  8. *
  9. * Software distributed under the License is distributed on an "AS IS" basis,
  10. * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11. * for the specific language governing rights and limitations under the
  12. * License.
  13. *
  14. * The Original Code is Observers.
  15. *
  16. * The Initial Developer of the Original Code is Daniel Aquino.
  17. * Portions created by the Initial Developer are Copyright (C) 2008
  18. * the Initial Developer. All Rights Reserved.
  19. *
  20. * Contributor(s):
  21. * Daniel Aquino <mr.danielaquino@gmail.com>
  22. * Myk Melez <myk@mozilla.org>
  23. *
  24. * Alternatively, the contents of this file may be used under the terms of
  25. * either the GNU General Public License Version 2 or later (the "GPL"), or
  26. * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27. * in which case the provisions of the GPL or the LGPL are applicable instead
  28. * of those above. If you wish to allow use of your version of this file only
  29. * under the terms of either the GPL or the LGPL, and not to allow others to
  30. * use your version of this file under the terms of the MPL, indicate your
  31. * decision by deleting the provisions above and replace them with the notice
  32. * and other provisions required by the GPL or the LGPL. If you do not delete
  33. * the provisions above, a recipient may use your version of this file under
  34. * the terms of any one of the MPL, the GPL or the LGPL.
  35. *
  36. * ***** END LICENSE BLOCK ***** */
  37. let EXPORTED_SYMBOLS = ["Observers"];
  38. const Cc = Components.classes;
  39. const Ci = Components.interfaces;
  40. const Cr = Components.results;
  41. const Cu = Components.utils;
  42. Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  43. /**
  44. * A service for adding, removing and notifying observers of notifications.
  45. * Wraps the nsIObserverService interface.
  46. *
  47. * @version 0.2
  48. */
  49. let Observers = {
  50. /**
  51. * Register the given callback as an observer of the given topic.
  52. *
  53. * @param topic {String}
  54. * the topic to observe
  55. *
  56. * @param callback {Object}
  57. * the callback; an Object that implements nsIObserver or a Function
  58. * that gets called when the notification occurs
  59. *
  60. * @param thisObject {Object} [optional]
  61. * the object to use as |this| when calling a Function callback
  62. *
  63. * @returns the observer
  64. */
  65. add: function(topic, callback, thisObject) {
  66. let observer = new Observer(topic, callback, thisObject);
  67. this._service.addObserver(observer, topic, true);
  68. cache.push(observer);
  69. return observer;
  70. },
  71. /**
  72. * Unregister the given callback as an observer of the given topic.
  73. *
  74. * @param topic {String}
  75. * the topic being observed
  76. *
  77. * @param callback {Object}
  78. * the callback doing the observing
  79. *
  80. * @param thisObject {Object} [optional]
  81. * the object being used as |this| when calling a Function callback
  82. */
  83. remove: function(topic, callback, thisObject) {
  84. // This seems fairly inefficient, but I'm not sure how much better
  85. // we can make it. We could index by topic, but we can't index by callback
  86. // or thisObject, as far as I know, since the keys to JavaScript hashes
  87. // (a.k.a. objects) can apparently only be primitive values.
  88. let [observer] = cache.filter(function(v) v.topic == topic &&
  89. v.callback == callback &&
  90. v.thisObject == thisObject);
  91. if (observer) {
  92. this._service.removeObserver(observer, topic);
  93. cache.splice(cache.indexOf(observer), 1);
  94. }
  95. },
  96. /**
  97. * Notify observers about something.
  98. *
  99. * @param topic {String}
  100. * the topic to notify observers about
  101. *
  102. * @param subject {Object} [optional]
  103. * some information about the topic; can be any JS object or primitive
  104. *
  105. * @param data {String} [optional] [deprecated]
  106. * some more information about the topic; deprecated as the subject
  107. * is sufficient to pass all needed information to the JS observers
  108. * that this module targets; if you have multiple values to pass to
  109. * the observer, wrap them in an object and pass them via the subject
  110. * parameter (i.e.: { foo: 1, bar: "some string", baz: myObject })
  111. */
  112. notify: function(topic, subject, data) {
  113. subject = (typeof subject == "undefined") ? null : new Subject(subject);
  114. data = (typeof data == "undefined") ? null : data;
  115. this._service.notifyObservers(subject, topic, data);
  116. },
  117. _service: Cc["@mozilla.org/observer-service;1"].
  118. getService(Ci.nsIObserverService)
  119. };
  120. /**
  121. * A cache of observers that have been added.
  122. *
  123. * We use this to remove observers when a caller calls |Observers.remove|.
  124. */
  125. let cache = [];
  126. function Observer(topic, callback, thisObject) {
  127. this.topic = topic;
  128. this.callback = callback;
  129. this.thisObject = thisObject;
  130. }
  131. Observer.prototype = {
  132. QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
  133. observe: function(subject, topic, data) {
  134. // Extract the wrapped object for subjects that are one of our wrappers
  135. // around a JS object. This way we support both wrapped subjects created
  136. // using this module and those that are real XPCOM components.
  137. if (subject && typeof subject == "object" &&
  138. ("wrappedJSObject" in subject) &&
  139. ("observersModuleSubjectWrapper" in subject.wrappedJSObject))
  140. subject = subject.wrappedJSObject.object;
  141. if (typeof this.callback == "function") {
  142. if (this.thisObject)
  143. this.callback.call(this.thisObject, subject, data);
  144. else
  145. this.callback(subject, data);
  146. }
  147. else // typeof this.callback == "object" (nsIObserver)
  148. this.callback.observe(subject, topic, data);
  149. }
  150. }
  151. function Subject(object) {
  152. // Double-wrap the object and set a property identifying the wrappedJSObject
  153. // as one of our wrappers to distinguish between subjects that are one of our
  154. // wrappers (which we should unwrap when notifying our observers) and those
  155. // that are real JS XPCOM components (which we should pass through unaltered).
  156. this.wrappedJSObject = { observersModuleSubjectWrapper: true, object: object };
  157. }
  158. Subject.prototype = {
  159. QueryInterface: XPCOMUtils.generateQI([]),
  160. getHelperForLanguage: function() {},
  161. getInterfaces: function() {}
  162. };