15
import type {HostConfig, Deadline} from 'ReactFiberReconciler';
16
import type {PriorityLevel} from 'ReactPriorityLevel';
17
import type {HydrationContext} from 'ReactFiberHydrationContext';
18
+import type {ExpirationTime} from 'ReactFiberExpirationTime';
19
20
export type CapturedError = {
21
componentName: ?string,
51
var {ReactCurrentOwner} = require('ReactGlobalSharedState');
52
var getComponentName = require('getComponentName');
53
53
-var {createWorkInProgress, largerPriority} = require('ReactFiber');
54
+var {createWorkInProgress} = require('ReactFiber');
55
var {onCommitRoot} = require('ReactFiberDevToolsHook');
56
57
var {
63
OffscreenPriority,
64
} = require('ReactPriorityLevel');
65
66
+var {
67
+ Never,
68
+ msToExpirationTime,
69
+ priorityToExpirationTime,
70
+ expirationTimeToPriorityLevel,
71
+} = require('ReactFiberExpirationTime');
72
+
73
var {AsyncUpdates} = require('ReactTypeOfInternalContext');
74
75
var {
91
ClassComponent,
92
} = require('ReactTypeOfWork');
93
86
-var {getUpdatePriority} = require('ReactFiberUpdateQueue');
94
+var {getUpdateExpirationTime} = require('ReactFiberUpdateQueue');
95
96
var {resetContext} = require('ReactFiberContext');
97
174
hydrationContext,
175
scheduleUpdate,
176
getPriorityContext,
177
+ recalculateCurrentTime,
178
+ getExpirationTimeForPriority,
179
);
180
const {completeWork} = ReactFiberCompleteWork(
181
config,
191
commitDetachRef,
192
} = ReactFiberCommitWork(config, captureError);
193
const {
194
+ now,
195
scheduleDeferredCallback,
196
useSyncScheduling,
197
prepareForCommit,
198
resetAfterCommit,
199
} = config;
200
201
+ // Represents the current time in ms.
202
+ const startTime = now();
203
+ let mostRecentCurrentTime: ExpirationTime = msToExpirationTime(0);
204
+
205
// The priority level to use when scheduling an update. We use NoWork to
206
// represent the default priority.
192
- // TODO: Should we change this to an array instead of using the call stack?
193
- // Might be less confusing.
207
let priorityContext: PriorityLevel = NoWork;
208
209
// Keeps track of whether we're currently in a work loop.
221
222
// The next work in progress fiber that we're currently working on.
223
let nextUnitOfWork: Fiber | null = null;
211
- let nextPriorityLevel: PriorityLevel = NoWork;
224
+ // The time at which we're currently rendering work.
225
+ let nextRenderExpirationTime: ExpirationTime = NoWork;
226
227
// The next fiber with an effect that we're currently committing.
228
let nextEffect: Fiber | null = null;
265
resetHostContainer();
266
}
267
254
- // resetNextUnitOfWork mutates the current priority context. It is reset after
255
- // after the workLoop exits, so never call resetNextUnitOfWork from outside
256
- // the work loop.
268
function resetNextUnitOfWork() {
269
// Clear out roots with no more work on them, or if they have uncaught errors
270
while (
271
nextScheduledRoot !== null &&
261
- nextScheduledRoot.current.pendingWorkPriority === NoWork
272
+ nextScheduledRoot.current.expirationTime === NoWork
273
) {
274
// Unschedule this root.
275
nextScheduledRoot.isScheduled = false;
281
if (nextScheduledRoot === lastScheduledRoot) {
282
nextScheduledRoot = null;
283
lastScheduledRoot = null;
273
- nextPriorityLevel = NoWork;
284
+ nextRenderExpirationTime = NoWork;
285
return null;
286
}
287
// Continue with the next root.
290
}
291
292
let root = nextScheduledRoot;
282
- let highestPriorityRoot = null;
283
- let highestPriorityLevel = NoWork;
293
+ let earliestExpirationRoot = null;
294
+ let earliestExpirationTime = NoWork;
295
while (root !== null) {
296
if (
286
- root.current.pendingWorkPriority !== NoWork &&
287
- (highestPriorityLevel === NoWork ||
288
- highestPriorityLevel > root.current.pendingWorkPriority)
297
+ root.current.expirationTime !== NoWork &&
298
+ (earliestExpirationTime === NoWork ||
299
+ earliestExpirationTime > root.current.expirationTime)
300
) {
290
- highestPriorityLevel = root.current.pendingWorkPriority;
291
- highestPriorityRoot = root;
301
+ earliestExpirationTime = root.current.expirationTime;
302
+ earliestExpirationRoot = root;
303
}
304
// We didn't find anything to do in this root, so let's try the next one.
305
root = root.nextScheduledRoot;
306
}
296
- if (highestPriorityRoot !== null) {
297
- nextPriorityLevel = highestPriorityLevel;
307
+ if (earliestExpirationRoot !== null) {
308
+ nextRenderExpirationTime = earliestExpirationTime;
309
// Before we start any new work, let's make sure that we have a fresh
310
// stack to work from.
311
// TODO: This call is buried a bit too deep. It would be nice to have
314
resetContextStack();
315
316
nextUnitOfWork = createWorkInProgress(
306
- highestPriorityRoot.current,
307
- highestPriorityLevel,
317
+ earliestExpirationRoot.current,
318
+ earliestExpirationTime,
319
);
309
- if (highestPriorityRoot !== nextRenderedTree) {
320
+ if (earliestExpirationRoot !== nextRenderedTree) {
321
// We've switched trees. Reset the nested update counter.
322
nestedUpdateCount = 0;
312
- nextRenderedTree = highestPriorityRoot;
323
+ nextRenderedTree = earliestExpirationRoot;
324
}
325
return;
326
}
327
317
- nextPriorityLevel = NoWork;
328
+ nextRenderExpirationTime = NoWork;
329
nextUnitOfWork = null;
330
nextRenderedTree = null;
331
return;
403
while (nextEffect !== null) {
404
const effectTag = nextEffect.effectTag;
405
395
- // Use Task priority for lifecycle updates
406
if (effectTag & (Update | Callback)) {
407
if (__DEV__) {
408
recordEffect();
456
'in React. Please file an issue.',
457
);
458
449
- if (
450
- nextPriorityLevel === SynchronousPriority ||
451
- nextPriorityLevel === TaskPriority
452
- ) {
459
+ if (nextRenderExpirationTime <= mostRecentCurrentTime) {
460
// Keep track of the number of iterations to prevent an infinite
461
// update loop.
462
nestedUpdateCount++;
590
commitPhaseBoundaries = null;
591
}
592
586
- // This tree is done. Reset the unit of work pointer to the next highest
587
- // priority root. If there's no more work left, the pointer is set to null.
593
+ // This tree is done. Reset the unit of work pointer to the root that
594
+ // expires soonest. If there's no work left, the pointer is set to null.
595
resetNextUnitOfWork();
596
}
597
591
- function resetWorkPriority(
598
+ function resetExpirationTime(
599
workInProgress: Fiber,
593
- renderPriority: PriorityLevel,
600
+ renderTime: ExpirationTime,
601
) {
595
- if (
596
- workInProgress.pendingWorkPriority !== NoWork &&
597
- workInProgress.pendingWorkPriority > renderPriority
598
- ) {
599
- // This was a down-prioritization. Don't bubble priority from children.
602
+ if (renderTime !== Never && workInProgress.expirationTime === Never) {
603
+ // The children of this component are hidden. Don't bubble their
604
+ // expiration times.
605
return;
606
}
607
603
- // Check for pending update priority.
604
- let newPriority = getUpdatePriority(workInProgress);
608
+ // Check for pending updates.
609
+ let newExpirationTime = getUpdateExpirationTime(workInProgress);
610
611
// TODO: Coroutines need to visit stateNode
612
613
+ // Bubble up the earliest expiration time.
614
let child = workInProgress.child;
615
while (child !== null) {
610
- // Ensure that remaining work priority bubbles up.
611
- newPriority = largerPriority(newPriority, child.pendingWorkPriority);
616
+ if (
617
+ child.expirationTime !== NoWork &&
618
+ (newExpirationTime === NoWork ||
619
+ newExpirationTime > child.expirationTime)
620
+ ) {
621
+ newExpirationTime = child.expirationTime;
622
+ }
623
child = child.sibling;
624
}
614
- workInProgress.pendingWorkPriority = newPriority;
625
+ workInProgress.expirationTime = newExpirationTime;
626
}
627
628
function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
635
if (__DEV__) {
636
ReactDebugCurrentFiber.setCurrentFiber(workInProgress);
637
}
627
- const next = completeWork(current, workInProgress, nextPriorityLevel);
638
+ const next = completeWork(
639
+ current,
640
+ workInProgress,
641
+ nextRenderExpirationTime,
642
+ );
643
if (__DEV__) {
644
ReactDebugCurrentFiber.resetCurrentFiber();
645
}
647
const returnFiber = workInProgress.return;
648
const siblingFiber = workInProgress.sibling;
649
635
- resetWorkPriority(workInProgress, nextPriorityLevel);
650
+ resetExpirationTime(workInProgress, nextRenderExpirationTime);
651
652
if (next !== null) {
653
if (__DEV__) {
735
startWorkTimer(workInProgress);
736
ReactDebugCurrentFiber.setCurrentFiber(workInProgress);
737
}
723
- let next = beginWork(current, workInProgress, nextPriorityLevel);
738
+ let next = beginWork(current, workInProgress, nextRenderExpirationTime);
739
if (__DEV__) {
740
ReactDebugCurrentFiber.resetCurrentFiber();
741
}
765
startWorkTimer(workInProgress);
766
ReactDebugCurrentFiber.setCurrentFiber(workInProgress);
767
}
753
- let next = beginFailedWork(current, workInProgress, nextPriorityLevel);
768
+ let next = beginFailedWork(
769
+ current,
770
+ workInProgress,
771
+ nextRenderExpirationTime,
772
+ );
773
if (__DEV__) {
774
ReactDebugCurrentFiber.resetCurrentFiber();
775
}
804
if (
805
capturedErrors !== null &&
806
capturedErrors.size > 0 &&
788
- nextPriorityLevel === TaskPriority
807
+ nextRenderExpirationTime !== NoWork &&
808
+ nextRenderExpirationTime <= mostRecentCurrentTime
809
) {
810
while (nextUnitOfWork !== null) {
811
if (hasCapturedError(nextUnitOfWork)) {
821
'a bug in React. Please file an issue.',
822
);
823
// We just completed a root. Commit it now.
804
- priorityContext = TaskPriority;
824
commitAllWork(pendingCommit);
806
- priorityContext = nextPriorityLevel;
807
-
825
if (
826
capturedErrors === null ||
827
capturedErrors.size === 0 ||
811
- nextPriorityLevel !== TaskPriority
828
+ nextRenderExpirationTime === NoWork ||
829
+ nextRenderExpirationTime > mostRecentCurrentTime
830
) {
831
// There are no more unhandled errors. We can exit this special
832
// work loop. If there's still additional work, we'll perform it
840
}
841
842
function workLoop(
825
- minPriorityLevel: PriorityLevel,
843
+ minExpirationTime: ExpirationTime,
844
deadline: Deadline | null,
845
) {
846
if (pendingCommit !== null) {
829
- priorityContext = TaskPriority;
847
commitAllWork(pendingCommit);
848
handleCommitPhaseErrors();
849
} else if (nextUnitOfWork === null) {
850
resetNextUnitOfWork();
851
}
852
836
- if (nextPriorityLevel === NoWork || nextPriorityLevel > minPriorityLevel) {
853
+ if (
854
+ nextRenderExpirationTime === NoWork ||
855
+ nextRenderExpirationTime > minExpirationTime
856
+ ) {
857
return;
858
}
859
840
- // During the render phase, updates should have the same priority at which
841
- // we're rendering.
842
- priorityContext = nextPriorityLevel;
843
-
860
loop: do {
845
- if (nextPriorityLevel <= TaskPriority) {
846
- // Flush all synchronous and task work.
861
+ if (nextRenderExpirationTime <= mostRecentCurrentTime) {
862
+ // Flush all expired work.
863
while (nextUnitOfWork !== null) {
864
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
865
if (nextUnitOfWork === null) {
869
'a bug in React. Please file an issue.',
870
);
871
// We just completed a root. Commit it now.
856
- priorityContext = TaskPriority;
872
commitAllWork(pendingCommit);
858
- priorityContext = nextPriorityLevel;
873
// Clear any errors that were scheduled during the commit phase.
874
handleCommitPhaseErrors();
861
- // The priority level may have changed. Check again.
875
+ // The render time may have changed. Check again.
876
if (
863
- nextPriorityLevel === NoWork ||
864
- nextPriorityLevel > minPriorityLevel ||
865
- nextPriorityLevel > TaskPriority
877
+ nextRenderExpirationTime === NoWork ||
878
+ nextRenderExpirationTime > minExpirationTime ||
879
+ nextRenderExpirationTime > mostRecentCurrentTime
880
) {
867
- // The priority level does not match.
881
+ // We've completed all the expired work.
882
break;
883
}
884
}
885
}
886
} else if (deadline !== null) {
873
- // Flush asynchronous work until the deadline expires.
887
+ // Flush asynchronous work until the deadline runs out of time.
888
while (nextUnitOfWork !== null && !deadlineHasExpired) {
889
if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
890
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
901
// We just completed a root. If we have time, commit it now.
902
// Otherwise, we'll commit it in the next frame.
903
if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
890
- priorityContext = TaskPriority;
904
commitAllWork(pendingCommit);
892
- priorityContext = nextPriorityLevel;
905
// Clear any errors that were scheduled during the commit phase.
906
handleCommitPhaseErrors();
895
- // The priority level may have changed. Check again.
907
+ // The render time may have changed. Check again.
908
if (
897
- nextPriorityLevel === NoWork ||
898
- nextPriorityLevel > minPriorityLevel ||
899
- nextPriorityLevel < HighPriority
909
+ nextRenderExpirationTime === NoWork ||
910
+ nextRenderExpirationTime > minExpirationTime ||
911
+ nextRenderExpirationTime <= mostRecentCurrentTime
912
) {
901
- // The priority level does not match.
913
+ // We've completed all the async work.
914
break;
915
}
916
} else {
925
926
// There might be work left. Depending on the priority, we should
927
// either perform it now or schedule a callback to perform it later.
916
- switch (nextPriorityLevel) {
928
+ const currentTime = recalculateCurrentTime();
929
+ switch (expirationTimeToPriorityLevel(
930
+ currentTime,
931
+ nextRenderExpirationTime,
932
+ )) {
933
case SynchronousPriority:
934
case TaskPriority:
935
// We have remaining synchronous or task work. Keep performing it,
936
// regardless of whether we're inside a callback.
921
- if (nextPriorityLevel <= minPriorityLevel) {
937
+ if (nextRenderExpirationTime <= minExpirationTime) {
938
continue loop;
939
}
940
break loop;
948
break loop;
949
}
950
// We are inside a callback.
935
- if (!deadlineHasExpired && nextPriorityLevel <= minPriorityLevel) {
951
+ if (
952
+ !deadlineHasExpired &&
953
+ nextRenderExpirationTime <= minExpirationTime
954
+ ) {
955
// We still have time. Keep working.
956
continue loop;
957
}
973
function performWorkCatchBlock(
974
failedWork: Fiber,
975
boundary: Fiber,
957
- minPriorityLevel: PriorityLevel,
976
+ minExpirationTime: ExpirationTime,
977
deadline: Deadline | null,
978
) {
979
// We're going to restart the error boundary that captured the error.
989
nextUnitOfWork = performFailedUnitOfWork(boundary);
990
991
// Continue working.
973
- workLoop(minPriorityLevel, deadline);
992
+ workLoop(minExpirationTime, deadline);
993
}
994
995
function performWork(
1007
);
1008
isPerformingWork = true;
1009
991
- // The priority context changes during the render phase. We'll need to
992
- // reset it at the end.
1010
+ // Updates that occur during the commit phase should have task priority
1011
+ // by default. (Render phase updates are special; getPriorityContext
1012
+ // accounts for their behavior.)
1013
const previousPriorityContext = priorityContext;
1014
+ priorityContext = TaskPriority;
1015
+
1016
+ // Read the current time from the host environment.
1017
+ const currentTime = recalculateCurrentTime();
1018
+ const minExpirationTime = getExpirationTimeForPriority(
1019
+ currentTime,
1020
+ minPriorityLevel,
1021
+ );
1022
+
1023
+ nestedUpdateCount = 0;
1024
1025
let didError = false;
1026
let error = null;
1027
if (__DEV__) {
998
- invokeGuardedCallback(null, workLoop, null, minPriorityLevel, deadline);
1028
+ invokeGuardedCallback(null, workLoop, null, minExpirationTime, deadline);
1029
if (hasCaughtError()) {
1030
didError = true;
1031
error = clearCaughtError();
1032
}
1033
} else {
1034
try {
1005
- workLoop(minPriorityLevel, deadline);
1035
+ workLoop(minExpirationTime, deadline);
1036
} catch (e) {
1037
didError = true;
1038
error = e;
1079
null,
1080
failedWork,
1081
boundary,
1052
- minPriorityLevel,
1082
+ minExpirationTime,
1083
deadline,
1084
);
1085
if (hasCaughtError()) {
1092
performWorkCatchBlock(
1093
failedWork,
1094
boundary,
1065
- minPriorityLevel,
1095
+ minExpirationTime,
1096
deadline,
1097
);
1098
error = null;
1106
break;
1107
}
1108
1079
- // Reset the priority context to its previous value.
1080
- priorityContext = previousPriorityContext;
1081
-
1109
// If we're inside a callback, set this to false, since we just flushed it.
1110
if (deadline !== null) {
1111
isCallbackScheduled = false;
1112
}
1113
// If there's remaining async work, make sure we schedule another callback.
1087
- if (nextPriorityLevel > TaskPriority && !isCallbackScheduled) {
1114
+ if (
1115
+ nextRenderExpirationTime > mostRecentCurrentTime &&
1116
+ !isCallbackScheduled
1117
+ ) {
1118
scheduleDeferredCallback(performDeferredWork);
1119
isCallbackScheduled = true;
1120
}
1121
1122
+ priorityContext = previousPriorityContext;
1123
+
1124
const errorToThrow = firstUncaughtError;
1125
1126
// We're done performing work. Time to clean up.
1385
}
1386
}
1387
1356
- function scheduleRoot(root: FiberRoot, priorityLevel: PriorityLevel) {
1357
- if (priorityLevel === NoWork) {
1388
+ function scheduleRoot(root: FiberRoot, expirationTime: ExpirationTime) {
1389
+ if (expirationTime === NoWork) {
1390
return;
1391
}
1392
1404
}
1405
}
1406
1375
- function scheduleUpdate(fiber: Fiber, priorityLevel: PriorityLevel) {
1376
- return scheduleUpdateImpl(fiber, priorityLevel, false);
1407
+ function scheduleUpdate(fiber: Fiber, expirationTime: ExpirationTime) {
1408
+ return scheduleUpdateImpl(fiber, expirationTime, false);
1409
}
1410
1411
function scheduleUpdateImpl(
1412
fiber: Fiber,
1381
- priorityLevel: PriorityLevel,
1413
+ expirationTime: ExpirationTime,
1414
isErrorRecovery: boolean,
1415
) {
1416
if (__DEV__) {
1428
);
1429
}
1430
1399
- if (!isPerformingWork && priorityLevel <= nextPriorityLevel) {
1431
+ if (!isPerformingWork && expirationTime <= nextRenderExpirationTime) {
1432
// We must reset the current unit of work pointer so that we restart the
1433
// search from the root during the next tick, in case there is now higher
1434
// priority work somewhere earlier than before.
1445
let node = fiber;
1446
let shouldContinue = true;
1447
while (node !== null && shouldContinue) {
1416
- // Walk the parent path to the root and update each node's priority. Once
1417
- // we reach a node whose priority matches (and whose alternate's priority
1418
- // matches) we can exit safely knowing that the rest of the path is correct.
1448
+ // Walk the parent path to the root and update each node's expiration
1449
+ // time. Once we reach a node whose expiration matches (and whose
1450
+ // alternate's expiration matches) we can exit safely knowing that the
1451
+ // rest of the path is correct.
1452
shouldContinue = false;
1453
if (
1421
- node.pendingWorkPriority === NoWork ||
1422
- node.pendingWorkPriority > priorityLevel
1454
+ node.expirationTime === NoWork ||
1455
+ node.expirationTime > expirationTime
1456
) {
1424
- // Priority did not match. Update and keep going.
1457
+ // Expiration time did not match. Update and keep going.
1458
shouldContinue = true;
1426
- node.pendingWorkPriority = priorityLevel;
1459
+ node.expirationTime = expirationTime;
1460
}
1461
if (node.alternate !== null) {
1462
if (
1430
- node.alternate.pendingWorkPriority === NoWork ||
1431
- node.alternate.pendingWorkPriority > priorityLevel
1463
+ node.alternate.expirationTime === NoWork ||
1464
+ node.alternate.expirationTime > expirationTime
1465
) {
1433
- // Priority did not match. Update and keep going.
1466
+ // Expiration time did not match. Update and keep going.
1467
shouldContinue = true;
1435
- node.alternate.pendingWorkPriority = priorityLevel;
1468
+ node.alternate.expirationTime = expirationTime;
1469
}
1470
}
1471
if (node.return === null) {
1472
if (node.tag === HostRoot) {
1473
const root: FiberRoot = (node.stateNode: any);
1441
- scheduleRoot(root, priorityLevel);
1474
+ scheduleRoot(root, expirationTime);
1475
if (!isPerformingWork) {
1476
+ const priorityLevel = expirationTimeToPriorityLevel(
1477
+ mostRecentCurrentTime,
1478
+ expirationTime,
1479
+ );
1480
switch (priorityLevel) {
1481
case SynchronousPriority:
1445
- // Perform this update now.
1482
if (isUnbatchingUpdates) {
1483
// We're inside unbatchedUpdates, which is inside either
1484
// batchedUpdates or a lifecycle. We should only flush
1493
invariant(
1494
isBatchingUpdates,
1495
'Task updates can only be scheduled as a nested update or ' +
1460
- 'inside batchedUpdates.',
1496
+ 'inside batchedUpdates. This error is likely caused by a ' +
1497
+ 'bug in React. Please file an issue.',
1498
);
1499
break;
1500
default:
1464
- // Schedule a callback to perform the work later.
1501
+ // This update is async. Schedule a callback.
1502
if (!isCallbackScheduled) {
1503
scheduleDeferredCallback(performDeferredWork);
1504
isCallbackScheduled = true;
1521
function getPriorityContext(
1522
fiber: Fiber,
1523
forceAsync: boolean,
1487
- ): PriorityLevel {
1524
+ ): PriorityLevel | null {
1525
+ if (isPerformingWork && !isCommitting) {
1526
+ // Updates during the render phase should expire at the same time as
1527
+ // the work that is being rendered. Return null to indicate.
1528
+ return null;
1529
+ }
1530
+
1531
let priorityLevel = priorityContext;
1532
if (priorityLevel === NoWork) {
1533
if (
1552
return priorityLevel;
1553
}
1554
1555
+ function getExpirationTimeForPriority(
1556
+ currentTime: ExpirationTime,
1557
+ priorityLevel: PriorityLevel | null,
1558
+ ): ExpirationTime {
1559
+ if (priorityLevel === null) {
1560
+ // A priorityLevel of null indicates that this update should expire at
1561
+ // the same time as whatever is currently being rendered.
1562
+ return nextRenderExpirationTime;
1563
+ }
1564
+ return priorityToExpirationTime(currentTime, priorityLevel);
1565
+ }
1566
+
1567
function scheduleErrorRecovery(fiber: Fiber) {
1513
- scheduleUpdateImpl(fiber, TaskPriority, true);
1568
+ const taskTime = getExpirationTimeForPriority(
1569
+ mostRecentCurrentTime,
1570
+ TaskPriority,
1571
+ );
1572
+ scheduleUpdateImpl(fiber, taskTime, true);
1573
+ }
1574
+
1575
+ function recalculateCurrentTime(): ExpirationTime {
1576
+ // Subtract initial time so it fits inside 32bits
1577
+ const ms = now() - startTime;
1578
+ mostRecentCurrentTime = msToExpirationTime(ms);
1579
+ return mostRecentCurrentTime;
1580
}
1581
1582
function batchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
1641
return {
1642
scheduleUpdate: scheduleUpdate,
1643
getPriorityContext: getPriorityContext,
1644
+ recalculateCurrentTime: recalculateCurrentTime,
1645
+ getExpirationTimeForPriority: getExpirationTimeForPriority,
1646
batchedUpdates: batchedUpdates,
1647
unbatchedUpdates: unbatchedUpdates,
1648
flushSync: flushSync,