From 2f5d455e44343aaf4a6a079e34b0e2bf569088b5 Mon Sep 17 00:00:00 2001 From: Jason Quense Date: Mon, 8 Jan 2018 10:36:24 -0500 Subject: [PATCH] Initial --- packages/events/EventPluginRegistry.js | 11 +- packages/events/PluginModuleType.js | 2 +- .../src/client/ReactDOMFiberComponent.js | 9 +- .../src/events/ReactBrowserEventEmitter.js | 11 +- .../react-dom/src/events/SimpleEventPlugin.js | 265 ++++-------------- .../src/shared/ReactDOMUnknownPropertyHook.js | 7 +- 6 files changed, 76 insertions(+), 229 deletions(-) diff --git a/packages/events/EventPluginRegistry.js b/packages/events/EventPluginRegistry.js index ccfaa872dcc..1140c479332 100644 --- a/packages/events/EventPluginRegistry.js +++ b/packages/events/EventPluginRegistry.js @@ -66,6 +66,10 @@ function recomputePluginOrdering(): void { ); plugins[pluginIndex] = pluginModule; const publishedEvents = pluginModule.eventTypes; + if (!publishedEvents) { + return; + } + for (const eventName in publishedEvents) { invariant( publishEventForPlugin( @@ -145,8 +149,11 @@ function publishRegistrationName( registrationName, ); registrationNameModules[registrationName] = pluginModule; - registrationNameDependencies[registrationName] = - pluginModule.eventTypes[eventName].dependencies; + + if (pluginModule.eventTypes) { + registrationNameDependencies[registrationName] = + pluginModule.eventTypes[eventName].dependencies; + } if (__DEV__) { const lowerCasedName = registrationName.toLowerCase(); diff --git a/packages/events/PluginModuleType.js b/packages/events/PluginModuleType.js index 5657ac3514b..ecba4560d1e 100644 --- a/packages/events/PluginModuleType.js +++ b/packages/events/PluginModuleType.js @@ -20,7 +20,7 @@ export type AnyNativeEvent = Event | KeyboardEvent | MouseEvent | Touch; export type PluginName = string; export type PluginModule = { - eventTypes: EventTypes, + eventTypes?: EventTypes, extractEvents: ( topLevelType: string, targetInst: Fiber, diff --git a/packages/react-dom/src/client/ReactDOMFiberComponent.js b/packages/react-dom/src/client/ReactDOMFiberComponent.js index 6959351fe09..8af80c62781 100644 --- a/packages/react-dom/src/client/ReactDOMFiberComponent.js +++ b/packages/react-dom/src/client/ReactDOMFiberComponent.js @@ -9,7 +9,6 @@ // TODO: direct imports like some-package/src/* are bad. Fix me. import ReactDebugCurrentFiber from 'react-reconciler/src/ReactDebugCurrentFiber'; -import {registrationNameModules} from 'events/EventPluginRegistry'; import emptyFunction from 'fbjs/lib/emptyFunction'; import warning from 'fbjs/lib/warning'; @@ -312,7 +311,7 @@ function setInitialDOMProperties( } else if (propKey === AUTOFOCUS) { // We polyfill it separately on the client during commit. // We blacklist it here rather than in the property list because we emit it in SSR. - } else if (registrationNameModules.hasOwnProperty(propKey)) { + } else if (propKey[0] === 'o' && propKey[1] === 'n') { if (nextProp != null) { if (__DEV__ && typeof nextProp !== 'function') { warnForInvalidEventListener(propKey, nextProp); @@ -651,7 +650,7 @@ export function diffProperties( // Noop } else if (propKey === AUTOFOCUS) { // Noop. It doesn't work on updates anyway. - } else if (registrationNameModules.hasOwnProperty(propKey)) { + } else if (propKey[0] === 'o' && propKey[1] === 'n') { // This is a special case. If any listener updates we need to ensure // that the "current" fiber pointer gets updated so we need a commit // to update this element. @@ -740,7 +739,7 @@ export function diffProperties( propKey === SUPPRESS_HYDRATION_WARNING ) { // Noop - } else if (registrationNameModules.hasOwnProperty(propKey)) { + } else if (propKey[0] === 'o' && propKey[1] === 'n') { if (nextProp != null) { // We eagerly listen to this even though we haven't committed yet. if (__DEV__ && typeof nextProp !== 'function') { @@ -966,7 +965,7 @@ export function diffHydratedProperties( updatePayload = [CHILDREN, '' + nextProp]; } } - } else if (registrationNameModules.hasOwnProperty(propKey)) { + } else if (propKey[0] === 'o' && propKey[1] === 'n') { if (nextProp != null) { if (__DEV__ && typeof nextProp !== 'function') { warnForInvalidEventListener(propKey, nextProp); diff --git a/packages/react-dom/src/events/ReactBrowserEventEmitter.js b/packages/react-dom/src/events/ReactBrowserEventEmitter.js index b0772c645d6..2a7a80ed4ab 100644 --- a/packages/react-dom/src/events/ReactBrowserEventEmitter.js +++ b/packages/react-dom/src/events/ReactBrowserEventEmitter.js @@ -81,6 +81,15 @@ let reactTopListenersCounter = 0; */ const topListenersIDKey = '_reactListenersID' + ('' + Math.random()).slice(2); +function getEventsFromRegistraionName(registrationName) { + if (!(registrationName in registrationNameDependencies)) { + registrationNameDependencies[registrationName] = [ + `top${registrationName.slice(2)}`, + ]; + } + return registrationNameDependencies[registrationName]; +} + function getListeningForDocument(mountAt) { // In IE8, `mountAt` is a host object and doesn't have `hasOwnProperty` // directly. @@ -115,7 +124,7 @@ function getListeningForDocument(mountAt) { export function listenTo(registrationName, contentDocumentHandle) { const mountAt = contentDocumentHandle; const isListening = getListeningForDocument(mountAt); - const dependencies = registrationNameDependencies[registrationName]; + const dependencies = getEventsFromRegistraionName(registrationName); for (let i = 0; i < dependencies.length; i++) { const dependency = dependencies[i]; diff --git a/packages/react-dom/src/events/SimpleEventPlugin.js b/packages/react-dom/src/events/SimpleEventPlugin.js index a6db694d0a3..f4ffda87dfa 100644 --- a/packages/react-dom/src/events/SimpleEventPlugin.js +++ b/packages/react-dom/src/events/SimpleEventPlugin.js @@ -13,11 +13,10 @@ import type { ReactSyntheticEvent, } from 'events/ReactSyntheticEventType'; import type {Fiber} from 'react-reconciler/src/ReactFiber'; -import type {EventTypes, PluginModule} from 'events/PluginModuleType'; +import type {PluginModule} from 'events/PluginModuleType'; import {accumulateTwoPhaseDispatches} from 'events/EventPropagators'; import SyntheticEvent from 'events/SyntheticEvent'; -import warning from 'fbjs/lib/warning'; import SyntheticAnimationEvent from './SyntheticAnimationEvent'; import SyntheticClipboardEvent from './SyntheticClipboardEvent'; @@ -31,247 +30,83 @@ import SyntheticUIEvent from './SyntheticUIEvent'; import SyntheticWheelEvent from './SyntheticWheelEvent'; import getEventCharCode from './getEventCharCode'; -/** - * Turns - * ['abort', ...] - * into - * eventTypes = { - * 'abort': { - * phasedRegistrationNames: { - * bubbled: 'onAbort', - * captured: 'onAbortCapture', - * }, - * dependencies: ['topAbort'], - * }, - * ... - * }; - * topLevelEventsToDispatchConfig = { - * 'topAbort': { sameConfig } - * }; - */ -const eventTypes: EventTypes = {}; const topLevelEventsToDispatchConfig: { [key: TopLevelTypes]: DispatchConfig, } = {}; -[ - 'abort', - 'animationEnd', - 'animationIteration', - 'animationStart', - 'blur', - 'cancel', - 'canPlay', - 'canPlayThrough', - 'click', - 'close', - 'contextMenu', - 'copy', - 'cut', - 'doubleClick', - 'drag', - 'dragEnd', - 'dragEnter', - 'dragExit', - 'dragLeave', - 'dragOver', - 'dragStart', - 'drop', - 'durationChange', - 'emptied', - 'encrypted', - 'ended', - 'error', - 'focus', - 'input', - 'invalid', - 'keyDown', - 'keyPress', - 'keyUp', - 'load', - 'loadedData', - 'loadedMetadata', - 'loadStart', - 'mouseDown', - 'mouseMove', - 'mouseOut', - 'mouseOver', - 'mouseUp', - 'paste', - 'pause', - 'play', - 'playing', - 'progress', - 'rateChange', - 'reset', - 'scroll', - 'seeked', - 'seeking', - 'stalled', - 'submit', - 'suspend', - 'timeUpdate', - 'toggle', - 'touchCancel', - 'touchEnd', - 'touchMove', - 'touchStart', - 'transitionEnd', - 'volumeChange', - 'waiting', - 'wheel', -].forEach(event => { - const capitalizedEvent = event[0].toUpperCase() + event.slice(1); - const onEvent = 'on' + capitalizedEvent; - const topEvent = 'top' + capitalizedEvent; - const type = { - phasedRegistrationNames: { - bubbled: onEvent, - captured: onEvent + 'Capture', - }, - dependencies: [topEvent], - }; - eventTypes[event] = type; - topLevelEventsToDispatchConfig[topEvent] = type; -}); - -// Only used in DEV for exhaustiveness validation. -const knownHTMLTopLevelTypes = [ - 'topAbort', - 'topCancel', - 'topCanPlay', - 'topCanPlayThrough', - 'topClose', - 'topDurationChange', - 'topEmptied', - 'topEncrypted', - 'topEnded', - 'topError', - 'topInput', - 'topInvalid', - 'topLoad', - 'topLoadedData', - 'topLoadedMetadata', - 'topLoadStart', - 'topPause', - 'topPlay', - 'topPlaying', - 'topProgress', - 'topRateChange', - 'topReset', - 'topSeeked', - 'topSeeking', - 'topStalled', - 'topSubmit', - 'topSuspend', - 'topTimeUpdate', - 'topToggle', - 'topVolumeChange', - 'topWaiting', -]; +function getDispatchConfig(topLevelType) { + if (!topLevelEventsToDispatchConfig[topLevelType]) { + const onEvent = `on${topLevelType.slice(3)}`; + topLevelEventsToDispatchConfig[topLevelType] = { + phasedRegistrationNames: { + bubbled: onEvent, + captured: `${onEvent}Capture`, + }, + dependencies: [topLevelType], + }; + } + return topLevelEventsToDispatchConfig[topLevelType]; +} const SimpleEventPlugin: PluginModule = { - eventTypes: eventTypes, - - extractEvents: function( + extractEvents( topLevelType: TopLevelTypes, targetInst: Fiber, nativeEvent: MouseEvent, nativeEventTarget: EventTarget, ): null | ReactSyntheticEvent { - const dispatchConfig = topLevelEventsToDispatchConfig[topLevelType]; - if (!dispatchConfig) { - return null; - } let EventConstructor; - switch (topLevelType) { - case 'topKeyPress': - // Firefox creates a keypress event for function keys too. This removes - // the unwanted keypress events. Enter is however both printable and - // non-printable. One would expect Tab to be as well (but it isn't). - if (getEventCharCode(nativeEvent) === 0) { - return null; - } - /* falls through */ - case 'topKeyDown': - case 'topKeyUp': - EventConstructor = SyntheticKeyboardEvent; + let nativeCtor = nativeEvent.constructor.name; + + switch (nativeCtor) { + case 'MouseEvent': + EventConstructor = SyntheticMouseEvent; break; - case 'topBlur': - case 'topFocus': + case 'FocusEvent': EventConstructor = SyntheticFocusEvent; break; - case 'topClick': - // Firefox creates a click event on right mouse clicks. This removes the - // unwanted click events. - if (nativeEvent.button === 2) { - return null; - } - /* falls through */ - case 'topDoubleClick': - case 'topMouseDown': - case 'topMouseMove': - case 'topMouseUp': - // TODO: Disabled elements should not respond to mouse events - /* falls through */ - case 'topMouseOut': - case 'topMouseOver': - case 'topContextMenu': - EventConstructor = SyntheticMouseEvent; - break; - case 'topDrag': - case 'topDragEnd': - case 'topDragEnter': - case 'topDragExit': - case 'topDragLeave': - case 'topDragOver': - case 'topDragStart': - case 'topDrop': - EventConstructor = SyntheticDragEvent; + case 'KeyboardEvent': + EventConstructor = SyntheticKeyboardEvent; break; - case 'topTouchCancel': - case 'topTouchEnd': - case 'topTouchMove': - case 'topTouchStart': - EventConstructor = SyntheticTouchEvent; + case 'TransitionEvent': + EventConstructor = SyntheticTransitionEvent; break; - case 'topAnimationEnd': - case 'topAnimationIteration': - case 'topAnimationStart': + case 'AnimationEvent': EventConstructor = SyntheticAnimationEvent; break; - case 'topTransitionEnd': - EventConstructor = SyntheticTransitionEvent; + case 'DragEvent': + EventConstructor = SyntheticDragEvent; break; - case 'topScroll': + case 'UIEvent': EventConstructor = SyntheticUIEvent; break; - case 'topWheel': - EventConstructor = SyntheticWheelEvent; - break; - case 'topCopy': - case 'topCut': - case 'topPaste': + case 'ClipboardEvent': EventConstructor = SyntheticClipboardEvent; break; + case 'TouchEvent': + EventConstructor = SyntheticTouchEvent; + break; + case 'WheelEvent': + EventConstructor = SyntheticWheelEvent; + break; default: - if (__DEV__) { - if (knownHTMLTopLevelTypes.indexOf(topLevelType) === -1) { - warning( - false, - 'SimpleEventPlugin: Unhandled event type, `%s`. This warning ' + - 'is likely caused by a bug in React. Please file an issue.', - topLevelType, - ); - } - } - // HTML Events - // @see http://www.w3.org/TR/html5/index.html#events-0 EventConstructor = SyntheticEvent; - break; } + + // Firefox creates a keypress event for function keys too. This removes + // the unwanted keypress events. Enter is however both printable and + // non-printable. One would expect Tab to be as well (but it isn't). + if (topLevelType === 'topKeyPress' && getEventCharCode(nativeEvent) === 0) { + return null; + } + // Firefox creates a click event on right mouse clicks. This removes the + // unwanted click events. + if (topLevelType === 'topKeyPress' && nativeEvent.button === 2) { + return null; + } + const event = EventConstructor.getPooled( - dispatchConfig, + getDispatchConfig(topLevelType), targetInst, nativeEvent, nativeEventTarget, diff --git a/packages/react-dom/src/shared/ReactDOMUnknownPropertyHook.js b/packages/react-dom/src/shared/ReactDOMUnknownPropertyHook.js index 399a7d89ce5..afc1d5cbb47 100644 --- a/packages/react-dom/src/shared/ReactDOMUnknownPropertyHook.js +++ b/packages/react-dom/src/shared/ReactDOMUnknownPropertyHook.js @@ -5,10 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { - registrationNameModules, - possibleRegistrationNames, -} from 'events/EventPluginRegistry'; +import {possibleRegistrationNames} from 'events/EventPluginRegistry'; import {ReactDebugCurrentFrame} from 'shared/ReactGlobalSharedState'; import warning from 'fbjs/lib/warning'; @@ -55,7 +52,7 @@ if (__DEV__) { // We can't rely on the event system being injected on the server. if (canUseEventSystem) { - if (registrationNameModules.hasOwnProperty(name)) { + if (name[0] === 'o' && name[1] === 'n') { return true; } const registrationName = possibleRegistrationNames.hasOwnProperty(