observers.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. this.EXPORTED_SYMBOLS = ["Observers"];
  5. var Cc = Components.classes;
  6. var Ci = Components.interfaces;
  7. var Cr = Components.results;
  8. var Cu = Components.utils;
  9. Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  10. /**
  11. * A service for adding, removing and notifying observers of notifications.
  12. * Wraps the nsIObserverService interface.
  13. *
  14. * @version 0.2
  15. */
  16. this.Observers = {
  17. /**
  18. * Register the given callback as an observer of the given topic.
  19. *
  20. * @param topic {String}
  21. * the topic to observe
  22. *
  23. * @param callback {Object}
  24. * the callback; an Object that implements nsIObserver or a Function
  25. * that gets called when the notification occurs
  26. *
  27. * @param thisObject {Object} [optional]
  28. * the object to use as |this| when calling a Function callback
  29. *
  30. * @returns the observer
  31. */
  32. add: function(topic, callback, thisObject) {
  33. let observer = new Observer(topic, callback, thisObject);
  34. this._cache.push(observer);
  35. this._service.addObserver(observer, topic, true);
  36. return observer;
  37. },
  38. /**
  39. * Unregister the given callback as an observer of the given topic.
  40. *
  41. * @param topic {String}
  42. * the topic being observed
  43. *
  44. * @param callback {Object}
  45. * the callback doing the observing
  46. *
  47. * @param thisObject {Object} [optional]
  48. * the object being used as |this| when calling a Function callback
  49. */
  50. remove: function(topic, callback, thisObject) {
  51. // This seems fairly inefficient, but I'm not sure how much better
  52. // we can make it. We could index by topic, but we can't index by callback
  53. // or thisObject, as far as I know, since the keys to JavaScript hashes
  54. // (a.k.a. objects) can apparently only be primitive values.
  55. let [observer] = this._cache.filter(v => v.topic == topic &&
  56. v.callback == callback &&
  57. v.thisObject == thisObject);
  58. if (observer) {
  59. this._service.removeObserver(observer, topic);
  60. this._cache.splice(this._cache.indexOf(observer), 1);
  61. }
  62. },
  63. /**
  64. * Notify observers about something.
  65. *
  66. * @param topic {String}
  67. * the topic to notify observers about
  68. *
  69. * @param subject {Object} [optional]
  70. * some information about the topic; can be any JS object or primitive
  71. *
  72. * @param data {String} [optional] [deprecated]
  73. * some more information about the topic; deprecated as the subject
  74. * is sufficient to pass all needed information to the JS observers
  75. * that this module targets; if you have multiple values to pass to
  76. * the observer, wrap them in an object and pass them via the subject
  77. * parameter (i.e.: { foo: 1, bar: "some string", baz: myObject })
  78. */
  79. notify: function(topic, subject, data) {
  80. subject = (typeof subject == "undefined") ? null : new Subject(subject);
  81. data = (typeof data == "undefined") ? null : data;
  82. this._service.notifyObservers(subject, topic, data);
  83. },
  84. _service: Cc["@mozilla.org/observer-service;1"].
  85. getService(Ci.nsIObserverService),
  86. /**
  87. * A cache of observers that have been added.
  88. *
  89. * We use this to remove observers when a caller calls |remove|.
  90. *
  91. * XXX This might result in reference cycles, causing memory leaks,
  92. * if we hold a reference to an observer that holds a reference to us.
  93. * Could we fix that by making this an independent top-level object
  94. * rather than a property of this object?
  95. */
  96. _cache: []
  97. };
  98. function Observer(topic, callback, thisObject) {
  99. this.topic = topic;
  100. this.callback = callback;
  101. this.thisObject = thisObject;
  102. }
  103. Observer.prototype = {
  104. QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
  105. observe: function(subject, topic, data) {
  106. // Extract the wrapped object for subjects that are one of our wrappers
  107. // around a JS object. This way we support both wrapped subjects created
  108. // using this module and those that are real XPCOM components.
  109. if (subject && typeof subject == "object" &&
  110. ("wrappedJSObject" in subject) &&
  111. ("observersModuleSubjectWrapper" in subject.wrappedJSObject))
  112. subject = subject.wrappedJSObject.object;
  113. if (typeof this.callback == "function") {
  114. if (this.thisObject)
  115. this.callback.call(this.thisObject, subject, data);
  116. else
  117. this.callback(subject, data);
  118. }
  119. else // typeof this.callback == "object" (nsIObserver)
  120. this.callback.observe(subject, topic, data);
  121. }
  122. }
  123. function Subject(object) {
  124. // Double-wrap the object and set a property identifying the wrappedJSObject
  125. // as one of our wrappers to distinguish between subjects that are one of our
  126. // wrappers (which we should unwrap when notifying our observers) and those
  127. // that are real JS XPCOM components (which we should pass through unaltered).
  128. this.wrappedJSObject = { observersModuleSubjectWrapper: true, object: object };
  129. }
  130. Subject.prototype = {
  131. QueryInterface: XPCOMUtils.generateQI([]),
  132. getScriptableHelper: function() {},
  133. getInterfaces: function() {}
  134. };