426
// variable from the one for renders because the commit phase may run
427
// concurrently to a render phase.
428
let didIncludeCommitPhaseUpdate: boolean = false;
429
-
429
// The most recent time we either committed a fallback, or when a fallback was
430
// filled in with the resolved UI. This lets us throttle the appearance of new
431
// content as it streams in, to minimize jank.
616
617
let legacyErrorBoundariesThatAlreadyFailed: Set<mixed> | null = null;
618
620
-let rootWithPendingPassiveEffects: FiberRoot | null = null;
621
-let pendingPassiveEffectsLanes: Lanes = NoLanes;
622
-let pendingPassiveEffectsRemainingLanes: Lanes = NoLanes;
623
-let pendingPassiveEffectsRenderEndTime: number = -0; // Profiling-only
619
+type SuspendedCommitReason = 0 | 1 | 2;
620
+const IMMEDIATE_COMMIT = 0;
621
+const SUSPENDED_COMMIT = 1;
622
+const THROTTLED_COMMIT = 2;
623
+
624
+const NO_PENDING_EFFECTS = 0;
625
+const PENDING_MUTATION_PHASE = 1;
626
+const PENDING_LAYOUT_PHASE = 2;
627
+const PENDING_PASSIVE_PHASE = 3;
628
+let pendingEffectsStatus: 0 | 1 | 2 | 3 = 0;
629
+let pendingEffectsRoot: FiberRoot = (null: any);
630
+let pendingFinishedWork: Fiber = (null: any);
631
+let pendingEffectsLanes: Lanes = NoLanes;
632
+let pendingEffectsRemainingLanes: Lanes = NoLanes;
633
+let pendingEffectsRenderEndTime: number = -0; // Profiling-only
634
let pendingPassiveTransitions: Array<Transition> | null = null;
635
+let pendingRecoverableErrors: null | Array<CapturedValue<mixed>> = null;
636
+let pendingDidIncludeRenderPhaseUpdate: boolean = false;
637
+let pendingSuspendedCommitReason: SuspendedCommitReason = IMMEDIATE_COMMIT; // Profiling-only
638
639
// Use these to prevent an infinite loop of nested updates
640
const NESTED_UPDATE_LIMIT = 50;
657
return workInProgressRootRenderLanes;
658
}
659
660
+export function hasPendingCommitEffects(): boolean {
661
+ return (
662
+ pendingEffectsStatus !== NO_PENDING_EFFECTS &&
663
+ pendingEffectsStatus !== PENDING_PASSIVE_PHASE
664
+ );
665
+}
666
+
667
export function getRootWithPendingPassiveEffects(): FiberRoot | null {
648
- return rootWithPendingPassiveEffects;
668
+ return pendingEffectsStatus === PENDING_PASSIVE_PHASE
669
+ ? pendingEffectsRoot
670
+ : null;
671
}
672
673
export function getPendingPassiveEffectsLanes(): Lanes {
652
- return pendingPassiveEffectsLanes;
674
+ return pendingEffectsLanes;
675
}
676
677
export function isWorkLoopSuspendedOnData(): boolean {
1644
// In legacy mode, we flush pending passive effects at the beginning of the
1645
// next event, not at the end of the previous one.
1646
if (
1625
- rootWithPendingPassiveEffects !== null &&
1647
+ pendingEffectsStatus !== NO_PENDING_EFFECTS &&
1648
!disableLegacyMode &&
1627
- rootWithPendingPassiveEffects.tag === LegacyRoot &&
1649
+ pendingEffectsRoot.tag === LegacyRoot &&
1650
(executionContext & (RenderContext | CommitContext)) === NoContext
1651
) {
1630
- flushPassiveEffects();
1652
+ flushPendingEffects();
1653
}
1654
1655
const prevExecutionContext = executionContext;
3142
workInProgress = null;
3143
}
3144
3123
-type SuspendedCommitReason = 0 | 1 | 2;
3124
-const IMMEDIATE_COMMIT = 0;
3125
-const SUSPENDED_COMMIT = 1;
3126
-const THROTTLED_COMMIT = 2;
3127
-
3145
function commitRoot(
3146
root: FiberRoot,
3147
finishedWork: null | Fiber,
3166
// no more pending effects.
3167
// TODO: Might be better if `flushPassiveEffects` did not automatically
3168
// flush synchronous work at the end, to avoid factoring hazards like this.
3152
- flushPassiveEffects();
3153
- } while (rootWithPendingPassiveEffects !== null);
3169
+ flushPendingEffects();
3170
+ } while (pendingEffectsStatus !== NO_PENDING_EFFECTS);
3171
flushRenderPhaseStrictModeWarningsInDEV();
3172
3173
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
3260
// times out.
3261
}
3262
3263
+ // workInProgressX might be overwritten, so we want
3264
+ // to store it in pendingPassiveX until they get processed
3265
+ // We need to pass this through as an argument to commitRoot
3266
+ // because workInProgressX might have changed between
3267
+ // the previous render and commit if we throttle the commit
3268
+ // with setTimeout
3269
+ pendingFinishedWork = finishedWork;
3270
+ pendingEffectsRoot = root;
3271
+ pendingEffectsLanes = lanes;
3272
+ pendingEffectsRemainingLanes = remainingLanes;
3273
+ pendingPassiveTransitions = transitions;
3274
+ pendingRecoverableErrors = recoverableErrors;
3275
+ pendingDidIncludeRenderPhaseUpdate = didIncludeRenderPhaseUpdate;
3276
+ if (enableProfilerTimer) {
3277
+ pendingEffectsRenderEndTime = completedRenderEndTime;
3278
+ pendingSuspendedCommitReason = suspendedCommitReason;
3279
+ }
3280
+
3281
// If there are pending passive effects, schedule a callback to process them.
3282
// Do this as early as possible, so it is queued before anything else that
3283
// might get scheduled in the commit phase. (See #16714.)
3291
(finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
3292
(finishedWork.flags & PassiveMask) !== NoFlags
3293
) {
3259
- pendingPassiveEffectsRemainingLanes = remainingLanes;
3260
- pendingPassiveEffectsRenderEndTime = completedRenderEndTime;
3261
- // workInProgressTransitions might be overwritten, so we want
3262
- // to store it in pendingPassiveTransitions until they get processed
3263
- // We need to pass this through as an argument to commitRoot
3264
- // because workInProgressTransitions might have changed between
3265
- // the previous render and commit if we throttle the commit
3266
- // with setTimeout
3267
- pendingPassiveTransitions = transitions;
3294
if (enableYieldingBeforePassive) {
3295
// We don't schedule a separate task for flushing passive effects.
3296
// Instead, we just rely on ensureRootIsScheduled below to schedule
3367
ReactSharedInternals.T = prevTransition;
3368
}
3369
}
3344
- flushMutationEffects(root, finishedWork, lanes);
3345
- flushLayoutEffects(
3346
- root,
3347
- finishedWork,
3348
- lanes,
3349
- recoverableErrors,
3350
- didIncludeRenderPhaseUpdate,
3351
- suspendedCommitReason,
3352
- completedRenderEndTime,
3353
- );
3370
+ pendingEffectsStatus = PENDING_MUTATION_PHASE;
3371
+ flushMutationEffects();
3372
+ flushLayoutEffects();
3373
}
3374
3356
-function flushMutationEffects(
3357
- root: FiberRoot,
3358
- finishedWork: Fiber,
3359
- lanes: Lanes,
3360
-): void {
3375
+function flushMutationEffects(): void {
3376
+ if (pendingEffectsStatus !== PENDING_MUTATION_PHASE) {
3377
+ return;
3378
+ }
3379
+ pendingEffectsStatus = NO_PENDING_EFFECTS;
3380
+
3381
+ const root = pendingEffectsRoot;
3382
+ const finishedWork = pendingFinishedWork;
3383
+ const lanes = pendingEffectsLanes;
3384
const subtreeMutationHasEffects =
3385
(finishedWork.subtreeFlags & MutationMask) !== NoFlags;
3386
const rootMutationHasEffect = (finishedWork.flags & MutationMask) !== NoFlags;
3415
// componentWillUnmount, but before the layout phase, so that the finished
3416
// work is current during componentDidMount/Update.
3417
root.current = finishedWork;
3418
+ pendingEffectsStatus = PENDING_LAYOUT_PHASE;
3419
}
3420
3397
-function flushLayoutEffects(
3398
- root: FiberRoot,
3399
- finishedWork: Fiber,
3400
- lanes: Lanes,
3401
- recoverableErrors: null | Array<CapturedValue<mixed>>,
3402
- didIncludeRenderPhaseUpdate: boolean,
3403
- suspendedCommitReason: SuspendedCommitReason, // Profiling-only
3404
- completedRenderEndTime: number, // Profiling-only
3405
-): void {
3421
+function flushLayoutEffects(): void {
3422
+ if (pendingEffectsStatus !== PENDING_LAYOUT_PHASE) {
3423
+ return;
3424
+ }
3425
+ pendingEffectsStatus = NO_PENDING_EFFECTS;
3426
+
3427
+ const root = pendingEffectsRoot;
3428
+ const finishedWork = pendingFinishedWork;
3429
+ const lanes = pendingEffectsLanes;
3430
+ const completedRenderEndTime = pendingEffectsRenderEndTime;
3431
+ const recoverableErrors = pendingRecoverableErrors;
3432
+ const didIncludeRenderPhaseUpdate = pendingDidIncludeRenderPhaseUpdate;
3433
+ const suspendedCommitReason = pendingSuspendedCommitReason;
3434
+
3435
const subtreeHasLayoutEffects =
3436
(finishedWork.subtreeFlags & LayoutMask) !== NoFlags;
3437
const rootHasLayoutEffect = (finishedWork.flags & LayoutMask) !== NoFlags;
3485
(finishedWork.flags & PassiveMask) !== NoFlags;
3486
3487
if (rootDidHavePassiveEffects) {
3459
- // This commit has passive effects. Stash a reference to them. But don't
3460
- // schedule a callback until after flushing layout work.
3461
- rootWithPendingPassiveEffects = root;
3462
- pendingPassiveEffectsLanes = lanes;
3488
+ pendingEffectsStatus = PENDING_PASSIVE_PHASE;
3489
} else {
3490
+ pendingEffectsStatus = NO_PENDING_EFFECTS;
3491
+ pendingEffectsRoot = (null: any); // Clear for GC purposes.
3492
// There were no passive effects, so we can immediately release the cache
3493
// pool for this render.
3494
releaseRootPooledCache(root, root.pendingLanes);
3574
// currently schedule the callback in multiple places, will wait until those
3575
// are consolidated.
3576
if (
3549
- includesSyncLane(pendingPassiveEffectsLanes) &&
3577
+ includesSyncLane(pendingEffectsLanes) &&
3578
(disableLegacyMode || root.tag !== LegacyRoot)
3579
) {
3552
- flushPassiveEffects();
3580
+ flushPendingEffects();
3581
}
3582
3583
// Always call this before exiting `commitRoot`, to ensure that any
3694
}
3695
}
3696
3669
-export function flushPassiveEffects(wasDelayedCommit?: boolean): boolean {
3697
+export function flushPendingEffects(wasDelayedCommit?: boolean): boolean {
3698
// Returns whether passive effects were flushed.
3671
- // TODO: Combine this check with the one in flushPassiveEFfectsImpl. We should
3672
- // probably just combine the two functions. I believe they were only separate
3699
+ flushMutationEffects();
3700
+ flushLayoutEffects();
3701
+ return flushPassiveEffects(wasDelayedCommit);
3702
+}
3703
+
3704
+function flushPassiveEffects(wasDelayedCommit?: boolean): boolean {
3705
+ if (pendingEffectsStatus !== PENDING_PASSIVE_PHASE) {
3706
+ return false;
3707
+ }
3708
+ // TODO: Merge flushPassiveEffectsImpl into this function. I believe they were only separate
3709
// in the first place because we used to wrap it with
3710
// `Scheduler.runWithPriority`, which accepts a function. But now we track the
3711
// priority within React itself, so we can mutate the variable directly.
3676
- if (rootWithPendingPassiveEffects !== null) {
3677
- // Cache the root since rootWithPendingPassiveEffects is cleared in
3678
- // flushPassiveEffectsImpl
3679
- const root = rootWithPendingPassiveEffects;
3680
- // Cache and clear the remaining lanes flag; it must be reset since this
3681
- // method can be called from various places, not always from commitRoot
3682
- // where the remaining lanes are known
3683
- const remainingLanes = pendingPassiveEffectsRemainingLanes;
3684
- pendingPassiveEffectsRemainingLanes = NoLanes;
3685
-
3686
- const renderPriority = lanesToEventPriority(pendingPassiveEffectsLanes);
3687
- const priority = lowerEventPriority(DefaultEventPriority, renderPriority);
3688
- const prevTransition = ReactSharedInternals.T;
3689
- const previousPriority = getCurrentUpdatePriority();
3712
+ // Cache the root since pendingEffectsRoot is cleared in
3713
+ // flushPassiveEffectsImpl
3714
+ const root = pendingEffectsRoot;
3715
+ // Cache and clear the remaining lanes flag; it must be reset since this
3716
+ // method can be called from various places, not always from commitRoot
3717
+ // where the remaining lanes are known
3718
+ const remainingLanes = pendingEffectsRemainingLanes;
3719
+ pendingEffectsRemainingLanes = NoLanes;
3720
+
3721
+ const renderPriority = lanesToEventPriority(pendingEffectsLanes);
3722
+ const priority = lowerEventPriority(DefaultEventPriority, renderPriority);
3723
+ const prevTransition = ReactSharedInternals.T;
3724
+ const previousPriority = getCurrentUpdatePriority();
3725
3691
- try {
3692
- setCurrentUpdatePriority(priority);
3693
- ReactSharedInternals.T = null;
3694
- return flushPassiveEffectsImpl(wasDelayedCommit);
3695
- } finally {
3696
- setCurrentUpdatePriority(previousPriority);
3697
- ReactSharedInternals.T = prevTransition;
3726
+ try {
3727
+ setCurrentUpdatePriority(priority);
3728
+ ReactSharedInternals.T = null;
3729
+ return flushPassiveEffectsImpl(wasDelayedCommit);
3730
+ } finally {
3731
+ setCurrentUpdatePriority(previousPriority);
3732
+ ReactSharedInternals.T = prevTransition;
3733
3699
- // Once passive effects have run for the tree - giving components a
3700
- // chance to retain cache instances they use - release the pooled
3701
- // cache at the root (if there is one)
3702
- releaseRootPooledCache(root, remainingLanes);
3703
- }
3734
+ // Once passive effects have run for the tree - giving components a
3735
+ // chance to retain cache instances they use - release the pooled
3736
+ // cache at the root (if there is one)
3737
+ releaseRootPooledCache(root, remainingLanes);
3738
}
3705
- return false;
3739
}
3740
3741
function flushPassiveEffectsImpl(wasDelayedCommit: void | boolean) {
3709
- if (rootWithPendingPassiveEffects === null) {
3710
- return false;
3711
- }
3712
-
3742
// Cache and clear the transitions flag
3743
const transitions = pendingPassiveTransitions;
3744
pendingPassiveTransitions = null;
3745
3717
- const root = rootWithPendingPassiveEffects;
3718
- const lanes = pendingPassiveEffectsLanes;
3719
- rootWithPendingPassiveEffects = null;
3720
- // TODO: This is sometimes out of sync with rootWithPendingPassiveEffects.
3746
+ const root = pendingEffectsRoot;
3747
+ const lanes = pendingEffectsLanes;
3748
+ pendingEffectsStatus = NO_PENDING_EFFECTS;
3749
+ pendingEffectsRoot = (null: any); // Clear for GC purposes.
3750
+ // TODO: This is sometimes out of sync with pendingEffectsRoot.
3751
// Figure out why and fix it. It's not causing any known issues (probably
3752
// because it's only used for profiling), but it's a refactor hazard.
3723
- pendingPassiveEffectsLanes = NoLanes;
3753
+ pendingEffectsLanes = NoLanes;
3754
3755
if (enableYieldingBeforePassive) {
3756
// We've finished our work for this render pass.
3797
root.current,
3798
lanes,
3799
transitions,
3770
- pendingPassiveEffectsRenderEndTime,
3800
+ pendingEffectsRenderEndTime,
3801
);
3802
3803
if (enableSchedulingProfiler) {