@samitouri / QOS-React-2 / commits / e33e32db04

Event API: normalize event timeStamp property to be in event system (#15598)

Dominic Gannaway committed May 9, 2019 at 16:05 UTC e33e32db040f19733d699d4b1bce6c20dcd7711e
8 files changed +80 -76
packages/react-dom/src/events/DOMEventResponderSystem.js
+21 -6
@@ -64,6 +64,7 @@ type ResponderTimer = {|
64 instance: ReactEventComponentInstance,
65 func: () => void,
66 id: Symbol,
67 + timeStamp: number,
68 |};
69
70 const activeTimeouts: Map<Symbol, ResponderTimeout> = new Map();
@@ -90,6 +91,7 @@ const responderOwners: Map<
91 > = new Map();
92 let globalOwner = null;
93
94 +let currentTimeStamp = 0;
95 let currentTimers = new Map();
96 let currentInstance: null | ReactEventComponentInstance = null;
97 let currentEventQueue: null | EventQueue = null;
@@ -101,11 +103,11 @@ const eventResponderContext: ReactResponderContext = {
103 {discrete}: ReactResponderDispatchEventOptions,
104 ): void {
105 validateResponderContext();
104 - const {target, type} = possibleEventObject;
106 + const {target, type, timeStamp} = possibleEventObject;
107
106 - if (target == null || type == null) {
108 + if (target == null || type == null || timeStamp == null) {
109 throw new Error(
108 - 'context.dispatchEvent: "target" and "type" fields on event object are required.',
110 + 'context.dispatchEvent: "target", "timeStamp", and "type" fields on event object are required.',
111 );
112 }
113 if (__DEV__) {
@@ -313,7 +315,7 @@ const eventResponderContext: ReactResponderContext = {
315 if (timeout === undefined) {
316 const timers = new Map();
317 const id = setTimeout(() => {
316 - processTimers(timers);
318 + processTimers(timers, delay);
319 }, delay);
320 timeout = {
321 id,
@@ -325,6 +327,7 @@ const eventResponderContext: ReactResponderContext = {
327 instance: ((currentInstance: any): ReactEventComponentInstance),
328 func,
329 id: timerId,
330 + timeStamp: currentTimeStamp,
331 });
332 activeTimeouts.set(timerId, timeout);
333 return timerId;
@@ -408,6 +411,9 @@ const eventResponderContext: ReactResponderContext = {
411 }
412 return currentTarget;
413 },
414 + getTimeStamp(): number {
415 + return currentTimeStamp;
416 + },
417 };
418
419 function isTargetWithinEventComponent(target: Element | Document): boolean {
@@ -478,13 +484,17 @@ function isFiberHostComponentFocusable(fiber: Fiber): boolean {
484 );
485 }
486
481 -function processTimers(timers: Map<Symbol, ResponderTimer>): void {
487 +function processTimers(
488 + timers: Map<Symbol, ResponderTimer>,
489 + delay: number,
490 +): void {
491 const timersArr = Array.from(timers.values());
492 currentEventQueue = createEventQueue();
493 try {
494 for (let i = 0; i < timersArr.length; i++) {
486 - const {instance, func, id} = timersArr[i];
495 + const {instance, func, id, timeStamp} = timersArr[i];
496 currentInstance = instance;
497 + currentTimeStamp = timeStamp + delay;
498 try {
499 func();
500 } finally {
@@ -496,6 +506,7 @@ function processTimers(timers: Map<Symbol, ResponderTimer>): void {
506 currentTimers = null;
507 currentInstance = null;
508 currentEventQueue = null;
509 + currentTimeStamp = 0;
510 }
511 }
512
@@ -861,8 +872,11 @@ export function dispatchEventForResponderEventSystem(
872 const previousEventQueue = currentEventQueue;
873 const previousInstance = currentInstance;
874 const previousTimers = currentTimers;
875 + const previousTimeStamp = currentTimeStamp;
876 currentTimers = null;
877 currentEventQueue = createEventQueue();
878 + // We might want to control timeStamp another way here
879 + currentTimeStamp = (nativeEvent: any).timeStamp;
880 try {
881 traverseAndHandleEventResponderInstances(
882 topLevelType,
@@ -876,6 +890,7 @@ export function dispatchEventForResponderEventSystem(
890 currentTimers = previousTimers;
891 currentInstance = previousInstance;
892 currentEventQueue = previousEventQueue;
893 + currentTimeStamp = previousTimeStamp;
894 }
895 }
896 }
packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js
+6
@@ -422,6 +422,7 @@ describe('DOMEventResponderSystem', () => {
422 target: event.target,
423 type: 'magicclick',
424 phase: 'bubble',
425 + timeStamp: context.getTimeStamp(),
426 };
427 context.dispatchEvent(syntheticEvent, props.onMagicClick, {
428 discrete: true,
@@ -434,6 +435,7 @@ describe('DOMEventResponderSystem', () => {
435 target: event.target,
436 type: 'magicclick',
437 phase: 'capture',
438 + timeStamp: context.getTimeStamp(),
439 };
440 context.dispatchEvent(syntheticEvent, props.onMagicClick, {
441 discrete: true,
@@ -477,6 +479,7 @@ describe('DOMEventResponderSystem', () => {
479 target: event.target,
480 type: 'press',
481 phase,
482 + timeStamp: context.getTimeStamp(),
483 };
484 context.dispatchEvent(pressEvent, props.onPress, {discrete: true});
485
@@ -486,6 +489,7 @@ describe('DOMEventResponderSystem', () => {
489 target: event.target,
490 type: 'longpress',
491 phase,
492 + timeStamp: context.getTimeStamp(),
493 };
494 context.dispatchEvent(longPressEvent, props.onLongPress, {
495 discrete: true,
@@ -497,6 +501,7 @@ describe('DOMEventResponderSystem', () => {
501 target: event.target,
502 type: 'longpresschange',
503 phase,
504 + timeStamp: context.getTimeStamp(),
505 };
506 context.dispatchEvent(longPressChangeEvent, props.onLongPressChange, {
507 discrete: true,
@@ -901,6 +906,7 @@ describe('DOMEventResponderSystem', () => {
906 const syntheticEvent = {
907 target: event.target,
908 type: 'click',
909 + timeStamp: context.getTimeStamp(),
910 };
911 context.dispatchEvent(syntheticEvent, props.onClick, {
912 discrete: true,
packages/react-events/src/Drag.js
+4 -1
@@ -50,11 +50,13 @@ type DragEventType = 'dragstart' | 'dragend' | 'dragchange' | 'dragmove';
50 type DragEvent = {|
51 target: Element | Document,
52 type: DragEventType,
53 + timeStamp: number,
54 diffX?: number,
55 diffY?: number,
56 |};
57
58 function createDragEvent(
59 + context: ReactResponderContext,
60 type: DragEventType,
61 target: Element | Document,
62 eventData?: EventData,
@@ -62,6 +64,7 @@ function createDragEvent(
64 return {
65 target,
66 type,
67 + timeStamp: context.getTimeStamp(),
68 ...eventData,
69 };
70 }
@@ -75,7 +78,7 @@ function dispatchDragEvent(
78 eventData?: EventData,
79 ): void {
80 const target = ((state.dragTarget: any): Element | Document);
78 - const syntheticEvent = createDragEvent(name, target, eventData);
81 + const syntheticEvent = createDragEvent(context, name, target, eventData);
82 context.dispatchEvent(syntheticEvent, listener, {discrete});
83 }
84
packages/react-events/src/Focus.js
+17 -6
@@ -33,6 +33,7 @@ type FocusEventType = 'focus' | 'blur' | 'focuschange' | 'focusvisiblechange';
33 type FocusEvent = {|
34 target: Element | Document,
35 type: FocusEventType,
36 + timeStamp: number,
37 |};
38
39 const targetEventTypes = [
@@ -56,12 +57,14 @@ const rootEventTypes = [
57 ];
58
59 function createFocusEvent(
60 + context: ReactResponderContext,
61 type: FocusEventType,
62 target: Element | Document,
63 ): FocusEvent {
64 return {
65 target,
66 type,
67 + timeStamp: context.getTimeStamp(),
68 };
69 }
70
@@ -72,21 +75,25 @@ function dispatchFocusInEvents(
75 ) {
76 const target = ((state.focusTarget: any): Element | Document);
77 if (props.onFocus) {
75 - const syntheticEvent = createFocusEvent('focus', target);
78 + const syntheticEvent = createFocusEvent(context, 'focus', target);
79 context.dispatchEvent(syntheticEvent, props.onFocus, {discrete: true});
80 }
81 if (props.onFocusChange) {
82 const listener = () => {
83 props.onFocusChange(true);
84 };
82 - const syntheticEvent = createFocusEvent('focuschange', target);
85 + const syntheticEvent = createFocusEvent(context, 'focuschange', target);
86 context.dispatchEvent(syntheticEvent, listener, {discrete: true});
87 }
88 if (props.onFocusVisibleChange && state.isLocalFocusVisible) {
89 const listener = () => {
90 props.onFocusVisibleChange(true);
91 };
89 - const syntheticEvent = createFocusEvent('focusvisiblechange', target);
92 + const syntheticEvent = createFocusEvent(
93 + context,
94 + 'focusvisiblechange',
95 + target,
96 + );
97 context.dispatchEvent(syntheticEvent, listener, {discrete: true});
98 }
99 }
@@ -98,14 +105,14 @@ function dispatchFocusOutEvents(
105 ) {
106 const target = ((state.focusTarget: any): Element | Document);
107 if (props.onBlur) {
101 - const syntheticEvent = createFocusEvent('blur', target);
108 + const syntheticEvent = createFocusEvent(context, 'blur', target);
109 context.dispatchEvent(syntheticEvent, props.onBlur, {discrete: true});
110 }
111 if (props.onFocusChange) {
112 const listener = () => {
113 props.onFocusChange(false);
114 };
108 - const syntheticEvent = createFocusEvent('focuschange', target);
115 + const syntheticEvent = createFocusEvent(context, 'focuschange', target);
116 context.dispatchEvent(syntheticEvent, listener, {discrete: true});
117 }
118 dispatchFocusVisibleOutEvent(context, props, state);
@@ -121,7 +128,11 @@ function dispatchFocusVisibleOutEvent(
128 const listener = () => {
129 props.onFocusVisibleChange(false);
130 };
124 - const syntheticEvent = createFocusEvent('focusvisiblechange', target);
131 + const syntheticEvent = createFocusEvent(
132 + context,
133 + 'focusvisiblechange',
134 + target,
135 + );
136 context.dispatchEvent(syntheticEvent, listener, {discrete: true});
137 state.isLocalFocusVisible = false;
138 }
packages/react-events/src/Hover.js
+7
@@ -42,6 +42,7 @@ type HoverEventType = 'hoverstart' | 'hoverend' | 'hoverchange' | 'hovermove';
42 type HoverEvent = {|
43 target: Element | Document,
44 type: HoverEventType,
45 + timeStamp: number,
46 |};
47
48 const DEFAULT_HOVER_END_DELAY_MS = 0;
@@ -60,12 +61,14 @@ if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
61 }
62
63 function createHoverEvent(
64 + context: ReactResponderContext,
65 type: HoverEventType,
66 target: Element | Document,
67 ): HoverEvent {
68 return {
69 target,
70 type,
71 + timeStamp: context.getTimeStamp(),
72 };
73 }
74
@@ -79,6 +82,7 @@ function dispatchHoverChangeEvent(
82 props.onHoverChange(bool);
83 };
84 const syntheticEvent = createHoverEvent(
85 + context,
86 'hoverchange',
87 ((state.hoverTarget: any): Element | Document),
88 );
@@ -115,6 +119,7 @@ function dispatchHoverStartEvents(
119
120 if (props.onHoverStart) {
121 const syntheticEvent = createHoverEvent(
122 + context,
123 'hoverstart',
124 ((target: any): Element | Document),
125 );
@@ -174,6 +179,7 @@ function dispatchHoverEndEvents(
179
180 if (props.onHoverEnd) {
181 const syntheticEvent = createHoverEvent(
182 + context,
183 'hoverend',
184 ((target: any): Element | Document),
185 );
@@ -311,6 +317,7 @@ const HoverResponder = {
317 } else {
318 if (props.onHoverMove && state.hoverTarget !== null) {
319 const syntheticEvent = createHoverEvent(
320 + context,
321 'hovermove',
322 state.hoverTarget,
323 );
packages/react-events/src/Press.js
+20 -62
@@ -84,7 +84,7 @@ type PressEvent = {|
84 target: Element | Document,
85 type: PressEventType,
86 pointerType: PointerType,
87 - timeStamp: null | number,
87 + timeStamp: number,
88 clientX: null | number,
89 clientY: null | number,
90 pageX: null | number,
@@ -135,23 +135,22 @@ if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
135 }
136
137 function createPressEvent(
138 + context: ReactResponderContext,
139 type: PressEventType,
140 target: Element | Document,
141 pointerType: PointerType,
142 event: ?ReactResponderEvent,
142 - delay: number,
143 ): PressEvent {
144 + const timeStamp = context.getTimeStamp();
145 let clientX = null;
146 let clientY = null;
147 let pageX = null;
148 let pageY = null;
149 let screenX = null;
150 let screenY = null;
150 - let timeStamp = null;
151
152 if (event) {
153 const nativeEvent = (event.nativeEvent: any);
154 - timeStamp = nativeEvent.timeStamp + delay;
154 // Only check for one property, checking for all of them is costly. We can assume
155 // if clientX exists, so do the rest.
156 let eventObject;
@@ -185,16 +184,15 @@ function dispatchEvent(
184 name: PressEventType,
185 listener: (e: Object) => void,
186 discrete: boolean,
188 - delay: number,
187 ): void {
188 const target = ((state.pressTarget: any): Element | Document);
189 const pointerType = state.pointerType;
190 const syntheticEvent = createPressEvent(
191 + context,
192 name,
193 target,
194 pointerType,
195 event,
197 - delay,
196 );
197 context.dispatchEvent(syntheticEvent, listener, {
198 discrete,
@@ -206,13 +204,12 @@ function dispatchPressChangeEvent(
204 context: ReactResponderContext,
205 props: PressProps,
206 state: PressState,
209 - delay: number,
207 ): void {
208 const bool = state.isActivePressed;
209 const listener = () => {
210 props.onPressChange(bool);
211 };
215 - dispatchEvent(event, context, state, 'presschange', listener, true, delay);
212 + dispatchEvent(event, context, state, 'presschange', listener, true);
213 }
214
215 function dispatchLongPressChangeEvent(
@@ -220,30 +217,15 @@ function dispatchLongPressChangeEvent(
217 context: ReactResponderContext,
218 props: PressProps,
219 state: PressState,
223 - delay: number,
220 ): void {
221 const bool = state.isLongPressed;
222 const listener = () => {
223 props.onLongPressChange(bool);
224 };
229 - dispatchEvent(
230 - event,
231 - context,
232 - state,
233 - 'longpresschange',
234 - listener,
235 - true,
236 - delay,
237 - );
225 + dispatchEvent(event, context, state, 'longpresschange', listener, true);
226 }
227
240 -function activate(
241 - event: ReactResponderEvent,
242 - context,
243 - props,
244 - state,
245 - delay: number,
246 -) {
228 +function activate(event: ReactResponderEvent, context, props, state) {
229 const nativeEvent: any = event.nativeEvent;
230 const {x, y} = getEventPageCoords(nativeEvent);
231 const wasActivePressed = state.isActivePressed;
@@ -263,41 +245,26 @@ function activate(
245 'pressstart',
246 props.onPressStart,
247 true,
266 - delay,
248 );
249 }
250 if (!wasActivePressed && props.onPressChange) {
270 - dispatchPressChangeEvent(event, context, props, state, delay);
251 + dispatchPressChangeEvent(event, context, props, state);
252 }
253 }
254
274 -function deactivate(
275 - event: ?ReactResponderEvent,
276 - context,
277 - props,
278 - state,
279 - delay: number,
280 -) {
255 +function deactivate(event: ?ReactResponderEvent, context, props, state) {
256 const wasLongPressed = state.isLongPressed;
257 state.isActivePressed = false;
258 state.isLongPressed = false;
259
260 if (props.onPressEnd) {
286 - dispatchEvent(
287 - event,
288 - context,
289 - state,
290 - 'pressend',
291 - props.onPressEnd,
292 - true,
293 - delay,
294 - );
261 + dispatchEvent(event, context, state, 'pressend', props.onPressEnd, true);
262 }
263 if (props.onPressChange) {
297 - dispatchPressChangeEvent(event, context, props, state, delay);
264 + dispatchPressChangeEvent(event, context, props, state);
265 }
266 if (wasLongPressed && props.onLongPressChange) {
300 - dispatchLongPressChangeEvent(event, context, props, state, delay);
267 + dispatchLongPressChangeEvent(event, context, props, state);
268 }
269 }
270
@@ -314,9 +281,9 @@ function dispatchPressStartEvents(
281 state.pressEndTimeout = null;
282 }
283
317 - const dispatch = (delay: number) => {
284 + const dispatch = () => {
285 state.isActivePressStart = true;
319 - activate(event, context, props, state, delay);
286 + activate(event, context, props, state);
287
288 if (
289 (props.onLongPress || props.onLongPressChange) &&
@@ -338,17 +305,10 @@ function dispatchPressStartEvents(
305 'longpress',
306 props.onLongPress,
307 true,
341 - delayLongPress + delay,
308 );
309 }
310 if (props.onLongPressChange) {
345 - dispatchLongPressChangeEvent(
346 - event,
347 - context,
348 - props,
349 - state,
350 - delayLongPress + delay,
351 - );
311 + dispatchLongPressChangeEvent(event, context, props, state);
312 }
313 }, delayLongPress);
314 }
@@ -363,10 +323,10 @@ function dispatchPressStartEvents(
323 if (delayPressStart > 0) {
324 state.pressStartTimeout = context.setTimeout(() => {
325 state.pressStartTimeout = null;
366 - dispatch(delayPressStart);
326 + dispatch();
327 }, delayPressStart);
328 } else {
369 - dispatch(0);
329 + dispatch();
330 }
331 }
332 }
@@ -394,7 +354,7 @@ function dispatchPressEndEvents(
354 // don't activate if a press has moved beyond the responder region
355 if (state.isPressWithinResponderRegion && event != null) {
356 // if we haven't yet activated (due to delays), activate now
397 - activate(event, context, props, state, 0);
357 + activate(event, context, props, state);
358 activationWasForced = true;
359 }
360 }
@@ -411,10 +371,10 @@ function dispatchPressEndEvents(
371 if (delayPressEnd > 0) {
372 state.pressEndTimeout = context.setTimeout(() => {
373 state.pressEndTimeout = null;
414 - deactivate(event, context, props, state, delayPressEnd);
374 + deactivate(event, context, props, state);
375 }, delayPressEnd);
376 } else {
417 - deactivate(event, context, props, state, 0);
377 + deactivate(event, context, props, state);
378 }
379 }
380 }
@@ -758,7 +718,6 @@ const PressResponder = {
718 'pressmove',
719 props.onPressMove,
720 false,
761 - 0,
721 );
722 }
723 if (
@@ -843,7 +802,6 @@ const PressResponder = {
802 'press',
803 props.onPress,
804 true,
846 - 0,
805 );
806 }
807 }
packages/react-events/src/Swipe.js
+4 -1
@@ -40,11 +40,13 @@ type SwipeEventType = 'swipeleft' | 'swiperight' | 'swipeend' | 'swipemove';
40 type SwipeEvent = {|
41 target: Element | Document,
42 type: SwipeEventType,
43 + timeStamp: number,
44 diffX?: number,
45 diffY?: number,
46 |};
47
48 function createSwipeEvent(
49 + context: ReactResponderContext,
50 type: SwipeEventType,
51 target: Element | Document,
52 eventData?: EventData,
@@ -52,6 +54,7 @@ function createSwipeEvent(
54 return {
55 target,
56 type,
57 + timeStamp: context.getTimeStamp(),
58 ...eventData,
59 };
60 }
@@ -65,7 +68,7 @@ function dispatchSwipeEvent(
68 eventData?: EventData,
69 ) {
70 const target = ((state.swipeTarget: any): Element | Document);
68 - const syntheticEvent = createSwipeEvent(name, target, eventData);
71 + const syntheticEvent = createSwipeEvent(context, name, target, eventData);
72 context.dispatchEvent(syntheticEvent, listener, {discrete});
73 }
74
packages/shared/ReactTypes.js
+1
@@ -193,4 +193,5 @@ export type ReactResponderContext = {
193 event: ReactResponderEvent,
194 ): '' | 'mouse' | 'keyboard' | 'pen' | 'touch',
195 getEventCurrentTarget(event: ReactResponderEvent): Element,
196 + getTimeStamp: () => number,
197 };