12
13
'use strict';
14
15
-import type { TrappedError } from 'ReactFiberErrorBoundary';
15
import type { Fiber } from 'ReactFiber';
16
import type { FiberRoot } from 'ReactFiberRoot';
17
import type { HostConfig, Deadline } from 'ReactFiberReconciler';
23
var ReactCurrentOwner = require('ReactCurrentOwner');
24
25
var { cloneFiber } = require('ReactFiber');
27
-var { trapError, acknowledgeErrorInBoundary } = require('ReactFiberErrorBoundary');
26
27
var {
28
NoWork,
31
- LowPriority,
32
- AnimationPriority,
29
SynchronousPriority,
30
+ TaskPriority,
31
+ AnimationPriority,
32
+ HighPriority,
33
+ LowPriority,
34
+ OffscreenPriority,
35
} = require('ReactPriorityLevel');
36
37
var {
49
50
var {
51
HostContainer,
52
+ ClassComponent,
53
} = require('ReactTypeOfWork');
54
55
if (__DEV__) {
58
59
var timeHeuristicForUnitOfWork = 1;
60
61
+type TrappedError = {
62
+ boundary: Fiber | null,
63
+ error: any,
64
+};
65
+
66
module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
67
const { beginWork } = ReactFiberBeginWork(config, scheduleUpdate);
68
const { completeWork } = ReactFiberCompleteWork(config);
69
const { commitInsertion, commitDeletion, commitWork, commitLifeCycles } =
63
- ReactFiberCommitWork(config);
70
+ ReactFiberCommitWork(config, trapError);
71
65
- const scheduleAnimationCallback = config.scheduleAnimationCallback;
66
- const scheduleDeferredCallback = config.scheduleDeferredCallback;
72
+ const hostScheduleAnimationCallback = config.scheduleAnimationCallback;
73
+ const hostScheduleDeferredCallback = config.scheduleDeferredCallback;
74
const useSyncScheduling = config.useSyncScheduling;
75
76
// The priority level to use when scheduling an update.
81
// Whether updates should be batched. Only applies when using sync scheduling.
82
let shouldBatchUpdates : boolean = false;
83
84
+ // Need this to prevent recursion while in a Task loop.
85
+ let isPerformingTaskWork : boolean = false;
86
+
87
// The next work in progress fiber that we're currently working on.
88
let nextUnitOfWork : ?Fiber = null;
89
let nextPriorityLevel : PriorityLevel = NoWork;
96
let isAnimationCallbackScheduled : boolean = false;
97
let isDeferredCallbackScheduled : boolean = false;
98
99
+ // Caught errors and error boundaries that are currently handling them
100
+ let activeErrorBoundaries : Set<Fiber> | null = null;
101
+ let nextTrappedErrors : Array<TrappedError> | null = null;
102
+
103
+ function scheduleAnimationCallback(callback) {
104
+ if (!isAnimationCallbackScheduled) {
105
+ isAnimationCallbackScheduled = true;
106
+ hostScheduleAnimationCallback(callback);
107
+ }
108
+ }
109
+
110
+ function scheduleDeferredCallback(callback) {
111
+ if (!isDeferredCallbackScheduled) {
112
+ isDeferredCallbackScheduled = true;
113
+ hostScheduleDeferredCallback(callback);
114
+ }
115
+ }
116
+
117
function findNextUnitOfWork() {
118
// Clear out roots with no more work on them.
119
while (nextScheduledRoot && nextScheduledRoot.current.pendingWorkPriority === NoWork) {
159
return null;
160
}
161
134
- function commitAllWork(finishedWork : Fiber, ignoreUnmountingErrors : boolean) {
162
+ function commitAllWork(finishedWork : Fiber) {
163
// Commit all the side-effects within a tree.
136
-
137
- // Commit phase is meant to be atomic and non-interruptible.
138
- // Any errors raised in it should be handled after it is over
139
- // so that we don't end up in an inconsistent state due to user code.
140
- // We'll keep track of all caught errors and handle them later.
141
- let allTrappedErrors = null;
142
-
164
// First, we'll perform all the host insertions, updates, deletions and
165
// ref unmounts.
166
let effectfulFiber = finishedWork.firstEffect;
171
commitInsertion(effectfulFiber);
172
// Clear the "placement" from effect tag so that we know that this is inserted, before
173
// any life-cycles like componentDidMount gets called.
153
- effectfulFiber.effectTag = NoEffect;
174
+ effectfulFiber.effectTag ^= Placement;
175
break;
176
}
177
case PlacementAndUpdate:
180
commitInsertion(effectfulFiber);
181
// Clear the "placement" from effect tag so that we know that this is inserted, before
182
// any life-cycles like componentDidMount gets called.
162
- effectfulFiber.effectTag = Update;
183
+ effectfulFiber.effectTag ^= Placement;
184
185
// Update
186
const current = effectfulFiber.alternate;
194
break;
195
case Deletion:
196
case DeletionAndCallback:
176
- // Deletion might cause an error in componentWillUnmount().
177
- // We will continue nevertheless and handle those later on.
178
- const trappedErrors = commitDeletion(effectfulFiber);
179
- // There is a special case where we completely ignore errors.
180
- // It happens when we already caught an error earlier, and the update
181
- // is caused by an error boundary trying to render an error message.
182
- // In this case, we want to blow away the tree without catching errors.
183
- if (trappedErrors && !ignoreUnmountingErrors) {
184
- if (!allTrappedErrors) {
185
- allTrappedErrors = trappedErrors;
186
- } else {
187
- allTrappedErrors.push.apply(allTrappedErrors, trappedErrors);
188
- }
189
- }
197
+ commitDeletion(effectfulFiber);
198
break;
199
}
200
208
while (effectfulFiber) {
209
if (effectfulFiber.effectTag & (Update | Callback)) {
210
const current = effectfulFiber.alternate;
203
- const trappedError = commitLifeCycles(current, effectfulFiber);
204
- if (trappedError) {
205
- allTrappedErrors = allTrappedErrors || [];
206
- allTrappedErrors.push(trappedError);
207
- }
211
+ commitLifeCycles(current, effectfulFiber);
212
}
213
const next = effectfulFiber.nextEffect;
214
// Ensure that we clean these up so that we don't accidentally keep them.
226
if (finishedWork.effectTag !== NoEffect) {
227
const current = finishedWork.alternate;
228
commitWork(current, finishedWork);
225
- const trappedError = commitLifeCycles(current, finishedWork);
226
- if (trappedError) {
227
- allTrappedErrors = allTrappedErrors || [];
228
- allTrappedErrors.push(trappedError);
229
- }
229
+ commitLifeCycles(current, finishedWork);
230
}
231
232
- // Now that the tree has been committed, we can handle errors.
233
- if (allTrappedErrors) {
234
- handleErrors(allTrappedErrors);
235
- }
232
+ // The task work includes batched updates and error handling.
233
+ performTaskWork();
234
}
235
236
function resetWorkPriority(workInProgress : Fiber) {
251
workInProgress.pendingWorkPriority = newPriority;
252
}
253
256
- function completeUnitOfWork(workInProgress : Fiber, ignoreUnmountingErrors : boolean) : ?Fiber {
254
+ function completeUnitOfWork(workInProgress : Fiber) : ?Fiber {
255
while (true) {
256
// The current, flushed, state of this fiber is the alternate.
257
// Ideally nothing should rely on this, but relying on it here
323
// "next" scheduled work since we've already scanned passed. That
324
// also ensures that work scheduled during reconciliation gets deferred.
325
// const hasMoreWork = workInProgress.pendingWorkPriority !== NoWork;
328
- commitAllWork(workInProgress, ignoreUnmountingErrors);
326
+ commitAllWork(workInProgress);
327
const nextWork = findNextUnitOfWork();
328
// if (!nextWork && hasMoreWork) {
329
// TODO: This can happen when some deep work completes and we don't
337
}
338
}
339
342
- function performUnitOfWork(workInProgress : Fiber, ignoreUnmountingErrors : boolean) : ?Fiber {
340
+ function performUnitOfWork(workInProgress : Fiber) : ?Fiber {
341
// The current, flushed, state of this fiber is the alternate.
342
// Ideally nothing should rely on this, but relying on it here
343
// means that we don't need an additional field on the work in
358
ReactFiberInstrumentation.debugTool.onWillCompleteWork(workInProgress);
359
}
360
// If this doesn't spawn new work, complete the current work.
363
- next = completeUnitOfWork(workInProgress, ignoreUnmountingErrors);
361
+ next = completeUnitOfWork(workInProgress);
362
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
363
ReactFiberInstrumentation.debugTool.onDidCompleteWork(workInProgress);
364
}
375
}
376
while (nextUnitOfWork) {
377
if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
380
- nextUnitOfWork = performUnitOfWork(nextUnitOfWork, false);
378
+ nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
379
if (!nextUnitOfWork) {
380
// Find more work. We might have time to complete some more.
381
nextUnitOfWork = findNextUnitOfWork();
382
}
383
} else {
386
- if (!isDeferredCallbackScheduled) {
387
- isDeferredCallbackScheduled = true;
388
- scheduleDeferredCallback(performDeferredWork);
389
- }
384
+ scheduleDeferredCallback(performDeferredWork);
385
return;
386
}
387
}
392
performAndHandleErrors(LowPriority, deadline);
393
}
394
400
- function scheduleDeferredWork(root : FiberRoot, priority : PriorityLevel) {
401
- // We must reset the current unit of work pointer so that we restart the
402
- // search from the root during the next tick, in case there is now higher
403
- // priority work somewhere earlier than before.
404
- if (priority <= nextPriorityLevel) {
405
- nextUnitOfWork = null;
406
- }
407
-
408
- // Set the priority on the root, without deprioritizing
409
- if (root.current.pendingWorkPriority === NoWork ||
410
- priority <= root.current.pendingWorkPriority) {
411
- root.current.pendingWorkPriority = priority;
412
- }
413
-
414
- if (!root.isScheduled) {
415
- root.isScheduled = true;
416
- if (lastScheduledRoot) {
417
- // Schedule ourselves to the end.
418
- lastScheduledRoot.nextScheduledRoot = root;
419
- lastScheduledRoot = root;
420
- } else {
421
- // We're the only work scheduled.
422
- nextScheduledRoot = root;
423
- lastScheduledRoot = root;
424
- }
425
- }
426
-
427
- if (!isDeferredCallbackScheduled) {
428
- isDeferredCallbackScheduled = true;
429
- scheduleDeferredCallback(performDeferredWork);
430
- }
431
- }
432
-
395
function performAnimationWorkUnsafe() {
396
// Always start from the root
397
nextUnitOfWork = findNextUnitOfWork();
398
while (nextUnitOfWork &&
437
- nextPriorityLevel !== NoWork &&
438
- nextPriorityLevel <= AnimationPriority) {
439
- nextUnitOfWork = performUnitOfWork(nextUnitOfWork, false);
399
+ nextPriorityLevel === AnimationPriority) {
400
+ nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
401
if (!nextUnitOfWork) {
402
// Keep searching for animation work until there's no more left
403
nextUnitOfWork = findNextUnitOfWork();
404
}
405
}
445
- if (nextUnitOfWork && nextPriorityLevel > AnimationPriority) {
446
- if (!isDeferredCallbackScheduled) {
447
- isDeferredCallbackScheduled = true;
448
- scheduleDeferredCallback(performDeferredWork);
449
- }
406
+ if (nextUnitOfWork) {
407
+ scheduleCallbackAtPriority(nextPriorityLevel);
408
}
409
}
410
413
performAndHandleErrors(AnimationPriority);
414
}
415
458
- function scheduleAnimationWork(root: FiberRoot, priorityLevel : PriorityLevel) {
459
- // Set the priority on the root, without deprioritizing
460
- if (root.current.pendingWorkPriority === NoWork ||
461
- priorityLevel <= root.current.pendingWorkPriority) {
462
- root.current.pendingWorkPriority = priorityLevel;
463
- }
464
-
465
- if (!root.isScheduled) {
466
- root.isScheduled = true;
467
- if (lastScheduledRoot) {
468
- // Schedule ourselves to the end.
469
- lastScheduledRoot.nextScheduledRoot = root;
470
- lastScheduledRoot = root;
471
- } else {
472
- // We're the only work scheduled.
473
- nextScheduledRoot = root;
474
- lastScheduledRoot = root;
475
- }
476
- }
477
-
478
- if (!isAnimationCallbackScheduled) {
479
- isAnimationCallbackScheduled = true;
480
- scheduleAnimationCallback(performAnimationWork);
481
- }
482
- }
483
-
484
- function scheduleErrorBoundaryWork(boundary : Fiber, priority) : FiberRoot {
485
- let root = null;
486
- let fiber = boundary;
487
- while (fiber) {
488
- fiber.pendingWorkPriority = priority;
489
- if (fiber.alternate) {
490
- fiber.alternate.pendingWorkPriority = priority;
491
- }
492
- if (!fiber.return) {
493
- if (fiber.tag === HostContainer) {
494
- // We found the root.
495
- // Remember it so we can update it.
496
- root = ((fiber.stateNode : any) : FiberRoot);
497
- break;
498
- } else {
499
- throw new Error('Invalid root');
500
- }
501
- }
502
- fiber = fiber.return;
503
- }
504
- if (!root) {
505
- throw new Error('Could not find root from the boundary.');
506
- }
507
- return root;
508
- }
509
-
416
function performSynchronousWorkUnsafe() {
511
- // Perform work now
417
nextUnitOfWork = findNextUnitOfWork();
418
while (nextUnitOfWork &&
419
nextPriorityLevel === SynchronousPriority) {
515
- nextUnitOfWork = performUnitOfWork(nextUnitOfWork, false);
420
+ nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
421
422
if (!nextUnitOfWork) {
423
nextUnitOfWork = findNextUnitOfWork();
424
}
425
}
426
if (nextUnitOfWork) {
522
- if (nextPriorityLevel > AnimationPriority) {
523
- if (!isDeferredCallbackScheduled) {
524
- isDeferredCallbackScheduled = true;
525
- scheduleDeferredCallback(performDeferredWork);
526
- }
527
- return;
528
- }
529
- if (!isAnimationCallbackScheduled) {
530
- isAnimationCallbackScheduled = true;
531
- scheduleAnimationCallback(performAnimationWork);
532
- }
427
+ scheduleCallbackAtPriority(nextPriorityLevel);
428
}
429
}
430
439
}
440
}
441
547
- function scheduleSynchronousWork(root : FiberRoot) {
548
- root.current.pendingWorkPriority = SynchronousPriority;
549
-
550
- if (root.isScheduled) {
551
- // If we're already scheduled, we can bail out.
552
- return;
442
+ function performTaskWorkUnsafe() {
443
+ if (isPerformingTaskWork) {
444
+ throw new Error('Already performing task work');
445
}
554
- root.isScheduled = true;
555
- if (lastScheduledRoot) {
556
- // Schedule ourselves to the end.
557
- lastScheduledRoot.nextScheduledRoot = root;
558
- lastScheduledRoot = root;
559
- } else {
560
- // We're the only work scheduled.
561
- nextScheduledRoot = root;
562
- lastScheduledRoot = root;
446
564
- if (!shouldBatchUpdates) {
565
- // Unless in batched mode, perform work immediately
566
- performSynchronousWork();
447
+ isPerformingTaskWork = true;
448
+ nextUnitOfWork = findNextUnitOfWork();
449
+ while (nextUnitOfWork &&
450
+ nextPriorityLevel === TaskPriority) {
451
+ nextUnitOfWork =
452
+ performUnitOfWork(nextUnitOfWork);
453
+
454
+ if (!nextUnitOfWork) {
455
+ nextUnitOfWork = findNextUnitOfWork();
456
}
457
}
458
+ if (nextUnitOfWork) {
459
+ scheduleCallbackAtPriority(nextPriorityLevel);
460
+ }
461
+ isPerformingTaskWork = false;
462
+ }
463
+
464
+ function performTaskWork() {
465
+ performAndHandleErrors(TaskPriority);
466
}
467
468
function performAndHandleErrors(priorityLevel : PriorityLevel, deadline : null | Deadline) {
469
// The exact priority level doesn't matter, so long as it's in range of the
470
// work (sync, animation, deferred) being performed.
471
try {
575
- if (priorityLevel === SynchronousPriority) {
576
- performSynchronousWorkUnsafe();
577
- } else if (priorityLevel > AnimationPriority) {
578
- if (!deadline) {
579
- throw new Error('No deadline');
580
- } else {
581
- performDeferredWorkUnsafe(deadline);
582
- }
583
- return;
584
- } else {
585
- performAnimationWorkUnsafe();
472
+ switch (priorityLevel) {
473
+ case SynchronousPriority:
474
+ performSynchronousWorkUnsafe();
475
+ break;
476
+ case TaskPriority:
477
+ if (!isPerformingTaskWork) {
478
+ performTaskWorkUnsafe();
479
+ }
480
+ break;
481
+ case AnimationPriority:
482
+ performAnimationWorkUnsafe();
483
+ break;
484
+ case HighPriority:
485
+ case LowPriority:
486
+ case OffscreenPriority:
487
+ if (!deadline) {
488
+ throw new Error('No deadline');
489
+ } else {
490
+ performDeferredWorkUnsafe(deadline);
491
+ }
492
+ break;
493
+ default:
494
+ break;
495
}
496
} catch (error) {
588
- const failedUnitOfWork = nextUnitOfWork;
589
- // Reset because it points to the error boundary:
590
- nextUnitOfWork = null;
591
- if (!failedUnitOfWork) {
592
- // We shouldn't end up here because nextUnitOfWork
593
- // should always be set while work is being performed.
594
- throw error;
595
- }
596
- const trappedError = trapError(failedUnitOfWork, error);
597
- handleErrors([trappedError]);
497
+ trapError(nextUnitOfWork, error, false);
498
+ }
499
+
500
+ // If there were errors and we aren't already handling them, handle them now
501
+ if (nextTrappedErrors && !activeErrorBoundaries) {
502
+ handleErrors();
503
}
504
}
505
601
- function handleErrors(initialTrappedErrors : Array<TrappedError>) : void {
602
- let nextTrappedErrors = initialTrappedErrors;
603
- let firstUncaughtError = null;
506
+ function handleErrors() : void {
507
+ if (activeErrorBoundaries) {
508
+ throw new Error('Already handling errors');
509
+ }
510
605
- // In each phase, we will attempt to pass errors to boundaries and re-render them.
606
- // If we get more errors, we propagate them to higher boundaries in the next iterations.
607
- while (nextTrappedErrors) {
608
- const trappedErrors = nextTrappedErrors;
609
- nextTrappedErrors = null;
511
+ // Start tracking active boundaries.
512
+ activeErrorBoundaries = new Set();
513
611
- // Pass errors to all affected boundaries.
612
- const affectedBoundaries : Set<Fiber> = new Set();
613
- trappedErrors.forEach(trappedError => {
514
+ // If we find unhandled errors, we'll only rethrow the first one.
515
+ let firstUncaughtError = null;
516
+
517
+ // All work created by error boundaries should have Task priority
518
+ // so that it finishes before this function exits.
519
+ const previousPriorityContext = priorityContext;
520
+ priorityContext = TaskPriority;
521
+
522
+ // Keep looping until there are no more trapped errors, or until we find
523
+ // an unhandled error.
524
+ while (nextTrappedErrors && nextTrappedErrors.length && !firstUncaughtError) {
525
+ // First, find all error boundaries and notify them about errors.
526
+ while (nextTrappedErrors && nextTrappedErrors.length) {
527
+ const trappedError = nextTrappedErrors.shift();
528
const boundary = trappedError.boundary;
529
const error = trappedError.error;
530
if (!boundary) {
531
firstUncaughtError = firstUncaughtError || error;
618
- return;
532
+ continue;
533
}
534
// Don't visit boundaries twice.
621
- if (affectedBoundaries.has(boundary)) {
622
- return;
623
- }
624
- // Give error boundary a chance to update its state.
625
- try {
626
- acknowledgeErrorInBoundary(boundary, error);
627
- affectedBoundaries.add(boundary);
628
- } catch (nextError) {
629
- // If it throws, propagate the error.
630
- nextTrappedErrors = nextTrappedErrors || [];
631
- nextTrappedErrors.push(trapError(boundary, nextError));
535
+ if (activeErrorBoundaries.has(boundary)) {
536
+ continue;
537
}
633
- });
634
-
635
- // We will process an update caused by each error boundary synchronously.
636
- affectedBoundaries.forEach(boundary => {
637
- const priority = priorityContext;
638
- const root = scheduleErrorBoundaryWork(boundary, priority);
639
- // This should use findNextUnitOfWork() when synchronous scheduling is implemented.
640
- let fiber = cloneFiber(root.current, priority);
538
try {
642
- while (fiber) {
643
- // TODO: this is the only place where we recurse and it's unfortunate.
644
- // (This may potentially get us into handleErrors() again.)
645
- fiber = performUnitOfWork(fiber, true);
646
- }
539
+ // This error boundary is now active.
540
+ // We will let it handle the error and retry rendering.
541
+ // If it fails again, the next error will be propagated to the parent
542
+ // boundary or rethrown.
543
+ activeErrorBoundaries.add(boundary);
544
+ // Give error boundary a chance to update its state.
545
+ // Updates will be scheduled with Task priority.
546
+ const instance = boundary.stateNode;
547
+ instance.unstable_handleError(error);
548
+
549
+ // Schedule an update, in case the boundary didn't call setState
550
+ // on itself.
551
+ scheduleUpdate(boundary);
552
} catch (nextError) {
648
- // If it throws, propagate the error.
649
- nextTrappedErrors = nextTrappedErrors || [];
650
- nextTrappedErrors.push(trapError(boundary, nextError));
553
+ // If an error is thrown, propagate the error to the parent boundary.
554
+ trapError(boundary, nextError, false);
555
}
652
- });
556
+ }
557
+
558
+ // Now that we attempt to flush any work that was scheduled by the boundaries.
559
+ // If this creates errors, they will be pushed to nextTrappedErrors and
560
+ // the outer loop will continue.
561
+ try {
562
+ performTaskWorkUnsafe();
563
+ } catch (error) {
564
+ isPerformingTaskWork = false;
565
+ trapError(nextUnitOfWork, error, false);
566
+ }
567
}
568
655
- ReactCurrentOwner.current = null;
569
+ nextTrappedErrors = null;
570
+ activeErrorBoundaries = null;
571
+ priorityContext = previousPriorityContext;
572
657
- // Surface the first error uncaught by the boundaries to the user.
573
if (firstUncaughtError) {
574
// We need to make sure any future root can get scheduled despite these errors.
575
// Currently after throwing, nothing gets scheduled because these fields are set.
576
// FIXME: this is likely a wrong fix! It's still better than ignoring updates though.
577
nextScheduledRoot = null;
578
lastScheduledRoot = null;
664
-
665
- // Throw any unhandled errors.
579
throw firstUncaughtError;
580
}
581
}
582
583
+ function findClosestErrorBoundary(fiber : Fiber): Fiber | null {
584
+ let maybeErrorBoundary = fiber.return;
585
+ while (maybeErrorBoundary) {
586
+ if (maybeErrorBoundary.tag === ClassComponent) {
587
+ const instance = maybeErrorBoundary.stateNode;
588
+ const isErrorBoundary = typeof instance.unstable_handleError === 'function';
589
+ if (isErrorBoundary) {
590
+ const isHandlingAnotherError = (
591
+ activeErrorBoundaries !== null &&
592
+ activeErrorBoundaries.has(maybeErrorBoundary)
593
+ );
594
+ if (!isHandlingAnotherError) {
595
+ return maybeErrorBoundary;
596
+ }
597
+ }
598
+ }
599
+ maybeErrorBoundary = maybeErrorBoundary.return;
600
+ }
601
+ return null;
602
+ }
603
+
604
+ function trapError(fiber : Fiber | null, error : any, isUnmounting : boolean) : void {
605
+ // It is no longer valid because we exited the user code.
606
+ ReactCurrentOwner.current = null;
607
+
608
+ if (isUnmounting && activeErrorBoundaries) {
609
+ // Ignore errors caused by unmounting during error handling.
610
+ // This lets error boundaries safely tear down already failed trees.
611
+ return;
612
+ }
613
+
614
+ if (!nextTrappedErrors) {
615
+ nextTrappedErrors = [];
616
+ }
617
+ nextTrappedErrors.push({
618
+ boundary: fiber ? findClosestErrorBoundary(fiber) : null,
619
+ error,
620
+ });
621
+ }
622
+
623
function scheduleWork(root : FiberRoot) {
671
- scheduleWorkAtPriority(root, priorityContext);
624
+ let priorityLevel = priorityContext;
625
+
626
+ // If we're in a batch, switch to task priority
627
+ if (priorityLevel === SynchronousPriority && shouldBatchUpdates) {
628
+ priorityLevel = TaskPriority;
629
+ }
630
+
631
+ scheduleWorkAtPriority(root, priorityLevel);
632
}
633
634
function scheduleWorkAtPriority(root : FiberRoot, priorityLevel : PriorityLevel) {
675
- if (priorityLevel === NoWork) {
676
- return;
677
- } else if (priorityLevel === SynchronousPriority) {
678
- scheduleSynchronousWork(root);
679
- } else if (priorityLevel <= AnimationPriority) {
680
- scheduleAnimationWork(root, priorityLevel);
681
- } else {
682
- scheduleDeferredWork(root, priorityLevel);
683
- return;
635
+ // Set the priority on the root, without deprioritizing
636
+ if (root.current.pendingWorkPriority === NoWork ||
637
+ priorityLevel <= root.current.pendingWorkPriority) {
638
+ root.current.pendingWorkPriority = priorityLevel;
639
+ }
640
+
641
+ if (!root.isScheduled) {
642
+ root.isScheduled = true;
643
+ if (lastScheduledRoot) {
644
+ // Schedule ourselves to the end.
645
+ lastScheduledRoot.nextScheduledRoot = root;
646
+ lastScheduledRoot = root;
647
+ } else {
648
+ // We're the only work scheduled.
649
+ nextScheduledRoot = root;
650
+ lastScheduledRoot = root;
651
+ }
652
+ }
653
+
654
+ // We must reset the current unit of work pointer so that we restart the
655
+ // search from the root during the next tick, in case there is now higher
656
+ // priority work somewhere earlier than before.
657
+ if (priorityLevel <= nextPriorityLevel) {
658
+ nextUnitOfWork = null;
659
+ }
660
+
661
+ scheduleCallbackAtPriority(priorityLevel);
662
+ }
663
+
664
+ function scheduleCallbackAtPriority(priorityLevel : PriorityLevel) {
665
+ switch (priorityLevel) {
666
+ case SynchronousPriority:
667
+ // Perform work immediately
668
+ performSynchronousWork();
669
+ return;
670
+ case TaskPriority:
671
+ // Do nothing. Task work should be flushed after committing.
672
+ return;
673
+ case AnimationPriority:
674
+ scheduleAnimationCallback(performAnimationWork);
675
+ return;
676
+ case HighPriority:
677
+ case LowPriority:
678
+ case OffscreenPriority:
679
+ scheduleDeferredCallback(performDeferredWork);
680
+ return;
681
}
682
}
683
684
function scheduleUpdate(fiber : Fiber) {
688
- const priorityLevel = priorityContext;
685
+ let priorityLevel = priorityContext;
686
+ // If we're in a batch, switch to task priority
687
+ if (priorityLevel === SynchronousPriority && shouldBatchUpdates) {
688
+ priorityLevel = TaskPriority;
689
+ }
690
+
691
while (true) {
692
if (fiber.pendingWorkPriority === NoWork ||
693
fiber.pendingWorkPriority >= priorityLevel) {
729
return fn();
730
} finally {
731
shouldBatchUpdates = prev;
730
- // If we've exited the batch, perform any scheduled sync work
732
+ // If we've exited the batch, perform any scheduled task work
733
if (!shouldBatchUpdates) {
732
- performSynchronousWork();
734
+ performTaskWork();
735
}
736
}
737
}
748
749
return {
750
scheduleWork: scheduleWork,
749
- scheduleDeferredWork: scheduleDeferredWork,
751
performWithPriority: performWithPriority,
752
batchedUpdates: batchedUpdates,
753
syncUpdates: syncUpdates,