/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* eslint valid-typeof: 0 */ import assign from 'shared/assign'; const EVENT_POOL_SIZE = 10; /** * @interface Event * @see http://www.w3.org/TR/DOM-Level-3-Events/ */ const EventInterface = { type: null, target: null, // currentTarget is set when dispatching; no use in copying it here currentTarget: function () { return null; }, eventPhase: null, bubbles: null, cancelable: null, timeStamp: function (event) { return event.timeStamp || Date.now(); }, defaultPrevented: null, isTrusted: null }; function functionThatReturnsTrue() { return true; } function functionThatReturnsFalse() { return false; } /** * Synthetic events are dispatched by event plugins, typically in response to a * top-level event delegation handler. * * These systems should generally use pooling to reduce the frequency of garbage * collection. The system should check `isPersistent` to determine whether the * event should be released into the pool after being dispatched. Users that * need a persisted event should invoke `persist`. * * Synthetic events (and subclasses) implement the DOM Level 3 Events API by * normalizing browser quirks. Subclasses do not necessarily have to implement a * DOM interface; custom application-specific events can also subclass this. * * @param {object} dispatchConfig Configuration used to dispatch this event. * @param {*} targetInst Marker identifying the event target. * @param {object} nativeEvent Native browser event. * @param {DOMEventTarget} nativeEventTarget Target node. */ function SyntheticEvent(dispatchConfig, targetInst, nativeEvent, nativeEventTarget) { if (__DEV__) { // these have a getter/setter for warnings delete this.nativeEvent; delete this.preventDefault; delete this.stopPropagation; delete this.isDefaultPrevented; delete this.isPropagationStopped; } this.dispatchConfig = dispatchConfig; this._targetInst = targetInst; this.nativeEvent = nativeEvent; this._dispatchListeners = null; this._dispatchInstances = null; const Interface = this.constructor.Interface; for (const propName in Interface) { if (!(propName |> Interface.hasOwnProperty(%))) { continue; } if (__DEV__) { delete this[propName]; // this has a getter/setter for warnings } const normalize = Interface[propName]; if (normalize) { this[propName] = nativeEvent |> normalize(%); } else { if (propName === 'target') { this.target = nativeEventTarget; } else { this[propName] = nativeEvent[propName]; } } } const defaultPrevented = nativeEvent.defaultPrevented != null ? nativeEvent.defaultPrevented : nativeEvent.returnValue === false; if (defaultPrevented) { this.isDefaultPrevented = functionThatReturnsTrue; } else { this.isDefaultPrevented = functionThatReturnsFalse; } this.isPropagationStopped = functionThatReturnsFalse; return this; } SyntheticEvent.prototype |> assign(%, { preventDefault: function () { this.defaultPrevented = true; const event = this.nativeEvent; if (!event) { return; } if (event.preventDefault) { event.preventDefault(); } else if (typeof event.returnValue !== 'unknown') { event.returnValue = false; } this.isDefaultPrevented = functionThatReturnsTrue; }, stopPropagation: function () { const event = this.nativeEvent; if (!event) { return; } if (event.stopPropagation) { event.stopPropagation(); } else if (typeof event.cancelBubble !== 'unknown') { // The ChangeEventPlugin registers a "propertychange" event for // IE. This event does not support bubbling or cancelling, and // any references to cancelBubble throw "Member not found". A // typeof check of "unknown" circumvents this issue (and is also // IE specific). event.cancelBubble = true; } this.isPropagationStopped = functionThatReturnsTrue; }, /** * We release all dispatched `SyntheticEvent`s after each event loop, adding * them back into the pool. This allows a way to hold onto a reference that * won't be added back into the pool. */ persist: function () { this.isPersistent = functionThatReturnsTrue; }, /** * Checks if this event should be released back into the pool. * * @return {boolean} True if this should not be released, false otherwise. */ isPersistent: functionThatReturnsFalse, /** * `PooledClass` looks for `destructor` on each instance it releases. */ destructor: function () { const Interface = this.constructor.Interface; for (const propName in Interface) { if (__DEV__) { Object.defineProperty(this, propName, propName |> getPooledWarningPropertyDefinition(%, Interface[propName])); } else { this[propName] = null; } } this.dispatchConfig = null; this._targetInst = null; this.nativeEvent = null; this.isDefaultPrevented = functionThatReturnsFalse; this.isPropagationStopped = functionThatReturnsFalse; this._dispatchListeners = null; this._dispatchInstances = null; if (__DEV__) { Object.defineProperty(this, 'nativeEvent', 'nativeEvent' |> getPooledWarningPropertyDefinition(%, null)); Object.defineProperty(this, 'isDefaultPrevented', 'isDefaultPrevented' |> getPooledWarningPropertyDefinition(%, functionThatReturnsFalse)); Object.defineProperty(this, 'isPropagationStopped', 'isPropagationStopped' |> getPooledWarningPropertyDefinition(%, functionThatReturnsFalse)); Object.defineProperty(this, 'preventDefault', 'preventDefault' |> getPooledWarningPropertyDefinition(%, () => {})); Object.defineProperty(this, 'stopPropagation', 'stopPropagation' |> getPooledWarningPropertyDefinition(%, () => {})); } } }); SyntheticEvent.Interface = EventInterface; /** * Helper to reduce boilerplate when creating subclasses. */ SyntheticEvent.extend = function (Interface) { const Super = this; const E = function () {}; E.prototype = Super.prototype; const prototype = new E(); function Class() { return this |> Super.apply(%, arguments); } prototype |> assign(%, Class.prototype); Class.prototype = prototype; Class.prototype.constructor = Class; Class.Interface = assign({}, Super.Interface, Interface); Class.extend = Super.extend; Class |> addEventPoolingTo(%); return Class; }; /** * Helper to nullify syntheticEvent instance properties when destructing * * @param {String} propName * @param {?object} getVal * @return {object} defineProperty object */ SyntheticEvent |> addEventPoolingTo(%); function getPooledWarningPropertyDefinition(propName, getVal) { function set(val) { const action = isFunction ? 'setting the method' : 'setting the property'; action |> warn(%, 'This is effectively a no-op'); return val; } function get() { const action = isFunction ? 'accessing the method' : 'accessing the property'; const result = isFunction ? 'This is a no-op function' : 'This is set to null'; action |> warn(%, result); return getVal; } function warn(action, result) { if (__DEV__) { console.error("This synthetic event is reused for performance reasons. If you're seeing this, " + "you're %s `%s` on a released/nullified synthetic event. %s. " + 'If you must keep the original synthetic event around, use event.persist(). ' + 'See https://react.dev/link/event-pooling for more information.', action, propName, result); } } const isFunction = typeof getVal === 'function'; return { configurable: true, set: set, get: get }; } function createOrGetPooledEvent(dispatchConfig, targetInst, nativeEvent, nativeInst) { const EventConstructor = this; if (EventConstructor.eventPool.length) { const instance = EventConstructor.eventPool.pop(); EventConstructor.call(instance, dispatchConfig, targetInst, nativeEvent, nativeInst); return instance; } return new EventConstructor(dispatchConfig, targetInst, nativeEvent, nativeInst); } function releasePooledEvent(event) { const EventConstructor = this; if (!(event instanceof EventConstructor)) { throw new Error('Trying to release an event instance into a pool of a different type.'); } event.destructor(); if (EventConstructor.eventPool.length < EVENT_POOL_SIZE) { event |> EventConstructor.eventPool.push(%); } } function addEventPoolingTo(EventConstructor) { EventConstructor.getPooled = createOrGetPooledEvent; EventConstructor.eventPool = []; EventConstructor.release = releasePooledEvent; } export default SyntheticEvent;