50
ScopeComponent,
51
Block,
52
OffscreenComponent,
53
+ LegacyHiddenComponent,
54
} from './ReactWorkTags';
55
import {
56
NoEffect,
80
import shallowEqual from 'shared/shallowEqual';
81
import getComponentName from 'shared/getComponentName';
82
import ReactStrictModeWarnings from './ReactStrictModeWarnings.new';
82
-import {REACT_LAZY_TYPE, getIteratorFn} from 'shared/ReactSymbols';
83
+import {
84
+ REACT_ELEMENT_TYPE,
85
+ REACT_LAZY_TYPE,
86
+ REACT_LEGACY_HIDDEN_TYPE,
87
+ getIteratorFn,
88
+} from 'shared/ReactSymbols';
89
import {
90
getCurrentFiberOwnerNameInDevOrNull,
91
setIsRendering,
193
renderDidSuspendDelayIfPossible,
194
markUnprocessedUpdateTime,
195
getWorkInProgressRoot,
196
+ pushRenderExpirationTime,
197
} from './ReactFiberWorkLoop.new';
198
199
import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev';
576
const nextProps: OffscreenProps = workInProgress.pendingProps;
577
const nextChildren = nextProps.children;
578
572
- if (current !== null) {
573
- if (nextProps.mode === 'hidden') {
574
- // TODO: Should currently be unreachable because Offscreen is only used as
575
- // an implementation detail of Suspense. Once this is a public API, it
576
- // will need to create an OffscreenState.
579
+ const prevState: OffscreenState | null =
580
+ current !== null ? current.memoizedState : null;
581
+
582
+ if (nextProps.mode === 'hidden') {
583
+ if (
584
+ !isSameExpirationTime(renderExpirationTime, (Never: ExpirationTimeOpaque))
585
+ ) {
586
+ let nextBaseTime;
587
+ if (prevState !== null) {
588
+ const prevBaseTime = prevState.baseTime;
589
+ nextBaseTime = !isSameOrHigherPriority(
590
+ prevBaseTime,
591
+ renderExpirationTime,
592
+ )
593
+ ? prevBaseTime
594
+ : renderExpirationTime;
595
+ } else {
596
+ nextBaseTime = renderExpirationTime;
597
+ }
598
+
599
+ // Schedule this fiber to re-render at offscreen priority. Then bailout.
600
+ if (enableSchedulerTracing) {
601
+ markSpawnedWork((Never: ExpirationTimeOpaque));
602
+ }
603
+ workInProgress.expirationTime_opaque = workInProgress.childExpirationTime_opaque = Never;
604
+ const nextState: OffscreenState = {
605
+ baseTime: nextBaseTime,
606
+ };
607
+ workInProgress.memoizedState = nextState;
608
+ // We're about to bail out, but we need to push this to the stack anyway
609
+ // to avoid a push/pop misalignment.
610
+ pushRenderExpirationTime(workInProgress, nextBaseTime);
611
+ return null;
612
} else {
578
- // Clear the offscreen state.
613
+ // Rendering at offscreen, so we can clear the base time.
614
+ const nextState: OffscreenState = {
615
+ baseTime: NoWork,
616
+ };
617
+ workInProgress.memoizedState = nextState;
618
+ pushRenderExpirationTime(workInProgress, renderExpirationTime);
619
+ }
620
+ } else {
621
+ let subtreeRenderTime;
622
+ if (prevState !== null) {
623
+ const baseTime = prevState.baseTime;
624
+ subtreeRenderTime = !isSameOrHigherPriority(
625
+ baseTime,
626
+ renderExpirationTime,
627
+ )
628
+ ? baseTime
629
+ : renderExpirationTime;
630
+
631
+ // Since we're not hidden anymore, reset the state
632
workInProgress.memoizedState = null;
633
+ } else {
634
+ // We weren't previously hidden, and we still aren't, so there's nothing
635
+ // special to do. Need to push to the stack regardless, though, to avoid
636
+ // a push/pop misalignment.
637
+ subtreeRenderTime = renderExpirationTime;
638
}
639
+ pushRenderExpirationTime(workInProgress, subtreeRenderTime);
640
}
641
642
reconcileChildren(
648
return workInProgress.child;
649
}
650
651
+// Note: These happen to have identical begin phases, for now. We shouldn't hold
652
+// ourselves to this constraint, though. If the behavior diverges, we should
653
+// fork the function.
654
+const updateLegacyHiddenComponent = updateOffscreenComponent;
655
+
656
function updateFragment(
657
current: Fiber | null,
658
workInProgress: Fiber,
1202
1203
markRef(current, workInProgress);
1204
1141
- // Check the host config to see if the children are offscreen/hidden.
1205
if (
1143
- workInProgress.mode & ConcurrentMode &&
1144
- !isSameExpirationTime(
1145
- renderExpirationTime,
1146
- (Never: ExpirationTimeOpaque),
1147
- ) &&
1148
- shouldDeprioritizeSubtree(type, nextProps)
1206
+ (workInProgress.mode & ConcurrentMode) !== NoMode &&
1207
+ nextProps.hasOwnProperty('hidden')
1208
) {
1150
- if (enableSchedulerTracing) {
1151
- markSpawnedWork((Never: ExpirationTimeOpaque));
1152
- }
1153
- // Schedule this fiber to re-render at offscreen priority. Then bailout.
1154
- workInProgress.expirationTime_opaque = workInProgress.childExpirationTime_opaque = Never;
1155
- return null;
1209
+ const wrappedChildren = {
1210
+ $$typeof: REACT_ELEMENT_TYPE,
1211
+ type: REACT_LEGACY_HIDDEN_TYPE,
1212
+ key: null,
1213
+ ref: null,
1214
+ props: {
1215
+ children: nextChildren,
1216
+ // Check the host config to see if the children are offscreen/hidden.
1217
+ mode: shouldDeprioritizeSubtree(type, nextProps) ? 'hidden' : 'visible',
1218
+ },
1219
+ _owner: __DEV__ ? {} : null,
1220
+ };
1221
+ nextChildren = wrappedChildren;
1222
}
1223
1224
reconcileChildren(
1717
}
1718
}
1719
1654
-function mountSuspenseState(
1720
+const SUSPENDED_MARKER: SuspenseState = {
1721
+ dehydrated: null,
1722
+ retryTime: NoWork,
1723
+};
1724
+
1725
+function mountSuspenseOffscreenState(
1726
renderExpirationTime: ExpirationTimeOpaque,
1656
-): SuspenseState {
1727
+): OffscreenState {
1728
return {
1658
- dehydrated: null,
1729
baseTime: renderExpirationTime,
1660
- retryTime: NoWork,
1730
};
1731
}
1732
1664
-function updateSuspenseState(
1665
- prevSuspenseState: SuspenseState,
1733
+function updateSuspenseOffscreenState(
1734
+ prevOffscreenState: OffscreenState,
1735
renderExpirationTime: ExpirationTimeOpaque,
1667
-): SuspenseState {
1668
- const prevSuspendedTime = prevSuspenseState.baseTime;
1736
+): OffscreenState {
1737
+ const prevBaseTime = prevOffscreenState.baseTime;
1738
return {
1670
- dehydrated: null,
1739
+ // Choose whichever time is inclusive of the other one. This represents
1740
+ // the union of all the levels that suspended.
1741
baseTime:
1672
- // Choose whichever time is inclusive of the other one. This represents
1673
- // the union of all the levels that suspended.
1674
- !isSameExpirationTime(
1675
- prevSuspendedTime,
1676
- (NoWork: ExpirationTimeOpaque),
1677
- ) && !isSameOrHigherPriority(prevSuspendedTime, renderExpirationTime)
1678
- ? prevSuspendedTime
1742
+ !isSameExpirationTime(prevBaseTime, (NoWork: ExpirationTimeOpaque)) &&
1743
+ !isSameOrHigherPriority(prevBaseTime, renderExpirationTime)
1744
+ ? prevBaseTime
1745
: renderExpirationTime,
1680
- retryTime: NoWork,
1746
};
1747
}
1748
1757
// For example, SuspenseList coordinates when nested content appears.
1758
if (current !== null) {
1759
const suspenseState: SuspenseState = current.memoizedState;
1695
- if (suspenseState !== null) {
1696
- // Currently showing a fallback. If the current render includes
1697
- // the level that triggered the fallback, we must continue showing it,
1698
- // regardless of what the Suspense context says.
1699
- const baseTime = suspenseState.baseTime;
1700
- if (
1701
- !isSameExpirationTime(baseTime, (NoWork: ExpirationTimeOpaque)) &&
1702
- !isSameOrHigherPriority(baseTime, renderExpirationTime)
1703
- ) {
1704
- return true;
1705
- }
1706
- // Otherwise, fall through to check the Suspense context.
1707
- } else {
1760
+ if (suspenseState === null) {
1761
// Currently showing content. Don't hide it, even if ForceSuspenseFallack
1762
// is true. More precise name might be "ForceRemainSuspenseFallback".
1763
// Note: This is a factoring smell. Can't remain on a fallback if there's
1765
return false;
1766
}
1767
}
1768
+
1769
// Not currently showing content. Consult the Suspense context.
1770
return hasSuspenseContext(
1771
suspenseContext,
1779
renderExpirationTime,
1780
) {
1781
const currentChildExpirationTime = current.childExpirationTime_opaque;
1728
- const currentSuspenseState: SuspenseState = current.memoizedState;
1729
- if (currentSuspenseState !== null) {
1730
- // This boundary already timed out. Check if this render includes the level
1731
- // that previously suspended.
1732
- const baseTime = currentSuspenseState.baseTime;
1733
- if (
1734
- !isSameExpirationTime(baseTime, (NoWork: ExpirationTimeOpaque)) &&
1735
- !isSameOrHigherPriority(baseTime, renderExpirationTime)
1736
- ) {
1737
- // There's pending work at a lower level that might now be unblocked.
1738
- return baseTime;
1739
- }
1740
- }
1741
-
1782
if (
1783
!isSameOrHigherPriority(currentChildExpirationTime, renderExpirationTime)
1784
) {
1920
renderExpirationTime,
1921
);
1922
const primaryChildFragment: Fiber = (workInProgress.child: any);
1883
- primaryChildFragment.memoizedState = ({baseTime: NoWork}: OffscreenState);
1884
- workInProgress.memoizedState = mountSuspenseState(renderExpirationTime);
1923
+ primaryChildFragment.memoizedState = mountSuspenseOffscreenState(
1924
+ renderExpirationTime,
1925
+ );
1926
+ workInProgress.memoizedState = SUSPENDED_MARKER;
1927
return fallbackFragment;
1928
} else {
1929
const nextPrimaryChildren = nextProps.children;
1977
renderExpirationTime,
1978
);
1979
const primaryChildFragment: Fiber = (workInProgress.child: any);
1938
- primaryChildFragment.memoizedState = ({
1939
- baseTime: NoWork,
1940
- }: OffscreenState);
1941
- workInProgress.memoizedState = updateSuspenseState(
1942
- current.memoizedState,
1980
+ primaryChildFragment.memoizedState = mountSuspenseOffscreenState(
1981
renderExpirationTime,
1982
);
1945
-
1983
+ workInProgress.memoizedState = SUSPENDED_MARKER;
1984
return fallbackChildFragment;
1985
}
1986
}
1997
renderExpirationTime,
1998
);
1999
const primaryChildFragment: Fiber = (workInProgress.child: any);
1962
- primaryChildFragment.memoizedState = ({
1963
- baseTime: NoWork,
1964
- }: OffscreenState);
2000
+ const prevOffscreenState: OffscreenState | null = (current.child: any)
2001
+ .memoizedState;
2002
+ primaryChildFragment.memoizedState =
2003
+ prevOffscreenState === null
2004
+ ? mountSuspenseOffscreenState(renderExpirationTime)
2005
+ : updateSuspenseOffscreenState(
2006
+ prevOffscreenState,
2007
+ renderExpirationTime,
2008
+ );
2009
primaryChildFragment.childExpirationTime_opaque = getRemainingWorkInPrimaryTree(
2010
current,
2011
workInProgress,
2012
renderExpirationTime,
2013
);
1970
- workInProgress.memoizedState = updateSuspenseState(
1971
- current.memoizedState,
1972
- renderExpirationTime,
1973
- );
2014
+ workInProgress.memoizedState = SUSPENDED_MARKER;
2015
return fallbackChildFragment;
2016
} else {
2017
const nextPrimaryChildren = nextProps.children;
2038
renderExpirationTime,
2039
);
2040
const primaryChildFragment: Fiber = (workInProgress.child: any);
2000
- primaryChildFragment.memoizedState = ({
2001
- baseTime: NoWork,
2002
- }: OffscreenState);
2041
+ const prevOffscreenState: OffscreenState | null = (current.child: any)
2042
+ .memoizedState;
2043
+ primaryChildFragment.memoizedState =
2044
+ prevOffscreenState === null
2045
+ ? mountSuspenseOffscreenState(renderExpirationTime)
2046
+ : updateSuspenseOffscreenState(
2047
+ prevOffscreenState,
2048
+ renderExpirationTime,
2049
+ );
2050
primaryChildFragment.childExpirationTime_opaque = getRemainingWorkInPrimaryTree(
2051
current,
2052
workInProgress,
2054
);
2055
// Skip the primary children, and continue working on the
2056
// fallback children.
2010
- workInProgress.memoizedState = mountSuspenseState(renderExpirationTime);
2057
+ workInProgress.memoizedState = SUSPENDED_MARKER;
2058
return fallbackChildFragment;
2059
} else {
2060
// Still haven't timed out. Continue rendering the children, like we
3262
break;
3263
case HostComponent:
3264
pushHostContext(workInProgress);
3218
- if (
3219
- workInProgress.mode & ConcurrentMode &&
3220
- !isSameExpirationTime(
3221
- renderExpirationTime,
3222
- (Never: ExpirationTimeOpaque),
3223
- ) &&
3224
- shouldDeprioritizeSubtree(workInProgress.type, newProps)
3225
- ) {
3226
- if (enableSchedulerTracing) {
3227
- markSpawnedWork((Never: ExpirationTimeOpaque));
3228
- }
3229
- // Schedule this fiber to re-render at offscreen priority. Then bailout.
3230
- workInProgress.expirationTime_opaque = workInProgress.childExpirationTime_opaque = Never;
3231
- return null;
3232
- }
3265
break;
3266
case ClassComponent: {
3267
const Component = workInProgress.type;
3416
return null;
3417
}
3418
}
3419
+ case OffscreenComponent:
3420
+ case LegacyHiddenComponent: {
3421
+ // Need to check if the tree still needs to be deferred. This is
3422
+ // almost identical to the logic used in the normal update path,
3423
+ // so we'll just enter that. The only difference is we'll bail out
3424
+ // at the next level instead of this one, because the child props
3425
+ // have not changed. Which is fine.
3426
+ // TODO: Probably should refactor `beginWork` to split the bailout
3427
+ // path from the normal path. I'm tempted to do a labeled break here
3428
+ // but I won't :)
3429
+ workInProgress.expirationTime_opaque = NoWork;
3430
+ return updateOffscreenComponent(
3431
+ current,
3432
+ workInProgress,
3433
+ renderExpirationTime,
3434
+ );
3435
+ }
3436
}
3437
return bailoutOnAlreadyFinishedWork(
3438
current,
3585
renderExpirationTime,
3586
);
3587
}
3539
- case OffscreenComponent: {
3540
- return updateOffscreenComponent(
3541
- current,
3542
- workInProgress,
3543
- renderExpirationTime,
3544
- );
3545
- }
3588
case SimpleMemoComponent: {
3589
return updateSimpleMemoComponent(
3590
current,
3651
}
3652
break;
3653
}
3654
+ case OffscreenComponent: {
3655
+ return updateOffscreenComponent(
3656
+ current,
3657
+ workInProgress,
3658
+ renderExpirationTime,
3659
+ );
3660
+ }
3661
+ case LegacyHiddenComponent: {
3662
+ return updateLegacyHiddenComponent(
3663
+ current,
3664
+ workInProgress,
3665
+ renderExpirationTime,
3666
+ );
3667
+ }
3668
}
3669
invariant(
3670
false,