core.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  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 UNCAUGHT_ERROR = 'An error event was emitted for which there was no listener.';
  9. const BAD_LISTENER = 'The event listener must be a function.';
  10. const { ns } = require('../core/namespace');
  11. const event = ns();
  12. const EVENT_TYPE_PATTERN = /^on([A-Z]\w+$)/;
  13. exports.EVENT_TYPE_PATTERN = EVENT_TYPE_PATTERN;
  14. // Utility function to access given event `target` object's event listeners for
  15. // the specific event `type`. If listeners for this type does not exists they
  16. // will be created.
  17. const observers = function observers(target, type) {
  18. if (!target) throw TypeError("Event target must be an object");
  19. let listeners = event(target);
  20. return type in listeners ? listeners[type] : listeners[type] = [];
  21. };
  22. /**
  23. * Registers an event `listener` that is called every time events of
  24. * specified `type` is emitted on the given event `target`.
  25. * @param {Object} target
  26. * Event target object.
  27. * @param {String} type
  28. * The type of event.
  29. * @param {Function} listener
  30. * The listener function that processes the event.
  31. */
  32. function on(target, type, listener) {
  33. if (typeof(listener) !== 'function')
  34. throw new Error(BAD_LISTENER);
  35. let listeners = observers(target, type);
  36. if (!~listeners.indexOf(listener))
  37. listeners.push(listener);
  38. }
  39. exports.on = on;
  40. /**
  41. * Registers an event `listener` that is called only the next time an event
  42. * of the specified `type` is emitted on the given event `target`.
  43. * @param {Object} target
  44. * Event target object.
  45. * @param {String} type
  46. * The type of the event.
  47. * @param {Function} listener
  48. * The listener function that processes the event.
  49. */
  50. function once(target, type, listener) {
  51. on(target, type, function observer(...args) {
  52. off(target, type, observer);
  53. listener.apply(target, args);
  54. });
  55. }
  56. exports.once = once;
  57. /**
  58. * Execute each of the listeners in order with the supplied arguments.
  59. * All the exceptions that are thrown by listeners during the emit
  60. * are caught and can be handled by listeners of 'error' event. Thrown
  61. * exceptions are passed as an argument to an 'error' event listener.
  62. * If no 'error' listener is registered exception will be logged into an
  63. * error console.
  64. * @param {Object} target
  65. * Event target object.
  66. * @param {String} type
  67. * The type of event.
  68. * @params {Object|Number|String|Boolean} args
  69. * Arguments that will be passed to listeners.
  70. */
  71. function emit (target, type, ...args) {
  72. let state = observers(target, type);
  73. let listeners = state.slice();
  74. let count = listeners.length;
  75. let index = 0;
  76. // If error event and there are no handlers then print error message
  77. // into a console.
  78. if (count === 0 && type === 'error') console.exception(args[0]);
  79. while (index < count) {
  80. try {
  81. let listener = listeners[index];
  82. // Dispatch only if listener is still registered.
  83. if (~state.indexOf(listener))
  84. listener.apply(target, args);
  85. }
  86. catch (error) {
  87. // If exception is not thrown by a error listener and error listener is
  88. // registered emit `error` event. Otherwise dump exception to the console.
  89. if (type !== 'error') emit(target, 'error', error);
  90. else console.exception(error);
  91. }
  92. index++;
  93. }
  94. // Also emit on `"*"` so that one could listen for all events.
  95. if (type !== '*') emit(target, '*', type, ...args);
  96. }
  97. exports.emit = emit;
  98. /**
  99. * Removes an event `listener` for the given event `type` on the given event
  100. * `target`. If no `listener` is passed removes all listeners of the given
  101. * `type`. If `type` is not passed removes all the listeners of the given
  102. * event `target`.
  103. * @param {Object} target
  104. * The event target object.
  105. * @param {String} type
  106. * The type of event.
  107. * @param {Function} listener
  108. * The listener function that processes the event.
  109. */
  110. function off(target, type, listener) {
  111. let length = arguments.length;
  112. if (length === 3) {
  113. let listeners = observers(target, type);
  114. let index = listeners.indexOf(listener);
  115. if (~index)
  116. listeners.splice(index, 1);
  117. }
  118. else if (length === 2) {
  119. observers(target, type).splice(0);
  120. }
  121. else if (length === 1) {
  122. let listeners = event(target);
  123. Object.keys(listeners).forEach(type => delete listeners[type]);
  124. }
  125. }
  126. exports.off = off;
  127. /**
  128. * Returns a number of event listeners registered for the given event `type`
  129. * on the given event `target`.
  130. */
  131. function count(target, type) {
  132. return observers(target, type).length;
  133. }
  134. exports.count = count;
  135. /**
  136. * Registers listeners on the given event `target` from the given `listeners`
  137. * dictionary. Iterates over the listeners and if property name matches name
  138. * pattern `onEventType` and property is a function, then registers it as
  139. * an `eventType` listener on `target`.
  140. *
  141. * @param {Object} target
  142. * The type of event.
  143. * @param {Object} listeners
  144. * Dictionary of listeners.
  145. */
  146. function setListeners(target, listeners) {
  147. Object.keys(listeners || {}).forEach(key => {
  148. let match = EVENT_TYPE_PATTERN.exec(key);
  149. let type = match && match[1].toLowerCase();
  150. if (!type) return;
  151. let listener = listeners[key];
  152. if (typeof(listener) === 'function')
  153. on(target, type, listener);
  154. });
  155. }
  156. exports.setListeners = setListeners;