listener.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. 'use strict';
  2. var shimmer = require('shimmer');
  3. var wrap = shimmer.wrap;
  4. var unwrap = shimmer.unwrap;
  5. // Default to complaining loudly when things don't go according to plan.
  6. // dunderscores are boring
  7. var SYMBOL = 'wrap@before';
  8. // Sets a property on an object, preserving its enumerability.
  9. // This function assumes that the property is already writable.
  10. function defineProperty(obj, name, value) {
  11. var enumerable = !!obj[name] && obj.propertyIsEnumerable(name);
  12. Object.defineProperty(obj, name, {
  13. configurable: true,
  14. enumerable: enumerable,
  15. writable: true,
  16. value: value
  17. });
  18. }
  19. function _process(self, listeners) {
  20. var l = listeners.length;
  21. for (var p = 0; p < l; p++) {
  22. var listener = listeners[p];
  23. // set up the listener so that onEmit can do whatever it needs
  24. var before = self[SYMBOL];
  25. if (typeof before === 'function') {
  26. before(listener);
  27. }
  28. else if (Array.isArray(before)) {
  29. var length = before.length;
  30. for (var i = 0; i < length; i++) before[i](listener);
  31. }
  32. }
  33. }
  34. function _listeners(self, event) {
  35. var listeners;
  36. listeners = self._events && self._events[event];
  37. if (!Array.isArray(listeners)) {
  38. if (listeners) {
  39. listeners = [listeners];
  40. }
  41. else {
  42. listeners = [];
  43. }
  44. }
  45. return listeners;
  46. }
  47. function _findAndProcess(self, event, before) {
  48. var after = _listeners(self, event);
  49. var unprocessed = after.filter(function(fn) { return before.indexOf(fn) === -1; });
  50. if (unprocessed.length > 0) _process(self, unprocessed);
  51. }
  52. function _wrap(unwrapped, visit) {
  53. if (!unwrapped) return;
  54. var wrapped = unwrapped;
  55. if (typeof unwrapped === 'function') {
  56. wrapped = visit(unwrapped);
  57. }
  58. else if (Array.isArray(unwrapped)) {
  59. wrapped = [];
  60. for (var i = 0; i < unwrapped.length; i++) {
  61. wrapped[i] = visit(unwrapped[i]);
  62. }
  63. }
  64. return wrapped;
  65. }
  66. module.exports = function wrapEmitter(emitter, onAddListener, onEmit) {
  67. if (!emitter || !emitter.on || !emitter.addListener ||
  68. !emitter.removeListener || !emitter.emit) {
  69. throw new Error("can only wrap real EEs");
  70. }
  71. if (!onAddListener) throw new Error("must have function to run on listener addition");
  72. if (!onEmit) throw new Error("must have function to wrap listeners when emitting");
  73. /* Attach a context to a listener, and make sure that this hook stays
  74. * attached to the emitter forevermore.
  75. */
  76. function adding(on) {
  77. return function added(event, listener) {
  78. var existing = _listeners(this, event).slice();
  79. try {
  80. var returned = on.call(this, event, listener);
  81. _findAndProcess(this, event, existing);
  82. return returned;
  83. }
  84. finally {
  85. // old-style streaming overwrites .on and .addListener, so rewrap
  86. if (!this.on.__wrapped) wrap(this, 'on', adding);
  87. if (!this.addListener.__wrapped) wrap(this, 'addListener', adding);
  88. }
  89. };
  90. }
  91. function emitting(emit) {
  92. return function emitted(event) {
  93. if (!this._events || !this._events[event]) return emit.apply(this, arguments);
  94. var unwrapped = this._events[event];
  95. /* Ensure that if removeListener gets called, it's working with the
  96. * unwrapped listeners.
  97. */
  98. function remover(removeListener) {
  99. return function removed() {
  100. this._events[event] = unwrapped;
  101. try {
  102. return removeListener.apply(this, arguments);
  103. }
  104. finally {
  105. unwrapped = this._events[event];
  106. this._events[event] = _wrap(unwrapped, onEmit);
  107. }
  108. };
  109. }
  110. wrap(this, 'removeListener', remover);
  111. try {
  112. /* At emit time, ensure that whatever else is going on, removeListener will
  113. * still work while at the same time running whatever hooks are necessary to
  114. * make sure the listener is run in the correct context.
  115. */
  116. this._events[event] = _wrap(unwrapped, onEmit);
  117. return emit.apply(this, arguments);
  118. }
  119. finally {
  120. /* Ensure that regardless of what happens when preparing and running the
  121. * listeners, the status quo ante is restored before continuing.
  122. */
  123. unwrap(this, 'removeListener');
  124. this._events[event] = unwrapped;
  125. }
  126. };
  127. }
  128. // support multiple onAddListeners
  129. if (!emitter[SYMBOL]) {
  130. defineProperty(emitter, SYMBOL, onAddListener);
  131. }
  132. else if (typeof emitter[SYMBOL] === 'function') {
  133. defineProperty(emitter, SYMBOL, [emitter[SYMBOL], onAddListener]);
  134. }
  135. else if (Array.isArray(emitter[SYMBOL])) {
  136. emitter[SYMBOL].push(onAddListener);
  137. }
  138. // only wrap the core functions once
  139. if (!emitter.__wrapped) {
  140. wrap(emitter, 'addListener', adding);
  141. wrap(emitter, 'on', adding);
  142. wrap(emitter, 'emit', emitting);
  143. defineProperty(emitter, '__unwrap', function () {
  144. unwrap(emitter, 'addListener');
  145. unwrap(emitter, 'on');
  146. unwrap(emitter, 'emit');
  147. delete emitter[SYMBOL];
  148. delete emitter.__wrapped;
  149. });
  150. defineProperty(emitter, '__wrapped', true);
  151. }
  152. };