13
import type {HydrationContext} from './ReactFiberHydrationContext';
14
import type {ExpirationTime} from './ReactFiberExpirationTime';
15
16
-import {getStackAddendumByWorkInProgressFiber} from 'shared/ReactFiberComponentTreeHook';
16
import ReactErrorUtils from 'shared/ReactErrorUtils';
17
import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
18
import ReactStrictModeWarnings from './ReactStrictModeWarnings';
19
import {
20
+ NoEffect,
21
PerformedWork,
22
Placement,
23
Update,
25
Deletion,
26
ContentReset,
27
Callback,
28
- Err,
28
+ DidCapture,
29
Ref,
30
+ Incomplete,
31
+ HostEffectMask,
32
+ ErrLog,
33
} from 'shared/ReactTypeOfSideEffect';
34
import {
35
HostRoot,
33
- HostComponent,
34
- HostPortal,
36
ClassComponent,
37
+ HostComponent,
38
ContextProvider,
39
+ HostPortal,
40
} from 'shared/ReactTypeOfWork';
41
import {
42
enableUserTimingAPI,
43
warnAboutDeprecatedLifecycles,
44
+ replayFailedUnitOfWorkWithInvokeGuardedCallback,
45
} from 'shared/ReactFeatureFlags';
46
import getComponentName from 'shared/getComponentName';
47
import invariant from 'fbjs/lib/invariant';
49
50
import ReactFiberBeginWork from './ReactFiberBeginWork';
51
import ReactFiberCompleteWork from './ReactFiberCompleteWork';
52
+import ReactFiberUnwindWork from './ReactFiberUnwindWork';
53
import ReactFiberCommitWork from './ReactFiberCommitWork';
54
import ReactFiberHostContext from './ReactFiberHostContext';
55
import ReactFiberHydrationContext from './ReactFiberHydrationContext';
72
startCommitLifeCyclesTimer,
73
stopCommitLifeCyclesTimer,
74
} from './ReactDebugFiberPerf';
70
-import {popContextProvider} from './ReactFiberContext';
71
-import {popProvider} from './ReactFiberNewContext';
75
import {reset} from './ReactFiberStack';
73
-import {logCapturedError} from './ReactFiberErrorLogger';
76
import {createWorkInProgress} from './ReactFiber';
77
import {onCommitRoot} from './ReactFiberDevToolsHook';
78
import {
84
computeExpirationBucket,
85
} from './ReactFiberExpirationTime';
86
import {AsyncMode} from './ReactTypeOfMode';
85
-import {getUpdateExpirationTime} from './ReactFiberUpdateQueue';
86
-import {resetContext as resetLegacyContext} from './ReactFiberContext';
87
+import {
88
+ resetContext as resetLegacyContext,
89
+ popContextProvider as popLegacyContextProvider,
90
+ popTopLevelContextObject as popTopLevelLegacyContextObject,
91
+} from './ReactFiberContext';
92
+import {popProvider} from './ReactFiberNewContext';
93
import {resetProviderStack} from './ReactFiberNewContext';
94
+import {
95
+ getUpdateExpirationTime,
96
+ insertUpdateIntoFiber,
97
+} from './ReactFiberUpdateQueue';
98
+import {createCapturedValue} from './ReactCapturedValue';
99
100
const {
101
invokeGuardedCallback,
103
clearCaughtError,
104
} = ReactErrorUtils;
105
95
-export type CapturedError = {
96
- componentName: ?string,
97
- componentStack: string,
98
- error: mixed,
99
- errorBoundary: ?Object,
100
- errorBoundaryFound: boolean,
101
- errorBoundaryName: string | null,
102
- willRetry: boolean,
103
-};
104
-
105
-export type HandleErrorInfo = {
106
- componentStack: string,
107
-};
108
-
106
let didWarnAboutStateTransition;
107
let didWarnSetStateChildContext;
108
let warnAboutUpdateOnUnmounted;
162
config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
163
) {
164
const hostContext = ReactFiberHostContext(config);
165
+ const {popHostContext, popHostContainer} = hostContext;
166
const hydrationContext: HydrationContext<C, CX> = ReactFiberHydrationContext(
167
config,
168
);
171
- const {popHostContainer, popHostContext, resetHostContainer} = hostContext;
172
- const {beginWork, beginFailedWork} = ReactFiberBeginWork(
169
+ const {resetHostContainer} = hostContext;
170
+ const {beginWork} = ReactFiberBeginWork(
171
config,
172
hostContext,
173
hydrationContext,
179
hostContext,
180
hydrationContext,
181
);
182
+ const {throwException, unwindWork} = ReactFiberUnwindWork(
183
+ hostContext,
184
+ scheduleWork,
185
+ isAlreadyFailedLegacyErrorBoundary,
186
+ );
187
const {
188
commitResetTextContent,
189
commitPlacement,
190
commitDeletion,
191
commitWork,
192
commitLifeCycles,
193
+ commitErrorLogging,
194
commitAttachRef,
195
commitDetachRef,
192
- } = ReactFiberCommitWork(config, captureError);
196
+ } = ReactFiberCommitWork(
197
+ config,
198
+ onCommitPhaseError,
199
+ scheduleWork,
200
+ computeExpirationForFiber,
201
+ markLegacyErrorBoundaryAsFailed,
202
+ recalculateCurrentTime,
203
+ );
204
const {
205
now,
206
scheduleDeferredCallback,
210
} = config;
211
212
// Represents the current time in ms.
202
- const startTime = now();
213
+ const originalStartTimeMs = now();
214
let mostRecentCurrentTime: ExpirationTime = msToExpirationTime(0);
215
+ let mostRecentCurrentTimeMs: number = originalStartTimeMs;
216
217
// Used to ensure computeUniqueAsyncExpiration is monotonically increases.
218
let lastUniqueAsyncExpiration: number = 0;
233
// The next fiber with an effect that we're currently committing.
234
let nextEffect: Fiber | null = null;
235
224
- // Keep track of which fibers have captured an error that need to be handled.
225
- // Work is removed from this collection after componentDidCatch is called.
226
- let capturedErrors: Map<Fiber, CapturedError> | null = null;
227
- // Keep track of which fibers have failed during the current batch of work.
228
- // This is a different set than capturedErrors, because it is not reset until
229
- // the end of the batch. This is needed to propagate errors correctly if a
230
- // subtree fails more than once.
231
- let failedBoundaries: Set<Fiber> | null = null;
232
- // Error boundaries that captured an error during the current commit.
233
- let commitPhaseBoundaries: Set<Fiber> | null = null;
234
- let firstUncaughtError: mixed | null = null;
235
- let didFatal: boolean = false;
236
-
236
let isCommitting: boolean = false;
238
- let isUnmounting: boolean = false;
237
+
238
+ let isRootReadyForCommit: boolean = false;
239
+
240
+ let legacyErrorBoundariesThatAlreadyFailed: Set<mixed> | null = null;
241
242
// Used for performance tracking.
243
let interruptedBy: Fiber | null = null;
244
245
+ let stashedWorkInProgressProperties;
246
+ let replayUnitOfWork;
247
+ if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
248
+ stashedWorkInProgressProperties = null;
249
+ replayUnitOfWork = (failedUnitOfWork: Fiber, isAsync: boolean) => {
250
+ // Retore the original state of the work-in-progress
251
+ Object.assign(failedUnitOfWork, stashedWorkInProgressProperties);
252
+ switch (failedUnitOfWork.tag) {
253
+ case HostRoot:
254
+ popHostContainer(failedUnitOfWork);
255
+ popTopLevelLegacyContextObject(failedUnitOfWork);
256
+ break;
257
+ case HostComponent:
258
+ popHostContext(failedUnitOfWork);
259
+ break;
260
+ case ClassComponent:
261
+ popLegacyContextProvider(failedUnitOfWork);
262
+ break;
263
+ case HostPortal:
264
+ popHostContainer(failedUnitOfWork);
265
+ break;
266
+ case ContextProvider:
267
+ popProvider(failedUnitOfWork);
268
+ break;
269
+ }
270
+ // Replay the begin phase.
271
+ invokeGuardedCallback(null, workLoop, null, isAsync);
272
+ if (hasCaughtError()) {
273
+ clearCaughtError();
274
+ } else {
275
+ // This should be unreachable because the render phase is
276
+ // idempotent
277
+ }
278
+ };
279
+ }
280
+
281
function resetContextStack() {
282
// Reset the stack
283
reset();
284
// Reset the cursors
285
resetLegacyContext();
248
- resetProviderStack();
286
resetHostContainer();
287
+
288
+ // TODO: Unify new context implementation with other stacks
289
+ resetProviderStack();
290
+
291
+ if (__DEV__) {
292
+ ReactStrictModeWarnings.discardPendingWarnings();
293
+ }
294
+
295
+ nextRoot = null;
296
+ nextRenderExpirationTime = NoWork;
297
+ nextUnitOfWork = null;
298
+
299
+ isRootReadyForCommit = false;
300
}
301
302
function commitAllHostEffects() {
322
// updates, and deletions. To avoid needing to add a case for every
323
// possible bitmap value, we remove the secondary effects from the
324
// effect tag and switch on that value.
275
- let primaryEffectTag =
276
- effectTag & ~(Callback | Err | ContentReset | Ref | PerformedWork);
325
+ let primaryEffectTag = effectTag & (Placement | Update | Deletion);
326
switch (primaryEffectTag) {
327
case Placement: {
328
commitPlacement(nextEffect);
352
break;
353
}
354
case Deletion: {
306
- isUnmounting = true;
355
commitDeletion(nextEffect);
308
- isUnmounting = false;
356
break;
357
}
358
}
364
}
365
}
366
320
- function commitAllLifeCycles() {
367
+ function commitAllLifeCycles(
368
+ finishedRoot: FiberRoot,
369
+ currentTime: ExpirationTime,
370
+ committedExpirationTime: ExpirationTime,
371
+ ) {
372
if (__DEV__) {
373
ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings();
374
376
ReactStrictModeWarnings.flushPendingDeprecationWarnings();
377
}
378
}
328
-
379
while (nextEffect !== null) {
380
const effectTag = nextEffect.effectTag;
381
382
if (effectTag & (Update | Callback)) {
383
recordEffect();
384
const current = nextEffect.alternate;
335
- commitLifeCycles(current, nextEffect);
385
+ commitLifeCycles(
386
+ finishedRoot,
387
+ current,
388
+ nextEffect,
389
+ currentTime,
390
+ committedExpirationTime,
391
+ );
392
}
393
338
- if (effectTag & Ref) {
339
- recordEffect();
340
- commitAttachRef(nextEffect);
394
+ if (effectTag & ErrLog) {
395
+ commitErrorLogging(nextEffect, onUncaughtError);
396
}
397
343
- if (effectTag & Err) {
398
+ if (effectTag & Ref) {
399
recordEffect();
345
- commitErrorHandling(nextEffect);
400
+ commitAttachRef(nextEffect);
401
}
402
403
const next = nextEffect.nextEffect;
412
}
413
}
414
415
+ function isAlreadyFailedLegacyErrorBoundary(instance: mixed): boolean {
416
+ return (
417
+ legacyErrorBoundariesThatAlreadyFailed !== null &&
418
+ legacyErrorBoundariesThatAlreadyFailed.has(instance)
419
+ );
420
+ }
421
+
422
+ function markLegacyErrorBoundaryAsFailed(instance: mixed) {
423
+ if (legacyErrorBoundariesThatAlreadyFailed === null) {
424
+ legacyErrorBoundariesThatAlreadyFailed = new Set([instance]);
425
+ } else {
426
+ legacyErrorBoundariesThatAlreadyFailed.add(instance);
427
+ }
428
+ }
429
+
430
function commitRoot(finishedWork: Fiber): ExpirationTime {
361
- // We keep track of this so that captureError can collect any boundaries
362
- // that capture an error during the commit phase. The reason these aren't
363
- // local to this function is because errors that occur during cWU are
364
- // captured elsewhere, to prevent the unmount from being interrupted.
431
isWorking = true;
432
isCommitting = true;
433
startCommitTimer();
439
'related to the return field. This error is likely caused by a bug ' +
440
'in React. Please file an issue.',
441
);
376
- root.isReadyForCommit = false;
442
+ const committedExpirationTime = root.pendingCommitExpirationTime;
443
+ invariant(
444
+ committedExpirationTime !== NoWork,
445
+ 'Cannot commit an incomplete root. This error is likely caused by a ' +
446
+ 'bug in React. Please file an issue.',
447
+ );
448
+ root.pendingCommitExpirationTime = NoWork;
449
+
450
+ const currentTime = recalculateCurrentTime();
451
452
// Reset this to null before calling lifecycles
453
ReactCurrentOwner.current = null;
499
'Should have next effect. This error is likely caused by a bug ' +
500
'in React. Please file an issue.',
501
);
428
- captureError(nextEffect, error);
502
+ onCommitPhaseError(nextEffect, error);
503
// Clean-up
504
if (nextEffect !== null) {
505
nextEffect = nextEffect.nextEffect;
526
let didError = false;
527
let error;
528
if (__DEV__) {
455
- invokeGuardedCallback(null, commitAllLifeCycles, null);
529
+ invokeGuardedCallback(
530
+ null,
531
+ commitAllLifeCycles,
532
+ null,
533
+ root,
534
+ currentTime,
535
+ committedExpirationTime,
536
+ );
537
if (hasCaughtError()) {
538
didError = true;
539
error = clearCaughtError();
540
}
541
} else {
542
try {
462
- commitAllLifeCycles();
543
+ commitAllLifeCycles(root, currentTime, committedExpirationTime);
544
} catch (e) {
545
didError = true;
546
error = e;
552
'Should have next effect. This error is likely caused by a bug ' +
553
'in React. Please file an issue.',
554
);
474
- captureError(nextEffect, error);
555
+ onCommitPhaseError(nextEffect, error);
556
if (nextEffect !== null) {
557
nextEffect = nextEffect.nextEffect;
558
}
570
ReactFiberInstrumentation.debugTool.onCommitWork(finishedWork);
571
}
572
492
- // If we caught any errors during this commit, schedule their boundaries
493
- // to update.
494
- if (commitPhaseBoundaries) {
495
- commitPhaseBoundaries.forEach(scheduleErrorRecovery);
496
- commitPhaseBoundaries = null;
497
- }
498
-
499
- if (firstUncaughtError !== null) {
500
- const error = firstUncaughtError;
501
- firstUncaughtError = null;
502
- onUncaughtError(error);
503
- }
504
-
573
const remainingTime = root.current.expirationTime;
506
-
574
if (remainingTime === NoWork) {
508
- capturedErrors = null;
509
- failedBoundaries = null;
575
+ // If there's no remaining work, we can clear the set of already failed
576
+ // error boundaries.
577
+ legacyErrorBoundariesThatAlreadyFailed = null;
578
}
511
-
579
return remainingTime;
580
}
581
610
}
611
612
function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
613
+ // Attempt to complete the current unit of work, then move to the
614
+ // next sibling. If there are no more siblings, return to the
615
+ // parent fiber.
616
while (true) {
617
// The current, flushed, state of this fiber is the alternate.
618
// Ideally nothing should rely on this, but relying on it here
622
if (__DEV__) {
623
ReactDebugCurrentFiber.setCurrentFiber(workInProgress);
624
}
555
- const next = completeWork(
556
- current,
557
- workInProgress,
558
- nextRenderExpirationTime,
559
- );
560
- if (__DEV__) {
561
- ReactDebugCurrentFiber.resetCurrentFiber();
562
- }
625
626
const returnFiber = workInProgress.return;
627
const siblingFiber = workInProgress.sibling;
628
567
- resetExpirationTime(workInProgress, nextRenderExpirationTime);
568
-
569
- if (next !== null) {
629
+ if ((workInProgress.effectTag & Incomplete) === NoEffect) {
630
+ // This fiber completed.
631
+ let next = completeWork(
632
+ current,
633
+ workInProgress,
634
+ nextRenderExpirationTime,
635
+ );
636
stopWorkTimer(workInProgress);
637
+ resetExpirationTime(workInProgress, nextRenderExpirationTime);
638
+ if (__DEV__) {
639
+ ReactDebugCurrentFiber.resetCurrentFiber();
640
+ }
641
+
642
+ if (next !== null) {
643
+ stopWorkTimer(workInProgress);
644
+ if (__DEV__ && ReactFiberInstrumentation.debugTool) {
645
+ ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
646
+ }
647
+ // If completing this work spawned new work, do that next. We'll come
648
+ // back here again.
649
+ return next;
650
+ }
651
+
652
+ if (
653
+ returnFiber !== null &&
654
+ // Do not append effects to parents if a sibling failed to complete
655
+ (returnFiber.effectTag & Incomplete) === NoEffect
656
+ ) {
657
+ // Append all the effects of the subtree and this fiber onto the effect
658
+ // list of the parent. The completion order of the children affects the
659
+ // side-effect order.
660
+ if (returnFiber.firstEffect === null) {
661
+ returnFiber.firstEffect = workInProgress.firstEffect;
662
+ }
663
+ if (workInProgress.lastEffect !== null) {
664
+ if (returnFiber.lastEffect !== null) {
665
+ returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
666
+ }
667
+ returnFiber.lastEffect = workInProgress.lastEffect;
668
+ }
669
+
670
+ // If this fiber had side-effects, we append it AFTER the children's
671
+ // side-effects. We can perform certain side-effects earlier if
672
+ // needed, by doing multiple passes over the effect list. We don't want
673
+ // to schedule our own side-effect on our own list because if end up
674
+ // reusing children we'll schedule this effect onto itself since we're
675
+ // at the end.
676
+ const effectTag = workInProgress.effectTag;
677
+ // Skip both NoWork and PerformedWork tags when creating the effect list.
678
+ // PerformedWork effect is read by React DevTools but shouldn't be committed.
679
+ if (effectTag > PerformedWork) {
680
+ if (returnFiber.lastEffect !== null) {
681
+ returnFiber.lastEffect.nextEffect = workInProgress;
682
+ } else {
683
+ returnFiber.firstEffect = workInProgress;
684
+ }
685
+ returnFiber.lastEffect = workInProgress;
686
+ }
687
+ }
688
+
689
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
690
ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
691
}
574
- // If completing this work spawned new work, do that next. We'll come
575
- // back here again.
576
- return next;
577
- }
692
579
- if (returnFiber !== null) {
580
- // Append all the effects of the subtree and this fiber onto the effect
581
- // list of the parent. The completion order of the children affects the
582
- // side-effect order.
583
- if (returnFiber.firstEffect === null) {
584
- returnFiber.firstEffect = workInProgress.firstEffect;
693
+ if (siblingFiber !== null) {
694
+ // If there is more work to do in this returnFiber, do that next.
695
+ return siblingFiber;
696
+ } else if (returnFiber !== null) {
697
+ // If there's no more work in this returnFiber. Complete the returnFiber.
698
+ workInProgress = returnFiber;
699
+ continue;
700
+ } else {
701
+ // We've reached the root.
702
+ isRootReadyForCommit = true;
703
+ return null;
704
}
586
- if (workInProgress.lastEffect !== null) {
587
- if (returnFiber.lastEffect !== null) {
588
- returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
589
- }
590
- returnFiber.lastEffect = workInProgress.lastEffect;
705
+ } else {
706
+ // This fiber did not complete because something threw. Pop values off
707
+ // the stack without entering the complete phase. If this is a boundary,
708
+ // capture values if possible.
709
+ const next = unwindWork(workInProgress);
710
+ // Because this fiber did not complete, don't reset its expiration time.
711
+ if (workInProgress.effectTag & DidCapture) {
712
+ // Restarting an error boundary
713
+ stopFailedWorkTimer(workInProgress);
714
+ } else {
715
+ stopWorkTimer(workInProgress);
716
}
717
593
- // If this fiber had side-effects, we append it AFTER the children's
594
- // side-effects. We can perform certain side-effects earlier if
595
- // needed, by doing multiple passes over the effect list. We don't want
596
- // to schedule our own side-effect on our own list because if end up
597
- // reusing children we'll schedule this effect onto itself since we're
598
- // at the end.
599
- const effectTag = workInProgress.effectTag;
600
- // Skip both NoWork and PerformedWork tags when creating the effect list.
601
- // PerformedWork effect is read by React DevTools but shouldn't be committed.
602
- if (effectTag > PerformedWork) {
603
- if (returnFiber.lastEffect !== null) {
604
- returnFiber.lastEffect.nextEffect = workInProgress;
605
- } else {
606
- returnFiber.firstEffect = workInProgress;
718
+ if (__DEV__) {
719
+ ReactDebugCurrentFiber.resetCurrentFiber();
720
+ }
721
+
722
+ if (next !== null) {
723
+ stopWorkTimer(workInProgress);
724
+ if (__DEV__ && ReactFiberInstrumentation.debugTool) {
725
+ ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
726
}
608
- returnFiber.lastEffect = workInProgress;
727
+ // If completing this work spawned new work, do that next. We'll come
728
+ // back here again.
729
+ // Since we're restarting, remove anything that is not a host effect
730
+ // from the effect tag.
731
+ next.effectTag &= HostEffectMask;
732
+ return next;
733
}
610
- }
734
612
- stopWorkTimer(workInProgress);
613
- if (__DEV__ && ReactFiberInstrumentation.debugTool) {
614
- ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
615
- }
735
+ if (returnFiber !== null) {
736
+ // Mark the parent fiber as incomplete and clear its effect list.
737
+ returnFiber.firstEffect = returnFiber.lastEffect = null;
738
+ returnFiber.effectTag |= Incomplete;
739
+ }
740
617
- if (siblingFiber !== null) {
618
- // If there is more work to do in this returnFiber, do that next.
619
- return siblingFiber;
620
- } else if (returnFiber !== null) {
621
- // If there's no more work in this returnFiber. Complete the returnFiber.
622
- workInProgress = returnFiber;
623
- continue;
624
- } else {
625
- // We've reached the root.
626
- const root: FiberRoot = workInProgress.stateNode;
627
- root.isReadyForCommit = true;
628
- return null;
741
+ if (__DEV__ && ReactFiberInstrumentation.debugTool) {
742
+ ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
743
+ }
744
+
745
+ if (siblingFiber !== null) {
746
+ // If there is more work to do in this returnFiber, do that next.
747
+ return siblingFiber;
748
+ } else if (returnFiber !== null) {
749
+ // If there's no more work in this returnFiber. Complete the returnFiber.
750
+ workInProgress = returnFiber;
751
+ continue;
752
+ } else {
753
+ return null;
754
+ }
755
}
756
}
757
774
ReactDebugCurrentFiber.setCurrentFiber(workInProgress);
775
}
776
651
- let next = beginWork(current, workInProgress, nextRenderExpirationTime);
652
- if (__DEV__) {
653
- ReactDebugCurrentFiber.resetCurrentFiber();
654
- }
655
- if (__DEV__ && ReactFiberInstrumentation.debugTool) {
656
- ReactFiberInstrumentation.debugTool.onBeginWork(workInProgress);
777
+ if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
778
+ stashedWorkInProgressProperties = Object.assign({}, workInProgress);
779
}
780
+ let next = beginWork(current, workInProgress, nextRenderExpirationTime);
781
659
- if (next === null) {
660
- // If this doesn't spawn new work, complete the current work.
661
- next = completeUnitOfWork(workInProgress);
662
- }
663
-
664
- ReactCurrentOwner.current = null;
665
-
666
- return next;
667
- }
668
-
669
- function performFailedUnitOfWork(workInProgress: Fiber): Fiber | null {
670
- if (__DEV__) {
671
- ReactStrictModeWarnings.discardPendingWarnings();
672
- }
673
-
674
- // The current, flushed, state of this fiber is the alternate.
675
- // Ideally nothing should rely on this, but relying on it here
676
- // means that we don't need an additional field on the work in
677
- // progress.
678
- const current = workInProgress.alternate;
679
-
680
- // See if beginning this work spawns more work.
681
- startWorkTimer(workInProgress);
682
- if (__DEV__) {
683
- ReactDebugCurrentFiber.setCurrentFiber(workInProgress);
684
- }
685
- let next = beginFailedWork(
686
- current,
687
- workInProgress,
688
- nextRenderExpirationTime,
689
- );
782
if (__DEV__) {
783
ReactDebugCurrentFiber.resetCurrentFiber();
784
}
797
}
798
799
function workLoop(isAsync) {
708
- if (capturedErrors !== null) {
709
- // If there are unhandled errors, switch to the slow work loop.
710
- // TODO: How to avoid this check in the fast path? Maybe the renderer
711
- // could keep track of which roots have unhandled errors and call a
712
- // forked version of renderRoot.
713
- slowWorkLoopThatChecksForFailedWork(isAsync);
714
- return;
715
- }
800
if (!isAsync) {
801
// Flush all expired work.
802
while (nextUnitOfWork !== null) {
810
}
811
}
812
729
- function slowWorkLoopThatChecksForFailedWork(isAsync) {
730
- if (!isAsync) {
731
- // Flush all expired work.
732
- while (nextUnitOfWork !== null) {
733
- if (hasCapturedError(nextUnitOfWork)) {
734
- // Use a forked version of performUnitOfWork
735
- nextUnitOfWork = performFailedUnitOfWork(nextUnitOfWork);
736
- } else {
737
- nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
738
- }
739
- }
740
- } else {
741
- // Flush asynchronous work until the deadline runs out of time.
742
- while (nextUnitOfWork !== null && !shouldYield()) {
743
- if (hasCapturedError(nextUnitOfWork)) {
744
- // Use a forked version of performUnitOfWork
745
- nextUnitOfWork = performFailedUnitOfWork(nextUnitOfWork);
746
- } else {
747
- nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
748
- }
749
- }
750
- }
751
- }
752
-
753
- function renderRootCatchBlock(
754
- root: FiberRoot,
755
- failedWork: Fiber,
756
- boundary: Fiber,
757
- isAsync: boolean,
758
- ) {
759
- // We're going to restart the error boundary that captured the error.
760
- // Conceptually, we're unwinding the stack. We need to unwind the
761
- // context stack, too.
762
- unwindContexts(failedWork, boundary);
763
-
764
- // Restart the error boundary using a forked version of
765
- // performUnitOfWork that deletes the boundary's children. The entire
766
- // failed subree will be unmounted. During the commit phase, a special
767
- // lifecycle method is called on the error boundary, which triggers
768
- // a re-render.
769
- nextUnitOfWork = performFailedUnitOfWork(boundary);
770
-
771
- // Continue working.
772
- workLoop(isAsync);
773
- }
774
-
813
function renderRoot(
814
root: FiberRoot,
815
expirationTime: ExpirationTime,
822
);
823
isWorking = true;
824
787
- // We're about to mutate the work-in-progress tree. If the root was pending
788
- // commit, it no longer is: we'll need to complete it again.
789
- root.isReadyForCommit = false;
790
-
825
// Check if we're starting from a fresh stack, or if we're resuming from
826
// previously yielded work.
827
if (
794
- root !== nextRoot ||
828
expirationTime !== nextRenderExpirationTime ||
829
+ root !== nextRoot ||
830
nextUnitOfWork === null
831
) {
832
// Reset the stack and start working from the root.
836
nextUnitOfWork = createWorkInProgress(
837
nextRoot.current,
838
null,
805
- expirationTime,
839
+ nextRenderExpirationTime,
840
);
841
+ root.pendingCommitExpirationTime = NoWork;
842
}
843
844
+ let didFatal = false;
845
+
846
startWorkLoopTimer(nextUnitOfWork);
847
811
- let didError = false;
812
- let error = null;
813
- if (__DEV__) {
814
- invokeGuardedCallback(null, workLoop, null, isAsync);
815
- if (hasCaughtError()) {
816
- didError = true;
817
- error = clearCaughtError();
818
- }
819
- } else {
848
+ do {
849
try {
850
workLoop(isAsync);
822
- } catch (e) {
823
- didError = true;
824
- error = e;
825
- }
826
- }
827
-
828
- // An error was thrown during the render phase.
829
- while (didError) {
830
- if (didFatal) {
831
- // This was a fatal error. Don't attempt to recover from it.
832
- firstUncaughtError = error;
833
- break;
834
- }
835
-
836
- const failedWork = nextUnitOfWork;
837
- if (failedWork === null) {
838
- // An error was thrown but there's no current unit of work. This can
839
- // happen during the commit phase if there's a bug in the renderer.
840
- didFatal = true;
841
- continue;
842
- }
843
-
844
- // "Capture" the error by finding the nearest boundary. If there is no
845
- // error boundary, we use the root.
846
- const boundary = captureError(failedWork, error);
847
- invariant(
848
- boundary !== null,
849
- 'Should have found an error boundary. This error is likely ' +
850
- 'caused by a bug in React. Please file an issue.',
851
- );
852
-
853
- if (didFatal) {
854
- // The error we just captured was a fatal error. This happens
855
- // when the error propagates to the root more than once.
856
- continue;
857
- }
851
+ } catch (thrownValue) {
852
+ if (nextUnitOfWork === null) {
853
+ // This is a fatal error.
854
+ didFatal = true;
855
+ onUncaughtError(thrownValue);
856
+ break;
857
+ }
858
859
- didError = false;
860
- error = null;
861
- if (__DEV__) {
862
- invokeGuardedCallback(
863
- null,
864
- renderRootCatchBlock,
865
- null,
866
- root,
867
- failedWork,
868
- boundary,
869
- isAsync,
870
- );
871
- if (hasCaughtError()) {
872
- didError = true;
873
- error = clearCaughtError();
874
- continue;
859
+ if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
860
+ const failedUnitOfWork = nextUnitOfWork;
861
+ replayUnitOfWork(failedUnitOfWork, isAsync);
862
}
876
- } else {
877
- try {
878
- renderRootCatchBlock(root, failedWork, boundary, isAsync);
879
- error = null;
880
- } catch (e) {
881
- didError = true;
882
- error = e;
883
- continue;
863
+
864
+ const sourceFiber: Fiber = nextUnitOfWork;
865
+ let returnFiber = sourceFiber.return;
866
+ if (returnFiber === null) {
867
+ // This is a fatal error.
868
+ didFatal = true;
869
+ onUncaughtError(thrownValue);
870
+ break;
871
}
872
+ throwException(returnFiber, sourceFiber, thrownValue);
873
+ nextUnitOfWork = completeUnitOfWork(sourceFiber);
874
}
886
- // We're finished working. Exit the error loop.
875
break;
888
- }
889
-
890
- const uncaughtError = firstUncaughtError;
876
+ } while (true);
877
878
// We're done performing work. Time to clean up.
879
stopWorkLoopTimer(interruptedBy);
880
interruptedBy = null;
881
isWorking = false;
896
- didFatal = false;
897
- firstUncaughtError = null;
898
-
899
- if (uncaughtError !== null) {
900
- onUncaughtError(uncaughtError);
901
- }
902
-
903
- return root.isReadyForCommit ? root.current.alternate : null;
904
- }
905
-
906
- // Returns the boundary that captured the error, or null if the error is ignored
907
- function captureError(failedWork: Fiber, error: mixed): Fiber | null {
908
- // It is no longer valid because we exited the user code.
909
- ReactCurrentOwner.current = null;
910
- if (__DEV__) {
911
- ReactDebugCurrentFiber.resetCurrentFiber();
912
- }
913
-
914
- // Search for the nearest error boundary.
915
- let boundary: Fiber | null = null;
916
-
917
- // Passed to logCapturedError()
918
- let errorBoundaryFound: boolean = false;
919
- let willRetry: boolean = false;
920
- let errorBoundaryName: string | null = null;
921
-
922
- // Host containers are a special case. If the failed work itself is a host
923
- // container, then it acts as its own boundary. In all other cases, we
924
- // ignore the work itself and only search through the parents.
925
- if (failedWork.tag === HostRoot) {
926
- boundary = failedWork;
927
-
928
- if (isFailedBoundary(failedWork)) {
929
- // If this root already failed, there must have been an error when
930
- // attempting to unmount it. This is a worst-case scenario and
931
- // should only be possible if there's a bug in the renderer.
932
- didFatal = true;
933
- }
934
- } else {
935
- let node = failedWork.return;
936
- while (node !== null && boundary === null) {
937
- if (node.tag === ClassComponent) {
938
- const instance = node.stateNode;
939
- if (typeof instance.componentDidCatch === 'function') {
940
- errorBoundaryFound = true;
941
- errorBoundaryName = getComponentName(node);
942
-
943
- // Found an error boundary!
944
- boundary = node;
945
- willRetry = true;
946
- }
947
- } else if (node.tag === HostRoot) {
948
- // Treat the root like a no-op error boundary
949
- boundary = node;
950
- }
882
952
- if (isFailedBoundary(node)) {
953
- // This boundary is already in a failed state.
954
-
955
- // If we're currently unmounting, that means this error was
956
- // thrown while unmounting a failed subtree. We should ignore
957
- // the error.
958
- if (isUnmounting) {
959
- return null;
960
- }
961
-
962
- // If we're in the commit phase, we should check to see if
963
- // this boundary already captured an error during this commit.
964
- // This case exists because multiple errors can be thrown during
965
- // a single commit without interruption.
966
- if (
967
- commitPhaseBoundaries !== null &&
968
- (commitPhaseBoundaries.has(node) ||
969
- (node.alternate !== null &&
970
- commitPhaseBoundaries.has(node.alternate)))
971
- ) {
972
- // If so, we should ignore this error.
973
- return null;
974
- }
975
-
976
- // The error should propagate to the next boundary -— we keep looking.
977
- boundary = null;
978
- willRetry = false;
979
- }
980
-
981
- node = node.return;
982
- }
983
- }
984
-
985
- if (boundary !== null) {
986
- // Add to the collection of failed boundaries. This lets us know that
987
- // subsequent errors in this subtree should propagate to the next boundary.
988
- if (failedBoundaries === null) {
989
- failedBoundaries = new Set();
990
- }
991
- failedBoundaries.add(boundary);
992
-
993
- // This method is unsafe outside of the begin and complete phases.
994
- // We might be in the commit phase when an error is captured.
995
- // The risk is that the return path from this Fiber may not be accurate.
996
- // That risk is acceptable given the benefit of providing users more context.
997
- const componentStack = getStackAddendumByWorkInProgressFiber(failedWork);
998
- const componentName = getComponentName(failedWork);
999
-
1000
- // Add to the collection of captured errors. This is stored as a global
1001
- // map of errors and their component stack location keyed by the boundaries
1002
- // that capture them. We mostly use this Map as a Set; it's a Map only to
1003
- // avoid adding a field to Fiber to store the error.
1004
- if (capturedErrors === null) {
1005
- capturedErrors = new Map();
1006
- }
1007
-
1008
- const capturedError = {
1009
- componentName,
1010
- componentStack,
1011
- error,
1012
- errorBoundary: errorBoundaryFound ? boundary.stateNode : null,
1013
- errorBoundaryFound,
1014
- errorBoundaryName,
1015
- willRetry,
1016
- };
1017
-
1018
- capturedErrors.set(boundary, capturedError);
1019
-
1020
- try {
1021
- logCapturedError(capturedError);
1022
- } catch (e) {
1023
- // Prevent cycle if logCapturedError() throws.
1024
- // A cycle may still occur if logCapturedError renders a component that throws.
1025
- const suppressLogging = e && e.suppressReactErrorLogging;
1026
- if (!suppressLogging) {
1027
- console.error(e);
1028
- }
1029
- }
1030
-
1031
- // If we're in the commit phase, defer scheduling an update on the
1032
- // boundary until after the commit is complete
1033
- if (isCommitting) {
1034
- if (commitPhaseBoundaries === null) {
1035
- commitPhaseBoundaries = new Set();
1036
- }
1037
- commitPhaseBoundaries.add(boundary);
883
+ // Yield back to main thread.
884
+ if (didFatal) {
885
+ // There was a fatal error.
886
+ return null;
887
+ } else if (nextUnitOfWork === null) {
888
+ // We reached the root.
889
+ if (isRootReadyForCommit) {
890
+ // The root successfully completed. It's ready for commit.
891
+ root.pendingCommitExpirationTime = expirationTime;
892
+ const finishedWork = root.current.alternate;
893
+ return finishedWork;
894
} else {
1039
- // Otherwise, schedule an update now.
1040
- // TODO: Is this actually necessary during the render phase? Is it
1041
- // possible to unwind and continue rendering at the same priority,
1042
- // without corrupting internal state?
1043
- scheduleErrorRecovery(boundary);
895
+ // The root did not complete.
896
+ invariant(
897
+ false,
898
+ 'Expired work should have completed. This error is likely caused ' +
899
+ 'by a bug in React. Please file an issue.',
900
+ );
901
}
1045
- return boundary;
1046
- } else if (firstUncaughtError === null) {
1047
- // If no boundary is found, we'll need to throw the error
1048
- firstUncaughtError = error;
902
+ } else {
903
+ // There's more work to do, but we ran out of time. Yield back to
904
+ // the renderer.
905
+ return null;
906
}
1050
- return null;
907
}
908
1053
- function hasCapturedError(fiber: Fiber): boolean {
1054
- // TODO: capturedErrors should store the boundary instance, to avoid needing
1055
- // to check the alternate.
1056
- return (
1057
- capturedErrors !== null &&
1058
- (capturedErrors.has(fiber) ||
1059
- (fiber.alternate !== null && capturedErrors.has(fiber.alternate)))
1060
- );
909
+ function scheduleCapture(sourceFiber, boundaryFiber, value, expirationTime) {
910
+ // TODO: We only support dispatching errors.
911
+ const capturedValue = createCapturedValue(value, sourceFiber);
912
+ const update = {
913
+ expirationTime,
914
+ partialState: null,
915
+ callback: null,
916
+ isReplace: false,
917
+ isForced: false,
918
+ capturedValue,
919
+ next: null,
920
+ };
921
+ insertUpdateIntoFiber(boundaryFiber, update);
922
+ scheduleWork(boundaryFiber, expirationTime);
923
}
924
1063
- function isFailedBoundary(fiber: Fiber): boolean {
1064
- // TODO: failedBoundaries should store the boundary instance, to avoid
1065
- // needing to check the alternate.
1066
- return (
1067
- failedBoundaries !== null &&
1068
- (failedBoundaries.has(fiber) ||
1069
- (fiber.alternate !== null && failedBoundaries.has(fiber.alternate)))
1070
- );
1071
- }
1072
-
1073
- function commitErrorHandling(effectfulFiber: Fiber) {
1074
- let capturedError;
1075
- if (capturedErrors !== null) {
1076
- capturedError = capturedErrors.get(effectfulFiber);
1077
- capturedErrors.delete(effectfulFiber);
1078
- if (capturedError == null) {
1079
- if (effectfulFiber.alternate !== null) {
1080
- effectfulFiber = effectfulFiber.alternate;
1081
- capturedError = capturedErrors.get(effectfulFiber);
1082
- capturedErrors.delete(effectfulFiber);
1083
- }
1084
- }
1085
- }
1086
-
925
+ function dispatch(
926
+ sourceFiber: Fiber,
927
+ value: mixed,
928
+ expirationTime: ExpirationTime,
929
+ ) {
930
invariant(
1088
- capturedError != null,
1089
- 'No error for given unit of work. This error is likely caused by a ' +
1090
- 'bug in React. Please file an issue.',
931
+ !isWorking || isCommitting,
932
+ 'dispatch: Cannot dispatch during the render phase.',
933
);
934
1093
- switch (effectfulFiber.tag) {
1094
- case ClassComponent:
1095
- const instance = effectfulFiber.stateNode;
1096
-
1097
- const info: HandleErrorInfo = {
1098
- componentStack: capturedError.componentStack,
1099
- };
935
+ // TODO: Handle arrays
936
1101
- // Allow the boundary to handle the error, usually by scheduling
1102
- // an update to itself
1103
- instance.componentDidCatch(capturedError.error, info);
1104
- return;
1105
- case HostRoot:
1106
- if (firstUncaughtError === null) {
1107
- firstUncaughtError = capturedError.error;
1108
- }
1109
- return;
1110
- default:
1111
- invariant(
1112
- false,
1113
- 'Invalid type of work. This error is likely caused by a bug in ' +
1114
- 'React. Please file an issue.',
1115
- );
1116
- }
1117
- }
1118
-
1119
- function unwindContexts(from: Fiber, to: Fiber) {
1120
- let node = from;
1121
- while (node !== null) {
1122
- switch (node.tag) {
937
+ let fiber = sourceFiber.return;
938
+ while (fiber !== null) {
939
+ switch (fiber.tag) {
940
case ClassComponent:
1124
- popContextProvider(node);
1125
- break;
1126
- case HostComponent:
1127
- popHostContext(node);
941
+ const ctor = fiber.type;
942
+ const instance = fiber.stateNode;
943
+ if (
944
+ typeof ctor.getDerivedStateFromCatch === 'function' ||
945
+ (typeof instance.componentDidCatch === 'function' &&
946
+ !isAlreadyFailedLegacyErrorBoundary(instance))
947
+ ) {
948
+ scheduleCapture(sourceFiber, fiber, value, expirationTime);
949
+ return;
950
+ }
951
break;
952
+ // TODO: Handle async boundaries
953
case HostRoot:
1130
- popHostContainer(node);
1131
- break;
1132
- case HostPortal:
1133
- popHostContainer(node);
1134
- break;
1135
- case ContextProvider:
1136
- popProvider(node);
1137
- break;
1138
- }
1139
- if (node === to || node.alternate === to) {
1140
- stopFailedWorkTimer(node);
1141
- break;
1142
- } else {
1143
- stopWorkTimer(node);
954
+ scheduleCapture(sourceFiber, fiber, value, expirationTime);
955
+ return;
956
}
1145
- node = node.return;
957
+ fiber = fiber.return;
958
+ }
959
+
960
+ if (sourceFiber.tag === HostRoot) {
961
+ // Error was thrown at the root. There is no parent, so the root
962
+ // itself should capture it.
963
+ scheduleCapture(sourceFiber, sourceFiber, value, expirationTime);
964
}
965
}
966
1149
- function computeAsyncExpiration() {
967
+ function onCommitPhaseError(fiber: Fiber, error: mixed) {
968
+ return dispatch(fiber, error, Sync);
969
+ }
970
+
971
+ function computeAsyncExpiration(currentTime: ExpirationTime) {
972
// Given the current clock time, returns an expiration time. We use rounding
973
// to batch like updates together.
974
// Should complete within ~1000ms. 1200ms max.
1153
- const currentTime = recalculateCurrentTime();
1154
- const expirationMs = 1000;
1155
- const bucketSizeMs = 200;
975
+ const expirationMs = 5000;
976
+ const bucketSizeMs = 250;
977
return computeExpirationBucket(currentTime, expirationMs, bucketSizeMs);
978
}
979
1159
- function computeInteractiveExpiration() {
980
+ function computeInteractiveExpiration(currentTime: ExpirationTime) {
981
// Should complete within ~500ms. 600ms max.
1161
- const currentTime = recalculateCurrentTime();
982
const expirationMs = 500;
983
const bucketSizeMs = 100;
984
return computeExpirationBucket(currentTime, expirationMs, bucketSizeMs);
986
987
// Creates a unique async expiration time.
988
function computeUniqueAsyncExpiration(): ExpirationTime {
1169
- let result = computeAsyncExpiration();
989
+ const currentTime = recalculateCurrentTime();
990
+ let result = computeAsyncExpiration(currentTime);
991
if (result <= lastUniqueAsyncExpiration) {
992
// Since we assume the current time monotonically increases, we only hit
993
// this branch when computeUniqueAsyncExpiration is fired multiple times
1019
if (fiber.mode & AsyncMode) {
1020
if (isBatchingInteractiveUpdates) {
1021
// This is an interactive update
1201
- expirationTime = computeInteractiveExpiration();
1022
+ const currentTime = recalculateCurrentTime();
1023
+ expirationTime = computeInteractiveExpiration(currentTime);
1024
} else {
1025
// This is an async update
1204
- expirationTime = computeAsyncExpiration();
1026
+ const currentTime = recalculateCurrentTime();
1027
+ expirationTime = computeAsyncExpiration(currentTime);
1028
}
1029
} else {
1030
// This is a sync update
1049
return scheduleWorkImpl(fiber, expirationTime, false);
1050
}
1051
1229
- function checkRootNeedsClearing(
1230
- root: FiberRoot,
1231
- fiber: Fiber,
1232
- expirationTime: ExpirationTime,
1233
- ) {
1234
- if (
1235
- !isWorking &&
1236
- root === nextRoot &&
1237
- expirationTime < nextRenderExpirationTime
1238
- ) {
1239
- // Restart the root from the top.
1240
- if (nextUnitOfWork !== null) {
1241
- // This is an interruption. (Used for performance tracking.)
1242
- interruptedBy = fiber;
1243
- }
1244
- nextRoot = null;
1245
- nextUnitOfWork = null;
1246
- nextRenderExpirationTime = NoWork;
1247
- }
1248
- }
1249
-
1052
function scheduleWorkImpl(
1053
fiber: Fiber,
1054
expirationTime: ExpirationTime,
1084
if (node.return === null) {
1085
if (node.tag === HostRoot) {
1086
const root: FiberRoot = (node.stateNode: any);
1285
-
1286
- checkRootNeedsClearing(root, fiber, expirationTime);
1287
- requestWork(root, expirationTime);
1288
- checkRootNeedsClearing(root, fiber, expirationTime);
1087
+ if (
1088
+ !isWorking &&
1089
+ nextRenderExpirationTime !== NoWork &&
1090
+ expirationTime < nextRenderExpirationTime
1091
+ ) {
1092
+ // This is an interruption. (Used for performance tracking.)
1093
+ interruptedBy = fiber;
1094
+ resetContextStack();
1095
+ }
1096
+ if (nextRoot !== root || !isWorking) {
1097
+ requestWork(root, expirationTime);
1098
+ }
1099
+ if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
1100
+ invariant(
1101
+ false,
1102
+ 'Maximum update depth exceeded. This can happen when a ' +
1103
+ 'component repeatedly calls setState inside ' +
1104
+ 'componentWillUpdate or componentDidUpdate. React limits ' +
1105
+ 'the number of nested updates to prevent infinite loops.',
1106
+ );
1107
+ }
1108
} else {
1109
if (__DEV__) {
1110
if (!isErrorRecovery && fiber.tag === ClassComponent) {
1118
}
1119
}
1120
1302
- function scheduleErrorRecovery(fiber: Fiber) {
1303
- scheduleWorkImpl(fiber, Sync, true);
1304
- }
1305
-
1121
function recalculateCurrentTime(): ExpirationTime {
1122
// Subtract initial time so it fits inside 32bits
1308
- const ms = now() - startTime;
1309
- mostRecentCurrentTime = msToExpirationTime(ms);
1123
+ mostRecentCurrentTimeMs = now() - originalStartTimeMs;
1124
+ mostRecentCurrentTime = msToExpirationTime(mostRecentCurrentTimeMs);
1125
return mostRecentCurrentTime;
1126
}
1127
1128
function deferredUpdates<A>(fn: () => A): A {
1129
const previousExpirationContext = expirationContext;
1315
- expirationContext = computeAsyncExpiration();
1130
+ const currentTime = recalculateCurrentTime();
1131
+ expirationContext = computeAsyncExpiration(currentTime);
1132
try {
1133
return fn();
1134
} finally {
1135
expirationContext = previousExpirationContext;
1136
}
1137
}
1322
-
1138
function syncUpdates<A, B, C0, D, R>(
1139
fn: (A, B, C0, D) => R,
1140
a: A,
1198
}
1199
1200
// Compute a timeout for the given expiration time.
1386
- const currentMs = now() - startTime;
1201
+ const currentMs = now() - originalStartTimeMs;
1202
const expirationMs = expirationTimeToMs(expirationTime);
1203
const timeout = expirationMs - currentMs;
1204
1209
// requestWork is called by the scheduler whenever a root receives an update.
1210
// It's up to the renderer to call renderRoot at some point in the future.
1211
function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
1397
- if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
1398
- invariant(
1399
- false,
1400
- 'Maximum update depth exceeded. This can happen when a ' +
1401
- 'component repeatedly calls setState inside componentWillUpdate or ' +
1402
- 'componentDidUpdate. React limits the number of nested updates to ' +
1403
- 'prevent infinite loops.',
1404
- );
1212
+ addRootToSchedule(root, expirationTime);
1213
+
1214
+ if (isRendering) {
1215
+ // Prevent reentrancy. Remaining work will be scheduled at the end of
1216
+ // the currently rendering batch.
1217
+ return;
1218
}
1219
1220
+ if (isBatchingUpdates) {
1221
+ // Flush work at the end of the batch.
1222
+ if (isUnbatchingUpdates) {
1223
+ // ...unless we're inside unbatchedUpdates, in which case we should
1224
+ // flush it now.
1225
+ nextFlushedRoot = root;
1226
+ nextFlushedExpirationTime = Sync;
1227
+ performWorkOnRoot(root, Sync, false);
1228
+ }
1229
+ return;
1230
+ }
1231
+
1232
+ // TODO: Get rid of Sync and use current time?
1233
+ if (expirationTime === Sync) {
1234
+ performSyncWork();
1235
+ } else {
1236
+ scheduleCallbackWithExpiration(expirationTime);
1237
+ }
1238
+ }
1239
+
1240
+ function addRootToSchedule(root: FiberRoot, expirationTime: ExpirationTime) {
1241
// Add the root to the schedule.
1242
// Check if this root is already part of the schedule.
1243
if (root.nextScheduledRoot === null) {
1262
root.remainingExpirationTime = expirationTime;
1263
}
1264
}
1431
-
1432
- if (isRendering) {
1433
- // Prevent reentrancy. Remaining work will be scheduled at the end of
1434
- // the currently rendering batch.
1435
- return;
1436
- }
1437
-
1438
- if (isBatchingUpdates) {
1439
- if (isUnbatchingUpdates) {
1440
- // Flush work at the end of the batch.
1441
- // ...unless we're inside unbatchedUpdates, in which case we should
1442
- // flush it now.
1443
- nextFlushedRoot = root;
1444
- nextFlushedExpirationTime = Sync;
1445
- performWorkOnRoot(root, Sync, false);
1446
- }
1447
- return;
1448
- }
1449
-
1450
- // TODO: Get rid of Sync and use current time?
1451
- if (expirationTime === Sync) {
1452
- performSyncWork();
1453
- } else {
1454
- scheduleCallbackWithExpiration(expirationTime);
1455
- }
1265
}
1266
1267
function findHighestPriorityRoot() {
1268
let highestPriorityWork = NoWork;
1269
let highestPriorityRoot = null;
1461
-
1270
if (lastScheduledRoot !== null) {
1271
let previousScheduledRoot = lastScheduledRoot;
1272
let root = firstScheduledRoot;
1328
const previousFlushedRoot = nextFlushedRoot;
1329
if (
1330
previousFlushedRoot !== null &&
1523
- previousFlushedRoot === highestPriorityRoot
1331
+ previousFlushedRoot === highestPriorityRoot &&
1332
+ highestPriorityWork === Sync
1333
) {
1334
nestedUpdateCount++;
1335
} else {
1408
// Clean-up.
1409
deadline = null;
1410
deadlineDidExpire = false;
1602
- nestedUpdateCount = 0;
1411
1412
finishRendering();
1413
}
1426
}
1427
1428
function finishRendering() {
1429
+ nestedUpdateCount = 0;
1430
+
1431
if (completedBatches !== null) {
1432
const batches = completedBatches;
1433
completedBatches = null;
1549
return true;
1550
}
1551
1742
- // TODO: Not happy about this hook. Conceptually, renderRoot should return a
1743
- // tuple of (isReadyForCommit, didError, error)
1552
function onUncaughtError(error) {
1553
invariant(
1554
nextFlushedRoot !== null,
1665
}
1666
1667
return {
1668
+ recalculateCurrentTime,
1669
computeExpirationForFiber,
1670
scheduleWork,
1671
requestWork,