1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  */
    
  7. 
    
  8. /* eslint valid-typeof: 0 */
    
  9. 
    
  10. import assign from 'shared/assign';
    
  11. 
    
  12. const EVENT_POOL_SIZE = 10;
    
  13. 
    
  14. /**
    
  15.  * @interface Event
    
  16.  * @see http://www.w3.org/TR/DOM-Level-3-Events/
    
  17.  */
    
  18. const EventInterface = {
    
  19.   type: null,
    
  20.   target: null,
    
  21.   // currentTarget is set when dispatching; no use in copying it here
    
  22.   currentTarget: function () {
    
  23.     return null;
    
  24.   },
    
  25.   eventPhase: null,
    
  26.   bubbles: null,
    
  27.   cancelable: null,
    
  28.   timeStamp: function (event) {
    
  29.     return event.timeStamp || Date.now();
    
  30.   },
    
  31.   defaultPrevented: null,
    
  32.   isTrusted: null,
    
  33. };
    
  34. 
    
  35. function functionThatReturnsTrue() {
    
  36.   return true;
    
  37. }
    
  38. 
    
  39. function functionThatReturnsFalse() {
    
  40.   return false;
    
  41. }
    
  42. 
    
  43. /**
    
  44.  * Synthetic events are dispatched by event plugins, typically in response to a
    
  45.  * top-level event delegation handler.
    
  46.  *
    
  47.  * These systems should generally use pooling to reduce the frequency of garbage
    
  48.  * collection. The system should check `isPersistent` to determine whether the
    
  49.  * event should be released into the pool after being dispatched. Users that
    
  50.  * need a persisted event should invoke `persist`.
    
  51.  *
    
  52.  * Synthetic events (and subclasses) implement the DOM Level 3 Events API by
    
  53.  * normalizing browser quirks. Subclasses do not necessarily have to implement a
    
  54.  * DOM interface; custom application-specific events can also subclass this.
    
  55.  *
    
  56.  * @param {object} dispatchConfig Configuration used to dispatch this event.
    
  57.  * @param {*} targetInst Marker identifying the event target.
    
  58.  * @param {object} nativeEvent Native browser event.
    
  59.  * @param {DOMEventTarget} nativeEventTarget Target node.
    
  60.  */
    
  61. function SyntheticEvent(
    
  62.   dispatchConfig,
    
  63.   targetInst,
    
  64.   nativeEvent,
    
  65.   nativeEventTarget,
    
  66. ) {
    
  67.   if (__DEV__) {
    
  68.     // these have a getter/setter for warnings
    
  69.     delete this.nativeEvent;
    
  70.     delete this.preventDefault;
    
  71.     delete this.stopPropagation;
    
  72.     delete this.isDefaultPrevented;
    
  73.     delete this.isPropagationStopped;
    
  74.   }
    
  75. 
    
  76.   this.dispatchConfig = dispatchConfig;
    
  77.   this._targetInst = targetInst;
    
  78.   this.nativeEvent = nativeEvent;
    
  79.   this._dispatchListeners = null;
    
  80.   this._dispatchInstances = null;
    
  81. 
    
  82.   const Interface = this.constructor.Interface;
    
  83.   for (const propName in Interface) {
    
  84.     if (!Interface.hasOwnProperty(propName)) {
    
  85.       continue;
    
  86.     }
    
  87.     if (__DEV__) {
    
  88.       delete this[propName]; // this has a getter/setter for warnings
    
  89.     }
    
  90.     const normalize = Interface[propName];
    
  91.     if (normalize) {
    
  92.       this[propName] = normalize(nativeEvent);
    
  93.     } else {
    
  94.       if (propName === 'target') {
    
  95.         this.target = nativeEventTarget;
    
  96.       } else {
    
  97.         this[propName] = nativeEvent[propName];
    
  98.       }
    
  99.     }
    
  100.   }
    
  101. 
    
  102.   const defaultPrevented =
    
  103.     nativeEvent.defaultPrevented != null
    
  104.       ? nativeEvent.defaultPrevented
    
  105.       : nativeEvent.returnValue === false;
    
  106.   if (defaultPrevented) {
    
  107.     this.isDefaultPrevented = functionThatReturnsTrue;
    
  108.   } else {
    
  109.     this.isDefaultPrevented = functionThatReturnsFalse;
    
  110.   }
    
  111.   this.isPropagationStopped = functionThatReturnsFalse;
    
  112.   return this;
    
  113. }
    
  114. 
    
  115. assign(SyntheticEvent.prototype, {
    
  116.   preventDefault: function () {
    
  117.     this.defaultPrevented = true;
    
  118.     const event = this.nativeEvent;
    
  119.     if (!event) {
    
  120.       return;
    
  121.     }
    
  122. 
    
  123.     if (event.preventDefault) {
    
  124.       event.preventDefault();
    
  125.     } else if (typeof event.returnValue !== 'unknown') {
    
  126.       event.returnValue = false;
    
  127.     }
    
  128.     this.isDefaultPrevented = functionThatReturnsTrue;
    
  129.   },
    
  130. 
    
  131.   stopPropagation: function () {
    
  132.     const event = this.nativeEvent;
    
  133.     if (!event) {
    
  134.       return;
    
  135.     }
    
  136. 
    
  137.     if (event.stopPropagation) {
    
  138.       event.stopPropagation();
    
  139.     } else if (typeof event.cancelBubble !== 'unknown') {
    
  140.       // The ChangeEventPlugin registers a "propertychange" event for
    
  141.       // IE. This event does not support bubbling or cancelling, and
    
  142.       // any references to cancelBubble throw "Member not found".  A
    
  143.       // typeof check of "unknown" circumvents this issue (and is also
    
  144.       // IE specific).
    
  145.       event.cancelBubble = true;
    
  146.     }
    
  147. 
    
  148.     this.isPropagationStopped = functionThatReturnsTrue;
    
  149.   },
    
  150. 
    
  151.   /**
    
  152.    * We release all dispatched `SyntheticEvent`s after each event loop, adding
    
  153.    * them back into the pool. This allows a way to hold onto a reference that
    
  154.    * won't be added back into the pool.
    
  155.    */
    
  156.   persist: function () {
    
  157.     this.isPersistent = functionThatReturnsTrue;
    
  158.   },
    
  159. 
    
  160.   /**
    
  161.    * Checks if this event should be released back into the pool.
    
  162.    *
    
  163.    * @return {boolean} True if this should not be released, false otherwise.
    
  164.    */
    
  165.   isPersistent: functionThatReturnsFalse,
    
  166. 
    
  167.   /**
    
  168.    * `PooledClass` looks for `destructor` on each instance it releases.
    
  169.    */
    
  170.   destructor: function () {
    
  171.     const Interface = this.constructor.Interface;
    
  172.     for (const propName in Interface) {
    
  173.       if (__DEV__) {
    
  174.         Object.defineProperty(
    
  175.           this,
    
  176.           propName,
    
  177.           getPooledWarningPropertyDefinition(propName, Interface[propName]),
    
  178.         );
    
  179.       } else {
    
  180.         this[propName] = null;
    
  181.       }
    
  182.     }
    
  183.     this.dispatchConfig = null;
    
  184.     this._targetInst = null;
    
  185.     this.nativeEvent = null;
    
  186.     this.isDefaultPrevented = functionThatReturnsFalse;
    
  187.     this.isPropagationStopped = functionThatReturnsFalse;
    
  188.     this._dispatchListeners = null;
    
  189.     this._dispatchInstances = null;
    
  190.     if (__DEV__) {
    
  191.       Object.defineProperty(
    
  192.         this,
    
  193.         'nativeEvent',
    
  194.         getPooledWarningPropertyDefinition('nativeEvent', null),
    
  195.       );
    
  196.       Object.defineProperty(
    
  197.         this,
    
  198.         'isDefaultPrevented',
    
  199.         getPooledWarningPropertyDefinition(
    
  200.           'isDefaultPrevented',
    
  201.           functionThatReturnsFalse,
    
  202.         ),
    
  203.       );
    
  204.       Object.defineProperty(
    
  205.         this,
    
  206.         'isPropagationStopped',
    
  207.         getPooledWarningPropertyDefinition(
    
  208.           'isPropagationStopped',
    
  209.           functionThatReturnsFalse,
    
  210.         ),
    
  211.       );
    
  212.       Object.defineProperty(
    
  213.         this,
    
  214.         'preventDefault',
    
  215.         getPooledWarningPropertyDefinition('preventDefault', () => {}),
    
  216.       );
    
  217.       Object.defineProperty(
    
  218.         this,
    
  219.         'stopPropagation',
    
  220.         getPooledWarningPropertyDefinition('stopPropagation', () => {}),
    
  221.       );
    
  222.     }
    
  223.   },
    
  224. });
    
  225. 
    
  226. SyntheticEvent.Interface = EventInterface;
    
  227. 
    
  228. /**
    
  229.  * Helper to reduce boilerplate when creating subclasses.
    
  230.  */
    
  231. SyntheticEvent.extend = function (Interface) {
    
  232.   const Super = this;
    
  233. 
    
  234.   const E = function () {};
    
  235.   E.prototype = Super.prototype;
    
  236.   const prototype = new E();
    
  237. 
    
  238.   function Class() {
    
  239.     return Super.apply(this, arguments);
    
  240.   }
    
  241.   assign(prototype, Class.prototype);
    
  242.   Class.prototype = prototype;
    
  243.   Class.prototype.constructor = Class;
    
  244. 
    
  245.   Class.Interface = assign({}, Super.Interface, Interface);
    
  246.   Class.extend = Super.extend;
    
  247.   addEventPoolingTo(Class);
    
  248. 
    
  249.   return Class;
    
  250. };
    
  251. 
    
  252. addEventPoolingTo(SyntheticEvent);
    
  253. 
    
  254. /**
    
  255.  * Helper to nullify syntheticEvent instance properties when destructing
    
  256.  *
    
  257.  * @param {String} propName
    
  258.  * @param {?object} getVal
    
  259.  * @return {object} defineProperty object
    
  260.  */
    
  261. function getPooledWarningPropertyDefinition(propName, getVal) {
    
  262.   function set(val) {
    
  263.     const action = isFunction ? 'setting the method' : 'setting the property';
    
  264.     warn(action, 'This is effectively a no-op');
    
  265.     return val;
    
  266.   }
    
  267. 
    
  268.   function get() {
    
  269.     const action = isFunction
    
  270.       ? 'accessing the method'
    
  271.       : 'accessing the property';
    
  272.     const result = isFunction
    
  273.       ? 'This is a no-op function'
    
  274.       : 'This is set to null';
    
  275.     warn(action, result);
    
  276.     return getVal;
    
  277.   }
    
  278. 
    
  279.   function warn(action, result) {
    
  280.     if (__DEV__) {
    
  281.       console.error(
    
  282.         "This synthetic event is reused for performance reasons. If you're seeing this, " +
    
  283.           "you're %s `%s` on a released/nullified synthetic event. %s. " +
    
  284.           'If you must keep the original synthetic event around, use event.persist(). ' +
    
  285.           'See https://reactjs.org/link/event-pooling for more information.',
    
  286.         action,
    
  287.         propName,
    
  288.         result,
    
  289.       );
    
  290.     }
    
  291.   }
    
  292.   const isFunction = typeof getVal === 'function';
    
  293.   return {
    
  294.     configurable: true,
    
  295.     set: set,
    
  296.     get: get,
    
  297.   };
    
  298. }
    
  299. 
    
  300. function createOrGetPooledEvent(
    
  301.   dispatchConfig,
    
  302.   targetInst,
    
  303.   nativeEvent,
    
  304.   nativeInst,
    
  305. ) {
    
  306.   const EventConstructor = this;
    
  307.   if (EventConstructor.eventPool.length) {
    
  308.     const instance = EventConstructor.eventPool.pop();
    
  309.     EventConstructor.call(
    
  310.       instance,
    
  311.       dispatchConfig,
    
  312.       targetInst,
    
  313.       nativeEvent,
    
  314.       nativeInst,
    
  315.     );
    
  316.     return instance;
    
  317.   }
    
  318.   return new EventConstructor(
    
  319.     dispatchConfig,
    
  320.     targetInst,
    
  321.     nativeEvent,
    
  322.     nativeInst,
    
  323.   );
    
  324. }
    
  325. 
    
  326. function releasePooledEvent(event) {
    
  327.   const EventConstructor = this;
    
  328. 
    
  329.   if (!(event instanceof EventConstructor)) {
    
  330.     throw new Error(
    
  331.       'Trying to release an event instance into a pool of a different type.',
    
  332.     );
    
  333.   }
    
  334. 
    
  335.   event.destructor();
    
  336.   if (EventConstructor.eventPool.length < EVENT_POOL_SIZE) {
    
  337.     EventConstructor.eventPool.push(event);
    
  338.   }
    
  339. }
    
  340. 
    
  341. function addEventPoolingTo(EventConstructor) {
    
  342.   EventConstructor.getPooled = createOrGetPooledEvent;
    
  343.   EventConstructor.eventPool = [];
    
  344.   EventConstructor.release = releasePooledEvent;
    
  345. }
    
  346. 
    
  347. export default SyntheticEvent;