109
HydratingAndUpdate,
110
} from './ReactSideEffectTags';
111
import {
112
+ NoLanePriority,
113
SyncLanePriority,
114
InputDiscreteLanePriority,
115
TransitionShortLanePriority,
119
SyncLane,
120
SyncBatchedLane,
121
OffscreenLane,
122
+ NoTimestamp,
123
findUpdateLane,
124
findTransitionLane,
125
includesSomeLane,
131
hasUpdatePriority,
132
getNextLanes,
133
returnNextLanesPriority,
134
+ markStarvedLanesAsExpired,
135
getLanesToRetrySynchronouslyOnError,
136
markRootUpdated,
137
markRootSuspended as markRootSuspended_dontCallThisOneDirectly,
256
// A fatal error, if one is thrown
257
let workInProgressRootFatalError: mixed = null;
258
// Most recent event time among processed updates during this render.
256
-let workInProgressRootLatestProcessedEventTime: number = -1;
257
-let workInProgressRootLatestSuspenseTimeout: number = -1;
259
+let workInProgressRootLatestProcessedEventTime: number = NoTimestamp;
260
+let workInProgressRootLatestSuspenseTimeout: number = NoTimestamp;
261
let workInProgressRootCanSuspendUsingConfig: null | SuspenseConfig = null;
262
// "Included" lanes refer to lanes that were worked on during this render. It's
263
// slightly different than `renderLanes` because `renderLanes` can change as you
313
// If two updates are scheduled within the same event, we should treat their
314
// event times as simultaneous, even if the actual clock time has advanced
315
// between the first and second call.
313
-let currentEventTime: number = -1;
316
+let currentEventTime: number = NoTimestamp;
317
let currentEventWipLanes: Lanes = NoLanes;
318
let currentEventPendingLanes: Lanes = NoLanes;
319
334
return now();
335
}
336
// We're not inside React, so we may be in the middle of a browser event.
334
- if (currentEventTime !== -1) {
337
+ if (currentEventTime !== NoTimestamp) {
338
// Use the same start time for all updates until we enter React again.
339
return currentEventTime;
340
}
437
return lane;
438
}
439
437
-export function scheduleUpdateOnFiber(fiber: Fiber, lane: Lane) {
440
+export function scheduleUpdateOnFiber(
441
+ fiber: Fiber,
442
+ lane: Lane,
443
+ eventTime: number,
444
+) {
445
checkForNestedUpdates();
446
warnAboutRenderPhaseUpdatesInDEV(fiber);
447
470
// should be deferred until the end of the batch.
471
performSyncWorkOnRoot(root);
472
} else {
466
- ensureRootIsScheduled(root);
473
+ ensureRootIsScheduled(root, eventTime);
474
schedulePendingInteractions(root, lane);
475
if (executionContext === NoContext) {
476
// Flush the synchronous work now, unless we're already working or inside
499
}
500
}
501
// Schedule other updates after in case the callback is sync.
495
- ensureRootIsScheduled(root);
502
+ ensureRootIsScheduled(root, eventTime);
503
schedulePendingInteractions(root, lane);
504
}
505
599
// expiration time of the existing task is the same as the expiration time of
600
// the next level that the root has work on. This function is called on every
601
// update, and right before exiting a task.
595
-function ensureRootIsScheduled(root: FiberRoot) {
602
+function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
603
const existingCallbackNode = root.callbackNode;
604
605
+ // Check if any lanes are being starved by other work. If so, mark them as
606
+ // expired so we know to work on those next.
607
+ markStarvedLanesAsExpired(root, currentTime);
608
+
609
+ // Determine the next lanes to work on, and their priority.
610
const newCallbackId = getNextLanes(
611
root,
612
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
613
);
614
// This returns the priority level computed during the `getNextLanes` call.
603
- const newCallbackPriorityLevel = returnNextLanesPriority();
615
+ const newCallbackPriority = returnNextLanesPriority();
616
617
if (newCallbackId === NoLanes) {
618
// Special case: There's nothing to work on.
619
if (existingCallbackNode !== null) {
620
cancelCallback(existingCallbackNode);
609
- root.expiresAt = -1;
621
root.callbackNode = null;
611
- root.callbackIsSync = false;
622
+ root.callbackPriority_new = NoLanePriority;
623
root.callbackId = NoLanes;
624
}
625
return;
626
}
627
617
- const newTaskIsSync = newCallbackPriorityLevel === SyncLanePriority;
618
-
628
// Check if there's an existing task. We may be able to reuse it.
620
- const existingTaskId = root.callbackId;
621
- const existingCallbackIsSync = root.callbackIsSync;
622
- if (existingTaskId !== NoLanes) {
623
- if (newCallbackId === existingTaskId) {
629
+ const existingCallbackId = root.callbackId;
630
+ const existingCallbackPriority = root.callbackPriority_new;
631
+ if (existingCallbackId !== NoLanes) {
632
+ if (newCallbackId === existingCallbackId) {
633
// This task is already scheduled. Let's check its priority.
625
- if (
626
- (newTaskIsSync && existingCallbackIsSync) ||
627
- (!newTaskIsSync && !existingCallbackIsSync)
628
- ) {
634
+ if (existingCallbackPriority === newCallbackPriority) {
635
// The priority hasn't changed. Exit.
636
return;
637
}
643
644
// Schedule a new callback.
645
let newCallbackNode;
640
- if (newTaskIsSync) {
646
+ if (newCallbackPriority === SyncLanePriority) {
647
// Special case: Sync React callbacks are scheduled on a special
648
// internal queue
649
newCallbackNode = scheduleSyncCallback(
651
);
652
} else {
653
const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
648
- newCallbackPriorityLevel,
654
+ newCallbackPriority,
655
);
656
newCallbackNode = scheduleCallback(
657
schedulerPriorityLevel,
660
}
661
662
root.callbackId = newCallbackId;
663
+ root.callbackPriority_new = newCallbackPriority;
664
root.callbackNode = newCallbackNode;
658
- root.callbackIsSync = newTaskIsSync;
665
}
666
667
// This is the entry point for every concurrent task, i.e. anything that
669
function performConcurrentWorkOnRoot(root, didTimeout) {
670
// Since we know we're in a React event, we can clear the current
671
// event time. The next update will compute a new event time.
666
- currentEventTime = -1;
672
+ currentEventTime = NoTimestamp;
673
currentEventWipLanes = NoLanes;
674
currentEventPendingLanes = NoLanes;
675
683
return null;
684
}
685
680
- // Check if any work has expired.
681
- const rootExpiresAt = root.expiresAt;
682
- if (rootExpiresAt !== -1 && rootExpiresAt < now()) {
686
+ // TODO: We only check `didTimeout` defensively, to account for a Scheduler
687
+ // bug where `shouldYield` sometimes returns `true` even if `didTimeout` is
688
+ // true, which leads to an infinite loop. Once the bug in Scheduler is
689
+ // fixed, we can remove this, since we track expiration ourselves.
690
+ if (didTimeout) {
691
// Something expired. Flush synchronously until there's no expired
692
// work left.
685
- // TODO: Should flush only the lanes that have expired, and maybe any lanes
686
- // that are higher priority than that.
693
markRootExpired(root, lanes);
694
// This will schedule a synchronous callback.
689
- ensureRootIsScheduled(root);
690
- return null;
691
- }
692
- // Similar branch, but for Scheduler.
693
- // TODO: This is only here to account for a Scheduler bug where `shouldYield`
694
- // sometimes returns `true` even if `didTimeout` is true, which leads to
695
- // an infinite loop. Once the bug in Scheduler is fixed, we can remove this,
696
- // since we track expiration times ourselves.
697
- if (didTimeout) {
698
- // The Scheduler task took too long to complete. Mark the root as expired to
699
- // prevent yielding to other tasks until this one finishes.
700
- markRootExpired(root, lanes);
701
- // This will schedule a synchronous callback.
702
- ensureRootIsScheduled(root);
695
+ ensureRootIsScheduled(root, now());
696
return null;
697
}
698
735
const fatalError = workInProgressRootFatalError;
736
prepareFreshStack(root, NoLanes);
737
markRootSuspended(root, lanes);
745
- ensureRootIsScheduled(root);
738
+ ensureRootIsScheduled(root, now());
739
throw fatalError;
740
}
741
747
finishConcurrentRender(root, finishedWork, exitStatus, lanes);
748
}
749
757
- ensureRootIsScheduled(root);
750
+ ensureRootIsScheduled(root, now());
751
if (root.callbackNode === originalCallbackNode) {
752
// The task node scheduled for this root is the same one that's
753
// currently executed. Need to return a continuation.
781
// have a new loading state ready. We want to ensure that we commit
782
// that as soon as possible.
783
const hasNotProcessedNewUpdates =
791
- workInProgressRootLatestProcessedEventTime === -1;
784
+ workInProgressRootLatestProcessedEventTime === NoTimestamp;
785
if (
786
hasNotProcessedNewUpdates &&
787
// do not delay if we're inside an act() scope
808
// suspended level. Ping the last suspended level to try
809
// rendering it again.
810
// FIXME: What if the suspended lanes are Idle? Should not restart.
818
- markRootPinged(root, suspendedLanes);
811
+ const eventTime = requestEventTime();
812
+ markRootPinged(root, suspendedLanes, eventTime);
813
break;
814
}
815
847
// suspended level. Ping the last suspended level to try
848
// rendering it again.
849
// FIXME: What if the suspended lanes are Idle? Should not restart.
856
- markRootPinged(root, suspendedLanes);
850
+ const eventTime = requestEventTime();
851
+ markRootPinged(root, suspendedLanes, eventTime);
852
break;
853
}
854
855
let msUntilTimeout;
861
- if (workInProgressRootLatestSuspenseTimeout !== -1) {
856
+ if (workInProgressRootLatestSuspenseTimeout !== NoTimestamp) {
857
// We have processed a suspense config whose expiration time we
858
// can use as the timeout.
859
msUntilTimeout = workInProgressRootLatestSuspenseTimeout - now();
865
- } else if (workInProgressRootLatestProcessedEventTime === -1) {
860
+ } else if (workInProgressRootLatestProcessedEventTime === NoTimestamp) {
861
// This should never normally happen because only new updates
862
// cause delayed states, so we should have processed something.
863
// However, this could also happen in an offscreen tree.
891
if (
892
// do not delay if we're inside an act() scope
893
!shouldForceFlushFallbacksInDEV() &&
899
- workInProgressRootLatestProcessedEventTime !== -1 &&
894
+ workInProgressRootLatestProcessedEventTime !== NoTimestamp &&
895
workInProgressRootCanSuspendUsingConfig !== null
896
) {
897
// If we have exceeded the minimum loading delay, which probably
987
const fatalError = workInProgressRootFatalError;
988
prepareFreshStack(root, NoLanes);
989
markRootSuspended(root, lanes);
995
- ensureRootIsScheduled(root);
990
+ ensureRootIsScheduled(root, now());
991
throw fatalError;
992
}
993
1000
1001
// Before exiting, make sure there's a callback scheduled for the next
1002
// pending level.
1008
- ensureRootIsScheduled(root);
1003
+ ensureRootIsScheduled(root, now());
1004
1005
return null;
1006
}
1007
1008
export function flushRoot(root: FiberRoot, lanes: Lanes) {
1009
markRootExpired(root, lanes);
1015
- ensureRootIsScheduled(root);
1010
+ ensureRootIsScheduled(root, now());
1011
if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
1012
flushSyncCallbackQueue();
1013
}
1054
rootsWithPendingDiscreteUpdates = null;
1055
roots.forEach(root => {
1056
markDiscreteUpdatesExpired(root);
1062
- ensureRootIsScheduled(root);
1057
+ ensureRootIsScheduled(root, now());
1058
});
1059
}
1060
// Now flush the immediate queue.
1209
workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes;
1210
workInProgressRootExitStatus = RootIncomplete;
1211
workInProgressRootFatalError = null;
1217
- workInProgressRootLatestProcessedEventTime = -1;
1218
- workInProgressRootLatestSuspenseTimeout = -1;
1212
+ workInProgressRootLatestProcessedEventTime = NoTimestamp;
1213
+ workInProgressRootLatestSuspenseTimeout = NoTimestamp;
1214
workInProgressRootCanSuspendUsingConfig = null;
1215
workInProgressRootSkippedLanes = NoLanes;
1216
workInProgressRootUpdatedLanes = NoLanes;
1780
// So we can clear these now to allow a new callback to be scheduled.
1781
root.callbackNode = null;
1782
root.callbackId = NoLanes;
1788
- // TODO: Use LanePriority instead of SchedulerPriority
1789
- if (renderPriorityLevel < ImmediateSchedulerPriority) {
1790
- // If this was a concurrent render, we can reset the expiration time.
1791
- root.expiresAt = -1;
1792
- }
1783
1784
// Update the first and last pending times on this root. The new first
1785
// pending time is whatever is left on the root fiber.
2036
2037
// Always call this before exiting `commitRoot`, to ensure that any
2038
// additional work on this root is scheduled.
2049
- ensureRootIsScheduled(root);
2039
+ ensureRootIsScheduled(root, now());
2040
2041
if (hasUncaughtError) {
2042
hasUncaughtError = false;
2494
const errorInfo = createCapturedValue(error, sourceFiber);
2495
const update = createRootErrorUpdate(rootFiber, errorInfo, (SyncLane: Lane));
2496
enqueueUpdate(rootFiber, update);
2497
+ const eventTime = requestEventTime();
2498
const root = markUpdateLaneFromFiberToRoot(rootFiber, (SyncLane: Lane));
2499
if (root !== null) {
2509
- ensureRootIsScheduled(root);
2500
+ ensureRootIsScheduled(root, eventTime);
2501
schedulePendingInteractions(root, SyncLane);
2502
}
2503
}
2530
(SyncLane: Lane),
2531
);
2532
enqueueUpdate(fiber, update);
2533
+ const eventTime = requestEventTime();
2534
const root = markUpdateLaneFromFiberToRoot(fiber, (SyncLane: Lane));
2535
if (root !== null) {
2544
- ensureRootIsScheduled(root);
2536
+ ensureRootIsScheduled(root, eventTime);
2537
schedulePendingInteractions(root, SyncLane);
2538
}
2539
return;
2555
pingCache.delete(wakeable);
2556
}
2557
2566
- markRootPinged(root, pingedLanes);
2558
+ const eventTime = requestEventTime();
2559
+ markRootPinged(root, pingedLanes, eventTime);
2560
2561
if (
2562
workInProgressRoot === root &&
2578
if (
2579
workInProgressRootExitStatus === RootSuspendedWithDelay ||
2580
(workInProgressRootExitStatus === RootSuspended &&
2588
- workInProgressRootLatestProcessedEventTime === -1 &&
2581
+ workInProgressRootLatestProcessedEventTime === NoTimestamp &&
2582
now() - globalMostRecentFallbackTime < FALLBACK_THROTTLE_MS)
2583
) {
2584
// Restart from the root.
2593
}
2594
}
2595
2603
- ensureRootIsScheduled(root);
2596
+ ensureRootIsScheduled(root, eventTime);
2597
schedulePendingInteractions(root, pingedLanes);
2598
}
2599
2609
retryLane = requestUpdateLane(boundaryFiber, suspenseConfig);
2610
}
2611
// TODO: Special case idle priority?
2612
+ const eventTime = requestEventTime();
2613
const root = markUpdateLaneFromFiberToRoot(boundaryFiber, retryLane);
2614
if (root !== null) {
2621
- ensureRootIsScheduled(root);
2615
+ ensureRootIsScheduled(root, eventTime);
2616
schedulePendingInteractions(root, retryLane);
2617
}
2618
}