events.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  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. 'use strict';
  5. module.metadata = {
  6. 'stability': 'unstable'
  7. };
  8. const { Cc, Ci } = require('chrome');
  9. const { Unknown } = require('../platform/xpcom');
  10. const { Class } = require('../core/heritage');
  11. const { ns } = require('../core/namespace');
  12. const { addObserver, removeObserver, notifyObservers } =
  13. Cc['@mozilla.org/observer-service;1'].getService(Ci.nsIObserverService);
  14. const Subject = Class({
  15. extends: Unknown,
  16. initialize: function initialize(object) {
  17. // Double-wrap the object and set a property identifying the
  18. // wrappedJSObject as one of our wrappers to distinguish between
  19. // subjects that are one of our wrappers (which we should unwrap
  20. // when notifying our observers) and those that are real JS XPCOM
  21. // components (which we should pass through unaltered).
  22. this.wrappedJSObject = {
  23. observersModuleSubjectWrapper: true,
  24. object: object
  25. };
  26. },
  27. getHelperForLanguage: function() {},
  28. getInterfaces: function() {}
  29. });
  30. function emit(type, event) {
  31. // From bug 910599
  32. // We must test to see if 'subject' or 'data' is a defined property
  33. // of the event object, but also allow primitives to be passed in,
  34. // which the `in` operator breaks, yet `null` is an object, hence
  35. // the long conditional
  36. let subject = event && typeof event === 'object' && 'subject' in event ?
  37. Subject(event.subject) :
  38. null;
  39. let data = event && typeof event === 'object' ?
  40. // An object either returns its `data` property or null
  41. ('data' in event ? event.data : null) :
  42. // All other types return themselves (and cast to strings/null
  43. // via observer service)
  44. event;
  45. notifyObservers(subject, type, data);
  46. }
  47. exports.emit = emit;
  48. const Observer = Class({
  49. extends: Unknown,
  50. initialize: function initialize(listener) {
  51. this.listener = listener;
  52. },
  53. interfaces: [ 'nsIObserver', 'nsISupportsWeakReference' ],
  54. observe: function(subject, topic, data) {
  55. // Extract the wrapped object for subjects that are one of our
  56. // wrappers around a JS object. This way we support both wrapped
  57. // subjects created using this module and those that are real
  58. // XPCOM components.
  59. if (subject && typeof(subject) == 'object' &&
  60. ('wrappedJSObject' in subject) &&
  61. ('observersModuleSubjectWrapper' in subject.wrappedJSObject))
  62. subject = subject.wrappedJSObject.object;
  63. try {
  64. this.listener({
  65. type: topic,
  66. subject: subject,
  67. data: data
  68. });
  69. }
  70. catch (error) {
  71. console.exception(error);
  72. }
  73. }
  74. });
  75. const subscribers = ns();
  76. function on(type, listener, strong) {
  77. // Unless last optional argument is `true` we use a weak reference to a
  78. // listener.
  79. let weak = !strong;
  80. // Take list of observers associated with given `listener` function.
  81. let observers = subscribers(listener);
  82. // If `observer` for the given `type` is not registered yet, then
  83. // associate an `observer` and register it.
  84. if (!(type in observers)) {
  85. let observer = Observer(listener);
  86. observers[type] = observer;
  87. addObserver(observer, type, weak);
  88. }
  89. }
  90. exports.on = on;
  91. function once(type, listener) {
  92. // Note: this code assumes order in which listeners are called, which is fine
  93. // as long as dispatch happens in same order as listener registration which
  94. // is the case now. That being said we should be aware that this may break
  95. // in a future if order will change.
  96. on(type, listener);
  97. on(type, function cleanup() {
  98. off(type, listener);
  99. off(type, cleanup);
  100. }, true);
  101. }
  102. exports.once = once;
  103. function off(type, listener) {
  104. // Take list of observers as with the given `listener`.
  105. let observers = subscribers(listener);
  106. // If `observer` for the given `type` is registered, then
  107. // remove it & unregister.
  108. if (type in observers) {
  109. let observer = observers[type];
  110. delete observers[type];
  111. removeObserver(observer, type);
  112. }
  113. }
  114. exports.off = off;