| 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 | * @flow |
| 8 | */ |
| 9 | |
| 10 | import type {DOMEventName} from '../../events/DOMEventNames'; |
| 11 | import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; |
| 12 | import type {AnyNativeEvent} from '../../events/PluginModuleType'; |
| 13 | import type {DispatchQueue} from '../DOMPluginEventSystem'; |
| 14 | import type {EventSystemFlags} from '../EventSystemFlags'; |
| 15 | import type {ReactSyntheticEvent} from '../ReactSyntheticEventType'; |
| 16 | |
| 17 | import { |
| 18 | SyntheticEvent, |
| 19 | SyntheticKeyboardEvent, |
| 20 | SyntheticFocusEvent, |
| 21 | SyntheticMouseEvent, |
| 22 | SyntheticDragEvent, |
| 23 | SyntheticTouchEvent, |
| 24 | SyntheticAnimationEvent, |
| 25 | SyntheticTransitionEvent, |
| 26 | SyntheticUIEvent, |
| 27 | SyntheticWheelEvent, |
| 28 | SyntheticClipboardEvent, |
| 29 | SyntheticPointerEvent, |
| 30 | SyntheticSubmitEvent, |
| 31 | SyntheticToggleEvent, |
| 32 | } from '../../events/SyntheticEvent'; |
| 33 | |
| 34 | import { |
| 35 | ANIMATION_END, |
| 36 | ANIMATION_ITERATION, |
| 37 | ANIMATION_START, |
| 38 | TRANSITION_END, |
| 39 | } from '../DOMEventNames'; |
| 40 | import { |
| 41 | topLevelEventsToReactNames, |
| 42 | registerSimpleEvents, |
| 43 | } from '../DOMEventProperties'; |
| 44 | import { |
| 45 | accumulateSinglePhaseListeners, |
| 46 | accumulateEventHandleNonManagedNodeListeners, |
| 47 | } from '../DOMPluginEventSystem'; |
| 48 | import { |
| 49 | IS_EVENT_HANDLE_NON_MANAGED_NODE, |
| 50 | IS_CAPTURE_PHASE, |
| 51 | } from '../EventSystemFlags'; |
| 52 | |
| 53 | import getEventCharCode from '../getEventCharCode'; |
| 54 | |
| 55 | import {enableCreateEventHandleAPI} from 'shared/ReactFeatureFlags'; |
| 56 | |
| 57 | function extractEvents( |
| 58 | dispatchQueue: DispatchQueue, |
| 59 | domEventName: DOMEventName, |
| 60 | targetInst: null | Fiber, |
| 61 | nativeEvent: AnyNativeEvent, |
| 62 | nativeEventTarget: null | EventTarget, |
| 63 | eventSystemFlags: EventSystemFlags, |
| 64 | targetContainer: EventTarget, |
| 65 | ): void { |
| 66 | const reactName = topLevelEventsToReactNames.get(domEventName); |
| 67 | if (reactName === undefined) { |
| 68 | return; |
| 69 | } |
| 70 | let SyntheticEventCtor = SyntheticEvent; |
| 71 | let reactEventType: string = domEventName; |
| 72 | switch (domEventName) { |
| 73 | case 'keypress': |
| 74 | // Firefox creates a keypress event for function keys too. This removes |
| 75 | // the unwanted keypress events. Enter is however both printable and |
| 76 | // non-printable. One would expect Tab to be as well (but it isn't). |
| 77 | // TODO: Fixed in https://bugzilla.mozilla.org/show_bug.cgi?id=968056. Can |
| 78 | // probably remove. |
| 79 | if (getEventCharCode(nativeEvent as any as KeyboardEvent) === 0) { |
| 80 | return; |
| 81 | } |
| 82 | /* falls through */ |
| 83 | case 'keydown': |
| 84 | case 'keyup': |
| 85 | SyntheticEventCtor = SyntheticKeyboardEvent; |
| 86 | break; |
| 87 | case 'focusin': |
| 88 | reactEventType = 'focus'; |
| 89 | SyntheticEventCtor = SyntheticFocusEvent; |
| 90 | break; |
| 91 | case 'focusout': |
| 92 | reactEventType = 'blur'; |
| 93 | SyntheticEventCtor = SyntheticFocusEvent; |
| 94 | break; |
| 95 | case 'beforeblur': |
| 96 | case 'afterblur': |
| 97 | SyntheticEventCtor = SyntheticFocusEvent; |
| 98 | break; |
| 99 | case 'click': |
| 100 | // Firefox creates a click event on right mouse clicks. This removes the |
| 101 | // unwanted click events. |
| 102 | // TODO: Fixed in https://phabricator.services.mozilla.com/D26793. Can |
| 103 | // probably remove. |
| 104 | if (nativeEvent.button === 2) { |
| 105 | return; |
| 106 | } |
| 107 | /* falls through */ |
| 108 | case 'auxclick': |
| 109 | case 'dblclick': |
| 110 | case 'mousedown': |
| 111 | case 'mousemove': |
| 112 | case 'mouseup': |
| 113 | // TODO: Disabled elements should not respond to mouse events |
| 114 | /* falls through */ |
| 115 | case 'mouseout': |
| 116 | case 'mouseover': |
| 117 | case 'contextmenu': |
| 118 | SyntheticEventCtor = SyntheticMouseEvent; |
| 119 | break; |
| 120 | case 'drag': |
| 121 | case 'dragend': |
| 122 | case 'dragenter': |
| 123 | case 'dragexit': |
| 124 | case 'dragleave': |
| 125 | case 'dragover': |
| 126 | case 'dragstart': |
| 127 | case 'drop': |
| 128 | SyntheticEventCtor = SyntheticDragEvent; |
| 129 | break; |
| 130 | case 'touchcancel': |
| 131 | case 'touchend': |
| 132 | case 'touchmove': |
| 133 | case 'touchstart': |
| 134 | SyntheticEventCtor = SyntheticTouchEvent; |
| 135 | break; |
| 136 | case ANIMATION_END: |
| 137 | case ANIMATION_ITERATION: |
| 138 | case ANIMATION_START: |
| 139 | SyntheticEventCtor = SyntheticAnimationEvent; |
| 140 | break; |
| 141 | case TRANSITION_END: |
| 142 | SyntheticEventCtor = SyntheticTransitionEvent; |
| 143 | break; |
| 144 | case 'scroll': |
| 145 | case 'scrollend': |
| 146 | SyntheticEventCtor = SyntheticUIEvent; |
| 147 | break; |
| 148 | case 'wheel': |
| 149 | SyntheticEventCtor = SyntheticWheelEvent; |
| 150 | break; |
| 151 | case 'copy': |
| 152 | case 'cut': |
| 153 | case 'paste': |
| 154 | SyntheticEventCtor = SyntheticClipboardEvent; |
| 155 | break; |
| 156 | case 'gotpointercapture': |
| 157 | case 'lostpointercapture': |
| 158 | case 'pointercancel': |
| 159 | case 'pointerdown': |
| 160 | case 'pointermove': |
| 161 | case 'pointerout': |
| 162 | case 'pointerover': |
| 163 | case 'pointerup': |
| 164 | SyntheticEventCtor = SyntheticPointerEvent; |
| 165 | break; |
| 166 | case 'submit': |
| 167 | SyntheticEventCtor = SyntheticSubmitEvent; |
| 168 | break; |
| 169 | case 'toggle': |
| 170 | case 'beforetoggle': |
| 171 | // MDN claims <details> should not receive ToggleEvent contradicting the spec: https://html.spec.whatwg.org/multipage/indices.html#event-toggle |
| 172 | SyntheticEventCtor = SyntheticToggleEvent; |
| 173 | break; |
| 174 | default: |
| 175 | // Unknown event. This is used by createEventHandle. |
| 176 | break; |
| 177 | } |
| 178 | |
| 179 | const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0; |
| 180 | if ( |
| 181 | enableCreateEventHandleAPI && |
| 182 | eventSystemFlags & IS_EVENT_HANDLE_NON_MANAGED_NODE |
| 183 | ) { |
| 184 | const listeners = accumulateEventHandleNonManagedNodeListeners( |
| 185 | // TODO: this cast may not make sense for events like |
| 186 | // "focus" where React listens to e.g. "focusin". |
| 187 | reactEventType as any as DOMEventName, |
| 188 | targetContainer, |
| 189 | inCapturePhase, |
| 190 | ); |
| 191 | if (listeners.length > 0) { |
| 192 | // Intentionally create event lazily. |
| 193 | const event: ReactSyntheticEvent = new SyntheticEventCtor( |
| 194 | reactName, |
| 195 | reactEventType, |
| 196 | null, |
| 197 | nativeEvent, |
| 198 | nativeEventTarget, |
| 199 | ); |
| 200 | dispatchQueue.push({event, listeners}); |
| 201 | } |
| 202 | } else { |
| 203 | // Some events don't bubble in the browser. |
| 204 | // In the past, React has always bubbled them, but this can be surprising. |
| 205 | // We're going to try aligning closer to the browser behavior by not bubbling |
| 206 | // them in React either. We'll start by not bubbling onScroll, and then expand. |
| 207 | const accumulateTargetOnly = |
| 208 | !inCapturePhase && |
| 209 | // TODO: ideally, we'd eventually add all events from |
| 210 | // nonDelegatedEvents list in DOMPluginEventSystem. |
| 211 | // Then we can remove this special list. |
| 212 | // This is a breaking change that can wait until React 18. |
| 213 | (domEventName === 'scroll' || domEventName === 'scrollend'); |
| 214 | |
| 215 | const listeners = accumulateSinglePhaseListeners( |
| 216 | targetInst, |
| 217 | reactName, |
| 218 | nativeEvent.type, |
| 219 | inCapturePhase, |
| 220 | accumulateTargetOnly, |
| 221 | nativeEvent, |
| 222 | ); |
| 223 | if (listeners.length > 0) { |
| 224 | // Intentionally create event lazily. |
| 225 | const event: ReactSyntheticEvent = new SyntheticEventCtor( |
| 226 | reactName, |
| 227 | reactEventType, |
| 228 | null, |
| 229 | nativeEvent, |
| 230 | nativeEventTarget, |
| 231 | ); |
| 232 | dispatchQueue.push({event, listeners}); |
| 233 | } |
| 234 | } |
| 235 | } |
| 236 | |
| 237 | export {registerSimpleEvents as registerEvents, extractEvents}; |