Compare commits

...

12 Commits

Author SHA1 Message Date
Tianyu Yao a9537b1249 Unset flag on finish 2022-11-04 19:57:58 -07:00
Tianyu Yao 0e7bde4290 set flag more correctly 2022-11-03 22:22:41 -07:00
Tianyu Yao 38802826ec Interrupt transition 2022-11-03 17:50:35 -07:00
Tianyu Yao cad26da39c fix build 2022-10-25 14:40:39 -07:00
Tianyu Yao 4f7c7d8dea Fix remaining tests 2022-10-21 19:26:47 -07:00
Tianyu Yao 9d8c26e1d3 Add feature flag 2022-10-20 18:22:26 -07:00
Tianyu Yao 9f894c2c63 Unify Default and Sync lane 2022-10-20 10:34:08 -07:00
Tianyu Yao 8cf2534491 Use frameAligned for DefaultUpdate 2022-10-11 17:50:49 -07:00
Rick Hanlon 287acc7f8e Re-use rAF, don't re-schedule it 2022-10-11 16:30:48 -07:00
Rick Hanlon 238402d713 Add frame-end scheduling (V3) 2022-10-11 16:30:44 -07:00
Tianyu Yao 651346967b Attach updatePriority to root 2022-10-11 16:14:42 -07:00
Tianyu Yao cfed71511a Separate EventPriority from Lane 2022-10-10 17:25:42 -07:00
60 changed files with 1559 additions and 345 deletions

View File

@ -144,6 +144,10 @@ function flushActWork(resolve, reject) {
// $FlowFixMe: Flow doesn't know about global Jest object
jest.runOnlyPendingTimers();
if (Scheduler.unstable_hasPendingWork()) {
// Flush scheduled rAF.
if (global.flushRequestAnimationFrameQueue) {
global.flushRequestAnimationFrameQueue();
}
// Committing a fallback scheduled additional work. Continue flushing.
flushActWork(resolve, reject);
return;

View File

@ -78,6 +78,7 @@ import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities';
// TODO: Remove this deep import when we delete the legacy root API
import {ConcurrentMode, NoMode} from 'react-reconciler/src/ReactTypeOfMode';
import * as Scheduler from 'scheduler';
import {
prepareToRenderResources,
@ -393,6 +394,11 @@ const localRequestAnimationFrame =
typeof requestAnimationFrame === 'function'
? requestAnimationFrame
: scheduleTimeout;
const localCancelAnimationFrame =
typeof window !== 'undefined' &&
typeof window.cancelAnimationFrame === 'function'
? window.cancelAnimationFrame
: cancelTimeout;
// -------------------
// Microtasks
// -------------------
@ -408,6 +414,60 @@ export const scheduleMicrotask: any =
.catch(handleErrorInNextTick)
: scheduleTimeout; // TODO: Determine the best fallback here.
export const supportsFrameAlignedTask = true;
type FrameAlignedTask = {|
rafNode: AnimationFrameID,
schedulerNode: number,
task: function,
|};
let currentTask: FrameAlignedTask | null = null;
function performFrameAlignedWork() {
const constCurrentTask = currentTask;
if (constCurrentTask != null) {
const task = constCurrentTask.task;
localCancelAnimationFrame(constCurrentTask.rafNode);
Scheduler.unstable_cancelCallback(constCurrentTask.schedulerNode);
currentTask = null;
if (task != null) {
task();
}
}
}
export function scheduleFrameAlignedTask(task: any): any {
if (currentTask === null) {
const rafNode = localRequestAnimationFrame(performFrameAlignedWork);
const schedulerNode = Scheduler.unstable_scheduleCallback(
Scheduler.unstable_NormalPriority,
performFrameAlignedWork,
);
currentTask = {
rafNode,
schedulerNode,
task,
};
} else {
currentTask.task = task;
currentTask.schedulerNode = Scheduler.unstable_scheduleCallback(
Scheduler.unstable_NormalPriority,
performFrameAlignedWork,
);
}
return currentTask;
}
export function cancelFrameAlignedTask(task: any) {
Scheduler.unstable_cancelCallback(task.schedulerNode);
// We don't cancel the rAF in case it gets re-used later.
// But clear the task so if it fires and shouldn't run, it won't.
task.task = null;
}
function handleErrorInNextTick(error) {
setTimeout(() => {
throw error;

View File

@ -160,7 +160,7 @@ describe('ReactDOMFiberAsync', () => {
handleChange = e => {
const nextValue = e.target.value;
requestIdleCallback(() => {
React.startTransition(() => {
this.setState({
asyncValue: nextValue,
});
@ -274,18 +274,32 @@ describe('ReactDOMFiberAsync', () => {
expect(container.textContent).toEqual('');
expect(ops).toEqual([]);
});
// Only the active updates have flushed
expect(container.textContent).toEqual('BC');
expect(ops).toEqual(['BC']);
if (gate(flags => flags.enableUnifiedSyncLane)) {
// DefaultUpdates are batched on the sync lane
expect(container.textContent).toEqual('ABC');
expect(ops).toEqual(['ABC']);
} else {
// Only the active updates have flushed
expect(container.textContent).toEqual('BC');
expect(ops).toEqual(['BC']);
}
instance.push('D');
expect(container.textContent).toEqual('BC');
expect(ops).toEqual(['BC']);
if (gate(flags => flags.enableUnifiedSyncLane)) {
expect(container.textContent).toEqual('ABC');
expect(ops).toEqual(['ABC']);
} else {
expect(container.textContent).toEqual('BC');
expect(ops).toEqual(['BC']);
}
// Flush the async updates
Scheduler.unstable_flushAll();
expect(container.textContent).toEqual('ABCD');
expect(ops).toEqual(['BC', 'ABCD']);
if (gate(flags => flags.enableUnifiedSyncLane)) {
expect(ops).toEqual(['ABC', 'ABCD']);
} else {
expect(ops).toEqual(['BC', 'ABCD']);
}
});
// @gate www
@ -545,6 +559,406 @@ describe('ReactDOMFiberAsync', () => {
// Therefore the form should have been submitted.
expect(formSubmitted).toBe(true);
});
// @gate enableFrameEndScheduling
it('Unknown update followed by default update is batched, scheduled in a rAF', () => {
let setState = null;
let counterRef = null;
function Counter() {
const [count, setCount] = React.useState(0);
const ref = React.useRef();
setState = setCount;
counterRef = ref;
Scheduler.unstable_yieldValue('Count: ' + count);
return <p ref={ref}>Count: {count}</p>;
}
const root = ReactDOMClient.createRoot(container);
act(() => {
root.render(<Counter />);
});
expect(Scheduler).toHaveYielded(['Count: 0']);
window.event = undefined;
setState(1);
// Unknown updates should schedule a rAF.
expect(global.requestAnimationFrameQueue.length).toBe(1);
window.event = 'test';
setState(2);
// Default updates after unknown should re-use the scheduled rAF.
expect(global.requestAnimationFrameQueue.length).toBe(1);
expect(Scheduler).toHaveYielded([]);
expect(counterRef.current.textContent).toBe('Count: 0');
global.flushRequestAnimationFrameQueue();
expect(Scheduler).toHaveYielded(['Count: 2']);
expect(counterRef.current.textContent).toBe('Count: 2');
});
// @gate enableFrameEndScheduling
it('Unknown update followed by default update is batched, scheduled in a task', () => {
let setState = null;
let counterRef = null;
function Counter() {
const [count, setCount] = React.useState(0);
const ref = React.useRef();
setState = setCount;
counterRef = ref;
Scheduler.unstable_yieldValue('Count: ' + count);
return <p ref={ref}>Count: {count}</p>;
}
const root = ReactDOMClient.createRoot(container);
act(() => {
root.render(<Counter />);
});
expect(Scheduler).toHaveYielded(['Count: 0']);
window.event = undefined;
setState(1);
// Unknown updates should schedule a rAF.
expect(global.requestAnimationFrameQueue.length).toBe(1);
window.event = 'test';
setState(2);
// Default updates after unknown should re-use the scheduled rAF.
expect(global.requestAnimationFrameQueue.length).toBe(1);
expect(Scheduler).toHaveYielded([]);
expect(counterRef.current.textContent).toBe('Count: 0');
expect(Scheduler).toFlushAndYield(['Count: 2']);
expect(counterRef.current.textContent).toBe('Count: 2');
});
// @gate enableFrameEndScheduling
it('Should re-use scheduled rAF, not cancel and schedule anew', () => {
let setState = null;
let counterRef = null;
function Counter() {
const [count, setCount] = React.useState(0);
const ref = React.useRef();
setState = setCount;
counterRef = ref;
Scheduler.unstable_yieldValue('Count: ' + count);
return <p ref={ref}>Count: {count}</p>;
}
const root = ReactDOMClient.createRoot(container);
act(() => {
root.render(<Counter />);
});
expect(Scheduler).toHaveYielded(['Count: 0']);
window.event = undefined;
setState(1);
// Unknown updates should schedule a rAF.
expect(global.requestAnimationFrameQueue.length).toBe(1);
const firstRaf = global.requestAnimationFrameQueue[0];
setState(2);
// Default updates after unknown should re-use the scheduled rAF.
expect(global.requestAnimationFrameQueue.length).toBe(1);
const secondRaf = global.requestAnimationFrameQueue[0];
expect(firstRaf).toBe(secondRaf);
expect(Scheduler).toHaveYielded([]);
expect(counterRef.current.textContent).toBe('Count: 0');
global.flushRequestAnimationFrameQueue();
expect(Scheduler).toHaveYielded(['Count: 2']);
expect(counterRef.current.textContent).toBe('Count: 2');
});
// @gate enableFrameEndScheduling
it('Default update followed by an unknown update is batched, scheduled in a rAF', () => {
let setState = null;
let counterRef = null;
function Counter() {
const [count, setCount] = React.useState(0);
const ref = React.useRef();
setState = setCount;
counterRef = ref;
Scheduler.unstable_yieldValue('Count: ' + count);
return <p ref={ref}>Count: {count}</p>;
}
const root = ReactDOMClient.createRoot(container);
act(() => {
root.render(<Counter />);
});
expect(Scheduler).toHaveYielded(['Count: 0']);
window.event = 'test';
setState(1);
// We should schedule a rAF for default updates.
expect(global.requestAnimationFrameQueue.length).toBe(1);
window.event = undefined;
setState(2);
// Unknown updates should schedule a rAF.
expect(global.requestAnimationFrameQueue.length).toBe(1);
expect(Scheduler).toHaveYielded([]);
expect(counterRef.current.textContent).toBe('Count: 0');
global.flushRequestAnimationFrameQueue();
expect(Scheduler).toHaveYielded(['Count: 2']);
expect(counterRef.current.textContent).toBe('Count: 2');
});
// @gate enableFrameEndScheduling
it('Default update followed by unknown update is batched, scheduled in a task', () => {
let setState = null;
let counterRef = null;
function Counter() {
const [count, setCount] = React.useState(0);
const ref = React.useRef();
setState = setCount;
counterRef = ref;
Scheduler.unstable_yieldValue('Count: ' + count);
return <p ref={ref}>Count: {count}</p>;
}
const root = ReactDOMClient.createRoot(container);
act(() => {
root.render(<Counter />);
});
expect(Scheduler).toHaveYielded(['Count: 0']);
window.event = 'test';
setState(1);
// We should schedule a rAF for default updates.
expect(global.requestAnimationFrameQueue.length).toBe(1);
window.event = undefined;
setState(2);
expect(global.requestAnimationFrameQueue.length).toBe(1);
expect(Scheduler).toHaveYielded([]);
expect(counterRef.current.textContent).toBe('Count: 0');
expect(Scheduler).toFlushAndYield(['Count: 2']);
expect(counterRef.current.textContent).toBe('Count: 2');
});
// @gate enableFrameEndScheduling || !allowConcurrentByDefault
it('When allowConcurrentByDefault is enabled, unknown updates should not be time sliced', () => {
let setState = null;
let counterRef = null;
function Counter() {
const [count, setCount] = React.useState(0);
const ref = React.useRef();
setState = setCount;
counterRef = ref;
Scheduler.unstable_yieldValue('Count: ' + count);
return <p ref={ref}>Count: {count}</p>;
}
const root = ReactDOMClient.createRoot(container, {
unstable_concurrentUpdatesByDefault: true,
});
act(() => {
root.render(<Counter />);
});
expect(Scheduler).toHaveYielded(['Count: 0']);
window.event = undefined;
setState(1);
expect(Scheduler).toFlushAndYieldThrough(['Count: 1']);
expect(counterRef.current.textContent).toBe('Count: 1');
});
// @gate enableFrameEndScheduling || !allowConcurrentByDefault
it('When allowConcurrentByDefault is enabled, unknown updates should not be time sliced event with default first', () => {
let setState = null;
let counterRef = null;
function Counter() {
const [count, setCount] = React.useState(0);
const ref = React.useRef();
setState = setCount;
counterRef = ref;
Scheduler.unstable_yieldValue('Count: ' + count);
return <p ref={ref}>Count: {count}</p>;
}
const root = ReactDOMClient.createRoot(container, {
unstable_concurrentUpdatesByDefault: true,
});
act(() => {
root.render(<Counter />);
});
expect(Scheduler).toHaveYielded(['Count: 0']);
window.event = 'test';
setState(1);
window.event = undefined;
setState(2);
expect(Scheduler).toFlushAndYieldThrough(['Count: 2']);
expect(counterRef.current.textContent).toBe('Count: 2');
});
// @gate enableFrameEndScheduling || !allowConcurrentByDefault
it('When allowConcurrentByDefault is enabled, unknown updates should not be time sliced event with default after', () => {
let setState = null;
let counterRef = null;
function Counter() {
const [count, setCount] = React.useState(0);
const ref = React.useRef();
setState = setCount;
counterRef = ref;
Scheduler.unstable_yieldValue('Count: ' + count);
return <p ref={ref}>Count: {count}</p>;
}
const root = ReactDOMClient.createRoot(container, {
unstable_concurrentUpdatesByDefault: true,
});
act(() => {
root.render(<Counter />);
});
expect(Scheduler).toHaveYielded(['Count: 0']);
window.event = undefined;
setState(1);
window.event = 'test';
setState(2);
expect(Scheduler).toFlushAndYieldThrough(['Count: 2']);
expect(counterRef.current.textContent).toBe('Count: 2');
});
// @gate enableFrameEndScheduling
it('unknown updates should be rescheduled in rAF after a higher priority update', async () => {
let setState = null;
let counterRef = null;
function Counter() {
const [count, setCount] = React.useState(0);
const ref = React.useRef();
setState = setCount;
counterRef = ref;
Scheduler.unstable_yieldValue('Count: ' + count);
return (
<p
ref={ref}
onClick={() => {
setCount(c => c + 1);
}}>
Count: {count}
</p>
);
}
const root = ReactDOMClient.createRoot(container, {
unstable_concurrentUpdatesByDefault: true,
});
act(() => {
root.render(<Counter />);
});
expect(Scheduler).toHaveYielded(['Count: 0']);
window.event = undefined;
setState(1);
// Dispatch a click event on the button.
const firstEvent = document.createEvent('Event');
firstEvent.initEvent('click', true, true);
counterRef.current.dispatchEvent(firstEvent);
await null;
if (gate(flags => flags.enableSyncDefaultUpdates)) {
expect(Scheduler).toHaveYielded(['Count: 2']);
expect(counterRef.current.textContent).toBe('Count: 2');
global.flushRequestAnimationFrameQueue();
expect(Scheduler).toHaveYielded([]);
} else {
expect(Scheduler).toHaveYielded(['Count: 1']);
expect(counterRef.current.textContent).toBe('Count: 1');
global.flushRequestAnimationFrameQueue();
expect(Scheduler).toHaveYielded(['Count: 2']);
expect(counterRef.current.textContent).toBe('Count: 2');
}
});
// @gate enableFrameEndScheduling
it('unknown updates should be rescheduled in rAF after suspending without a boundary', async () => {
let setState = null;
let setThrowing = null;
let counterRef = null;
let promise = null;
let unsuspend = null;
function Counter() {
const [count, setCount] = React.useState(0);
const [isThrowing, setThrowingState] = React.useState(false);
setThrowing = setThrowingState;
const ref = React.useRef();
setState = setCount;
counterRef = ref;
Scheduler.unstable_yieldValue('Count: ' + count);
if (isThrowing) {
if (promise === null) {
promise = new Promise(resolve => {
unsuspend = () => {
resolve();
};
});
}
Scheduler.unstable_yieldValue('suspending');
throw promise;
}
return (
<p
ref={ref}
onClick={() => {
setCount(c => c + 1);
}}>
Count: {count}
</p>
);
}
const root = ReactDOMClient.createRoot(container, {
unstable_concurrentUpdatesByDefault: true,
});
act(() => {
root.render(<Counter />);
});
expect(Scheduler).toHaveYielded(['Count: 0']);
window.event = undefined;
console.log('set state');
setState(1);
//expect(global.requestAnimationFrameQueue.length).toBe(1);
global.flushRequestAnimationFrameQueue();
expect(Scheduler).toHaveYielded(['Count: 1']);
setState(2);
setThrowing(true);
expect(global.requestAnimationFrameQueue.length).toBe(1);
global.flushRequestAnimationFrameQueue();
expect(Scheduler).toHaveYielded(['Count: 2', 'suspending']);
expect(counterRef.current.textContent).toBe('Count: 1');
unsuspend();
// Default update should be scheduled in a rAF.
window.event = 'test';
setThrowing(false);
setState(2);
global.flushRequestAnimationFrameQueue();
expect(Scheduler).toHaveYielded(['Count: 2']);
expect(counterRef.current.textContent).toBe('Count: 2');
});
});
it('regression test: does not drop passive effects across roots (#17066)', () => {

View File

@ -399,7 +399,12 @@ describe('ReactDOMRoot', () => {
expect(container.textContent).toEqual('a');
expect(Scheduler).toFlushAndYieldThrough(['b']);
if (gate(flags => flags.allowConcurrentByDefault)) {
if (
gate(
flags =>
flags.allowConcurrentByDefault && !flags.enableFrameEndScheduling,
)
) {
expect(container.textContent).toEqual('a');
} else {
expect(container.textContent).toEqual('b');

View File

@ -1432,12 +1432,16 @@ describe('ReactDOMServerPartialHydration', () => {
// While we're part way through the hydration, we update the state.
// This will schedule an update on the children of the suspense boundary.
expect(() => updateText('Hi')).toErrorDev(
expect(() => {
act(() => {
updateText('Hi');
});
}).toErrorDev(
"Can't perform a React state update on a component that hasn't mounted yet.",
);
// This will throw it away and rerender.
expect(Scheduler).toFlushAndYield(['Child', 'Sibling']);
expect(Scheduler).toHaveYielded(['Child', 'Sibling']);
expect(container.textContent).toBe('Hello');

View File

@ -430,6 +430,10 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
})
: setTimeout,
supportsFrameAlignedTask: false,
scheduleFrameAlignedTask: undefined,
cancelFrameAlignedTask: undefined,
prepareForCommit(): null | Object {
return null;
},

View File

@ -206,6 +206,17 @@ Set this to true to indicate that your renderer supports `scheduleMicrotask`. We
Optional. You can proxy this to `queueMicrotask` or its equivalent in your environment.
#### `supportsFrameAlignedTask`
TODO
### `scheduleFrameAlignedTask(fn)`
TODO
#### `cancelFrameAlignedTask(fn)`
TODO
#### `isPrimaryRenderer`
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`.

View File

@ -9,6 +9,7 @@
import type {Lane, Lanes} from './ReactFiberLane.old';
import type {Wakeable} from 'shared/ReactTypes';
import type {EventPriority} from './ReactEventPriorities.old';
import {enableDebugTracing} from 'shared/ReactFeatureFlags';
@ -62,11 +63,14 @@ function log(...logArgs): void {
const REACT_LOGO_STYLE =
'background-color: #20232a; color: #61dafb; padding: 0 2px;';
export function logCommitStarted(lanes: Lanes): void {
export function logCommitStarted(
lanes: Lanes,
updatePriority: EventPriority,
): void {
if (__DEV__) {
if (enableDebugTracing) {
group(
`%c⚛%c commit%c (${formatLanes(lanes)})`,
`%c⚛%c commit%c (${formatLanes(lanes)}) (${updatePriority})`,
REACT_LOGO_STYLE,
'',
'font-weight: normal;',
@ -133,11 +137,14 @@ export function logComponentSuspended(
}
}
export function logLayoutEffectsStarted(lanes: Lanes): void {
export function logLayoutEffectsStarted(
lanes: Lanes,
updatePriority: EventPriority,
): void {
if (__DEV__) {
if (enableDebugTracing) {
group(
`%c⚛%c layout effects%c (${formatLanes(lanes)})`,
`%c⚛%c layout effects%c (${formatLanes(lanes)}) (${updatePriority})`,
REACT_LOGO_STYLE,
'',
'font-weight: normal;',
@ -154,11 +161,14 @@ export function logLayoutEffectsStopped(): void {
}
}
export function logPassiveEffectsStarted(lanes: Lanes): void {
export function logPassiveEffectsStarted(
lanes: Lanes,
updatePriority: EventPriority,
): void {
if (__DEV__) {
if (enableDebugTracing) {
group(
`%c⚛%c passive effects%c (${formatLanes(lanes)})`,
`%c⚛%c passive effects%c (${formatLanes(lanes)}) (${updatePriority})`,
REACT_LOGO_STYLE,
'',
'font-weight: normal;',
@ -175,11 +185,14 @@ export function logPassiveEffectsStopped(): void {
}
}
export function logRenderStarted(lanes: Lanes): void {
export function logRenderStarted(
lanes: Lanes,
updatePriority: EventPriority,
): void {
if (__DEV__) {
if (enableDebugTracing) {
group(
`%c⚛%c render%c (${formatLanes(lanes)})`,
`%c⚛%c render%c (${formatLanes(lanes)}) (${updatePriority})`,
REACT_LOGO_STYLE,
'',
'font-weight: normal;',

View File

@ -31,7 +31,7 @@ import {
isHigherEventPriority as isHigherEventPriority_new,
} from './ReactEventPriorities.new';
export opaque type EventPriority = number;
export type EventPriority = number;
export const DiscreteEventPriority: EventPriority = enableNewReconciler
? (DiscreteEventPriority_new: any)

View File

@ -13,20 +13,24 @@ import {
NoLane,
SyncLane,
InputContinuousLane,
DefaultLane,
IdleLane,
getHighestPriorityLane,
includesNonIdleWork,
DefaultLane,
} from './ReactFiberLane.new';
import {enableUnifiedSyncLane} from '../../shared/ReactFeatureFlags';
export opaque type EventPriority = Lane;
// TODO: Ideally this would be opaque but that doesn't work well with
// our reconciler fork infra, since these leak into non-reconciler packages.
export type EventPriority = number;
export const NoEventPriority: EventPriority = NoLane;
export const DiscreteEventPriority: EventPriority = SyncLane;
export const ContinuousEventPriority: EventPriority = InputContinuousLane;
export const DefaultEventPriority: EventPriority = DefaultLane;
export const ContinuousEventPriority: EventPriority = SyncLane | (2 << 1);
export const DefaultEventPriority: EventPriority = SyncLane | (1 << 1);
export const IdleEventPriority: EventPriority = IdleLane;
let currentUpdatePriority: EventPriority = NoLane;
let currentUpdatePriority: EventPriority = NoEventPriority;
export function getCurrentUpdatePriority(): EventPriority {
return currentUpdatePriority;
@ -67,10 +71,13 @@ export function isHigherEventPriority(
return a !== 0 && a < b;
}
export function lanesToEventPriority(lanes: Lanes): EventPriority {
export function lanesToEventPriority(
lanes: Lanes,
syncUpdatePriority: EventPriority,
): EventPriority {
const lane = getHighestPriorityLane(lanes);
if (!isHigherEventPriority(DiscreteEventPriority, lane)) {
return DiscreteEventPriority;
return enableUnifiedSyncLane ? syncUpdatePriority : DiscreteEventPriority;
}
if (!isHigherEventPriority(ContinuousEventPriority, lane)) {
return ContinuousEventPriority;
@ -80,3 +87,19 @@ export function lanesToEventPriority(lanes: Lanes): EventPriority {
}
return IdleEventPriority;
}
export function laneToEventPriority(
lane: Lane,
syncUpdatePriority: EventPriority,
): EventPriority {
if (enableUnifiedSyncLane && lane === SyncLane) {
return syncUpdatePriority;
}
if (!enableUnifiedSyncLane && lane === DefaultLane) {
return DefaultEventPriority;
}
if (lane === InputContinuousLane) {
return ContinuousEventPriority;
}
return (lane: any);
}

View File

@ -13,20 +13,24 @@ import {
NoLane,
SyncLane,
InputContinuousLane,
DefaultLane,
IdleLane,
getHighestPriorityLane,
includesNonIdleWork,
DefaultLane,
} from './ReactFiberLane.old';
import {enableUnifiedSyncLane} from '../../shared/ReactFeatureFlags';
export opaque type EventPriority = Lane;
// TODO: Ideally this would be opaque but that doesn't work well with
// our reconciler fork infra, since these leak into non-reconciler packages.
export type EventPriority = number;
export const NoEventPriority: EventPriority = NoLane;
export const DiscreteEventPriority: EventPriority = SyncLane;
export const ContinuousEventPriority: EventPriority = InputContinuousLane;
export const DefaultEventPriority: EventPriority = DefaultLane;
export const ContinuousEventPriority: EventPriority = SyncLane | (2 << 1);
export const DefaultEventPriority: EventPriority = SyncLane | (1 << 1);
export const IdleEventPriority: EventPriority = IdleLane;
let currentUpdatePriority: EventPriority = NoLane;
let currentUpdatePriority: EventPriority = NoEventPriority;
export function getCurrentUpdatePriority(): EventPriority {
return currentUpdatePriority;
@ -67,10 +71,13 @@ export function isHigherEventPriority(
return a !== 0 && a < b;
}
export function lanesToEventPriority(lanes: Lanes): EventPriority {
export function lanesToEventPriority(
lanes: Lanes,
syncUpdatePriority: EventPriority,
): EventPriority {
const lane = getHighestPriorityLane(lanes);
if (!isHigherEventPriority(DiscreteEventPriority, lane)) {
return DiscreteEventPriority;
return enableUnifiedSyncLane ? syncUpdatePriority : DiscreteEventPriority;
}
if (!isHigherEventPriority(ContinuousEventPriority, lane)) {
return ContinuousEventPriority;
@ -80,3 +87,19 @@ export function lanesToEventPriority(lanes: Lanes): EventPriority {
}
return IdleEventPriority;
}
export function laneToEventPriority(
lane: Lane,
syncUpdatePriority: EventPriority,
): EventPriority {
if (enableUnifiedSyncLane && lane === SyncLane) {
return syncUpdatePriority;
}
if (!enableUnifiedSyncLane && lane === DefaultLane) {
return DefaultEventPriority;
}
if (lane === InputContinuousLane) {
return ContinuousEventPriority;
}
return (lane: any);
}

View File

@ -275,6 +275,7 @@ import {
pushRootMarkerInstance,
TransitionTracingMarker,
} from './ReactFiberTracingMarkerComponent.new';
import {DefaultEventPriority} from './ReactEventPriorities';
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
@ -2821,6 +2822,7 @@ function updateDehydratedSuspenseComponent(
current,
attemptHydrationAtLane,
eventTime,
DefaultEventPriority,
);
} else {
// We have already tried to ping at a higher priority than we're rendering with

View File

@ -275,6 +275,7 @@ import {
pushRootMarkerInstance,
TransitionTracingMarker,
} from './ReactFiberTracingMarkerComponent.old';
import {DefaultEventPriority} from './ReactEventPriorities';
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
@ -2821,6 +2822,7 @@ function updateDehydratedSuspenseComponent(
current,
attemptHydrationAtLane,
eventTime,
DefaultEventPriority,
);
} else {
// We have already tried to ping at a higher priority than we're rendering with

View File

@ -70,6 +70,7 @@ import {readContext, checkIfContextChanged} from './ReactFiberNewContext.new';
import {
requestEventTime,
requestUpdateLane,
requestUpdateLane_getUpdatePriority,
scheduleUpdateOnFiber,
} from './ReactFiberWorkLoop.new';
import {logForceUpdateScheduled, logStateUpdateScheduled} from './DebugTracing';
@ -204,6 +205,7 @@ const classComponentUpdater = {
const fiber = getInstance(inst);
const eventTime = requestEventTime();
const lane = requestUpdateLane(fiber);
const updatePriority = requestUpdateLane_getUpdatePriority();
const update = createUpdate(eventTime, lane);
update.payload = payload;
@ -216,7 +218,7 @@ const classComponentUpdater = {
const root = enqueueUpdate(fiber, update, lane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
scheduleUpdateOnFiber(root, fiber, lane, eventTime, updatePriority);
entangleTransitions(root, fiber, lane);
}
@ -237,6 +239,7 @@ const classComponentUpdater = {
const fiber = getInstance(inst);
const eventTime = requestEventTime();
const lane = requestUpdateLane(fiber);
const updatePriority = requestUpdateLane_getUpdatePriority();
const update = createUpdate(eventTime, lane);
update.tag = ReplaceState;
@ -251,7 +254,7 @@ const classComponentUpdater = {
const root = enqueueUpdate(fiber, update, lane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
scheduleUpdateOnFiber(root, fiber, lane, eventTime, updatePriority);
entangleTransitions(root, fiber, lane);
}
@ -272,6 +275,7 @@ const classComponentUpdater = {
const fiber = getInstance(inst);
const eventTime = requestEventTime();
const lane = requestUpdateLane(fiber);
const updatePriority = requestUpdateLane_getUpdatePriority();
const update = createUpdate(eventTime, lane);
update.tag = ForceUpdate;
@ -285,7 +289,7 @@ const classComponentUpdater = {
const root = enqueueUpdate(fiber, update, lane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
scheduleUpdateOnFiber(root, fiber, lane, eventTime, updatePriority);
entangleTransitions(root, fiber, lane);
}

View File

@ -70,6 +70,7 @@ import {readContext, checkIfContextChanged} from './ReactFiberNewContext.old';
import {
requestEventTime,
requestUpdateLane,
requestUpdateLane_getUpdatePriority,
scheduleUpdateOnFiber,
} from './ReactFiberWorkLoop.old';
import {logForceUpdateScheduled, logStateUpdateScheduled} from './DebugTracing';
@ -204,6 +205,7 @@ const classComponentUpdater = {
const fiber = getInstance(inst);
const eventTime = requestEventTime();
const lane = requestUpdateLane(fiber);
const updatePriority = requestUpdateLane_getUpdatePriority();
const update = createUpdate(eventTime, lane);
update.payload = payload;
@ -216,7 +218,7 @@ const classComponentUpdater = {
const root = enqueueUpdate(fiber, update, lane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
scheduleUpdateOnFiber(root, fiber, lane, eventTime, updatePriority);
entangleTransitions(root, fiber, lane);
}
@ -237,6 +239,7 @@ const classComponentUpdater = {
const fiber = getInstance(inst);
const eventTime = requestEventTime();
const lane = requestUpdateLane(fiber);
const updatePriority = requestUpdateLane_getUpdatePriority();
const update = createUpdate(eventTime, lane);
update.tag = ReplaceState;
@ -251,7 +254,7 @@ const classComponentUpdater = {
const root = enqueueUpdate(fiber, update, lane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
scheduleUpdateOnFiber(root, fiber, lane, eventTime, updatePriority);
entangleTransitions(root, fiber, lane);
}
@ -272,6 +275,7 @@ const classComponentUpdater = {
const fiber = getInstance(inst);
const eventTime = requestEventTime();
const lane = requestUpdateLane(fiber);
const updatePriority = requestUpdateLane_getUpdatePriority();
const update = createUpdate(eventTime, lane);
update.tag = ForceUpdate;
@ -285,7 +289,7 @@ const classComponentUpdater = {
const root = enqueueUpdate(fiber, update, lane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
scheduleUpdateOnFiber(root, fiber, lane, eventTime, updatePriority);
entangleTransitions(root, fiber, lane);
}

View File

@ -77,6 +77,7 @@ import {
getCurrentUpdatePriority,
setCurrentUpdatePriority,
higherEventPriority,
DiscreteEventPriority,
} from './ReactEventPriorities.new';
import {readContext, checkIfContextChanged} from './ReactFiberNewContext.new';
import {HostRoot, CacheComponent} from './ReactWorkTags';
@ -101,6 +102,7 @@ import {
getWorkInProgressRootRenderLanes,
scheduleUpdateOnFiber,
requestUpdateLane,
requestUpdateLane_getUpdatePriority,
requestEventTime,
markSkippedUpdateLanes,
isInvalidExecutionContextForEventFunction,
@ -1331,7 +1333,6 @@ function useMutableSource<Source, Snapshot>(
// Record a pending mutable source update with the same expiration time.
const lane = requestUpdateLane(fiber);
markRootMutableRead(root, lane);
} catch (error) {
// A selector might throw after a source mutation.
@ -1670,7 +1671,13 @@ function checkIfSnapshotChanged<T>(inst: StoreInstance<T>): boolean {
function forceStoreRerender(fiber) {
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
scheduleUpdateOnFiber(
root,
fiber,
SyncLane,
NoTimestamp,
DiscreteEventPriority,
);
}
}
@ -2381,11 +2388,18 @@ function refreshCache<T>(fiber: Fiber, seedKey: ?() => T, seedValue: T) {
case HostRoot: {
// Schedule an update on the cache boundary to trigger a refresh.
const lane = requestUpdateLane(provider);
const updatePriority = requestUpdateLane_getUpdatePriority();
const eventTime = requestEventTime();
const refreshUpdate = createLegacyQueueUpdate(eventTime, lane);
const root = enqueueLegacyQueueUpdate(provider, refreshUpdate, lane);
if (root !== null) {
scheduleUpdateOnFiber(root, provider, lane, eventTime);
scheduleUpdateOnFiber(
root,
provider,
lane,
eventTime,
updatePriority,
);
entangleLegacyQueueTransitions(root, provider, lane);
}
@ -2427,6 +2441,7 @@ function dispatchReducerAction<S, A>(
}
const lane = requestUpdateLane(fiber);
const updatePriority = requestUpdateLane_getUpdatePriority();
const update: Update<S, A> = {
lane,
@ -2442,7 +2457,7 @@ function dispatchReducerAction<S, A>(
const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
if (root !== null) {
const eventTime = requestEventTime();
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
scheduleUpdateOnFiber(root, fiber, lane, eventTime, updatePriority);
entangleTransitionUpdate(root, queue, lane);
}
}
@ -2466,6 +2481,7 @@ function dispatchSetState<S, A>(
}
const lane = requestUpdateLane(fiber);
const updatePriority = requestUpdateLane_getUpdatePriority();
const update: Update<S, A> = {
lane,
@ -2524,7 +2540,7 @@ function dispatchSetState<S, A>(
const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
if (root !== null) {
const eventTime = requestEventTime();
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
scheduleUpdateOnFiber(root, fiber, lane, eventTime, updatePriority);
entangleTransitionUpdate(root, queue, lane);
}
}

View File

@ -77,6 +77,7 @@ import {
getCurrentUpdatePriority,
setCurrentUpdatePriority,
higherEventPriority,
DiscreteEventPriority,
} from './ReactEventPriorities.old';
import {readContext, checkIfContextChanged} from './ReactFiberNewContext.old';
import {HostRoot, CacheComponent} from './ReactWorkTags';
@ -101,6 +102,7 @@ import {
getWorkInProgressRootRenderLanes,
scheduleUpdateOnFiber,
requestUpdateLane,
requestUpdateLane_getUpdatePriority,
requestEventTime,
markSkippedUpdateLanes,
isInvalidExecutionContextForEventFunction,
@ -1331,7 +1333,6 @@ function useMutableSource<Source, Snapshot>(
// Record a pending mutable source update with the same expiration time.
const lane = requestUpdateLane(fiber);
markRootMutableRead(root, lane);
} catch (error) {
// A selector might throw after a source mutation.
@ -1670,7 +1671,13 @@ function checkIfSnapshotChanged<T>(inst: StoreInstance<T>): boolean {
function forceStoreRerender(fiber) {
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
scheduleUpdateOnFiber(
root,
fiber,
SyncLane,
NoTimestamp,
DiscreteEventPriority,
);
}
}
@ -2381,11 +2388,18 @@ function refreshCache<T>(fiber: Fiber, seedKey: ?() => T, seedValue: T) {
case HostRoot: {
// Schedule an update on the cache boundary to trigger a refresh.
const lane = requestUpdateLane(provider);
const updatePriority = requestUpdateLane_getUpdatePriority();
const eventTime = requestEventTime();
const refreshUpdate = createLegacyQueueUpdate(eventTime, lane);
const root = enqueueLegacyQueueUpdate(provider, refreshUpdate, lane);
if (root !== null) {
scheduleUpdateOnFiber(root, provider, lane, eventTime);
scheduleUpdateOnFiber(
root,
provider,
lane,
eventTime,
updatePriority,
);
entangleLegacyQueueTransitions(root, provider, lane);
}
@ -2427,6 +2441,7 @@ function dispatchReducerAction<S, A>(
}
const lane = requestUpdateLane(fiber);
const updatePriority = requestUpdateLane_getUpdatePriority();
const update: Update<S, A> = {
lane,
@ -2442,7 +2457,7 @@ function dispatchReducerAction<S, A>(
const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
if (root !== null) {
const eventTime = requestEventTime();
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
scheduleUpdateOnFiber(root, fiber, lane, eventTime, updatePriority);
entangleTransitionUpdate(root, queue, lane);
}
}
@ -2466,6 +2481,7 @@ function dispatchSetState<S, A>(
}
const lane = requestUpdateLane(fiber);
const updatePriority = requestUpdateLane_getUpdatePriority();
const update: Update<S, A> = {
lane,
@ -2524,7 +2540,7 @@ function dispatchSetState<S, A>(
const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
if (root !== null) {
const eventTime = requestEventTime();
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
scheduleUpdateOnFiber(root, fiber, lane, eventTime, updatePriority);
entangleTransitionUpdate(root, queue, lane);
}
}

View File

@ -21,3 +21,6 @@ function shim(...args: any): empty {
// Test selectors (when unsupported)
export const supportsMicrotasks = false;
export const scheduleMicrotask = shim;
export const supportsFrameAlignedTask = false;
export const scheduleFrameAlignedTask = shim;
export const cancelFrameAlignedTask = shim;

View File

@ -49,6 +49,7 @@ import {
REACT_LAZY_TYPE,
} from 'shared/ReactSymbols';
import {enableFloat} from 'shared/ReactFeatureFlags';
import {DiscreteEventPriority} from './ReactEventPriorities';
let resolveFamily: RefreshHandler | null = null;
let failedBoundaries: WeakSet<Fiber> | null = null;
@ -313,8 +314,15 @@ function scheduleFibersWithFamiliesRecursively(
}
if (needsRemount || needsRender) {
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
scheduleUpdateOnFiber(
root,
fiber,
SyncLane,
NoTimestamp,
DiscreteEventPriority,
);
}
}
if (child !== null && !needsRemount) {

View File

@ -49,6 +49,7 @@ import {
REACT_LAZY_TYPE,
} from 'shared/ReactSymbols';
import {enableFloat} from 'shared/ReactFeatureFlags';
import {DiscreteEventPriority} from './ReactEventPriorities';
let resolveFamily: RefreshHandler | null = null;
let failedBoundaries: WeakSet<Fiber> | null = null;
@ -313,8 +314,15 @@ function scheduleFibersWithFamiliesRecursively(
}
if (needsRemount || needsRender) {
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
scheduleUpdateOnFiber(
root,
fiber,
SyncLane,
NoTimestamp,
DiscreteEventPriority,
);
}
}
if (child !== null && !needsRemount) {

View File

@ -10,6 +10,7 @@
import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {Transition} from './ReactFiberTracingMarkerComponent.new';
import type {ConcurrentUpdate} from './ReactFiberConcurrentUpdates.new';
import type {EventPriority} from './ReactEventPriorities.new';
// TODO: Ideally these types would be opaque but that doesn't work well with
// our reconciler fork infra, since these leak into non-reconciler packages.
@ -23,6 +24,8 @@ import {
enableUpdaterTracking,
allowConcurrentByDefault,
enableTransitionTracing,
enableFrameEndScheduling,
enableUnifiedSyncLane,
} from 'shared/ReactFeatureFlags';
import {isDevToolsPresent} from './ReactFiberDevToolsHook.new';
import {ConcurrentUpdatesByDefaultMode, NoMode} from './ReactTypeOfMode';
@ -81,6 +84,9 @@ export const IdleLane: Lane = /* */ 0b0100000000000000000
export const OffscreenLane: Lane = /* */ 0b1000000000000000000000000000000;
// Copied from ReactEventPriorities to avoid cyclic dependencies
const DefaultEventPriority = SyncLane | (1 << 1);
// This function is used for the experimental timeline (react-devtools-timeline)
// It should be kept in sync with the Lanes values above.
export function getLabelForLane(lane: Lane): string | void {
@ -97,9 +103,6 @@ export function getLabelForLane(lane: Lane): string | void {
if (lane & DefaultHydrationLane) {
return 'DefaultHydration';
}
if (lane & DefaultLane) {
return 'Default';
}
if (lane & TransitionHydrationLane) {
return 'TransitionHydration';
}
@ -247,7 +250,9 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
// Default priority updates should not interrupt transition updates. The
// only difference between default updates and transition updates is that
// default updates do not support refresh transitions.
(nextLane === DefaultLane && (wipLane & TransitionLanes) !== NoLanes)
(!enableUnifiedSyncLane &&
nextLane === DefaultLane &&
(wipLane & TransitionLanes) !== NoLanes)
) {
// Keep working on the existing in-progress tree. Do not interrupt.
return wipLanes;
@ -264,7 +269,8 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
// and default updates, so they render in the same batch. The only reason
// they use separate lanes is because continuous updates should interrupt
// transitions, but default updates should not.
nextLanes |= pendingLanes & DefaultLane;
nextLanes |=
pendingLanes & (enableUnifiedSyncLane ? SyncLane : DefaultLane);
}
// Check for entangled lanes and add them to the batch.
@ -325,6 +331,7 @@ export function getMostRecentEventTime(root: FiberRoot, lanes: Lanes): number {
return mostRecentEventTime;
}
////
function computeExpirationTime(lane: Lane, currentTime: number) {
switch (lane) {
case SyncLane:
@ -340,8 +347,8 @@ function computeExpirationTime(lane: Lane, currentTime: number) {
// expiration times are an important safeguard when starvation
// does happen.
return currentTime + 250;
case DefaultHydrationLane:
case DefaultLane:
case DefaultHydrationLane:
case TransitionHydrationLane:
case TransitionLane1:
case TransitionLane2:
@ -469,7 +476,8 @@ export function includesOnlyRetries(lanes: Lanes): boolean {
return (lanes & RetryLanes) === lanes;
}
export function includesOnlyNonUrgentLanes(lanes: Lanes): boolean {
const UrgentLanes = SyncLane | InputContinuousLane | DefaultLane;
const UrgentLanes =
SyncLane | InputContinuousLane | (enableUnifiedSyncLane ? 0 : DefaultLane);
return (lanes & UrgentLanes) === NoLanes;
}
export function includesOnlyTransitions(lanes: Lanes): boolean {
@ -481,14 +489,23 @@ export function includesBlockingLane(root: FiberRoot, lanes: Lanes): boolean {
allowConcurrentByDefault &&
(root.current.mode & ConcurrentUpdatesByDefaultMode) !== NoMode
) {
// Concurrent updates by default always use time slicing.
if (
enableFrameEndScheduling &&
(lanes & (enableUnifiedSyncLane ? SyncLane : DefaultLane)) !== NoLanes &&
root.updatePriority === DefaultEventPriority
) {
// Unknown updates should flush synchronously, even in concurrent by default.
return true;
}
// Otherwise, concurrent updates by default always use time slicing.
return false;
}
const SyncDefaultLanes =
InputContinuousHydrationLane |
InputContinuousLane |
DefaultHydrationLane |
DefaultLane;
(enableUnifiedSyncLane ? SyncLane : DefaultLane);
return (lanes & SyncDefaultLanes) !== NoLanes;
}
@ -588,8 +605,13 @@ export function markRootUpdated(
root: FiberRoot,
updateLane: Lane,
eventTime: number,
updatePriority: EventPriority,
) {
root.pendingLanes |= updateLane;
if ((updateLane & SyncLane) !== NoLane) {
// Only set priority for the sync lane
root.updatePriority = updatePriority;
}
// If there are any suspended transitions, it's possible this new update
// could unblock them. Clear the suspended lanes so that we can try rendering
@ -618,7 +640,6 @@ export function markRootUpdated(
export function markRootSuspended(root: FiberRoot, suspendedLanes: Lanes) {
root.suspendedLanes |= suspendedLanes;
root.pingedLanes &= ~suspendedLanes;
// The suspended lanes are no longer CPU-bound. Clear their expiration times.
const expirationTimes = root.expirationTimes;
let lanes = suspendedLanes;
@ -649,6 +670,8 @@ export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) {
root.pendingLanes = remainingLanes;
// TODO: clearing the priority causes priority to be missing in retryTimedOutBoundary
// Let's try everything again
root.suspendedLanes = NoLanes;
root.pingedLanes = NoLanes;
@ -752,6 +775,16 @@ export function getBumpedLaneForHydration(
case InputContinuousLane:
lane = InputContinuousHydrationLane;
break;
case SyncLane:
if (
enableUnifiedSyncLane &&
root.updatePriority === DefaultEventPriority
) {
lane = DefaultHydrationLane;
} else {
lane = NoLane;
}
break;
case DefaultLane:
lane = DefaultHydrationLane;
break;

View File

@ -10,6 +10,7 @@
import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {Transition} from './ReactFiberTracingMarkerComponent.old';
import type {ConcurrentUpdate} from './ReactFiberConcurrentUpdates.old';
import type {EventPriority} from './ReactEventPriorities.old';
// TODO: Ideally these types would be opaque but that doesn't work well with
// our reconciler fork infra, since these leak into non-reconciler packages.
@ -23,6 +24,8 @@ import {
enableUpdaterTracking,
allowConcurrentByDefault,
enableTransitionTracing,
enableFrameEndScheduling,
enableUnifiedSyncLane,
} from 'shared/ReactFeatureFlags';
import {isDevToolsPresent} from './ReactFiberDevToolsHook.old';
import {ConcurrentUpdatesByDefaultMode, NoMode} from './ReactTypeOfMode';
@ -81,6 +84,9 @@ export const IdleLane: Lane = /* */ 0b0100000000000000000
export const OffscreenLane: Lane = /* */ 0b1000000000000000000000000000000;
// Copied from ReactEventPriorities to avoid cyclic dependencies
const DefaultEventPriority = SyncLane | (1 << 1);
// This function is used for the experimental timeline (react-devtools-timeline)
// It should be kept in sync with the Lanes values above.
export function getLabelForLane(lane: Lane): string | void {
@ -97,9 +103,6 @@ export function getLabelForLane(lane: Lane): string | void {
if (lane & DefaultHydrationLane) {
return 'DefaultHydration';
}
if (lane & DefaultLane) {
return 'Default';
}
if (lane & TransitionHydrationLane) {
return 'TransitionHydration';
}
@ -247,7 +250,9 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
// Default priority updates should not interrupt transition updates. The
// only difference between default updates and transition updates is that
// default updates do not support refresh transitions.
(nextLane === DefaultLane && (wipLane & TransitionLanes) !== NoLanes)
(!enableUnifiedSyncLane &&
nextLane === DefaultLane &&
(wipLane & TransitionLanes) !== NoLanes)
) {
// Keep working on the existing in-progress tree. Do not interrupt.
return wipLanes;
@ -264,7 +269,8 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
// and default updates, so they render in the same batch. The only reason
// they use separate lanes is because continuous updates should interrupt
// transitions, but default updates should not.
nextLanes |= pendingLanes & DefaultLane;
nextLanes |=
pendingLanes & (enableUnifiedSyncLane ? SyncLane : DefaultLane);
}
// Check for entangled lanes and add them to the batch.
@ -325,6 +331,7 @@ export function getMostRecentEventTime(root: FiberRoot, lanes: Lanes): number {
return mostRecentEventTime;
}
////
function computeExpirationTime(lane: Lane, currentTime: number) {
switch (lane) {
case SyncLane:
@ -340,8 +347,8 @@ function computeExpirationTime(lane: Lane, currentTime: number) {
// expiration times are an important safeguard when starvation
// does happen.
return currentTime + 250;
case DefaultHydrationLane:
case DefaultLane:
case DefaultHydrationLane:
case TransitionHydrationLane:
case TransitionLane1:
case TransitionLane2:
@ -469,7 +476,8 @@ export function includesOnlyRetries(lanes: Lanes): boolean {
return (lanes & RetryLanes) === lanes;
}
export function includesOnlyNonUrgentLanes(lanes: Lanes): boolean {
const UrgentLanes = SyncLane | InputContinuousLane | DefaultLane;
const UrgentLanes =
SyncLane | InputContinuousLane | (enableUnifiedSyncLane ? 0 : DefaultLane);
return (lanes & UrgentLanes) === NoLanes;
}
export function includesOnlyTransitions(lanes: Lanes): boolean {
@ -481,14 +489,23 @@ export function includesBlockingLane(root: FiberRoot, lanes: Lanes): boolean {
allowConcurrentByDefault &&
(root.current.mode & ConcurrentUpdatesByDefaultMode) !== NoMode
) {
// Concurrent updates by default always use time slicing.
if (
enableFrameEndScheduling &&
(lanes & (enableUnifiedSyncLane ? SyncLane : DefaultLane)) !== NoLanes &&
root.updatePriority === DefaultEventPriority
) {
// Unknown updates should flush synchronously, even in concurrent by default.
return true;
}
// Otherwise, concurrent updates by default always use time slicing.
return false;
}
const SyncDefaultLanes =
InputContinuousHydrationLane |
InputContinuousLane |
DefaultHydrationLane |
DefaultLane;
(enableUnifiedSyncLane ? SyncLane : DefaultLane);
return (lanes & SyncDefaultLanes) !== NoLanes;
}
@ -588,8 +605,13 @@ export function markRootUpdated(
root: FiberRoot,
updateLane: Lane,
eventTime: number,
updatePriority: EventPriority,
) {
root.pendingLanes |= updateLane;
if ((updateLane & SyncLane) !== NoLane) {
// Only set priority for the sync lane
root.updatePriority = updatePriority;
}
// If there are any suspended transitions, it's possible this new update
// could unblock them. Clear the suspended lanes so that we can try rendering
@ -618,7 +640,6 @@ export function markRootUpdated(
export function markRootSuspended(root: FiberRoot, suspendedLanes: Lanes) {
root.suspendedLanes |= suspendedLanes;
root.pingedLanes &= ~suspendedLanes;
// The suspended lanes are no longer CPU-bound. Clear their expiration times.
const expirationTimes = root.expirationTimes;
let lanes = suspendedLanes;
@ -649,6 +670,8 @@ export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) {
root.pendingLanes = remainingLanes;
// TODO: clearing the priority causes priority to be missing in retryTimedOutBoundary
// Let's try everything again
root.suspendedLanes = NoLanes;
root.pingedLanes = NoLanes;
@ -752,6 +775,16 @@ export function getBumpedLaneForHydration(
case InputContinuousLane:
lane = InputContinuousHydrationLane;
break;
case SyncLane:
if (
enableUnifiedSyncLane &&
root.updatePriority === DefaultEventPriority
) {
lane = DefaultHydrationLane;
} else {
lane = NoLane;
}
break;
case DefaultLane:
lane = DefaultHydrationLane;
break;

View File

@ -57,6 +57,7 @@ import {
import {
requestEventTime,
requestUpdateLane,
requestUpdateLane_getUpdatePriority,
scheduleUpdateOnFiber,
scheduleInitialHydrationOnRoot,
flushRoot,
@ -87,6 +88,7 @@ import {
NoTimestamp,
getHighestPriorityPendingLanes,
higherPriorityLane,
NoLane,
} from './ReactFiberLane.new';
import {
getCurrentUpdatePriority,
@ -99,6 +101,10 @@ import {
findHostInstancesForRefresh,
} from './ReactFiberHotReloading.new';
import ReactVersion from 'shared/ReactVersion';
import {
DefaultEventPriority,
DiscreteEventPriority,
} from './ReactEventPriorities';
export {registerMutableSourceForHydration} from './ReactMutableSource.new';
export {createPortal} from './ReactPortal';
export {
@ -330,6 +336,7 @@ export function updateContainer(
const current = container.current;
const eventTime = requestEventTime();
const lane = requestUpdateLane(current);
const updatePriority = requestUpdateLane_getUpdatePriority();
if (enableSchedulingProfiler) {
markRenderScheduled(lane);
@ -380,7 +387,7 @@ export function updateContainer(
const root = enqueueUpdate(current, update, lane);
if (root !== null) {
scheduleUpdateOnFiber(root, current, lane, eventTime);
scheduleUpdateOnFiber(root, current, lane, eventTime, updatePriority);
entangleTransitions(root, current, lane);
}
@ -419,6 +426,9 @@ export function attemptSynchronousHydration(fiber: Fiber): void {
if (isRootDehydrated(root)) {
// Flush the first scheduled "update".
const lanes = getHighestPriorityPendingLanes(root);
if ((lanes & SyncLane) !== NoLane) {
root.updatePriority = DiscreteEventPriority;
}
flushRoot(root, lanes);
}
break;
@ -428,7 +438,13 @@ export function attemptSynchronousHydration(fiber: Fiber): void {
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
const eventTime = requestEventTime();
scheduleUpdateOnFiber(root, fiber, SyncLane, eventTime);
scheduleUpdateOnFiber(
root,
fiber,
SyncLane,
eventTime,
DiscreteEventPriority,
);
}
});
// If we're still blocked after this, we need to increase
@ -472,7 +488,7 @@ export function attemptDiscreteHydration(fiber: Fiber): void {
const root = enqueueConcurrentRenderForLane(fiber, lane);
if (root !== null) {
const eventTime = requestEventTime();
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
scheduleUpdateOnFiber(root, fiber, lane, eventTime, DiscreteEventPriority);
}
markRetryLaneIfNotHydrated(fiber, lane);
}
@ -489,7 +505,7 @@ export function attemptContinuousHydration(fiber: Fiber): void {
const root = enqueueConcurrentRenderForLane(fiber, lane);
if (root !== null) {
const eventTime = requestEventTime();
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
scheduleUpdateOnFiber(root, fiber, lane, eventTime, DefaultEventPriority);
}
markRetryLaneIfNotHydrated(fiber, lane);
}
@ -501,10 +517,11 @@ export function attemptHydrationAtCurrentPriority(fiber: Fiber): void {
return;
}
const lane = requestUpdateLane(fiber);
const updatePriority = requestUpdateLane_getUpdatePriority();
const root = enqueueConcurrentRenderForLane(fiber, lane);
if (root !== null) {
const eventTime = requestEventTime();
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
scheduleUpdateOnFiber(root, fiber, lane, eventTime, updatePriority);
}
markRetryLaneIfNotHydrated(fiber, lane);
}
@ -683,7 +700,13 @@ if (__DEV__) {
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
scheduleUpdateOnFiber(
root,
fiber,
SyncLane,
NoTimestamp,
root.updatePriority,
);
}
}
};
@ -707,7 +730,13 @@ if (__DEV__) {
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
scheduleUpdateOnFiber(
root,
fiber,
SyncLane,
NoTimestamp,
root.updatePriority,
);
}
}
};
@ -732,7 +761,13 @@ if (__DEV__) {
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
scheduleUpdateOnFiber(
root,
fiber,
SyncLane,
NoTimestamp,
root.updatePriority,
);
}
}
};
@ -745,7 +780,13 @@ if (__DEV__) {
}
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
scheduleUpdateOnFiber(
root,
fiber,
SyncLane,
NoTimestamp,
root.updatePriority,
);
}
};
overridePropsDeletePath = (fiber: Fiber, path: Array<string | number>) => {
@ -755,7 +796,13 @@ if (__DEV__) {
}
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
scheduleUpdateOnFiber(
root,
fiber,
SyncLane,
NoTimestamp,
root.updatePriority,
);
}
};
overridePropsRenamePath = (
@ -769,14 +816,26 @@ if (__DEV__) {
}
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
scheduleUpdateOnFiber(
root,
fiber,
SyncLane,
NoTimestamp,
root.updatePriority,
);
}
};
scheduleUpdate = (fiber: Fiber) => {
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
scheduleUpdateOnFiber(
root,
fiber,
SyncLane,
NoTimestamp,
root.updatePriority,
);
}
};

View File

@ -57,6 +57,7 @@ import {
import {
requestEventTime,
requestUpdateLane,
requestUpdateLane_getUpdatePriority,
scheduleUpdateOnFiber,
scheduleInitialHydrationOnRoot,
flushRoot,
@ -87,6 +88,7 @@ import {
NoTimestamp,
getHighestPriorityPendingLanes,
higherPriorityLane,
NoLane,
} from './ReactFiberLane.old';
import {
getCurrentUpdatePriority,
@ -99,6 +101,10 @@ import {
findHostInstancesForRefresh,
} from './ReactFiberHotReloading.old';
import ReactVersion from 'shared/ReactVersion';
import {
DefaultEventPriority,
DiscreteEventPriority,
} from './ReactEventPriorities';
export {registerMutableSourceForHydration} from './ReactMutableSource.old';
export {createPortal} from './ReactPortal';
export {
@ -330,6 +336,7 @@ export function updateContainer(
const current = container.current;
const eventTime = requestEventTime();
const lane = requestUpdateLane(current);
const updatePriority = requestUpdateLane_getUpdatePriority();
if (enableSchedulingProfiler) {
markRenderScheduled(lane);
@ -380,7 +387,7 @@ export function updateContainer(
const root = enqueueUpdate(current, update, lane);
if (root !== null) {
scheduleUpdateOnFiber(root, current, lane, eventTime);
scheduleUpdateOnFiber(root, current, lane, eventTime, updatePriority);
entangleTransitions(root, current, lane);
}
@ -419,6 +426,9 @@ export function attemptSynchronousHydration(fiber: Fiber): void {
if (isRootDehydrated(root)) {
// Flush the first scheduled "update".
const lanes = getHighestPriorityPendingLanes(root);
if ((lanes & SyncLane) !== NoLane) {
root.updatePriority = DiscreteEventPriority;
}
flushRoot(root, lanes);
}
break;
@ -428,7 +438,13 @@ export function attemptSynchronousHydration(fiber: Fiber): void {
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
const eventTime = requestEventTime();
scheduleUpdateOnFiber(root, fiber, SyncLane, eventTime);
scheduleUpdateOnFiber(
root,
fiber,
SyncLane,
eventTime,
DiscreteEventPriority,
);
}
});
// If we're still blocked after this, we need to increase
@ -472,7 +488,7 @@ export function attemptDiscreteHydration(fiber: Fiber): void {
const root = enqueueConcurrentRenderForLane(fiber, lane);
if (root !== null) {
const eventTime = requestEventTime();
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
scheduleUpdateOnFiber(root, fiber, lane, eventTime, DiscreteEventPriority);
}
markRetryLaneIfNotHydrated(fiber, lane);
}
@ -489,7 +505,7 @@ export function attemptContinuousHydration(fiber: Fiber): void {
const root = enqueueConcurrentRenderForLane(fiber, lane);
if (root !== null) {
const eventTime = requestEventTime();
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
scheduleUpdateOnFiber(root, fiber, lane, eventTime, DefaultEventPriority);
}
markRetryLaneIfNotHydrated(fiber, lane);
}
@ -501,10 +517,11 @@ export function attemptHydrationAtCurrentPriority(fiber: Fiber): void {
return;
}
const lane = requestUpdateLane(fiber);
const updatePriority = requestUpdateLane_getUpdatePriority();
const root = enqueueConcurrentRenderForLane(fiber, lane);
if (root !== null) {
const eventTime = requestEventTime();
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
scheduleUpdateOnFiber(root, fiber, lane, eventTime, updatePriority);
}
markRetryLaneIfNotHydrated(fiber, lane);
}
@ -683,7 +700,13 @@ if (__DEV__) {
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
scheduleUpdateOnFiber(
root,
fiber,
SyncLane,
NoTimestamp,
root.updatePriority,
);
}
}
};
@ -707,7 +730,13 @@ if (__DEV__) {
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
scheduleUpdateOnFiber(
root,
fiber,
SyncLane,
NoTimestamp,
root.updatePriority,
);
}
}
};
@ -732,7 +761,13 @@ if (__DEV__) {
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
scheduleUpdateOnFiber(
root,
fiber,
SyncLane,
NoTimestamp,
root.updatePriority,
);
}
}
};
@ -745,7 +780,13 @@ if (__DEV__) {
}
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
scheduleUpdateOnFiber(
root,
fiber,
SyncLane,
NoTimestamp,
root.updatePriority,
);
}
};
overridePropsDeletePath = (fiber: Fiber, path: Array<string | number>) => {
@ -755,7 +796,13 @@ if (__DEV__) {
}
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
scheduleUpdateOnFiber(
root,
fiber,
SyncLane,
NoTimestamp,
root.updatePriority,
);
}
};
overridePropsRenamePath = (
@ -769,14 +816,26 @@ if (__DEV__) {
}
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
scheduleUpdateOnFiber(
root,
fiber,
SyncLane,
NoTimestamp,
root.updatePriority,
);
}
};
scheduleUpdate = (fiber: Fiber) => {
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
scheduleUpdateOnFiber(
root,
fiber,
SyncLane,
NoTimestamp,
root.updatePriority,
);
}
};

View File

@ -20,12 +20,12 @@ import type {Container} from './ReactFiberHostConfig';
import {noTimeout, supportsHydration} from './ReactFiberHostConfig';
import {createHostRootFiber} from './ReactFiber.new';
import {
NoLane,
NoLanes,
NoTimestamp,
TotalLanes,
createLaneMap,
} from './ReactFiberLane.new';
import {NoEventPriority} from './ReactEventPriorities.new';
import {
enableSuspenseCallback,
enableCache,
@ -61,7 +61,8 @@ function FiberRootNode(
this.context = null;
this.pendingContext = null;
this.callbackNode = null;
this.callbackPriority = NoLane;
this.callbackPriority = NoEventPriority;
this.updatePriority = NoEventPriority;
this.eventTimes = createLaneMap(NoLanes);
this.expirationTimes = createLaneMap(NoTimestamp);

View File

@ -20,12 +20,12 @@ import type {Container} from './ReactFiberHostConfig';
import {noTimeout, supportsHydration} from './ReactFiberHostConfig';
import {createHostRootFiber} from './ReactFiber.old';
import {
NoLane,
NoLanes,
NoTimestamp,
TotalLanes,
createLaneMap,
} from './ReactFiberLane.old';
import {NoEventPriority} from './ReactEventPriorities.old';
import {
enableSuspenseCallback,
enableCache,
@ -61,7 +61,8 @@ function FiberRootNode(
this.context = null;
this.pendingContext = null;
this.callbackNode = null;
this.callbackPriority = NoLane;
this.callbackPriority = NoEventPriority;
this.updatePriority = NoEventPriority;
this.eventTimes = createLaneMap(NoLanes);
this.expirationTimes = createLaneMap(NoTimestamp);

View File

@ -40,6 +40,7 @@ import {
enableDebugTracing,
enableLazyContextPropagation,
enableUpdaterTracking,
enableUnifiedSyncLane,
} from 'shared/ReactFeatureFlags';
import {createCapturedValueAtFiber} from './ReactCapturedValue';
import {
@ -77,6 +78,7 @@ import {
markDidThrowWhileHydratingDEV,
queueHydrationError,
} from './ReactFiberHydrationContext.new';
import {DiscreteEventPriority} from './ReactEventPriorities.new';
function createRootErrorUpdate(
fiber: Fiber,
@ -421,8 +423,15 @@ function throwException(
} else {
// No boundary was found. Unless this is a sync update, this is OK.
// We can suspend and wait for more data to arrive.
if (!includesSyncLane(rootRenderLanes)) {
if (
!(
includesSyncLane(rootRenderLanes) &&
!(
enableUnifiedSyncLane &&
root.updatePriority !== DiscreteEventPriority
)
)
) {
// This is not a sync update. Suspend. Since we're not activating a
// Suspense boundary, this will unwind all the way to the root without
// performing a second pass to render a fallback. (This is arguably how

View File

@ -40,6 +40,7 @@ import {
enableDebugTracing,
enableLazyContextPropagation,
enableUpdaterTracking,
enableUnifiedSyncLane,
} from 'shared/ReactFeatureFlags';
import {createCapturedValueAtFiber} from './ReactCapturedValue';
import {
@ -77,6 +78,7 @@ import {
markDidThrowWhileHydratingDEV,
queueHydrationError,
} from './ReactFiberHydrationContext.old';
import {DiscreteEventPriority} from './ReactEventPriorities.old';
function createRootErrorUpdate(
fiber: Fiber,
@ -421,8 +423,15 @@ function throwException(
} else {
// No boundary was found. Unless this is a sync update, this is OK.
// We can suspend and wait for more data to arrive.
if (!includesSyncLane(rootRenderLanes)) {
if (
!(
includesSyncLane(rootRenderLanes) &&
!(
enableUnifiedSyncLane &&
root.updatePriority !== DiscreteEventPriority
)
)
) {
// This is not a sync update. Suspend. Since we're not activating a
// Suspense boundary, this will unwind all the way to the root without
// performing a second pass to render a fallback. (This is arguably how

View File

@ -40,6 +40,8 @@ import {
enableUpdaterTracking,
enableCache,
enableTransitionTracing,
enableFrameEndScheduling,
enableUnifiedSyncLane,
useModernStrictMode,
} from 'shared/ReactFeatureFlags';
import ReactSharedInternals from 'shared/ReactSharedInternals';
@ -86,8 +88,10 @@ import {
scheduleMicrotask,
prepareRendererToRender,
resetRendererAfterRender,
cancelFrameAlignedTask,
scheduleFrameAlignedTask,
supportsFrameAlignedTask,
} from './ReactFiberHostConfig';
import {
createWorkInProgress,
assignFiberPropertiesInDEV,
@ -163,16 +167,20 @@ import {
movePendingFibersToMemoized,
addTransitionToLanesMap,
getTransitionsForLanes,
InputContinuousLane,
DefaultLane,
} from './ReactFiberLane.new';
import {
DiscreteEventPriority,
ContinuousEventPriority,
DefaultEventPriority,
NoEventPriority,
IdleEventPriority,
getCurrentUpdatePriority,
setCurrentUpdatePriority,
lowerEventPriority,
lanesToEventPriority,
laneToEventPriority,
} from './ReactEventPriorities.new';
import {requestCurrentTransition, NoTransition} from './ReactFiberTransition';
import {beginWork as originalBeginWork} from './ReactFiberBeginWork.new';
@ -599,10 +607,17 @@ export function getCurrentTime(): number {
return now();
}
let currentUpdatePriority = NoEventPriority;
export function requestUpdateLane_getUpdatePriority(): EventPriority {
return currentUpdatePriority;
}
export function requestUpdateLane(fiber: Fiber): Lane {
// Special cases
const mode = fiber.mode;
if ((mode & ConcurrentMode) === NoMode) {
currentUpdatePriority = DiscreteEventPriority;
return (SyncLane: Lane);
} else if (
!deferRenderPhaseUpdateToNextBatch &&
@ -618,7 +633,11 @@ export function requestUpdateLane(fiber: Fiber): Lane {
// This behavior is only a fallback. The flag only exists until we can roll
// out the setState warning, since existing code might accidentally rely on
// the current behavior.
return pickArbitraryLane(workInProgressRootRenderLanes);
const nextLane = pickArbitraryLane(workInProgressRootRenderLanes);
if ((nextLane & SyncLane) === NoLane) {
currentUpdatePriority = NoEventPriority;
}
return nextLane;
}
const isTransition = requestCurrentTransition() !== NoTransition;
@ -642,28 +661,42 @@ export function requestUpdateLane(fiber: Fiber): Lane {
// All transitions within the same event are assigned the same lane.
currentEventTransitionLane = claimNextTransitionLane();
}
if ((currentEventTransitionLane & SyncLane) === NoLane) {
currentUpdatePriority = NoEventPriority;
}
return currentEventTransitionLane;
}
// Updates originating inside certain React methods, like flushSync, have
// their priority set by tracking it with a context variable.
//
// The opaque type returned by the host config is internally a lane, so we can
// use that directly.
// TODO: Move this type conversion to the event priority module.
const updateLane: Lane = (getCurrentUpdatePriority(): any);
if (updateLane !== NoLane) {
return updateLane;
const updatePriority = getCurrentUpdatePriority();
currentUpdatePriority = updatePriority;
if (updatePriority !== NoEventPriority) {
if (updatePriority === DefaultEventPriority) {
return enableUnifiedSyncLane ? SyncLane : DefaultLane;
}
if (updatePriority === ContinuousEventPriority) {
return InputContinuousLane;
}
return (updatePriority: any);
}
// This update originated outside React. Ask the host environment for an
// appropriate priority, based on the type of event.
//
// The opaque type returned by the host config is internally a lane, so we can
// use that directly.
// TODO: Move this type conversion to the event priority module.
const eventLane: Lane = (getCurrentEventPriority(): any);
return eventLane;
const eventPriority = getCurrentEventPriority();
currentUpdatePriority = eventPriority;
if (eventPriority === DefaultEventPriority) {
return enableUnifiedSyncLane ? SyncLane : DefaultLane;
}
if (eventPriority === ContinuousEventPriority) {
return InputContinuousLane;
}
return (eventPriority: any);
}
function requestRetryLane(fiber: Fiber) {
@ -674,8 +707,10 @@ function requestRetryLane(fiber: Fiber) {
// Special cases
const mode = fiber.mode;
if ((mode & ConcurrentMode) === NoMode) {
currentUpdatePriority = DiscreteEventPriority;
return (SyncLane: Lane);
}
currentUpdatePriority = DefaultEventPriority;
return claimNextRetryLane();
}
@ -685,6 +720,7 @@ export function scheduleUpdateOnFiber(
fiber: Fiber,
lane: Lane,
eventTime: number,
updatePriority: EventPriority,
) {
if (__DEV__) {
if (isRunningInsertionEffect) {
@ -697,9 +733,8 @@ export function scheduleUpdateOnFiber(
didScheduleUpdateDuringPassiveEffects = true;
}
}
// Mark that the root has a pending update.
markRootUpdated(root, lane, eventTime);
markRootUpdated(root, lane, eventTime, updatePriority);
if (
(executionContext & RenderContext) !== NoLanes &&
@ -820,7 +855,12 @@ export function scheduleInitialHydrationOnRoot(
// match what was rendered on the server.
const current = root.current;
current.lanes = lane;
markRootUpdated(root, lane, eventTime);
markRootUpdated(
root,
lane,
eventTime,
root.tag === LegacyRoot ? DiscreteEventPriority : DefaultEventPriority,
);
ensureRootIsScheduled(root, eventTime);
}
@ -860,12 +900,15 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
cancelCallback(existingCallbackNode);
}
root.callbackNode = null;
root.callbackPriority = NoLane;
root.callbackPriority = NoEventPriority;
return;
}
// We use the highest priority lane to represent the priority of the callback.
const newCallbackPriority = getHighestPriorityLane(nextLanes);
const newCallbackPriority = laneToEventPriority(
getHighestPriorityLane(nextLanes),
root.updatePriority,
);
// Check if there's an existing task. We may be able to reuse it.
const existingCallbackPriority = root.callbackPriority;
@ -886,25 +929,42 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
// TODO: Temporary until we confirm this warning is not fired.
if (
existingCallbackNode == null &&
existingCallbackPriority !== SyncLane
existingCallbackPriority !== DiscreteEventPriority
) {
console.error(
'Expected scheduled callback to exist. This error is likely caused by a bug in React. Please file an issue.',
);
}
}
// The priority hasn't changed. We can reuse the existing task. Exit.
return;
if (
enableFrameEndScheduling &&
supportsFrameAlignedTask &&
newCallbackPriority === DefaultEventPriority
) {
// Do nothing, we need to schedule a new rAF.
} else {
// The priority hasn't changed. We can reuse the existing task. Exit.
return;
}
}
if (existingCallbackNode != null) {
if (existingCallbackNode !== null) {
// Cancel the existing callback. We'll schedule a new one below.
cancelCallback(existingCallbackNode);
if (
enableFrameEndScheduling &&
supportsFrameAlignedTask &&
existingCallbackPriority === DefaultEventPriority
) {
cancelFrameAlignedTask(existingCallbackNode);
} else {
cancelCallback(existingCallbackNode);
}
}
// Schedule a new callback.
let newCallbackNode;
if (newCallbackPriority === SyncLane) {
if (newCallbackPriority === DiscreteEventPriority) {
// Special case: Sync React callbacks are scheduled on a special
// internal queue
if (root.tag === LegacyRoot) {
@ -943,9 +1003,26 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbacks);
}
newCallbackNode = null;
} else if (
enableFrameEndScheduling &&
supportsFrameAlignedTask &&
newCallbackPriority === DefaultEventPriority
) {
if (__DEV__ && ReactCurrentActQueue.current !== null) {
// Inside `act`, use our internal `act` queue so that these get flushed
// at the end of the current scope even when using the sync version
// of `act`.
ReactCurrentActQueue.current.push(
performConcurrentWorkOnRoot.bind(null, root),
);
} else {
newCallbackNode = scheduleFrameAlignedTask(
performConcurrentWorkOnRoot.bind(null, root),
);
}
} else {
let schedulerPriorityLevel;
switch (lanesToEventPriority(nextLanes)) {
switch (lanesToEventPriority(nextLanes, root.updatePriority)) {
case DiscreteEventPriority:
schedulerPriorityLevel = ImmediateSchedulerPriority;
break;
@ -1883,7 +1960,7 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
if (__DEV__) {
if (enableDebugTracing) {
logRenderStarted(lanes);
logRenderStarted(lanes, root.updatePriority);
}
}
@ -1984,7 +2061,7 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
if (__DEV__) {
if (enableDebugTracing) {
logRenderStarted(lanes);
logRenderStarted(lanes, root.updatePriority);
}
}
@ -2339,7 +2416,7 @@ function commitRootImpl(
if (__DEV__) {
if (enableDebugTracing) {
logCommitStarted(lanes);
logCommitStarted(lanes, root.updatePriority);
}
}
@ -2382,7 +2459,7 @@ function commitRootImpl(
// commitRoot never returns a continuation; it always finishes synchronously.
// So we can clear these now to allow a new callback to be scheduled.
root.callbackNode = null;
root.callbackPriority = NoLane;
root.callbackPriority = NoEventPriority;
// Check which lanes no longer have any work scheduled on them, and mark
// those as finished.
@ -2435,6 +2512,7 @@ function commitRootImpl(
}
}
const prevRootUpdatePriority = root.updatePriority;
// Check if there are any effects in the whole tree.
// TODO: This is left over from the effect list implementation, where we had
// to check for the existence of `firstEffect` to satisfy Flow. I think the
@ -2506,7 +2584,7 @@ function commitRootImpl(
// layout, but class component lifecycles also fire here for legacy reasons.
if (__DEV__) {
if (enableDebugTracing) {
logLayoutEffectsStarted(lanes);
logLayoutEffectsStarted(lanes, root.updatePriority);
}
}
if (enableSchedulingProfiler) {
@ -2635,8 +2713,11 @@ function commitRootImpl(
// TODO: We can optimize this by not scheduling the callback earlier. Since we
// currently schedule the callback in multiple places, will wait until those
// are consolidated.
//// TODO: Need to clear the updatePriority inorder to remove the sync lane check
if (
includesSomeLane(pendingPassiveEffectsLanes, SyncLane) &&
(enableUnifiedSyncLane
? prevRootUpdatePriority === DiscreteEventPriority
: includesSomeLane(pendingPassiveEffectsLanes, SyncLane)) &&
root.tag !== LegacyRoot
) {
flushPassiveEffects();
@ -2644,7 +2725,7 @@ function commitRootImpl(
// Read this again, since a passive effect might have updated it
remainingLanes = root.pendingLanes;
if (includesSomeLane(remainingLanes, (SyncLane: Lane))) {
if (includesSomeLane(remainingLanes, SyncLane)) {
if (enableProfilerTimer && enableProfilerNestedUpdatePhase) {
markNestedUpdateScheduled();
}
@ -2659,6 +2740,7 @@ function commitRootImpl(
}
} else {
nestedUpdateCount = 0;
root.updatePriority = NoEventPriority;
}
// If layout work was scheduled, flush it now.
@ -2766,7 +2848,10 @@ export function flushPassiveEffects(): boolean {
const remainingLanes = pendingPassiveEffectsRemainingLanes;
pendingPassiveEffectsRemainingLanes = NoLanes;
const renderPriority = lanesToEventPriority(pendingPassiveEffectsLanes);
const renderPriority = lanesToEventPriority(
pendingPassiveEffectsLanes,
root.updatePriority,
);
const priority = lowerEventPriority(DefaultEventPriority, renderPriority);
const prevTransition = ReactCurrentBatchConfig.transition;
const previousPriority = getCurrentUpdatePriority();
@ -2827,7 +2912,7 @@ function flushPassiveEffectsImpl() {
didScheduleUpdateDuringPassiveEffects = false;
if (enableDebugTracing) {
logPassiveEffectsStarted(lanes);
logPassiveEffectsStarted(lanes, root.updatePriority);
}
}
@ -2951,7 +3036,7 @@ function captureCommitPhaseErrorOnRoot(
const root = enqueueUpdate(rootFiber, update, (SyncLane: Lane));
const eventTime = requestEventTime();
if (root !== null) {
markRootUpdated(root, SyncLane, eventTime);
markRootUpdated(root, SyncLane, eventTime, DiscreteEventPriority);
ensureRootIsScheduled(root, eventTime);
}
}
@ -3000,7 +3085,7 @@ export function captureCommitPhaseError(
const root = enqueueUpdate(fiber, update, (SyncLane: Lane));
const eventTime = requestEventTime();
if (root !== null) {
markRootUpdated(root, SyncLane, eventTime);
markRootUpdated(root, SyncLane, eventTime, DiscreteEventPriority);
ensureRootIsScheduled(root, eventTime);
}
return;
@ -3136,7 +3221,8 @@ function retryTimedOutBoundary(boundaryFiber: Fiber, retryLane: Lane) {
const eventTime = requestEventTime();
const root = enqueueConcurrentRenderForLane(boundaryFiber, retryLane);
if (root !== null) {
markRootUpdated(root, retryLane, eventTime);
// console.log(retryLane,root.updatePriority, currentUpdatePriority, new Error().stack);
markRootUpdated(root, retryLane, eventTime, currentUpdatePriority);
ensureRootIsScheduled(root, eventTime);
}
}

View File

@ -40,6 +40,8 @@ import {
enableUpdaterTracking,
enableCache,
enableTransitionTracing,
enableFrameEndScheduling,
enableUnifiedSyncLane,
useModernStrictMode,
} from 'shared/ReactFeatureFlags';
import ReactSharedInternals from 'shared/ReactSharedInternals';
@ -86,8 +88,10 @@ import {
scheduleMicrotask,
prepareRendererToRender,
resetRendererAfterRender,
cancelFrameAlignedTask,
scheduleFrameAlignedTask,
supportsFrameAlignedTask,
} from './ReactFiberHostConfig';
import {
createWorkInProgress,
assignFiberPropertiesInDEV,
@ -163,16 +167,20 @@ import {
movePendingFibersToMemoized,
addTransitionToLanesMap,
getTransitionsForLanes,
InputContinuousLane,
DefaultLane,
} from './ReactFiberLane.old';
import {
DiscreteEventPriority,
ContinuousEventPriority,
DefaultEventPriority,
NoEventPriority,
IdleEventPriority,
getCurrentUpdatePriority,
setCurrentUpdatePriority,
lowerEventPriority,
lanesToEventPriority,
laneToEventPriority,
} from './ReactEventPriorities.old';
import {requestCurrentTransition, NoTransition} from './ReactFiberTransition';
import {beginWork as originalBeginWork} from './ReactFiberBeginWork.old';
@ -599,10 +607,17 @@ export function getCurrentTime(): number {
return now();
}
let currentUpdatePriority = NoEventPriority;
export function requestUpdateLane_getUpdatePriority(): EventPriority {
return currentUpdatePriority;
}
export function requestUpdateLane(fiber: Fiber): Lane {
// Special cases
const mode = fiber.mode;
if ((mode & ConcurrentMode) === NoMode) {
currentUpdatePriority = DiscreteEventPriority;
return (SyncLane: Lane);
} else if (
!deferRenderPhaseUpdateToNextBatch &&
@ -618,7 +633,11 @@ export function requestUpdateLane(fiber: Fiber): Lane {
// This behavior is only a fallback. The flag only exists until we can roll
// out the setState warning, since existing code might accidentally rely on
// the current behavior.
return pickArbitraryLane(workInProgressRootRenderLanes);
const nextLane = pickArbitraryLane(workInProgressRootRenderLanes);
if ((nextLane & SyncLane) === NoLane) {
currentUpdatePriority = NoEventPriority;
}
return nextLane;
}
const isTransition = requestCurrentTransition() !== NoTransition;
@ -642,28 +661,42 @@ export function requestUpdateLane(fiber: Fiber): Lane {
// All transitions within the same event are assigned the same lane.
currentEventTransitionLane = claimNextTransitionLane();
}
if ((currentEventTransitionLane & SyncLane) === NoLane) {
currentUpdatePriority = NoEventPriority;
}
return currentEventTransitionLane;
}
// Updates originating inside certain React methods, like flushSync, have
// their priority set by tracking it with a context variable.
//
// The opaque type returned by the host config is internally a lane, so we can
// use that directly.
// TODO: Move this type conversion to the event priority module.
const updateLane: Lane = (getCurrentUpdatePriority(): any);
if (updateLane !== NoLane) {
return updateLane;
const updatePriority = getCurrentUpdatePriority();
currentUpdatePriority = updatePriority;
if (updatePriority !== NoEventPriority) {
if (updatePriority === DefaultEventPriority) {
return enableUnifiedSyncLane ? SyncLane : DefaultLane;
}
if (updatePriority === ContinuousEventPriority) {
return InputContinuousLane;
}
return (updatePriority: any);
}
// This update originated outside React. Ask the host environment for an
// appropriate priority, based on the type of event.
//
// The opaque type returned by the host config is internally a lane, so we can
// use that directly.
// TODO: Move this type conversion to the event priority module.
const eventLane: Lane = (getCurrentEventPriority(): any);
return eventLane;
const eventPriority = getCurrentEventPriority();
currentUpdatePriority = eventPriority;
if (eventPriority === DefaultEventPriority) {
return enableUnifiedSyncLane ? SyncLane : DefaultLane;
}
if (eventPriority === ContinuousEventPriority) {
return InputContinuousLane;
}
return (eventPriority: any);
}
function requestRetryLane(fiber: Fiber) {
@ -674,8 +707,10 @@ function requestRetryLane(fiber: Fiber) {
// Special cases
const mode = fiber.mode;
if ((mode & ConcurrentMode) === NoMode) {
currentUpdatePriority = DiscreteEventPriority;
return (SyncLane: Lane);
}
currentUpdatePriority = DefaultEventPriority;
return claimNextRetryLane();
}
@ -685,6 +720,7 @@ export function scheduleUpdateOnFiber(
fiber: Fiber,
lane: Lane,
eventTime: number,
updatePriority: EventPriority,
) {
if (__DEV__) {
if (isRunningInsertionEffect) {
@ -697,9 +733,8 @@ export function scheduleUpdateOnFiber(
didScheduleUpdateDuringPassiveEffects = true;
}
}
// Mark that the root has a pending update.
markRootUpdated(root, lane, eventTime);
markRootUpdated(root, lane, eventTime, updatePriority);
if (
(executionContext & RenderContext) !== NoLanes &&
@ -820,7 +855,12 @@ export function scheduleInitialHydrationOnRoot(
// match what was rendered on the server.
const current = root.current;
current.lanes = lane;
markRootUpdated(root, lane, eventTime);
markRootUpdated(
root,
lane,
eventTime,
root.tag === LegacyRoot ? DiscreteEventPriority : DefaultEventPriority,
);
ensureRootIsScheduled(root, eventTime);
}
@ -860,12 +900,15 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
cancelCallback(existingCallbackNode);
}
root.callbackNode = null;
root.callbackPriority = NoLane;
root.callbackPriority = NoEventPriority;
return;
}
// We use the highest priority lane to represent the priority of the callback.
const newCallbackPriority = getHighestPriorityLane(nextLanes);
const newCallbackPriority = laneToEventPriority(
getHighestPriorityLane(nextLanes),
root.updatePriority,
);
// Check if there's an existing task. We may be able to reuse it.
const existingCallbackPriority = root.callbackPriority;
@ -886,25 +929,42 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
// TODO: Temporary until we confirm this warning is not fired.
if (
existingCallbackNode == null &&
existingCallbackPriority !== SyncLane
existingCallbackPriority !== DiscreteEventPriority
) {
console.error(
'Expected scheduled callback to exist. This error is likely caused by a bug in React. Please file an issue.',
);
}
}
// The priority hasn't changed. We can reuse the existing task. Exit.
return;
if (
enableFrameEndScheduling &&
supportsFrameAlignedTask &&
newCallbackPriority === DefaultEventPriority
) {
// Do nothing, we need to schedule a new rAF.
} else {
// The priority hasn't changed. We can reuse the existing task. Exit.
return;
}
}
if (existingCallbackNode != null) {
if (existingCallbackNode !== null) {
// Cancel the existing callback. We'll schedule a new one below.
cancelCallback(existingCallbackNode);
if (
enableFrameEndScheduling &&
supportsFrameAlignedTask &&
existingCallbackPriority === DefaultEventPriority
) {
cancelFrameAlignedTask(existingCallbackNode);
} else {
cancelCallback(existingCallbackNode);
}
}
// Schedule a new callback.
let newCallbackNode;
if (newCallbackPriority === SyncLane) {
if (newCallbackPriority === DiscreteEventPriority) {
// Special case: Sync React callbacks are scheduled on a special
// internal queue
if (root.tag === LegacyRoot) {
@ -943,9 +1003,26 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbacks);
}
newCallbackNode = null;
} else if (
enableFrameEndScheduling &&
supportsFrameAlignedTask &&
newCallbackPriority === DefaultEventPriority
) {
if (__DEV__ && ReactCurrentActQueue.current !== null) {
// Inside `act`, use our internal `act` queue so that these get flushed
// at the end of the current scope even when using the sync version
// of `act`.
ReactCurrentActQueue.current.push(
performConcurrentWorkOnRoot.bind(null, root),
);
} else {
newCallbackNode = scheduleFrameAlignedTask(
performConcurrentWorkOnRoot.bind(null, root),
);
}
} else {
let schedulerPriorityLevel;
switch (lanesToEventPriority(nextLanes)) {
switch (lanesToEventPriority(nextLanes, root.updatePriority)) {
case DiscreteEventPriority:
schedulerPriorityLevel = ImmediateSchedulerPriority;
break;
@ -1883,7 +1960,7 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
if (__DEV__) {
if (enableDebugTracing) {
logRenderStarted(lanes);
logRenderStarted(lanes, root.updatePriority);
}
}
@ -1984,7 +2061,7 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
if (__DEV__) {
if (enableDebugTracing) {
logRenderStarted(lanes);
logRenderStarted(lanes, root.updatePriority);
}
}
@ -2339,7 +2416,7 @@ function commitRootImpl(
if (__DEV__) {
if (enableDebugTracing) {
logCommitStarted(lanes);
logCommitStarted(lanes, root.updatePriority);
}
}
@ -2382,7 +2459,7 @@ function commitRootImpl(
// commitRoot never returns a continuation; it always finishes synchronously.
// So we can clear these now to allow a new callback to be scheduled.
root.callbackNode = null;
root.callbackPriority = NoLane;
root.callbackPriority = NoEventPriority;
// Check which lanes no longer have any work scheduled on them, and mark
// those as finished.
@ -2435,6 +2512,7 @@ function commitRootImpl(
}
}
const prevRootUpdatePriority = root.updatePriority;
// Check if there are any effects in the whole tree.
// TODO: This is left over from the effect list implementation, where we had
// to check for the existence of `firstEffect` to satisfy Flow. I think the
@ -2506,7 +2584,7 @@ function commitRootImpl(
// layout, but class component lifecycles also fire here for legacy reasons.
if (__DEV__) {
if (enableDebugTracing) {
logLayoutEffectsStarted(lanes);
logLayoutEffectsStarted(lanes, root.updatePriority);
}
}
if (enableSchedulingProfiler) {
@ -2635,8 +2713,11 @@ function commitRootImpl(
// TODO: We can optimize this by not scheduling the callback earlier. Since we
// currently schedule the callback in multiple places, will wait until those
// are consolidated.
//// TODO: Need to clear the updatePriority inorder to remove the sync lane check
if (
includesSomeLane(pendingPassiveEffectsLanes, SyncLane) &&
(enableUnifiedSyncLane
? prevRootUpdatePriority === DiscreteEventPriority
: includesSomeLane(pendingPassiveEffectsLanes, SyncLane)) &&
root.tag !== LegacyRoot
) {
flushPassiveEffects();
@ -2644,7 +2725,7 @@ function commitRootImpl(
// Read this again, since a passive effect might have updated it
remainingLanes = root.pendingLanes;
if (includesSomeLane(remainingLanes, (SyncLane: Lane))) {
if (includesSomeLane(remainingLanes, SyncLane)) {
if (enableProfilerTimer && enableProfilerNestedUpdatePhase) {
markNestedUpdateScheduled();
}
@ -2659,6 +2740,7 @@ function commitRootImpl(
}
} else {
nestedUpdateCount = 0;
root.updatePriority = NoEventPriority;
}
// If layout work was scheduled, flush it now.
@ -2766,7 +2848,10 @@ export function flushPassiveEffects(): boolean {
const remainingLanes = pendingPassiveEffectsRemainingLanes;
pendingPassiveEffectsRemainingLanes = NoLanes;
const renderPriority = lanesToEventPriority(pendingPassiveEffectsLanes);
const renderPriority = lanesToEventPriority(
pendingPassiveEffectsLanes,
root.updatePriority,
);
const priority = lowerEventPriority(DefaultEventPriority, renderPriority);
const prevTransition = ReactCurrentBatchConfig.transition;
const previousPriority = getCurrentUpdatePriority();
@ -2827,7 +2912,7 @@ function flushPassiveEffectsImpl() {
didScheduleUpdateDuringPassiveEffects = false;
if (enableDebugTracing) {
logPassiveEffectsStarted(lanes);
logPassiveEffectsStarted(lanes, root.updatePriority);
}
}
@ -2951,7 +3036,7 @@ function captureCommitPhaseErrorOnRoot(
const root = enqueueUpdate(rootFiber, update, (SyncLane: Lane));
const eventTime = requestEventTime();
if (root !== null) {
markRootUpdated(root, SyncLane, eventTime);
markRootUpdated(root, SyncLane, eventTime, DiscreteEventPriority);
ensureRootIsScheduled(root, eventTime);
}
}
@ -3000,7 +3085,7 @@ export function captureCommitPhaseError(
const root = enqueueUpdate(fiber, update, (SyncLane: Lane));
const eventTime = requestEventTime();
if (root !== null) {
markRootUpdated(root, SyncLane, eventTime);
markRootUpdated(root, SyncLane, eventTime, DiscreteEventPriority);
ensureRootIsScheduled(root, eventTime);
}
return;
@ -3136,7 +3221,8 @@ function retryTimedOutBoundary(boundaryFiber: Fiber, retryLane: Lane) {
const eventTime = requestEventTime();
const root = enqueueConcurrentRenderForLane(boundaryFiber, retryLane);
if (root !== null) {
markRootUpdated(root, retryLane, eventTime);
// console.log(retryLane,root.updatePriority, currentUpdatePriority, new Error().stack);
markRootUpdated(root, retryLane, eventTime, currentUpdatePriority);
ensureRootIsScheduled(root, eventTime);
}
}

View File

@ -22,7 +22,8 @@ import type {
import type {WorkTag} from './ReactWorkTags';
import type {TypeOfMode} from './ReactTypeOfMode';
import type {Flags} from './ReactFiberFlags';
import type {Lane, Lanes, LaneMap} from './ReactFiberLane.old';
import type {Lanes, LaneMap} from './ReactFiberLane.old';
import type {EventPriority} from './ReactEventPriorities';
import type {RootTag} from './ReactRootTags';
import type {
Container,
@ -239,7 +240,9 @@ type BaseFiberRootProperties = {
// Node returned by Scheduler.scheduleCallback. Represents the next rendering
// task that the root will work on.
callbackNode: any,
callbackPriority: Lane,
callbackPriority: EventPriority,
frameAlignedNode?: number | null,
updatePriority: EventPriority,
eventTimes: LaneMap<number>,
expirationTimes: LaneMap<number>,
hiddenUpdates: LaneMap<Array<ConcurrentUpdate> | null>,

View File

@ -16,8 +16,9 @@ describe('DebugTracing', () => {
let logs;
const DEFAULT_LANE_STRING = '0b0000000000000000000000000010000';
const SYNC_LANE_STRING = '0b0000000000000000000000000000001';
const RETRY_LANE_STRING = '0b0000000010000000000000000000000';
const DEFAULT_EVENT_PRIORITY = 3;
global.IS_REACT_ACT_ENVIRONMENT = true;
@ -87,9 +88,9 @@ describe('DebugTracing', () => {
);
expect(logs).toEqual([
'group: ⚛️ render (0b0000000000000000000000000000001)',
'group: ⚛️ render (0b0000000000000000000000000000001) (1)',
'log: ⚛️ Example suspended',
'groupEnd: ⚛️ render (0b0000000000000000000000000000001)',
'groupEnd: ⚛️ render (0b0000000000000000000000000000001) (1)',
]);
logs.splice(0);
@ -121,9 +122,9 @@ describe('DebugTracing', () => {
);
expect(logs).toEqual([
'group: ⚛️ render (0b0000000000000000000000000000001)',
'group: ⚛️ render (0b0000000000000000000000000000001) (1)',
'log: <Wrapper/>',
'groupEnd: ⚛️ render (0b0000000000000000000000000000001)',
'groupEnd: ⚛️ render (0b0000000000000000000000000000001) (1)',
]);
logs.splice(0);
@ -131,13 +132,13 @@ describe('DebugTracing', () => {
expect(Scheduler).toFlushUntilNextPaint([]);
expect(logs).toEqual([
`group: ⚛️ render (${RETRY_LANE_STRING})`,
`group: ⚛️ render (${RETRY_LANE_STRING}) (0)`,
'log: <Example/>',
`groupEnd: ⚛️ render (${RETRY_LANE_STRING})`,
`groupEnd: ⚛️ render (${RETRY_LANE_STRING}) (0)`,
]);
});
// @gate experimental && build === 'development' && enableDebugTracing
// @gate experimental && build === 'development' && enableDebugTracing && enableUnifiedSyncLane
it('should log concurrent render with suspense', async () => {
let isResolved = false;
let resolveFakeSuspensePromise;
@ -167,9 +168,9 @@ describe('DebugTracing', () => {
);
expect(logs).toEqual([
`group: ⚛️ render (${DEFAULT_LANE_STRING})`,
`group: ⚛️ render (${SYNC_LANE_STRING}) (${DEFAULT_EVENT_PRIORITY})`,
'log: ⚛️ Example suspended',
`groupEnd: ⚛️ render (${DEFAULT_LANE_STRING})`,
`groupEnd: ⚛️ render (${SYNC_LANE_STRING}) (${DEFAULT_EVENT_PRIORITY})`,
]);
logs.splice(0);
@ -178,7 +179,7 @@ describe('DebugTracing', () => {
expect(logs).toEqual(['log: ⚛️ Example resolved']);
});
// @gate experimental && build === 'development' && enableDebugTracing && enableCPUSuspense
// @gate experimental && build === 'development' && enableDebugTracing && enableCPUSuspense && enableUnifiedSyncLane
it('should log concurrent render with CPU suspense', () => {
function Example() {
console.log('<Example/>');
@ -204,16 +205,16 @@ describe('DebugTracing', () => {
);
expect(logs).toEqual([
`group: ⚛️ render (${DEFAULT_LANE_STRING})`,
`group: ⚛️ render (${SYNC_LANE_STRING}) (${DEFAULT_EVENT_PRIORITY})`,
'log: <Wrapper/>',
`groupEnd: ⚛️ render (${DEFAULT_LANE_STRING})`,
`group: ⚛️ render (${RETRY_LANE_STRING})`,
`groupEnd: ⚛️ render (${SYNC_LANE_STRING}) (${DEFAULT_EVENT_PRIORITY})`,
`group: ⚛️ render (${RETRY_LANE_STRING}) (0)`,
'log: <Example/>',
`groupEnd: ⚛️ render (${RETRY_LANE_STRING})`,
`groupEnd: ⚛️ render (${RETRY_LANE_STRING}) (0)`,
]);
});
// @gate experimental && build === 'development' && enableDebugTracing
// @gate experimental && build === 'development' && enableDebugTracing && enableUnifiedSyncLane
it('should log cascading class component updates', () => {
class Example extends React.Component {
state = {didMount: false};
@ -235,15 +236,15 @@ describe('DebugTracing', () => {
);
expect(logs).toEqual([
`group: ⚛️ commit (${DEFAULT_LANE_STRING})`,
`group: ⚛️ layout effects (${DEFAULT_LANE_STRING})`,
`group: ⚛️ commit (${SYNC_LANE_STRING}) (${DEFAULT_EVENT_PRIORITY})`,
`group: ⚛️ layout effects (${SYNC_LANE_STRING}) (${DEFAULT_EVENT_PRIORITY})`,
'log: ⚛️ Example updated state (0b0000000000000000000000000000001)',
`groupEnd: ⚛️ layout effects (${DEFAULT_LANE_STRING})`,
`groupEnd: ⚛️ commit (${DEFAULT_LANE_STRING})`,
`groupEnd: ⚛️ layout effects (${SYNC_LANE_STRING}) (${DEFAULT_EVENT_PRIORITY})`,
`groupEnd: ⚛️ commit (${SYNC_LANE_STRING}) (${DEFAULT_EVENT_PRIORITY})`,
]);
});
// @gate experimental && build === 'development' && enableDebugTracing
// @gate experimental && build === 'development' && enableDebugTracing && enableUnifiedSyncLane
it('should log render phase state updates for class component', () => {
class Example extends React.Component {
state = {didRender: false};
@ -267,13 +268,13 @@ describe('DebugTracing', () => {
}).toErrorDev('Cannot update during an existing state transition');
expect(logs).toEqual([
`group: ⚛️ render (${DEFAULT_LANE_STRING})`,
`log: ⚛️ Example updated state (${DEFAULT_LANE_STRING})`,
`groupEnd: ⚛️ render (${DEFAULT_LANE_STRING})`,
`group: ⚛️ render (${SYNC_LANE_STRING}) (${DEFAULT_EVENT_PRIORITY})`,
`log: ⚛️ Example updated state (${SYNC_LANE_STRING})`,
`groupEnd: ⚛️ render (${SYNC_LANE_STRING}) (${DEFAULT_EVENT_PRIORITY})`,
]);
});
// @gate experimental && build === 'development' && enableDebugTracing
// @gate experimental && build === 'development' && enableDebugTracing && enableUnifiedSyncLane
it('should log cascading layout updates', () => {
function Example() {
const [didMount, setDidMount] = React.useState(false);
@ -293,15 +294,15 @@ describe('DebugTracing', () => {
);
expect(logs).toEqual([
`group: ⚛️ commit (${DEFAULT_LANE_STRING})`,
`group: ⚛️ layout effects (${DEFAULT_LANE_STRING})`,
`group: ⚛️ commit (${SYNC_LANE_STRING}) (${DEFAULT_EVENT_PRIORITY})`,
`group: ⚛️ layout effects (${SYNC_LANE_STRING}) (${DEFAULT_EVENT_PRIORITY})`,
'log: ⚛️ Example updated state (0b0000000000000000000000000000001)',
`groupEnd: ⚛️ layout effects (${DEFAULT_LANE_STRING})`,
`groupEnd: ⚛️ commit (${DEFAULT_LANE_STRING})`,
`groupEnd: ⚛️ layout effects (${SYNC_LANE_STRING}) (${DEFAULT_EVENT_PRIORITY})`,
`groupEnd: ⚛️ commit (${SYNC_LANE_STRING}) (${DEFAULT_EVENT_PRIORITY})`,
]);
});
// @gate experimental && build === 'development' && enableDebugTracing
// @gate experimental && build === 'development' && enableDebugTracing && enableUnifiedSyncLane
it('should log cascading passive updates', () => {
function Example() {
const [didMount, setDidMount] = React.useState(false);
@ -320,13 +321,14 @@ describe('DebugTracing', () => {
);
});
expect(logs).toEqual([
`group: ⚛️ passive effects (${DEFAULT_LANE_STRING})`,
`log: ⚛️ Example updated state (${DEFAULT_LANE_STRING})`,
`groupEnd: ⚛️ passive effects (${DEFAULT_LANE_STRING})`,
// TODO: why does this become 0?
`group: ⚛️ passive effects (${SYNC_LANE_STRING}) (0)`,
`log: ⚛️ Example updated state (${SYNC_LANE_STRING})`,
`groupEnd: ⚛️ passive effects (${SYNC_LANE_STRING}) (0)`,
]);
});
// @gate experimental && build === 'development' && enableDebugTracing
// @gate experimental && build === 'development' && enableDebugTracing && enableUnifiedSyncLane
it('should log render phase updates', () => {
function Example() {
const [didRender, setDidRender] = React.useState(false);
@ -346,13 +348,13 @@ describe('DebugTracing', () => {
});
expect(logs).toEqual([
`group: ⚛️ render (${DEFAULT_LANE_STRING})`,
`log: ⚛️ Example updated state (${DEFAULT_LANE_STRING})`,
`groupEnd: ⚛️ render (${DEFAULT_LANE_STRING})`,
`group: ⚛️ render (${SYNC_LANE_STRING}) (${DEFAULT_EVENT_PRIORITY})`,
`log: ⚛️ Example updated state (${SYNC_LANE_STRING})`,
`groupEnd: ⚛️ render (${SYNC_LANE_STRING}) (${DEFAULT_EVENT_PRIORITY})`,
]);
});
// @gate experimental && build === 'development' && enableDebugTracing
// @gate experimental && build === 'development' && enableDebugTracing && enableUnifiedSyncLane
it('should log when user code logs', () => {
function Example() {
console.log('Hello from user code');
@ -369,9 +371,9 @@ describe('DebugTracing', () => {
);
expect(logs).toEqual([
`group: ⚛️ render (${DEFAULT_LANE_STRING})`,
`group: ⚛️ render (${SYNC_LANE_STRING}) (${DEFAULT_EVENT_PRIORITY})`,
'log: Hello from user code',
`groupEnd: ⚛️ render (${DEFAULT_LANE_STRING})`,
`groupEnd: ⚛️ render (${SYNC_LANE_STRING}) (${DEFAULT_EVENT_PRIORITY})`,
]);
});

View File

@ -157,12 +157,17 @@ describe('ReactBlockingMode', () => {
}),
);
// Only the second update should have flushed synchronously
expect(Scheduler).toHaveYielded(['B1']);
expect(root).toMatchRenderedOutput('A0B1');
if (gate(flags => flags.enableUnifiedSyncLane)) {
expect(Scheduler).toHaveYielded(['A1', 'B1']);
expect(root).toMatchRenderedOutput('A1B1');
} else {
// Only the second update should have flushed synchronously
expect(Scheduler).toHaveYielded(['B1']);
expect(root).toMatchRenderedOutput('A0B1');
// Now flush the first update
expect(Scheduler).toFlushAndYield(['A1']);
expect(root).toMatchRenderedOutput('A1B1');
// Now flush the first update
expect(Scheduler).toFlushAndYield(['A1']);
expect(root).toMatchRenderedOutput('A1B1');
}
});
});

View File

@ -35,9 +35,17 @@ describe('ReactClassSetStateCallback', () => {
expect(Scheduler).toHaveYielded([0]);
await act(async () => {
app.setState({step: 1}, () =>
Scheduler.unstable_yieldValue('Callback 1'),
);
if (gate(flags => flags.enableUnifiedSyncLane)) {
React.startTransition(() => {
app.setState({step: 1}, () =>
Scheduler.unstable_yieldValue('Callback 1'),
);
});
} else {
app.setState({step: 1}, () =>
Scheduler.unstable_yieldValue('Callback 1'),
);
}
ReactNoop.flushSync(() => {
app.setState({step: 2}, () =>
Scheduler.unstable_yieldValue('Callback 2'),

View File

@ -339,7 +339,9 @@ describe('ReactExpiration', () => {
// Before the update can finish, update again. Even though no time has
// advanced, this update should be given a different expiration time than
// the currently rendering one. So, C and D should render with 1, not 2.
subscribers.forEach(s => s.setState({text: '2'}));
React.startTransition(() => {
subscribers.forEach(s => s.setState({text: '2'}));
});
expect(Scheduler).toFlushAndYieldThrough([
'1 [C] [render]',
'1 [D] [render]',

View File

@ -69,6 +69,7 @@ describe('ReactFiberHostContext', () => {
prepareRendererToRender: function() {},
resetRendererAfterRender: function() {},
supportsMutation: true,
supportsFrameAlignedTask: false,
requestPostPaintCallback: function() {},
});
@ -136,6 +137,7 @@ describe('ReactFiberHostContext', () => {
prepareRendererToRender: function() {},
resetRendererAfterRender: function() {},
supportsMutation: true,
shouldScheduleAnimationFrame: () => false,
});
const container = Renderer.createContainer(

View File

@ -36,7 +36,7 @@ describe('ReactFlushSync', () => {
ReactNoop.flushSync(() => setSyncState(1));
}
}, [syncState, state]);
return <Text text={`${syncState}, ${state}`} />;
return <Text text={`${syncState} / ${state}`} />;
}
const root = ReactNoop.createRoot();
@ -49,22 +49,28 @@ describe('ReactFlushSync', () => {
root.render(<App />);
}
// This will yield right before the passive effect fires
expect(Scheduler).toFlushUntilNextPaint(['0, 0']);
expect(Scheduler).toFlushUntilNextPaint(['0 / 0']);
// The passive effect will schedule a sync update and a normal update.
// They should commit in two separate batches. First the sync one.
expect(() => {
expect(Scheduler).toFlushUntilNextPaint(['1, 0']);
expect(Scheduler).toFlushUntilNextPaint(
gate(flags => flags.enableUnifiedSyncLane) ? ['1 / 1'] : ['1 / 0'],
);
}).toErrorDev('flushSync was called from inside a lifecycle method');
// The remaining update is not sync
ReactNoop.flushSync();
expect(Scheduler).toHaveYielded([]);
// Now flush it.
expect(Scheduler).toFlushUntilNextPaint(['1, 1']);
if (gate(flags => flags.enableUnifiedSyncLane)) {
expect(Scheduler).toFlushUntilNextPaint([]);
} else {
// Now flush it.
expect(Scheduler).toFlushUntilNextPaint(['1 / 1']);
}
});
expect(root).toMatchRenderedOutput('1, 1');
expect(root).toMatchRenderedOutput('1 / 1');
});
test('nested with startTransition', async () => {

View File

@ -568,8 +568,13 @@ describe('ReactHooks', () => {
});
};
// Update at normal priority
ReactTestRenderer.unstable_batchedUpdates(() => update(n => n * 100));
if (gate(flags => flags.enableUnifiedSyncLane)) {
// Update at low priority
React.startTransition(() => update(n => n * 100));
} else {
// Update at normal priority
ReactTestRenderer.unstable_batchedUpdates(() => update(n => n * 100));
}
// The new state is eagerly computed.
expect(Scheduler).toHaveYielded(['Compute state (1 -> 100)']);

View File

@ -815,7 +815,13 @@ describe('ReactHooksWithNoopRenderer', () => {
ReactNoop.discreteUpdates(() => {
setRow(5);
});
setRow(20);
if (gate(flags => flags.enableUnifiedSyncLane)) {
React.startTransition(() => {
setRow(20);
});
} else {
setRow(20);
}
});
expect(Scheduler).toHaveYielded(['Up', 'Down']);
expect(root).toMatchRenderedOutput(<span prop="Down" />);
@ -955,11 +961,16 @@ describe('ReactHooksWithNoopRenderer', () => {
ReactNoop.flushSync(() => {
counter.current.dispatch(INCREMENT);
});
expect(Scheduler).toHaveYielded(['Count: 1']);
expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
if (gate(flags => flags.enableUnifiedSyncLane)) {
expect(Scheduler).toHaveYielded(['Count: 4']);
expect(ReactNoop.getChildren()).toEqual([span('Count: 4')]);
} else {
expect(Scheduler).toHaveYielded(['Count: 1']);
expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
expect(Scheduler).toFlushAndYield(['Count: 4']);
expect(ReactNoop.getChildren()).toEqual([span('Count: 4')]);
expect(Scheduler).toFlushAndYield(['Count: 4']);
expect(ReactNoop.getChildren()).toEqual([span('Count: 4')]);
}
});
});
@ -1717,11 +1728,16 @@ describe('ReactHooksWithNoopRenderer', () => {
// As a result we, somewhat surprisingly, commit them in the opposite order.
// This should be fine because any non-discrete set of work doesn't guarantee order
// and easily could've happened slightly later too.
expect(Scheduler).toHaveYielded([
'Will set count to 1',
'Count: 2',
'Count: 1',
]);
if (gate(flags => flags.enableUnifiedSyncLane)) {
// updateCounts are batched
expect(Scheduler).toHaveYielded(['Will set count to 1', 'Count: 1']);
} else {
expect(Scheduler).toHaveYielded([
'Will set count to 1',
'Count: 2',
'Count: 1',
]);
}
expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
});

View File

@ -1878,28 +1878,24 @@ describe('ReactIncremental', () => {
'ShowLocale {"locale":"de"}',
'ShowBoth {"locale":"de"}',
]);
ReactNoop.render(
<Intl locale="sv">
<ShowLocale />
<div>
<ShowBoth />
</div>
</Intl>,
);
if (gate(flags => flags.enableSyncDefaultUpdates)) {
React.startTransition(() => {
ReactNoop.render(
<Intl locale="sv">
<ShowLocale />
<div>
<ShowBoth />
</div>
</Intl>,
);
});
expect(Scheduler).toFlushAndYieldThrough([
'Intl {}',
'ShowLocale {"locale":"sv"}',
'ShowBoth {"locale":"sv"}',
]);
} else {
ReactNoop.render(
<Intl locale="sv">
<ShowLocale />
<div>
<ShowBoth />
</div>
</Intl>,
);
expect(Scheduler).toFlushAndYieldThrough(['Intl {}']);
}
expect(Scheduler).toFlushAndYieldThrough(['Intl {}']);
ReactNoop.render(
<Intl locale="en">
@ -1910,21 +1906,37 @@ describe('ReactIncremental', () => {
<ShowBoth />
</Intl>,
);
expect(Scheduler).toFlushAndYield([
'ShowLocale {"locale":"sv"}',
'ShowBoth {"locale":"sv"}',
'Intl {}',
'ShowLocale {"locale":"en"}',
'Router {}',
'Indirection {}',
'ShowLocale {"locale":"en"}',
'ShowRoute {"route":"/about"}',
'ShowNeither {}',
'Intl {}',
'ShowBoth {"locale":"ru","route":"/about"}',
'ShowBoth {"locale":"en","route":"/about"}',
'ShowBoth {"locale":"en"}',
]);
if (gate(flags => flags.enableSyncDefaultUpdates)) {
expect(Scheduler).toFlushAndYield([
'Intl {}',
'ShowLocale {"locale":"en"}',
'Router {}',
'Indirection {}',
'ShowLocale {"locale":"en"}',
'ShowRoute {"route":"/about"}',
'ShowNeither {}',
'Intl {}',
'ShowBoth {"locale":"ru","route":"/about"}',
'ShowBoth {"locale":"en","route":"/about"}',
'ShowBoth {"locale":"en"}',
]);
} else {
expect(Scheduler).toFlushAndYield([
'ShowLocale {"locale":"sv"}',
'ShowBoth {"locale":"sv"}',
'Intl {}',
'ShowLocale {"locale":"en"}',
'Router {}',
'Indirection {}',
'ShowLocale {"locale":"en"}',
'ShowRoute {"route":"/about"}',
'ShowNeither {}',
'Intl {}',
'ShowBoth {"locale":"ru","route":"/about"}',
'ShowBoth {"locale":"en","route":"/about"}',
'ShowBoth {"locale":"en"}',
]);
}
});
it('does not leak own context into context provider', () => {
@ -2746,19 +2758,22 @@ describe('ReactIncremental', () => {
return null;
}
ReactNoop.render(<Parent step={1} />);
if (gate(flags => flags.enableSyncDefaultUpdates)) {
React.startTransition(() => {
ReactNoop.render(<Parent step={1} />);
});
expect(Scheduler).toFlushAndYieldThrough(['Parent: 1', 'Child: 1']);
} else {
ReactNoop.render(<Parent step={1} />);
expect(Scheduler).toFlushAndYieldThrough(['Parent: 1']);
}
expect(Scheduler).toFlushAndYieldThrough(['Parent: 1']);
// Interrupt at same priority
ReactNoop.render(<Parent step={2} />);
expect(Scheduler).toFlushAndYield(['Child: 1', 'Parent: 2', 'Child: 2']);
if (gate(flags => flags.enableSyncDefaultUpdates)) {
expect(Scheduler).toFlushAndYield(['Parent: 2', 'Child: 2']);
} else {
expect(Scheduler).toFlushAndYield(['Child: 1', 'Parent: 2', 'Child: 2']);
}
});
it('does not interrupt for update at lower priority', () => {
@ -2772,20 +2787,22 @@ describe('ReactIncremental', () => {
return null;
}
ReactNoop.render(<Parent step={1} />);
if (gate(flags => flags.enableSyncDefaultUpdates)) {
React.startTransition(() => {
ReactNoop.render(<Parent step={1} />);
});
expect(Scheduler).toFlushAndYieldThrough(['Parent: 1', 'Child: 1']);
} else {
ReactNoop.render(<Parent step={1} />);
expect(Scheduler).toFlushAndYieldThrough(['Parent: 1']);
}
expect(Scheduler).toFlushAndYieldThrough(['Parent: 1']);
// Interrupt at lower priority
ReactNoop.expire(2000);
ReactNoop.render(<Parent step={2} />);
expect(Scheduler).toFlushAndYield(['Child: 1', 'Parent: 2', 'Child: 2']);
if (gate(flags => flags.enableSyncDefaultUpdates)) {
expect(Scheduler).toFlushAndYield(['Parent: 2', 'Child: 2']);
} else {
expect(Scheduler).toFlushAndYield(['Child: 1', 'Parent: 2', 'Child: 2']);
}
});
it('does interrupt for update at higher priority', () => {

View File

@ -47,7 +47,7 @@ describe('ReactIncrementalUpdates', () => {
state = {};
componentDidMount() {
Scheduler.unstable_yieldValue('commit');
ReactNoop.deferredUpdates(() => {
React.startTransition(() => {
// Has low priority
this.setState({b: 'b'});
this.setState({c: 'c'});
@ -111,13 +111,13 @@ describe('ReactIncrementalUpdates', () => {
expect(Scheduler).toFlushAndYield(['render', 'componentDidMount']);
ReactNoop.flushSync(() => {
ReactNoop.deferredUpdates(() => {
React.startTransition(() => {
instance.setState({x: 'x'});
instance.setState({y: 'y'});
});
instance.setState({a: 'a'});
instance.setState({b: 'b'});
ReactNoop.deferredUpdates(() => {
React.startTransition(() => {
instance.updater.enqueueReplaceState(instance, {c: 'c'});
instance.setState({d: 'd'});
});
@ -190,27 +190,45 @@ describe('ReactIncrementalUpdates', () => {
});
// The sync updates should have flushed, but not the async ones
expect(Scheduler).toHaveYielded(['e', 'f']);
expect(ReactNoop.getChildren()).toEqual([span('ef')]);
if (gate(flags => flags.enableUnifiedSyncLane)) {
expect(Scheduler).toHaveYielded(['d', 'e', 'f']);
expect(ReactNoop.getChildren()).toEqual([span('def')]);
} else {
expect(Scheduler).toHaveYielded(['e', 'f']);
expect(ReactNoop.getChildren()).toEqual([span('ef')]);
}
// Now flush the remaining work. Even though e and f were already processed,
// they should be processed again, to ensure that the terminal state
// is deterministic.
expect(Scheduler).toFlushAndYield([
// Since 'g' is in a transition, we'll process 'd' separately first.
// That causes us to process 'd' with 'e' and 'f' rebased.
'd',
'e',
'f',
// Then we'll re-process everything for 'g'.
'a',
'b',
'c',
'd',
'e',
'f',
'g',
]);
if (gate(flags => flags.enableUnifiedSyncLane)) {
expect(Scheduler).toFlushAndYield([
// Then we'll re-process everything for 'g'.
'a',
'b',
'c',
'd',
'e',
'f',
'g',
]);
} else {
expect(Scheduler).toFlushAndYield([
// Since 'g' is in a transition, we'll process 'd' separately first.
// That causes us to process 'd' with 'e' and 'f' rebased.
'd',
'e',
'f',
// Then we'll re-process everything for 'g'.
'a',
'b',
'c',
'd',
'e',
'f',
'g',
]);
}
expect(ReactNoop.getChildren()).toEqual([span('abcdefg')]);
} else {
instance.setState(createUpdate('d'));
@ -292,27 +310,44 @@ describe('ReactIncrementalUpdates', () => {
});
// The sync updates should have flushed, but not the async ones.
expect(Scheduler).toHaveYielded(['e', 'f']);
if (gate(flags => flags.enableUnifiedSyncLane)) {
expect(Scheduler).toHaveYielded(['d', 'e', 'f']);
} else {
expect(Scheduler).toHaveYielded(['e', 'f']);
}
expect(ReactNoop.getChildren()).toEqual([span('f')]);
// Now flush the remaining work. Even though e and f were already processed,
// they should be processed again, to ensure that the terminal state
// is deterministic.
expect(Scheduler).toFlushAndYield([
// Since 'g' is in a transition, we'll process 'd' separately first.
// That causes us to process 'd' with 'e' and 'f' rebased.
'd',
'e',
'f',
// Then we'll re-process everything for 'g'.
'a',
'b',
'c',
'd',
'e',
'f',
'g',
]);
if (gate(flags => flags.enableUnifiedSyncLane)) {
expect(Scheduler).toFlushAndYield([
// Then we'll re-process everything for 'g'.
'a',
'b',
'c',
'd',
'e',
'f',
'g',
]);
} else {
expect(Scheduler).toFlushAndYield([
// Since 'g' is in a transition, we'll process 'd' separately first.
// That causes us to process 'd' with 'e' and 'f' rebased.
'd',
'e',
'f',
// Then we'll re-process everything for 'g'.
'a',
'b',
'c',
'd',
'e',
'f',
'g',
]);
}
expect(ReactNoop.getChildren()).toEqual([span('fg')]);
} else {
instance.setState(createUpdate('d'));

View File

@ -569,8 +569,15 @@ describe('ReactOffscreen', () => {
);
// Before the inner update can finish, we receive another pair of updates.
setOuter(2);
setInner(2);
if (gate(flags => flags.enableUnifiedSyncLane)) {
React.startTransition(() => {
setOuter(2);
setInner(2);
});
} else {
setOuter(2);
setInner(2);
}
// Also, before either of these new updates are processed, the hidden
// tree is revealed at high priority.

View File

@ -381,7 +381,9 @@ describe('ReactOffscreen', () => {
expect(root).toMatchRenderedOutput(<span hidden={true}>A0</span>);
await act(async () => {
setStep(1);
startTransition(() => {
setStep(1);
});
ReactNoop.flushSync(() => {
setText('B');
});
@ -523,8 +525,10 @@ describe('ReactOffscreen', () => {
// Before the tree commits, schedule a concurrent event. The inner update
// is to a tree that's just about to be hidden.
setOuter(2);
setInner(2);
startTransition(() => {
setOuter(2);
setInner(2);
});
// Commit the previous render.
jest.runAllTimers();

View File

@ -934,16 +934,31 @@ describe('ReactTransition', () => {
updateNormalPri();
});
expect(Scheduler).toHaveYielded([
// Finish transition update.
'Normal pri: 0',
'Commit',
// Normal pri update.
'Transition pri: 1',
'Normal pri: 1',
'Commit',
]);
if (gate(flags => flags.enableSyncDefaultUpdates)) {
expect(Scheduler).toHaveYielded([
// Interrupt transition.
'Transition pri: 0',
'Normal pri: 1',
'Commit',
// Normal pri update.
'Transition pri: 1',
'Normal pri: 1',
'Commit',
]);
} else {
expect(Scheduler).toHaveYielded([
// Finish transition update.
'Normal pri: 0',
'Commit',
// Normal pri update.
'Transition pri: 1',
'Normal pri: 1',
'Commit',
]);
}
expect(root).toMatchRenderedOutput('Transition pri: 1, Normal pri: 1');
});

View File

@ -186,6 +186,10 @@ describe('updaters', () => {
let triggerActiveCascade = null;
let triggerPassiveCascade = null;
// TODO: What should we do in tests like this,
// where we're simulating default events?
window.event = 'test';
const Parent = () => <SchedulingComponent />;
const SchedulingComponent = () => {
const [cascade, setCascade] = React.useState(null);
@ -367,7 +371,13 @@ describe('updaters', () => {
onCommitRootShouldYield = true;
await act(async () => {
triggerError();
if (gate(flags => flags.enableUnifiedSyncLane)) {
React.startTransition(() => {
triggerError();
});
} else {
triggerError();
}
});
expect(Scheduler).toHaveYielded(['onCommitRoot', 'error', 'onCommitRoot']);
expect(allSchedulerTypes).toEqual([[Parent], [ErrorBoundary]]);

View File

@ -1558,8 +1558,15 @@ describe('useMutableSource', () => {
expect(Scheduler).toFlushAndYieldThrough(['a0', 'b0']);
// Mutate in an event. This schedules a subscription update on a, which
// already mounted, but not b, which hasn't subscribed yet.
mutateA('a1');
mutateB('b1');
if (gate(flags => flags.enableUnifiedSyncLane)) {
React.startTransition(() => {
mutateA('a1');
mutateB('b1');
});
} else {
mutateA('a1');
mutateB('b1');
}
// Mutate again at lower priority. This will schedule another subscription
// update on a, but not b. When b mounts and subscriptions, the value it

View File

@ -78,6 +78,14 @@ export const resetRendererAfterRender = $$$hostConfig.resetRendererAfterRender;
export const supportsMicrotasks = $$$hostConfig.supportsMicrotasks;
export const scheduleMicrotask = $$$hostConfig.scheduleMicrotask;
// -------------------
// Animation Frame
// (optional)
// -------------------
export const supportsFrameAlignedTask = $$$hostConfig.supportsFrameAlignedTask;
export const scheduleFrameAlignedTask = $$$hostConfig.scheduleFrameAlignedTask;
export const cancelFrameAlignedTask = $$$hostConfig.cancelFrameAlignedTask;
// -------------------
// Test selectors
// (optional)

View File

@ -85,6 +85,8 @@ export const enableLegacyFBSupport = false;
export const enableCache = __EXPERIMENTAL__;
export const enableCacheElement = __EXPERIMENTAL__;
export const enableFrameEndScheduling = __EXPERIMENTAL__;
export const enableTransitionTracing = false;
// No known bugs, but needs performance testing
@ -122,6 +124,8 @@ export const enableUseMemoCacheHook = __EXPERIMENTAL__;
export const enableUseEventHook = __EXPERIMENTAL__;
export const enableUnifiedSyncLane = __EXPERIMENTAL__;
// -----------------------------------------------------------------------------
// Chopping Block
//

View File

@ -29,6 +29,7 @@ export const enableProfilerNestedUpdateScheduledHook = false;
export const enableUpdaterTracking = __PROFILE__;
export const enableCache = false;
export const enableCacheElement = true;
export const enableFrameEndScheduling = false;
export const enableSchedulerDebugging = false;
export const debugRenderPhaseSideEffectsForStrictMode = true;
export const disableJavaScriptURLs = false;
@ -87,5 +88,7 @@ export const enableFloat = false;
export const useModernStrictMode = false;
export const enableUnifiedSyncLane = false;
// Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType);

View File

@ -22,6 +22,7 @@ export const enableProfilerNestedUpdateScheduledHook = false;
export const enableUpdaterTracking = __PROFILE__;
export const enableCache = false;
export const enableCacheElement = false;
export const enableFrameEndScheduling = false;
export const disableJavaScriptURLs = false;
export const disableCommentsAsDOMContainers = true;
export const disableInputAttributeSyncing = false;
@ -76,5 +77,7 @@ export const enableFloat = false;
export const useModernStrictMode = false;
export const enableUnifiedSyncLane = false;
// Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType);

View File

@ -22,6 +22,8 @@ export const enableProfilerNestedUpdateScheduledHook = false;
export const enableUpdaterTracking = false;
export const enableCache = __EXPERIMENTAL__;
export const enableCacheElement = __EXPERIMENTAL__;
export const enableFrameEndScheduling = __EXPERIMENTAL__;
export const enableUnifiedSyncLane = __EXPERIMENTAL__;
export const disableJavaScriptURLs = false;
export const disableCommentsAsDOMContainers = true;
export const disableInputAttributeSyncing = false;

View File

@ -22,6 +22,7 @@ export const enableProfilerNestedUpdateScheduledHook = false;
export const enableUpdaterTracking = false;
export const enableCache = true;
export const enableCacheElement = true;
export const enableFrameEndScheduling = false;
export const disableJavaScriptURLs = false;
export const disableCommentsAsDOMContainers = true;
export const disableInputAttributeSyncing = false;
@ -57,6 +58,7 @@ export const enableClientRenderFallbackOnTextMismatch = true;
export const enableStrictEffects = false;
export const createRootStrictEffectsByDefault = false;
export const enableUseRefAccessWarning = false;
export const enableUnifiedSyncLane = false;
export const disableSchedulerTimeoutInWorkLoop = false;
export const enableLazyContextPropagation = false;

View File

@ -22,6 +22,7 @@ export const enableProfilerNestedUpdateScheduledHook = false;
export const enableUpdaterTracking = false;
export const enableCache = true;
export const enableCacheElement = true;
export const enableFrameEndScheduling = false;
export const enableSchedulerDebugging = false;
export const disableJavaScriptURLs = false;
export const disableCommentsAsDOMContainers = true;
@ -78,5 +79,7 @@ export const enableFloat = false;
export const useModernStrictMode = false;
export const enableUnifiedSyncLane = false;
// Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType);

View File

@ -22,6 +22,8 @@ export const enableProfilerNestedUpdateScheduledHook = false;
export const enableUpdaterTracking = false;
export const enableCache = __EXPERIMENTAL__;
export const enableCacheElement = __EXPERIMENTAL__;
export const enableFrameEndScheduling = __EXPERIMENTAL__;
export const enableUnifiedSyncLane = __EXPERIMENTAL__;
export const disableJavaScriptURLs = false;
export const disableCommentsAsDOMContainers = true;
export const disableInputAttributeSyncing = false;

View File

@ -22,6 +22,7 @@ export const enableProfilerNestedUpdateScheduledHook = false;
export const enableUpdaterTracking = false;
export const enableCache = true;
export const enableCacheElement = true;
export const enableFrameEndScheduling = false;
export const disableJavaScriptURLs = true;
export const disableCommentsAsDOMContainers = true;
export const disableInputAttributeSyncing = false;
@ -77,5 +78,7 @@ export const enableFloat = false;
export const useModernStrictMode = false;
export const enableUnifiedSyncLane = false;
// Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType);

View File

@ -28,6 +28,8 @@ export const consoleManagedByDevToolsDuringStrictMode = __VARIANT__;
export const enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay = __VARIANT__;
export const enableClientRenderFallbackOnTextMismatch = __VARIANT__;
export const enableTransitionTracing = __VARIANT__;
export const enableFrameEndScheduling = __VARIANT__;
export const enableUnifiedSyncLane = __VARIANT__;
// Enable this flag to help with concurrent mode debugging.
// It logs information to the console about React scheduling, rendering, and commit phases.
//

View File

@ -33,7 +33,9 @@ export const {
enableSyncDefaultUpdates,
enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay,
enableClientRenderFallbackOnTextMismatch,
enableFrameEndScheduling,
enableTransitionTracing,
enableUnifiedSyncLane,
} = dynamicFeatureFlags;
// On WWW, __EXPERIMENTAL__ is used for a new modern build.

View File

@ -454,7 +454,13 @@ describe('useSubscription', () => {
observableA.next('a-2');
// Update again
renderer.update(<Parent observed={observableA} />);
if (gate(flags => flags.enableUnifiedSyncLane)) {
React.startTransition(() => {
renderer.update(<Parent observed={observableA} />);
});
} else {
renderer.update(<Parent observed={observableA} />);
}
// Flush everything and ensure that the correct subscribable is used
expect(Scheduler).toFlushAndYield([

View File

@ -35,4 +35,28 @@ if (typeof window !== 'undefined') {
global.cancelIdleCallback = function(callbackID) {
clearTimeout(callbackID);
};
// We need to mock rAF because Jest 26 does not flush rAF.
// Once we upgrade to Jest 27+, rAF is flushed every 16ms.
global.requestAnimationFrameQueue = null;
global.requestAnimationFrame = function(callback) {
if (global.requestAnimationFrameQueue == null) {
global.requestAnimationFrameQueue = [];
}
global.requestAnimationFrameQueue.push(callback);
return global.requestAnimationFrameQueue.length - 1;
};
global.cancelAnimationFrame = function(id) {
if (global.requestAnimationFrameQueue != null) {
global.requestAnimationFrameQueue.splice(id, 1);
}
};
global.flushRequestAnimationFrameQueue = function() {
if (global.requestAnimationFrameQueue != null) {
global.requestAnimationFrameQueue.forEach(callback => callback());
global.requestAnimationFrameQueue = null;
}
};
}

View File

@ -64,6 +64,12 @@ if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
if (error) {
throw error;
}
if (global.requestAnimationFrameQueue != null) {
console.warn('requestAnimationFrameQueue has not been flushed.');
}
});
env.beforeEach(() => {
global.requestAnimationFrameQueue = null;
});
// TODO: Consider consolidating this with `yieldValue`. In both cases, tests