8
8
*/
9
9
10
10
import type { Fiber } from './ReactInternalTypes' ;
11
- import type { Lanes } from './ReactFiberLane' ;
11
+ import type { Lanes , Lane } from './ReactFiberLane' ;
12
12
import type {
13
13
ReactFundamentalComponentInstance ,
14
14
ReactScopeInstance ,
@@ -58,14 +58,20 @@ import {
58
58
OffscreenComponent ,
59
59
LegacyHiddenComponent ,
60
60
} from './ReactWorkTags' ;
61
- import { NoMode , BlockingMode , ProfileMode } from './ReactTypeOfMode' ;
61
+ import {
62
+ NoMode ,
63
+ BlockingMode ,
64
+ ConcurrentMode ,
65
+ ProfileMode ,
66
+ } from './ReactTypeOfMode' ;
62
67
import {
63
68
Ref ,
64
69
Update ,
65
70
NoFlags ,
66
71
DidCapture ,
67
72
Snapshot ,
68
73
MutationMask ,
74
+ StaticMask ,
69
75
} from './ReactFiberFlags' ;
70
76
import invariant from 'shared/invariant' ;
71
77
@@ -137,9 +143,16 @@ import {
137
143
renderHasNotSuspendedYet ,
138
144
popRenderLanes ,
139
145
getRenderTargetTime ,
146
+ subtreeRenderLanes ,
140
147
} from './ReactFiberWorkLoop.new' ;
141
148
import { createFundamentalStateInstance } from './ReactFiberFundamental.new' ;
142
- import { OffscreenLane , SomeRetryLane } from './ReactFiberLane' ;
149
+ import {
150
+ OffscreenLane ,
151
+ SomeRetryLane ,
152
+ NoLanes ,
153
+ includesSomeLane ,
154
+ mergeLanes ,
155
+ } from './ReactFiberLane' ;
143
156
import { resetChildFibers } from './ReactChildFiber.new' ;
144
157
import { createScopeInstance } from './ReactFiberScope.new' ;
145
158
import { transferActualDuration } from './ReactProfilerTimer.new' ;
@@ -668,6 +681,150 @@ function cutOffTailIfNeeded(
668
681
}
669
682
}
670
683
684
+ function bubbleProperties ( completedWork : Fiber ) {
685
+ if (
686
+ // TODO: Move this check out of the hot path by moving `resetChildLanes`
687
+ // to switch statement in `completeWork`.
688
+ ( completedWork . tag === LegacyHiddenComponent ||
689
+ completedWork . tag === OffscreenComponent ) &&
690
+ completedWork . memoizedState !== null &&
691
+ ! includesSomeLane ( subtreeRenderLanes , ( OffscreenLane : Lane ) ) &&
692
+ ( completedWork . mode & ConcurrentMode ) !== NoLanes
693
+ ) {
694
+ // The children of this component are hidden. Don't bubble their
695
+ // expiration times.
696
+ return ;
697
+ }
698
+
699
+ const didBailout =
700
+ completedWork . alternate !== null &&
701
+ completedWork . alternate . child === completedWork . child ;
702
+
703
+ let newChildLanes = NoLanes ;
704
+ let subtreeFlags = NoFlags ;
705
+
706
+ if ( ! didBailout ) {
707
+ // Bubble up the earliest expiration time.
708
+ if ( enableProfilerTimer && ( completedWork . mode & ProfileMode ) !== NoMode ) {
709
+ // In profiling mode, resetChildExpirationTime is also used to reset
710
+ // profiler durations.
711
+ let actualDuration = completedWork . actualDuration ;
712
+ let treeBaseDuration = ( ( completedWork . selfBaseDuration : any ) : number ) ;
713
+
714
+ let child = completedWork . child ;
715
+ while ( child !== null ) {
716
+ newChildLanes = mergeLanes (
717
+ newChildLanes ,
718
+ mergeLanes ( child . lanes , child . childLanes ) ,
719
+ ) ;
720
+
721
+ subtreeFlags |= child . subtreeFlags ;
722
+ subtreeFlags |= child . flags ;
723
+
724
+ // When a fiber is cloned, its actualDuration is reset to 0. This value will
725
+ // only be updated if work is done on the fiber (i.e. it doesn't bailout).
726
+ // When work is done, it should bubble to the parent's actualDuration. If
727
+ // the fiber has not been cloned though, (meaning no work was done), then
728
+ // this value will reflect the amount of time spent working on a previous
729
+ // render. In that case it should not bubble. We determine whether it was
730
+ // cloned by comparing the child pointer.
731
+ actualDuration += child . actualDuration ;
732
+
733
+ treeBaseDuration += child . treeBaseDuration ;
734
+ child = child . sibling ;
735
+ }
736
+
737
+ const isTimedOutSuspense =
738
+ completedWork . tag === SuspenseComponent &&
739
+ completedWork . memoizedState !== null ;
740
+ if ( isTimedOutSuspense ) {
741
+ // Don't count time spent in a timed out Suspense subtree as part of the base duration.
742
+ const primaryChildFragment = completedWork . child ;
743
+ if ( primaryChildFragment !== null ) {
744
+ treeBaseDuration -= ( ( primaryChildFragment . treeBaseDuration : any ) : number ) ;
745
+ }
746
+ }
747
+
748
+ completedWork . actualDuration = actualDuration ;
749
+ completedWork . treeBaseDuration = treeBaseDuration ;
750
+ } else {
751
+ let child = completedWork . child ;
752
+ while ( child !== null ) {
753
+ newChildLanes = mergeLanes (
754
+ newChildLanes ,
755
+ mergeLanes ( child . lanes , child . childLanes ) ,
756
+ ) ;
757
+
758
+ subtreeFlags |= child . subtreeFlags ;
759
+ subtreeFlags |= child . flags ;
760
+
761
+ child = child . sibling ;
762
+ }
763
+ }
764
+
765
+ completedWork . subtreeFlags |= subtreeFlags ;
766
+ } else {
767
+ // Bubble up the earliest expiration time.
768
+ if ( enableProfilerTimer && ( completedWork . mode & ProfileMode ) !== NoMode ) {
769
+ // In profiling mode, resetChildExpirationTime is also used to reset
770
+ // profiler durations.
771
+ let treeBaseDuration = ( ( completedWork . selfBaseDuration : any ) : number ) ;
772
+
773
+ let child = completedWork . child ;
774
+ while ( child !== null ) {
775
+ newChildLanes = mergeLanes (
776
+ newChildLanes ,
777
+ mergeLanes ( child . lanes , child . childLanes ) ,
778
+ ) ;
779
+
780
+ // "Static" flags share the lifetime of the fiber/hook they belong to,
781
+ // so we should bubble those up even during a bailout. All the other
782
+ // flags have a lifetime only of a single render + commit, so we should
783
+ // ignore them.
784
+ subtreeFlags |= child . subtreeFlags & StaticMask ;
785
+ subtreeFlags |= child . flags & StaticMask ;
786
+
787
+ treeBaseDuration += child . treeBaseDuration ;
788
+ child = child . sibling ;
789
+ }
790
+
791
+ const isTimedOutSuspense =
792
+ completedWork . tag === SuspenseComponent &&
793
+ completedWork . memoizedState !== null ;
794
+ if ( isTimedOutSuspense ) {
795
+ // Don't count time spent in a timed out Suspense subtree as part of the base duration.
796
+ const primaryChildFragment = completedWork . child ;
797
+ if ( primaryChildFragment !== null ) {
798
+ treeBaseDuration -= ( ( primaryChildFragment . treeBaseDuration : any ) : number ) ;
799
+ }
800
+ }
801
+
802
+ completedWork . treeBaseDuration = treeBaseDuration ;
803
+ } else {
804
+ let child = completedWork . child ;
805
+ while ( child !== null ) {
806
+ newChildLanes = mergeLanes (
807
+ newChildLanes ,
808
+ mergeLanes ( child . lanes , child . childLanes ) ,
809
+ ) ;
810
+
811
+ // "Static" flags share the lifetime of the fiber/hook they belong to,
812
+ // so we should bubble those up even during a bailout. All the other
813
+ // flags have a lifetime only of a single render + commit, so we should
814
+ // ignore them.
815
+ subtreeFlags |= child . subtreeFlags & StaticMask ;
816
+ subtreeFlags |= child . flags & StaticMask ;
817
+
818
+ child = child . sibling ;
819
+ }
820
+ }
821
+
822
+ completedWork . subtreeFlags |= subtreeFlags ;
823
+ }
824
+
825
+ completedWork . childLanes = newChildLanes ;
826
+ }
827
+
671
828
function completeWork (
672
829
current : Fiber | null ,
673
830
workInProgress : Fiber ,
@@ -686,12 +843,14 @@ function completeWork(
686
843
case Profiler :
687
844
case ContextConsumer :
688
845
case MemoComponent :
846
+ bubbleProperties ( workInProgress ) ;
689
847
return null ;
690
848
case ClassComponent : {
691
849
const Component = workInProgress . type ;
692
850
if ( isLegacyContextProvider ( Component ) ) {
693
851
popLegacyContext ( workInProgress ) ;
694
852
}
853
+ bubbleProperties ( workInProgress ) ;
695
854
return null ;
696
855
}
697
856
case HostRoot : {
@@ -720,6 +879,7 @@ function completeWork(
720
879
}
721
880
}
722
881
updateHostContainer ( current , workInProgress ) ;
882
+ bubbleProperties ( workInProgress ) ;
723
883
return null ;
724
884
}
725
885
case HostComponent : {
@@ -746,6 +906,7 @@ function completeWork(
746
906
'caused by a bug in React. Please file an issue.' ,
747
907
) ;
748
908
// This can happen when we abort work.
909
+ bubbleProperties ( workInProgress ) ;
749
910
return null ;
750
911
}
751
912
@@ -803,6 +964,7 @@ function completeWork(
803
964
markRef ( workInProgress ) ;
804
965
}
805
966
}
967
+ bubbleProperties ( workInProgress ) ;
806
968
return null ;
807
969
}
808
970
case HostText : {
@@ -837,6 +999,7 @@ function completeWork(
837
999
) ;
838
1000
}
839
1001
}
1002
+ bubbleProperties ( workInProgress ) ;
840
1003
return null ;
841
1004
}
842
1005
case SuspenseComponent : {
@@ -856,6 +1019,7 @@ function completeWork(
856
1019
if ( enableSchedulerTracing ) {
857
1020
markSpawnedWork ( OffscreenLane ) ;
858
1021
}
1022
+ bubbleProperties ( workInProgress ) ;
859
1023
return null ;
860
1024
} else {
861
1025
// We should never have been in a hydration state if we didn't have a current.
@@ -872,6 +1036,7 @@ function completeWork(
872
1036
// If something suspended, schedule an effect to attach retry listeners.
873
1037
// So we might as well always mark this.
874
1038
workInProgress . flags |= Update ;
1039
+ bubbleProperties ( workInProgress ) ;
875
1040
return null ;
876
1041
}
877
1042
}
@@ -964,6 +1129,7 @@ function completeWork(
964
1129
// Always notify the callback
965
1130
workInProgress . flags |= Update ;
966
1131
}
1132
+ bubbleProperties ( workInProgress ) ;
967
1133
return null ;
968
1134
}
969
1135
case HostPortal :
@@ -972,10 +1138,12 @@ function completeWork(
972
1138
if ( current === null ) {
973
1139
preparePortalMount ( workInProgress . stateNode . containerInfo ) ;
974
1140
}
1141
+ bubbleProperties ( workInProgress ) ;
975
1142
return null ;
976
1143
case ContextProvider :
977
1144
// Pop provider fiber
978
1145
popProvider ( workInProgress ) ;
1146
+ bubbleProperties ( workInProgress ) ;
979
1147
return null ;
980
1148
case IncompleteClassComponent : {
981
1149
// Same as class component case. I put it down here so that the tags are
@@ -984,6 +1152,7 @@ function completeWork(
984
1152
if ( isLegacyContextProvider ( Component ) ) {
985
1153
popLegacyContext ( workInProgress ) ;
986
1154
}
1155
+ bubbleProperties ( workInProgress ) ;
987
1156
return null ;
988
1157
}
989
1158
case SuspenseListComponent : {
@@ -995,6 +1164,7 @@ function completeWork(
995
1164
if ( renderState === null ) {
996
1165
// We're running in the default, "independent" mode.
997
1166
// We don't do anything in this mode.
1167
+ bubbleProperties ( workInProgress ) ;
998
1168
return null ;
999
1169
}
1000
1170
@@ -1117,6 +1287,7 @@ function completeWork(
1117
1287
! getIsHydrating ( ) // We don't cut it if we're hydrating.
1118
1288
) {
1119
1289
// We're done.
1290
+ bubbleProperties ( workInProgress ) ;
1120
1291
return null ;
1121
1292
}
1122
1293
} else if (
@@ -1190,6 +1361,7 @@ function completeWork(
1190
1361
// Do a pass over the next row.
1191
1362
return next ;
1192
1363
}
1364
+ bubbleProperties ( workInProgress ) ;
1193
1365
return null ;
1194
1366
}
1195
1367
case FundamentalComponent : {
@@ -1217,6 +1389,7 @@ function completeWork(
1217
1389
) : any ) : Instance ) ;
1218
1390
fundamentalInstance . instance = instance ;
1219
1391
if ( fundamentalImpl . reconcileChildren === false ) {
1392
+ bubbleProperties ( workInProgress ) ;
1220
1393
return null ;
1221
1394
}
1222
1395
appendAllChildren ( instance , workInProgress , false , false ) ;
@@ -1239,6 +1412,7 @@ function completeWork(
1239
1412
markUpdate ( workInProgress ) ;
1240
1413
}
1241
1414
}
1415
+ bubbleProperties ( workInProgress ) ;
1242
1416
return null ;
1243
1417
}
1244
1418
break ;
@@ -1261,31 +1435,44 @@ function completeWork(
1261
1435
markRef ( workInProgress ) ;
1262
1436
}
1263
1437
}
1438
+ bubbleProperties ( workInProgress ) ;
1264
1439
return null ;
1265
1440
}
1266
1441
break ;
1267
1442
}
1268
1443
case Block :
1269
1444
if ( enableBlocksAPI ) {
1445
+ bubbleProperties ( workInProgress ) ;
1270
1446
return null ;
1271
1447
}
1272
1448
break ;
1273
1449
case OffscreenComponent :
1274
1450
case LegacyHiddenComponent : {
1275
1451
popRenderLanes ( workInProgress ) ;
1452
+ const nextState : OffscreenState | null = workInProgress . memoizedState ;
1453
+ const nextIsHidden = nextState !== null ;
1454
+
1276
1455
if ( current !== null ) {
1277
- const nextState : OffscreenState | null = workInProgress . memoizedState ;
1278
1456
const prevState : OffscreenState | null = current . memoizedState ;
1279
1457
1280
1458
const prevIsHidden = prevState !== null ;
1281
- const nextIsHidden = nextState !== null ;
1282
1459
if (
1283
1460
prevIsHidden !== nextIsHidden &&
1284
1461
newProps . mode !== 'unstable-defer-without-hiding'
1285
1462
) {
1286
1463
workInProgress . flags |= Update ;
1287
1464
}
1288
1465
}
1466
+
1467
+ // Don't bubble properties for hidden children.
1468
+ if (
1469
+ ! nextIsHidden ||
1470
+ includesSomeLane ( subtreeRenderLanes , ( OffscreenLane : Lane ) ) ||
1471
+ ( workInProgress . mode & ConcurrentMode ) === NoLanes
1472
+ ) {
1473
+ bubbleProperties ( workInProgress ) ;
1474
+ }
1475
+
1289
1476
return null ;
1290
1477
}
1291
1478
}
0 commit comments