22
TransitionAbort,
23
} from './ReactFiberTracingMarkerComponent';
24
import type {OffscreenInstance} from './ReactFiberOffscreenComponent';
25
+import type {RenderTaskFn} from './ReactFiberRootScheduler';
26
27
import {
28
replayFailedUnitOfWorkWithInvokeGuardedCallback,
46
import {
47
// Aliased because `act` will override and push to an internal queue
48
scheduleCallback as Scheduler_scheduleCallback,
48
- cancelCallback as Scheduler_cancelCallback,
49
shouldYield,
50
requestPaint,
51
now,
52
- ImmediatePriority as ImmediateSchedulerPriority,
53
- UserBlockingPriority as UserBlockingSchedulerPriority,
52
NormalPriority as NormalSchedulerPriority,
53
IdlePriority as IdleSchedulerPriority,
54
} from './Scheduler';
57
-import {
58
- flushSyncCallbacks,
59
- flushSyncCallbacksOnlyInLegacyMode,
60
- scheduleSyncCallback,
61
- scheduleLegacySyncCallback,
62
-} from './ReactFiberSyncTaskQueue';
55
import {
56
logCommitStarted,
57
logCommitStopped,
70
noTimeout,
71
afterActiveInstanceBlur,
72
getCurrentEventPriority,
81
- supportsMicrotasks,
73
errorHydratingContainer,
83
- scheduleMicrotask,
74
prepareRendererToRender,
75
resetRendererAfterRender,
76
startSuspendingCommit,
143
includesBlockingLane,
144
includesExpiredLane,
145
getNextLanes,
156
- markStarvedLanesAsExpired,
146
getLanesToRetrySynchronouslyOnError,
147
getMostRecentEventTime,
148
markRootUpdated,
150
markRootPinged,
151
markRootEntangled,
152
markRootFinished,
164
- getHighestPriorityLane,
153
addFiberToLanesMap,
154
movePendingFibersToMemoized,
155
addTransitionToLanesMap,
160
} from './ReactFiberLane';
161
import {
162
DiscreteEventPriority,
175
- ContinuousEventPriority,
163
DefaultEventPriority,
177
- IdleEventPriority,
164
getCurrentUpdatePriority,
165
setCurrentUpdatePriority,
166
lowerEventPriority,
275
} from './ReactFiberSuspenseContext';
276
import {resolveDefaultProps} from './ReactFiberLazyComponent';
277
import {resetChildReconcilerOnUnwind} from './ReactChildFiber';
278
+import {
279
+ ensureRootIsScheduled,
280
+ flushSyncWorkOnAllRoots,
281
+ flushSyncWorkOnLegacyRootsOnly,
282
+ getContinuationForRoot,
283
+} from './ReactFiberRootScheduler';
284
285
const ceil = Math.ceil;
286
601
return workInProgressRootRenderLanes;
602
}
603
604
+export function isWorkLoopSuspendedOnData(): boolean {
605
+ return workInProgressSuspendedReason === SuspendedOnData;
606
+}
607
+
608
export function requestEventTime(): number {
609
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
610
// We're inside React, so it's fine to read the actual time.
815
}
816
}
817
822
- ensureRootIsScheduled(root, eventTime);
818
+ ensureRootIsScheduled(root);
819
if (
820
lane === SyncLane &&
821
executionContext === NoContext &&
826
- (fiber.mode & ConcurrentMode) === NoMode &&
827
- // Treat `act` as if it's inside `batchedUpdates`, even in legacy mode.
828
- !(__DEV__ && ReactCurrentActQueue.isBatchingLegacy)
822
+ (fiber.mode & ConcurrentMode) === NoMode
823
) {
830
- // Flush the synchronous work now, unless we're already working or inside
831
- // a batch. This is intentionally inside scheduleUpdateOnFiber instead of
832
- // scheduleCallbackForFiber to preserve the ability to schedule a callback
833
- // without immediately flushing it. We only do this for user-initiated
834
- // updates, to preserve historical behavior of legacy mode.
835
- resetRenderTimer();
836
- flushSyncCallbacksOnlyInLegacyMode();
824
+ if (__DEV__ && ReactCurrentActQueue.isBatchingLegacy) {
825
+ // Treat `act` as if it's inside `batchedUpdates`, even in legacy mode.
826
+ ReactCurrentActQueue.didScheduleLegacyUpdate = true;
827
+ } else {
828
+ // Flush the synchronous work now, unless we're already working or inside
829
+ // a batch. This is intentionally inside scheduleUpdateOnFiber instead of
830
+ // scheduleCallbackForFiber to preserve the ability to schedule a callback
831
+ // without immediately flushing it. We only do this for user-initiated
832
+ // updates, to preserve historical behavior of legacy mode.
833
+ resetRenderTimer();
834
+ flushSyncWorkOnLegacyRootsOnly();
835
+ }
836
}
837
}
838
}
854
const current = root.current;
855
current.lanes = lane;
856
markRootUpdated(root, lane, eventTime);
858
- ensureRootIsScheduled(root, eventTime);
857
+ ensureRootIsScheduled(root);
858
}
859
860
export function isUnsafeClassRenderPhaseUpdate(fiber: Fiber): boolean {
863
return (executionContext & RenderContext) !== NoContext;
864
}
865
867
-// Use this function to schedule a task for a root. There's only one task per
868
-// root; if a task was already scheduled, we'll check to make sure the priority
869
-// of the existing task is the same as the priority of the next level that the
870
-// root has work on. This function is called on every update, and right before
871
-// exiting a task.
872
-function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
873
- const existingCallbackNode = root.callbackNode;
874
-
875
- // Check if any lanes are being starved by other work. If so, mark them as
876
- // expired so we know to work on those next.
877
- markStarvedLanesAsExpired(root, currentTime);
878
-
879
- // Determine the next lanes to work on, and their priority.
880
- const nextLanes = getNextLanes(
881
- root,
882
- root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
883
- );
884
-
885
- if (nextLanes === NoLanes) {
886
- // Special case: There's nothing to work on.
887
- if (existingCallbackNode !== null) {
888
- cancelCallback(existingCallbackNode);
889
- }
890
- root.callbackNode = null;
891
- root.callbackPriority = NoLane;
892
- return;
893
- }
894
-
895
- // If this root is currently suspended and waiting for data to resolve, don't
896
- // schedule a task to render it. We'll either wait for a ping, or wait to
897
- // receive an update.
898
- if (
899
- workInProgressSuspendedReason === SuspendedOnData &&
900
- workInProgressRoot === root
901
- ) {
902
- root.callbackPriority = NoLane;
903
- root.callbackNode = null;
904
- return;
905
- }
906
-
907
- const cancelPendingCommit = root.cancelPendingCommit;
908
- if (cancelPendingCommit !== null) {
909
- // We should only interrupt a pending commit if the new update
910
- // is urgent.
911
- if (includesOnlyNonUrgentLanes(nextLanes)) {
912
- // The new update is not urgent. Don't interrupt the pending commit.
913
- root.callbackPriority = NoLane;
914
- root.callbackNode = null;
915
- return;
916
- }
917
- }
918
-
919
- // We use the highest priority lane to represent the priority of the callback.
920
- const newCallbackPriority = getHighestPriorityLane(nextLanes);
921
-
922
- // Check if there's an existing task. We may be able to reuse it.
923
- const existingCallbackPriority = root.callbackPriority;
924
- if (
925
- existingCallbackPriority === newCallbackPriority &&
926
- // Special case related to `act`. If the currently scheduled task is a
927
- // Scheduler task, rather than an `act` task, cancel it and re-scheduled
928
- // on the `act` queue.
929
- !(
930
- __DEV__ &&
931
- ReactCurrentActQueue.current !== null &&
932
- existingCallbackNode !== fakeActCallbackNode
933
- )
934
- ) {
935
- if (__DEV__) {
936
- // If we're going to re-use an existing task, it needs to exist.
937
- // Assume that discrete update microtasks are non-cancellable and null.
938
- // TODO: Temporary until we confirm this warning is not fired.
939
- if (
940
- existingCallbackNode == null &&
941
- !includesSyncLane(existingCallbackPriority)
942
- ) {
943
- console.error(
944
- 'Expected scheduled callback to exist. This error is likely caused by a bug in React. Please file an issue.',
945
- );
946
- }
947
- }
948
- // The priority hasn't changed. We can reuse the existing task. Exit.
949
- return;
950
- }
951
-
952
- if (existingCallbackNode != null) {
953
- // Cancel the existing callback. We'll schedule a new one below.
954
- cancelCallback(existingCallbackNode);
955
- }
956
-
957
- // Schedule a new callback.
958
- let newCallbackNode;
959
- if (includesSyncLane(newCallbackPriority)) {
960
- // Special case: Sync React callbacks are scheduled on a special
961
- // internal queue
962
- if (root.tag === LegacyRoot) {
963
- if (__DEV__ && ReactCurrentActQueue.isBatchingLegacy !== null) {
964
- ReactCurrentActQueue.didScheduleLegacyUpdate = true;
965
- }
966
- scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));
967
- } else {
968
- scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
969
- }
970
- if (supportsMicrotasks) {
971
- // Flush the queue in a microtask.
972
- if (__DEV__ && ReactCurrentActQueue.current !== null) {
973
- // Inside `act`, use our internal `act` queue so that these get flushed
974
- // at the end of the current scope even when using the sync version
975
- // of `act`.
976
- ReactCurrentActQueue.current.push(flushSyncCallbacks);
977
- } else {
978
- scheduleMicrotask(() => {
979
- // In Safari, appending an iframe forces microtasks to run.
980
- // https://github.com/facebook/react/issues/22459
981
- // We don't support running callbacks in the middle of render
982
- // or commit so we need to check against that.
983
- if (
984
- (executionContext & (RenderContext | CommitContext)) ===
985
- NoContext
986
- ) {
987
- // Note that this would still prematurely flush the callbacks
988
- // if this happens outside render or commit phase (e.g. in an event).
989
- flushSyncCallbacks();
990
- }
991
- });
992
- }
993
- } else {
994
- // Flush the queue in an Immediate task.
995
- scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbacks);
996
- }
997
- newCallbackNode = null;
998
- } else {
999
- let schedulerPriorityLevel;
1000
- switch (lanesToEventPriority(nextLanes)) {
1001
- case DiscreteEventPriority:
1002
- schedulerPriorityLevel = ImmediateSchedulerPriority;
1003
- break;
1004
- case ContinuousEventPriority:
1005
- schedulerPriorityLevel = UserBlockingSchedulerPriority;
1006
- break;
1007
- case DefaultEventPriority:
1008
- schedulerPriorityLevel = NormalSchedulerPriority;
1009
- break;
1010
- case IdleEventPriority:
1011
- schedulerPriorityLevel = IdleSchedulerPriority;
1012
- break;
1013
- default:
1014
- schedulerPriorityLevel = NormalSchedulerPriority;
1015
- break;
1016
- }
1017
- newCallbackNode = scheduleCallback(
1018
- schedulerPriorityLevel,
1019
- performConcurrentWorkOnRoot.bind(null, root),
1020
- );
1021
- }
1022
-
1023
- root.callbackPriority = newCallbackPriority;
1024
- root.callbackNode = newCallbackNode;
1025
-}
1026
-
866
// This is the entry point for every concurrent task, i.e. anything that
867
// goes through Scheduler.
1029
-function performConcurrentWorkOnRoot(
868
+export function performConcurrentWorkOnRoot(
869
root: FiberRoot,
870
didTimeout: boolean,
1032
-): $FlowFixMe {
871
+): RenderTaskFn | null {
872
if (enableProfilerTimer && enableProfilerNestedUpdatePhase) {
873
resetNestedUpdateFlag();
874
}
901
902
// Determine the next lanes to work on, using the fields stored
903
// on the root.
904
+ // TODO: This was already computed in the caller. Pass it as an argument.
905
let lanes = getNextLanes(
906
root,
907
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
948
const fatalError = workInProgressRootFatalError;
949
prepareFreshStack(root, NoLanes);
950
markRootSuspended(root, lanes);
1111
- ensureRootIsScheduled(root, now());
951
+ ensureRootIsScheduled(root);
952
throw fatalError;
953
}
954
997
const fatalError = workInProgressRootFatalError;
998
prepareFreshStack(root, NoLanes);
999
markRootSuspended(root, lanes);
1160
- ensureRootIsScheduled(root, now());
1000
+ ensureRootIsScheduled(root);
1001
throw fatalError;
1002
}
1003
1013
}
1014
}
1015
1176
- ensureRootIsScheduled(root, now());
1177
- if (root.callbackNode === originalCallbackNode) {
1178
- // The task node scheduled for this root is the same one that's
1179
- // currently executed. Need to return a continuation.
1180
- return performConcurrentWorkOnRoot.bind(null, root);
1181
- }
1182
- return null;
1016
+ ensureRootIsScheduled(root);
1017
+ return getContinuationForRoot(root, originalCallbackNode);
1018
}
1019
1020
function recoverFromConcurrentError(
1366
1367
// This is the entry point for synchronous tasks that don't go
1368
// through Scheduler
1534
-function performSyncWorkOnRoot(root: FiberRoot) {
1369
+export function performSyncWorkOnRoot(root: FiberRoot): null {
1370
if (enableProfilerTimer && enableProfilerNestedUpdatePhase) {
1371
syncNestedUpdateFlag();
1372
}
1377
1378
flushPassiveEffects();
1379
1380
+ // TODO: This was already computed in the caller. Pass it as an argument.
1381
let lanes = getNextLanes(root, NoLanes);
1382
if (!includesSyncLane(lanes)) {
1383
// There's no remaining sync work left.
1548
- ensureRootIsScheduled(root, now());
1384
+ ensureRootIsScheduled(root);
1385
return null;
1386
}
1387
1410
const fatalError = workInProgressRootFatalError;
1411
prepareFreshStack(root, NoLanes);
1412
markRootSuspended(root, lanes);
1577
- ensureRootIsScheduled(root, now());
1413
+ ensureRootIsScheduled(root);
1414
throw fatalError;
1415
}
1416
1419
// cases where need to exit the current render without producing a
1420
// consistent tree or committing.
1421
markRootSuspended(root, lanes);
1586
- ensureRootIsScheduled(root, now());
1422
+ ensureRootIsScheduled(root);
1423
return null;
1424
}
1425
1436
1437
// Before exiting, make sure there's a callback scheduled for the next
1438
// pending level.
1603
- ensureRootIsScheduled(root, now());
1439
+ ensureRootIsScheduled(root);
1440
1441
return null;
1442
}
1444
export function flushRoot(root: FiberRoot, lanes: Lanes) {
1445
if (lanes !== NoLanes) {
1446
markRootEntangled(root, mergeLanes(lanes, SyncLane));
1611
- ensureRootIsScheduled(root, now());
1447
+ ensureRootIsScheduled(root);
1448
if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
1449
resetRenderTimer();
1614
- flushSyncCallbacks();
1450
+ // TODO: For historical reasons this flushes all sync work across all
1451
+ // roots. It shouldn't really matter either way, but we could change this
1452
+ // to only flush the given root.
1453
+ flushSyncWorkOnAllRoots();
1454
}
1455
}
1456
}
1488
!(__DEV__ && ReactCurrentActQueue.isBatchingLegacy)
1489
) {
1490
resetRenderTimer();
1652
- flushSyncCallbacksOnlyInLegacyMode();
1491
+ flushSyncWorkOnLegacyRootsOnly();
1492
}
1493
}
1494
}
1556
// Note that this will happen even if batchedUpdates is higher up
1557
// the stack.
1558
if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
1720
- flushSyncCallbacks();
1559
+ flushSyncWorkOnAllRoots();
1560
}
1561
}
1562
}
2152
// Ensure the root is scheduled. We should do this even if we're
2153
// currently working on a different root, so that we resume
2154
// rendering later.
2316
- ensureRootIsScheduled(root, now());
2155
+ ensureRootIsScheduled(root);
2156
};
2157
thenable.then(onResolution, onResolution);
2158
break outer;
2989
2990
// Always call this before exiting `commitRoot`, to ensure that any
2991
// additional work on this root is scheduled.
3153
- ensureRootIsScheduled(root, now());
2992
+ ensureRootIsScheduled(root);
2993
2994
if (recoverableErrors !== null) {
2995
// There were errors during this render, but recovered from them without
3044
}
3045
3046
// If layout work was scheduled, flush it now.
3208
- flushSyncCallbacks();
3047
+ flushSyncWorkOnAllRoots();
3048
3049
if (__DEV__) {
3050
if (enableDebugTracing) {
3250
3251
executionContext = prevExecutionContext;
3252
3414
- flushSyncCallbacks();
3253
+ flushSyncWorkOnAllRoots();
3254
3255
if (enableTransitionTracing) {
3256
const prevPendingTransitionCallbacks = currentPendingTransitionCallbacks;
3335
const eventTime = requestEventTime();
3336
if (root !== null) {
3337
markRootUpdated(root, SyncLane, eventTime);
3499
- ensureRootIsScheduled(root, eventTime);
3338
+ ensureRootIsScheduled(root);
3339
}
3340
}
3341
3378
const eventTime = requestEventTime();
3379
if (root !== null) {
3380
markRootUpdated(root, SyncLane, eventTime);
3542
- ensureRootIsScheduled(root, eventTime);
3381
+ ensureRootIsScheduled(root);
3382
}
3383
return;
3384
}
3456
pingCache.delete(wakeable);
3457
}
3458
3620
- const eventTime = requestEventTime();
3459
markRootPinged(root, pingedLanes);
3460
3461
warnIfSuspenseResolutionNotWrappedWithActDEV(root);
3498
}
3499
}
3500
3663
- ensureRootIsScheduled(root, eventTime);
3501
+ ensureRootIsScheduled(root);
3502
}
3503
3504
function retryTimedOutBoundary(boundaryFiber: Fiber, retryLane: Lane) {
3516
const root = enqueueConcurrentRenderForLane(boundaryFiber, retryLane);
3517
if (root !== null) {
3518
markRootUpdated(root, retryLane, eventTime);
3681
- ensureRootIsScheduled(root, eventTime);
3519
+ ensureRootIsScheduled(root);
3520
}
3521
}
3522
3998
}
3999
}
4000
4163
-function cancelCallback(callbackNode: any) {
4164
- if (__DEV__ && callbackNode === fakeActCallbackNode) {
4165
- return;
4166
- }
4167
- // In production, always call Scheduler. This function will be stripped out.
4168
- return Scheduler_cancelCallback(callbackNode);
4169
-}
4170
-
4001
function shouldForceFlushFallbacksInDEV() {
4002
// Never force flush in production. This function should get stripped out.
4003
return __DEV__ && ReactCurrentActQueue.current !== null;