Skip to content

Commit 97fce31

Browse files
authored
Experiment: Infer the current event priority from the native event (#20748)
* Add the feature flag * Add a host config method * Wire it up to the work loop * Export constants for third-party renderers * Document for third-party renderers
1 parent 6c526c5 commit 97fce31

24 files changed

+179
-11
lines changed

packages/react-art/src/ReactARTHostConfig.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,18 @@
77

88
import Transform from 'art/core/transform';
99
import Mode from 'art/modes/current';
10+
import {enableNewReconciler} from 'shared/ReactFeatureFlags';
1011
import invariant from 'shared/invariant';
1112

1213
import {TYPES, EVENT_TYPES, childrenAsString} from './ReactARTInternals';
1314

15+
import {DefaultLanePriority as DefaultLanePriority_old} from 'react-reconciler/src/ReactFiberLane.old';
16+
import {DefaultLanePriority as DefaultLanePriority_new} from 'react-reconciler/src/ReactFiberLane.new';
17+
18+
const DefaultLanePriority = enableNewReconciler
19+
? DefaultLanePriority_new
20+
: DefaultLanePriority_old;
21+
1422
const pooledTransform = new Transform();
1523

1624
const NO_CONTEXT = {};
@@ -340,6 +348,10 @@ export function shouldSetTextContent(type, props) {
340348
);
341349
}
342350

351+
export function getCurrentEventPriority() {
352+
return DefaultLanePriority;
353+
}
354+
343355
// The ART renderer is secondary to the React DOM renderer.
344356
export const isPrimaryRenderer = false;
345357

packages/react-dom/src/client/ReactDOMHostConfig.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import {validateDOMNesting, updatedAncestorInfo} from './validateDOMNesting';
4747
import {
4848
isEnabled as ReactBrowserEventEmitterIsEnabled,
4949
setEnabled as ReactBrowserEventEmitterSetEnabled,
50+
getEventPriority,
5051
} from '../events/ReactDOMEventListener';
5152
import {getChildNamespace} from '../shared/DOMNamespaces';
5253
import {
@@ -65,10 +66,18 @@ import {
6566
enableSuspenseServerRenderer,
6667
enableCreateEventHandleAPI,
6768
enableScopeAPI,
69+
enableNewReconciler,
6870
} from 'shared/ReactFeatureFlags';
6971
import {HostComponent, HostText} from 'react-reconciler/src/ReactWorkTags';
7072
import {listenToAllSupportedEvents} from '../events/DOMPluginEventSystem';
7173

74+
import {DefaultLanePriority as DefaultLanePriority_old} from 'react-reconciler/src/ReactFiberLane.old';
75+
import {DefaultLanePriority as DefaultLanePriority_new} from 'react-reconciler/src/ReactFiberLane.new';
76+
77+
const DefaultLanePriority = enableNewReconciler
78+
? DefaultLanePriority_new
79+
: DefaultLanePriority_old;
80+
7281
export type Type = string;
7382
export type Props = {
7483
autoFocus?: boolean,
@@ -372,6 +381,14 @@ export function createTextInstance(
372381
return textNode;
373382
}
374383

384+
export function getCurrentEventPriority(): * {
385+
const currentEvent = window.event;
386+
if (currentEvent === undefined) {
387+
return DefaultLanePriority;
388+
}
389+
return getEventPriority(currentEvent.type);
390+
}
391+
375392
export const isPrimaryRenderer = true;
376393
export const warnsIfNotActing = true;
377394
// This initialization code may run even on server environments

packages/react-dom/src/events/ReactDOMEventListener.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ export function attemptToDispatchEvent(
348348
return null;
349349
}
350350

351-
function getEventPriority(domEventName: DOMEventName) {
351+
export function getEventPriority(domEventName: DOMEventName): * {
352352
switch (domEventName) {
353353
// Used by SimpleEventPlugin:
354354
case 'cancel':

packages/react-native-renderer/src/ReactFabricHostConfig.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,18 @@ import type {
2121
import {mountSafeCallback_NOT_REALLY_SAFE} from './NativeMethodsMixinUtils';
2222
import {create, diff} from './ReactNativeAttributePayload';
2323

24+
import {enableNewReconciler} from 'shared/ReactFeatureFlags';
2425
import invariant from 'shared/invariant';
2526

2627
import {dispatchEvent} from './ReactFabricEventEmitter';
2728

29+
import {DefaultLanePriority as DefaultLanePriority_old} from 'react-reconciler/src/ReactFiberLane.old';
30+
import {DefaultLanePriority as DefaultLanePriority_new} from 'react-reconciler/src/ReactFiberLane.new';
31+
32+
const DefaultLanePriority = enableNewReconciler
33+
? DefaultLanePriority_new
34+
: DefaultLanePriority_old;
35+
2836
// Modules provided by RN:
2937
import {
3038
ReactNativeViewConfigRegistry,
@@ -339,6 +347,10 @@ export function shouldSetTextContent(type: string, props: Props): boolean {
339347
return false;
340348
}
341349

350+
export function getCurrentEventPriority(): * {
351+
return DefaultLanePriority;
352+
}
353+
342354
// The Fabric renderer is secondary to the existing React Native renderer.
343355
export const isPrimaryRenderer = false;
344356

packages/react-native-renderer/src/ReactNativeHostConfig.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import type {TouchedViewDataAtPoint} from './ReactNativeTypes';
1111

1212
import invariant from 'shared/invariant';
13+
import {enableNewReconciler} from 'shared/ReactFeatureFlags';
1314

1415
// Modules provided by RN:
1516
import {
@@ -26,6 +27,13 @@ import {
2627
} from './ReactNativeComponentTree';
2728
import ReactNativeFiberHostComponent from './ReactNativeFiberHostComponent';
2829

30+
import {DefaultLanePriority as DefaultLanePriority_old} from 'react-reconciler/src/ReactFiberLane.old';
31+
import {DefaultLanePriority as DefaultLanePriority_new} from 'react-reconciler/src/ReactFiberLane.new';
32+
33+
const DefaultLanePriority = enableNewReconciler
34+
? DefaultLanePriority_new
35+
: DefaultLanePriority_old;
36+
2937
const {get: getViewConfigForType} = ReactNativeViewConfigRegistry;
3038

3139
export type Type = string;
@@ -261,6 +269,10 @@ export function shouldSetTextContent(type: string, props: Props): boolean {
261269
return false;
262270
}
263271

272+
export function getCurrentEventPriority(): * {
273+
return DefaultLanePriority;
274+
}
275+
264276
// -------------------
265277
// Mutation
266278
// -------------------

packages/react-noop-renderer/src/createReactNoop.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,10 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
391391

392392
resetAfterCommit(): void {},
393393

394+
getCurrentEventPriority() {
395+
return NoopRenderer.DefaultEventPriority;
396+
},
397+
394398
now: Scheduler.unstable_now,
395399

396400
isPrimaryRenderer: true,

packages/react-reconciler/README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,32 @@ You can proxy this to `queueMicrotask` or its equivalent in your environment.
211211

212212
This is a property (not a function) that should be set to `true` if your renderer is the main one on the page. For example, if you're writing a renderer for the Terminal, it makes sense to set it to `true`, but if your renderer is used *on top of* React DOM or some other existing renderer, set it to `false`.
213213

214+
#### `getCurrentEventPriority`
215+
216+
To implement this method, you'll need some constants available on the _returned_ `Renderer` object:
217+
218+
```js
219+
const HostConfig = {
220+
// ...
221+
getCurrentEventPriority() {
222+
return MyRenderer.DefaultEventPriority;
223+
},
224+
// ...
225+
}
226+
227+
const MyRenderer = Reconciler(HostConfig);
228+
```
229+
230+
The constant you return depends on which event, if any, is being handled right now. (In the browser, you can check this using `window.event && window.event.type`).
231+
232+
* **Discrete events:** If the active event is _directly caused by the user_ (such as mouse and keyboard events) and _each event in a sequence is intentional_ (e.g. `click`), return `MyRenderer.DiscreteEventPriority`. This tells React that they should interrupt any background work and cannot be batched across time.
233+
234+
* **Continuous events:** If the active event is _directly caused by the user_ but _the user can't distinguish between individual events in a sequence_ (e.g. `mouseover`), return `MyRenderer.ContinuousEventPriority`. This tells React they should interrupt any background work but can be batched across time.
235+
236+
* **Other events / No active event:** In all other cases, return `MyRenderer.DefaultEventPriority`. This tells React that this event is considered background work, and interactive events will be prioritized over it.
237+
238+
You can consult the `getCurrentEventPriority()` implementation in `ReactDOMHostConfig.js` for a reference implementation.
239+
214240
### Mutation Methods
215241

216242
If you're using React in mutation mode (you probably do), you'll need to implement a few more methods.

packages/react-reconciler/src/ReactFiberReconciler.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ import {
5252
registerMutableSourceForHydration as registerMutableSourceForHydration_old,
5353
runWithPriority as runWithPriority_old,
5454
getCurrentUpdateLanePriority as getCurrentUpdateLanePriority_old,
55+
DefaultEventPriority as DefaultEventPriority_old,
56+
DiscreteEventPriority as DiscreteEventPriority_old,
57+
ContinuousEventPriority as ContinuousEventPriority_old,
5558
} from './ReactFiberReconciler.old';
5659

5760
import {
@@ -92,6 +95,9 @@ import {
9295
registerMutableSourceForHydration as registerMutableSourceForHydration_new,
9396
runWithPriority as runWithPriority_new,
9497
getCurrentUpdateLanePriority as getCurrentUpdateLanePriority_new,
98+
DefaultEventPriority as DefaultEventPriority_new,
99+
DiscreteEventPriority as DiscreteEventPriority_new,
100+
ContinuousEventPriority as ContinuousEventPriority_new,
95101
} from './ReactFiberReconciler.new';
96102

97103
export const createContainer = enableNewReconciler
@@ -168,6 +174,15 @@ export const createPortal = enableNewReconciler
168174
export const createComponentSelector = enableNewReconciler
169175
? createComponentSelector_new
170176
: createComponentSelector_old;
177+
export const DefaultEventPriority = enableNewReconciler
178+
? DefaultEventPriority_new
179+
: DefaultEventPriority_old;
180+
export const DiscreteEventPriority = enableNewReconciler
181+
? DiscreteEventPriority_new
182+
: DiscreteEventPriority_old;
183+
export const ContinuousEventPriority = enableNewReconciler
184+
? ContinuousEventPriority_new
185+
: ContinuousEventPriority_old;
171186

172187
//TODO: "psuedo" is spelled "pseudo"
173188
export const createHasPsuedoClassSelector = enableNewReconciler

packages/react-reconciler/src/ReactFiberReconciler.new.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ import {
9494
} from './ReactFiberHotReloading.new';
9595
import {markRenderScheduled} from './SchedulingProfiler';
9696

97+
// Ideally host configs would import these constants from the reconciler
98+
// entry point, but we can't do this because of a circular dependency.
99+
// They are used by third-party renderers so they need to stay up to date.
100+
export {
101+
InputDiscreteLanePriority as DiscreteEventPriority,
102+
InputContinuousLanePriority as ContinuousEventPriority,
103+
DefaultLanePriority as DefaultEventPriority,
104+
} from './ReactFiberLane.new';
105+
97106
export {registerMutableSourceForHydration} from './ReactMutableSource.new';
98107
export {createPortal} from './ReactPortal';
99108
export {

packages/react-reconciler/src/ReactFiberReconciler.old.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ import {
9494
} from './ReactFiberHotReloading.old';
9595
import {markRenderScheduled} from './SchedulingProfiler';
9696

97+
// Ideally host configs would import these constants from the reconciler
98+
// entry point, but we can't do this because of a circular dependency.
99+
// They are used by third-party renderers so they need to stay up to date.
100+
export {
101+
InputDiscreteLanePriority as DiscreteEventPriority,
102+
InputContinuousLanePriority as ContinuousEventPriority,
103+
DefaultLanePriority as DefaultEventPriority,
104+
} from './ReactFiberLane.old';
105+
97106
export {registerMutableSourceForHydration} from './ReactMutableSource.new';
98107
export {createPortal} from './ReactPortal';
99108
export {

packages/react-reconciler/src/ReactFiberWorkLoop.new.js

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
enableDoubleInvokingEffects,
3636
skipUnmountedBoundaries,
3737
enableTransitionEntanglement,
38+
enableNativeEventPriorityInference,
3839
} from 'shared/ReactFeatureFlags';
3940
import ReactSharedInternals from 'shared/ReactSharedInternals';
4041
import invariant from 'shared/invariant';
@@ -94,6 +95,7 @@ import {
9495
afterActiveInstanceBlur,
9596
clearContainer,
9697
scheduleMicrotask,
98+
getCurrentEventPriority,
9799
} from './ReactFiberHostConfig';
98100

99101
import {
@@ -461,11 +463,23 @@ export function requestUpdateLane(fiber: Fiber): Lane {
461463
const currentLanePriority = getCurrentUpdateLanePriority();
462464
lane = findUpdateLane(currentLanePriority, currentEventWipLanes);
463465
} else {
464-
const schedulerLanePriority = schedulerPriorityToLanePriority(
465-
schedulerPriority,
466-
);
467-
468-
lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
466+
if (enableNativeEventPriorityInference) {
467+
const eventLanePriority = getCurrentEventPriority();
468+
if (eventLanePriority === DefaultLanePriority) {
469+
// TODO: move this case into the ReactDOM host config.
470+
const schedulerLanePriority = schedulerPriorityToLanePriority(
471+
schedulerPriority,
472+
);
473+
lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
474+
} else {
475+
lane = findUpdateLane(eventLanePriority, currentEventWipLanes);
476+
}
477+
} else {
478+
const schedulerLanePriority = schedulerPriorityToLanePriority(
479+
schedulerPriority,
480+
);
481+
lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
482+
}
469483
}
470484

471485
return lane;

packages/react-reconciler/src/ReactFiberWorkLoop.old.js

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
enableDoubleInvokingEffects,
3636
skipUnmountedBoundaries,
3737
enableTransitionEntanglement,
38+
enableNativeEventPriorityInference,
3839
} from 'shared/ReactFeatureFlags';
3940
import ReactSharedInternals from 'shared/ReactSharedInternals';
4041
import invariant from 'shared/invariant';
@@ -94,6 +95,7 @@ import {
9495
afterActiveInstanceBlur,
9596
clearContainer,
9697
scheduleMicrotask,
98+
getCurrentEventPriority,
9799
} from './ReactFiberHostConfig';
98100

99101
import {
@@ -461,11 +463,23 @@ export function requestUpdateLane(fiber: Fiber): Lane {
461463
const currentLanePriority = getCurrentUpdateLanePriority();
462464
lane = findUpdateLane(currentLanePriority, currentEventWipLanes);
463465
} else {
464-
const schedulerLanePriority = schedulerPriorityToLanePriority(
465-
schedulerPriority,
466-
);
467-
468-
lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
466+
if (enableNativeEventPriorityInference) {
467+
const eventLanePriority = getCurrentEventPriority();
468+
if (eventLanePriority === DefaultLanePriority) {
469+
// TODO: move this case into the ReactDOM host config.
470+
const schedulerLanePriority = schedulerPriorityToLanePriority(
471+
schedulerPriority,
472+
);
473+
lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
474+
} else {
475+
lane = findUpdateLane(eventLanePriority, currentEventWipLanes);
476+
}
477+
} else {
478+
const schedulerLanePriority = schedulerPriorityToLanePriority(
479+
schedulerPriority,
480+
);
481+
lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
482+
}
469483
}
470484

471485
return lane;

packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export const afterActiveInstanceBlur = $$$hostConfig.afterActiveInstanceBlur;
7373
export const preparePortalMount = $$$hostConfig.preparePortalMount;
7474
export const prepareScopeUpdate = $$$hostConfig.preparePortalMount;
7575
export const getInstanceFromScope = $$$hostConfig.getInstanceFromScope;
76+
export const getCurrentEventPriority = $$$hostConfig.getCurrentEventPriority;
7677

7778
// -------------------
7879
// Test selectors

packages/react-test-renderer/src/ReactTestHostConfig.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@
88
*/
99

1010
import {REACT_OPAQUE_ID_TYPE} from 'shared/ReactSymbols';
11+
import {enableNewReconciler} from 'shared/ReactFeatureFlags';
12+
13+
import {DefaultLanePriority as DefaultLanePriority_old} from 'react-reconciler/src/ReactFiberLane.old';
14+
import {DefaultLanePriority as DefaultLanePriority_new} from 'react-reconciler/src/ReactFiberLane.new';
15+
16+
const DefaultLanePriority = enableNewReconciler
17+
? DefaultLanePriority_new
18+
: DefaultLanePriority_old;
1119

1220
export type Type = string;
1321
export type Props = Object;
@@ -213,6 +221,10 @@ export function createTextInstance(
213221
};
214222
}
215223

224+
export function getCurrentEventPriority(): * {
225+
return DefaultLanePriority;
226+
}
227+
216228
export const isPrimaryRenderer = false;
217229
export const warnsIfNotActing = true;
218230

packages/shared/ReactFeatureFlags.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,5 @@ export const disableSchedulerTimeoutInWorkLoop = false;
151151
export const enableTransitionEntanglement = false;
152152

153153
export const enableDiscreteEventMicroTasks = false;
154+
155+
export const enableNativeEventPriorityInference = false;

packages/shared/forks/ReactFeatureFlags.native-fb.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export const enableRecursiveCommitTraversal = false;
5959
export const disableSchedulerTimeoutInWorkLoop = false;
6060
export const enableTransitionEntanglement = false;
6161
export const enableDiscreteEventMicroTasks = false;
62+
export const enableNativeEventPriorityInference = false;
6263

6364
// Flow magic to verify the exports of this file match the original version.
6465
// eslint-disable-next-line no-unused-vars

0 commit comments

Comments
 (0)