Track subtreeFlags et al with bubbleProperties
Original PR: #19836
Andrew Clark committed
Nov 17, 2020 at 13:51 UTC
b66ae09b6e9feba21aaea248da0e0c870193b597
8 files changed
+458
-178
packages/react-reconciler/src/ReactFiber.new.js
+4
-1
@@ -28,7 +28,7 @@ import {
28
enableFundamentalAPI,
29
enableScopeAPI,
30
} from 'shared/ReactFeatureFlags';
31
-import {NoFlags, Placement} from './ReactFiberFlags';
31
+import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
32
import {ConcurrentRoot, BlockingRoot} from './ReactRootTags';
33
import {
34
IndeterminateComponent,
@@ -299,6 +299,9 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
299
}
300
}
301
302
+ // Reset all effects except static ones.
303
+ // Static effects are not specific to a render.
304
+ workInProgress.flags = current.flags & StaticMask;
305
workInProgress.childLanes = current.childLanes;
306
workInProgress.lanes = current.lanes;
307
packages/react-reconciler/src/ReactFiber.old.js
+4
-1
@@ -28,7 +28,7 @@ import {
28
enableFundamentalAPI,
29
enableScopeAPI,
30
} from 'shared/ReactFeatureFlags';
31
-import {NoFlags, Placement} from './ReactFiberFlags';
31
+import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
32
import {ConcurrentRoot, BlockingRoot} from './ReactRootTags';
33
import {
34
IndeterminateComponent,
@@ -299,6 +299,9 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
299
}
300
}
301
302
+ // Reset all effects except static ones.
303
+ // Static effects are not specific to a render.
304
+ workInProgress.flags = current.flags & StaticMask;
305
workInProgress.childLanes = current.childLanes;
306
workInProgress.lanes = current.lanes;
307
packages/react-reconciler/src/ReactFiberBeginWork.new.js
+7
@@ -63,6 +63,7 @@ import {
63
Deletion,
64
ChildDeletion,
65
ForceUpdateForLegacySuspense,
66
+ StaticMask,
67
} from './ReactFiberFlags';
68
import ReactSharedInternals from 'shared/ReactSharedInternals';
69
import {
@@ -2092,6 +2093,12 @@ function updateSuspenseFallbackChildren(
2093
currentPrimaryChildFragment,
2094
primaryChildProps,
2095
);
2096
+
2097
+ // Since we're reusing a current tree, we need to reuse the flags, too.
2098
+ // (We don't do this in legacy mode, because in legacy mode we don't re-use
2099
+ // the current tree; see previous branch.)
2100
+ primaryChildFragment.subtreeFlags =
2101
+ currentPrimaryChildFragment.subtreeFlags & StaticMask;
2102
}
2103
let fallbackChildFragment;
2104
if (currentFallbackChildFragment !== null) {
packages/react-reconciler/src/ReactFiberBeginWork.old.js
+7
@@ -63,6 +63,7 @@ import {
63
Deletion,
64
ChildDeletion,
65
ForceUpdateForLegacySuspense,
66
+ StaticMask,
67
} from './ReactFiberFlags';
68
import ReactSharedInternals from 'shared/ReactSharedInternals';
69
import {
@@ -2092,6 +2093,12 @@ function updateSuspenseFallbackChildren(
2093
currentPrimaryChildFragment,
2094
primaryChildProps,
2095
);
2096
+
2097
+ // Since we're reusing a current tree, we need to reuse the flags, too.
2098
+ // (We don't do this in legacy mode, because in legacy mode we don't re-use
2099
+ // the current tree; see previous branch.)
2100
+ primaryChildFragment.subtreeFlags =
2101
+ currentPrimaryChildFragment.subtreeFlags & StaticMask;
2102
}
2103
let fallbackChildFragment;
2104
if (currentFallbackChildFragment !== null) {
packages/react-reconciler/src/ReactFiberCompleteWork.new.js
+216
-7
@@ -8,7 +8,7 @@
8
*/
9
10
import type {Fiber} from './ReactInternalTypes';
11
-import type {Lanes} from './ReactFiberLane.new';
11
+import type {Lanes, Lane} from './ReactFiberLane.new';
12
import type {
13
ReactFundamentalComponentInstance,
14
ReactScopeInstance,
@@ -57,8 +57,20 @@ import {
57
OffscreenComponent,
58
LegacyHiddenComponent,
59
} from './ReactWorkTags';
60
-import {NoMode, BlockingMode, ProfileMode} from './ReactTypeOfMode';
61
-import {Ref, Update, NoFlags, DidCapture, Snapshot} from './ReactFiberFlags';
60
+import {
61
+ NoMode,
62
+ BlockingMode,
63
+ ConcurrentMode,
64
+ ProfileMode,
65
+} from './ReactTypeOfMode';
66
+import {
67
+ Ref,
68
+ Update,
69
+ NoFlags,
70
+ DidCapture,
71
+ Snapshot,
72
+ StaticMask,
73
+} from './ReactFiberFlags';
74
import invariant from 'shared/invariant';
75
76
import {
@@ -128,9 +140,16 @@ import {
140
renderHasNotSuspendedYet,
141
popRenderLanes,
142
getRenderTargetTime,
143
+ subtreeRenderLanes,
144
} from './ReactFiberWorkLoop.new';
145
import {createFundamentalStateInstance} from './ReactFiberFundamental.new';
133
-import {OffscreenLane, SomeRetryLane} from './ReactFiberLane.new';
146
+import {
147
+ OffscreenLane,
148
+ SomeRetryLane,
149
+ NoLanes,
150
+ includesSomeLane,
151
+ mergeLanes,
152
+} from './ReactFiberLane.new';
153
import {resetChildFibers} from './ReactChildFiber.new';
154
import {createScopeInstance} from './ReactFiberScope.new';
155
import {transferActualDuration} from './ReactProfilerTimer.new';
@@ -640,6 +659,126 @@ function cutOffTailIfNeeded(
659
}
660
}
661
662
+function bubbleProperties(completedWork: Fiber) {
663
+ const didBailout =
664
+ completedWork.alternate !== null &&
665
+ completedWork.alternate.child === completedWork.child;
666
+
667
+ let newChildLanes = NoLanes;
668
+ let subtreeFlags = NoFlags;
669
+
670
+ if (!didBailout) {
671
+ // Bubble up the earliest expiration time.
672
+ if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
673
+ // In profiling mode, resetChildExpirationTime is also used to reset
674
+ // profiler durations.
675
+ let actualDuration = completedWork.actualDuration;
676
+ let treeBaseDuration = ((completedWork.selfBaseDuration: any): number);
677
+
678
+ let child = completedWork.child;
679
+ while (child !== null) {
680
+ newChildLanes = mergeLanes(
681
+ newChildLanes,
682
+ mergeLanes(child.lanes, child.childLanes),
683
+ );
684
+
685
+ subtreeFlags |= child.subtreeFlags;
686
+ subtreeFlags |= child.flags;
687
+
688
+ // When a fiber is cloned, its actualDuration is reset to 0. This value will
689
+ // only be updated if work is done on the fiber (i.e. it doesn't bailout).
690
+ // When work is done, it should bubble to the parent's actualDuration. If
691
+ // the fiber has not been cloned though, (meaning no work was done), then
692
+ // this value will reflect the amount of time spent working on a previous
693
+ // render. In that case it should not bubble. We determine whether it was
694
+ // cloned by comparing the child pointer.
695
+ actualDuration += child.actualDuration;
696
+
697
+ treeBaseDuration += child.treeBaseDuration;
698
+ child = child.sibling;
699
+ }
700
+
701
+ completedWork.actualDuration = actualDuration;
702
+ completedWork.treeBaseDuration = treeBaseDuration;
703
+ } else {
704
+ let child = completedWork.child;
705
+ while (child !== null) {
706
+ newChildLanes = mergeLanes(
707
+ newChildLanes,
708
+ mergeLanes(child.lanes, child.childLanes),
709
+ );
710
+
711
+ subtreeFlags |= child.subtreeFlags;
712
+ subtreeFlags |= child.flags;
713
+
714
+ // Update the return pointer so the tree is consistent. This is a code
715
+ // smell because it assumes the commit phase is never concurrent with
716
+ // the render phase. Will address during refactor to alternate model.
717
+ child.return = completedWork;
718
+
719
+ child = child.sibling;
720
+ }
721
+ }
722
+
723
+ completedWork.subtreeFlags |= subtreeFlags;
724
+ } else {
725
+ // Bubble up the earliest expiration time.
726
+ if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
727
+ // In profiling mode, resetChildExpirationTime is also used to reset
728
+ // profiler durations.
729
+ let treeBaseDuration = ((completedWork.selfBaseDuration: any): number);
730
+
731
+ let child = completedWork.child;
732
+ while (child !== null) {
733
+ newChildLanes = mergeLanes(
734
+ newChildLanes,
735
+ mergeLanes(child.lanes, child.childLanes),
736
+ );
737
+
738
+ // "Static" flags share the lifetime of the fiber/hook they belong to,
739
+ // so we should bubble those up even during a bailout. All the other
740
+ // flags have a lifetime only of a single render + commit, so we should
741
+ // ignore them.
742
+ subtreeFlags |= child.subtreeFlags & StaticMask;
743
+ subtreeFlags |= child.flags & StaticMask;
744
+
745
+ treeBaseDuration += child.treeBaseDuration;
746
+ child = child.sibling;
747
+ }
748
+
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
+ // "Static" flags share the lifetime of the fiber/hook they belong to,
759
+ // so we should bubble those up even during a bailout. All the other
760
+ // flags have a lifetime only of a single render + commit, so we should
761
+ // ignore them.
762
+ subtreeFlags |= child.subtreeFlags & StaticMask;
763
+ subtreeFlags |= child.flags & StaticMask;
764
+
765
+ // Update the return pointer so the tree is consistent. This is a code
766
+ // smell because it assumes the commit phase is never concurrent with
767
+ // the render phase. Will address during refactor to alternate model.
768
+ child.return = completedWork;
769
+
770
+ child = child.sibling;
771
+ }
772
+ }
773
+
774
+ completedWork.subtreeFlags |= subtreeFlags;
775
+ }
776
+
777
+ completedWork.childLanes = newChildLanes;
778
+
779
+ return didBailout;
780
+}
781
+
782
function completeWork(
783
current: Fiber | null,
784
workInProgress: Fiber,
@@ -658,12 +797,14 @@ function completeWork(
797
case Profiler:
798
case ContextConsumer:
799
case MemoComponent:
800
+ bubbleProperties(workInProgress);
801
return null;
802
case ClassComponent: {
803
const Component = workInProgress.type;
804
if (isLegacyContextProvider(Component)) {
805
popLegacyContext(workInProgress);
806
}
807
+ bubbleProperties(workInProgress);
808
return null;
809
}
810
case HostRoot: {
@@ -692,6 +833,7 @@ function completeWork(
833
}
834
}
835
updateHostContainer(workInProgress);
836
+ bubbleProperties(workInProgress);
837
return null;
838
}
839
case HostComponent: {
@@ -718,6 +860,7 @@ function completeWork(
860
'caused by a bug in React. Please file an issue.',
861
);
862
// This can happen when we abort work.
863
+ bubbleProperties(workInProgress);
864
return null;
865
}
866
@@ -775,6 +918,7 @@ function completeWork(
918
markRef(workInProgress);
919
}
920
}
921
+ bubbleProperties(workInProgress);
922
return null;
923
}
924
case HostText: {
@@ -809,6 +953,7 @@ function completeWork(
953
);
954
}
955
}
956
+ bubbleProperties(workInProgress);
957
return null;
958
}
959
case SuspenseComponent: {
@@ -828,6 +973,20 @@ function completeWork(
973
if (enableSchedulerTracing) {
974
markSpawnedWork(OffscreenLane);
975
}
976
+ bubbleProperties(workInProgress);
977
+ if (enableProfilerTimer) {
978
+ if ((workInProgress.mode & ProfileMode) !== NoMode) {
979
+ const isTimedOutSuspense = nextState !== null;
980
+ if (isTimedOutSuspense) {
981
+ // Don't count time spent in a timed out Suspense subtree as part of the base duration.
982
+ const primaryChildFragment = workInProgress.child;
983
+ if (primaryChildFragment !== null) {
984
+ // $FlowFixMe Flow doens't support type casting in combiation with the -= operator
985
+ workInProgress.treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number);
986
+ }
987
+ }
988
+ }
989
+ }
990
return null;
991
} else {
992
// We should never have been in a hydration state if we didn't have a current.
@@ -844,6 +1003,20 @@ function completeWork(
1003
// If something suspended, schedule an effect to attach retry listeners.
1004
// So we might as well always mark this.
1005
workInProgress.flags |= Update;
1006
+ bubbleProperties(workInProgress);
1007
+ if (enableProfilerTimer) {
1008
+ if ((workInProgress.mode & ProfileMode) !== NoMode) {
1009
+ const isTimedOutSuspense = nextState !== null;
1010
+ if (isTimedOutSuspense) {
1011
+ // Don't count time spent in a timed out Suspense subtree as part of the base duration.
1012
+ const primaryChildFragment = workInProgress.child;
1013
+ if (primaryChildFragment !== null) {
1014
+ // $FlowFixMe Flow doens't support type casting in combiation with the -= operator
1015
+ workInProgress.treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number);
1016
+ }
1017
+ }
1018
+ }
1019
+ }
1020
return null;
1021
}
1022
}
@@ -859,6 +1032,7 @@ function completeWork(
1032
) {
1033
transferActualDuration(workInProgress);
1034
}
1035
+ // Don't bubble properties in this case.
1036
return workInProgress;
1037
}
1038
@@ -936,6 +1110,19 @@ function completeWork(
1110
// Always notify the callback
1111
workInProgress.flags |= Update;
1112
}
1113
+ bubbleProperties(workInProgress);
1114
+ if (enableProfilerTimer) {
1115
+ if ((workInProgress.mode & ProfileMode) !== NoMode) {
1116
+ if (nextDidTimeout) {
1117
+ // Don't count time spent in a timed out Suspense subtree as part of the base duration.
1118
+ const primaryChildFragment = workInProgress.child;
1119
+ if (primaryChildFragment !== null) {
1120
+ // $FlowFixMe Flow doens't support type casting in combiation with the -= operator
1121
+ workInProgress.treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number);
1122
+ }
1123
+ }
1124
+ }
1125
+ }
1126
return null;
1127
}
1128
case HostPortal:
@@ -944,10 +1131,12 @@ function completeWork(
1131
if (current === null) {
1132
preparePortalMount(workInProgress.stateNode.containerInfo);
1133
}
1134
+ bubbleProperties(workInProgress);
1135
return null;
1136
case ContextProvider:
1137
// Pop provider fiber
1138
popProvider(workInProgress);
1139
+ bubbleProperties(workInProgress);
1140
return null;
1141
case IncompleteClassComponent: {
1142
// Same as class component case. I put it down here so that the tags are
@@ -956,6 +1145,7 @@ function completeWork(
1145
if (isLegacyContextProvider(Component)) {
1146
popLegacyContext(workInProgress);
1147
}
1148
+ bubbleProperties(workInProgress);
1149
return null;
1150
}
1151
case SuspenseListComponent: {
@@ -967,6 +1157,7 @@ function completeWork(
1157
if (renderState === null) {
1158
// We're running in the default, "independent" mode.
1159
// We don't do anything in this mode.
1160
+ bubbleProperties(workInProgress);
1161
return null;
1162
}
1163
@@ -1025,6 +1216,7 @@ function completeWork(
1216
}
1217
workInProgress.lastEffect = renderState.lastEffect;
1218
// Reset the child fibers to their original state.
1219
+ workInProgress.subtreeFlags = NoFlags;
1220
resetChildFibers(workInProgress, renderLanes);
1221
1222
// Set up the Suspense Context to force suspense and immediately
@@ -1036,6 +1228,7 @@ function completeWork(
1228
ForceSuspenseFallback,
1229
),
1230
);
1231
+ // Don't bubble properties in this case.
1232
return workInProgress.child;
1233
}
1234
row = row.sibling;
@@ -1102,6 +1295,7 @@ function completeWork(
1295
lastEffect.nextEffect = null;
1296
}
1297
// We're done.
1298
+ bubbleProperties(workInProgress);
1299
return null;
1300
}
1301
} else if (
@@ -1177,8 +1371,10 @@ function completeWork(
1371
}
1372
pushSuspenseContext(workInProgress, suspenseContext);
1373
// Do a pass over the next row.
1374
+ // Don't bubble properties in this case.
1375
return next;
1376
}
1377
+ bubbleProperties(workInProgress);
1378
return null;
1379
}
1380
case FundamentalComponent: {
@@ -1206,6 +1402,7 @@ function completeWork(
1402
): any): Instance);
1403
fundamentalInstance.instance = instance;
1404
if (fundamentalImpl.reconcileChildren === false) {
1405
+ bubbleProperties(workInProgress);
1406
return null;
1407
}
1408
appendAllChildren(instance, workInProgress, false, false);
@@ -1228,6 +1425,7 @@ function completeWork(
1425
markUpdate(workInProgress);
1426
}
1427
}
1428
+ bubbleProperties(workInProgress);
1429
return null;
1430
}
1431
break;
@@ -1250,6 +1448,7 @@ function completeWork(
1448
markRef(workInProgress);
1449
}
1450
}
1451
+ bubbleProperties(workInProgress);
1452
return null;
1453
}
1454
break;
@@ -1257,12 +1456,12 @@ function completeWork(
1456
case OffscreenComponent:
1457
case LegacyHiddenComponent: {
1458
popRenderLanes(workInProgress);
1459
+ const nextState: OffscreenState | null = workInProgress.memoizedState;
1460
+ const nextIsHidden = nextState !== null;
1461
+
1462
if (current !== null) {
1261
- const nextState: OffscreenState | null = workInProgress.memoizedState;
1463
const prevState: OffscreenState | null = current.memoizedState;
1263
-
1464
const prevIsHidden = prevState !== null;
1265
- const nextIsHidden = nextState !== null;
1465
if (
1466
prevIsHidden !== nextIsHidden &&
1467
newProps.mode !== 'unstable-defer-without-hiding'
@@ -1270,6 +1469,16 @@ function completeWork(
1469
workInProgress.flags |= Update;
1470
}
1471
}
1472
+
1473
+ // Don't bubble properties for hidden children.
1474
+ if (
1475
+ !nextIsHidden ||
1476
+ includesSomeLane(subtreeRenderLanes, (OffscreenLane: Lane)) ||
1477
+ (workInProgress.mode & ConcurrentMode) === NoMode
1478
+ ) {
1479
+ bubbleProperties(workInProgress);
1480
+ }
1481
+
1482
return null;
1483
}
1484
}
packages/react-reconciler/src/ReactFiberCompleteWork.old.js
+216
-7
@@ -8,7 +8,7 @@
8
*/
9
10
import type {Fiber} from './ReactInternalTypes';
11
-import type {Lanes} from './ReactFiberLane.old';
11
+import type {Lanes, Lane} from './ReactFiberLane.old';
12
import type {
13
ReactFundamentalComponentInstance,
14
ReactScopeInstance,
@@ -57,8 +57,20 @@ import {
57
OffscreenComponent,
58
LegacyHiddenComponent,
59
} from './ReactWorkTags';
60
-import {NoMode, BlockingMode, ProfileMode} from './ReactTypeOfMode';
61
-import {Ref, Update, NoFlags, DidCapture, Snapshot} from './ReactFiberFlags';
60
+import {
61
+ NoMode,
62
+ BlockingMode,
63
+ ConcurrentMode,
64
+ ProfileMode,
65
+} from './ReactTypeOfMode';
66
+import {
67
+ Ref,
68
+ Update,
69
+ NoFlags,
70
+ DidCapture,
71
+ Snapshot,
72
+ StaticMask,
73
+} from './ReactFiberFlags';
74
import invariant from 'shared/invariant';
75
76
import {
@@ -128,9 +140,16 @@ import {
140
renderHasNotSuspendedYet,
141
popRenderLanes,
142
getRenderTargetTime,
143
+ subtreeRenderLanes,
144
} from './ReactFiberWorkLoop.old';
145
import {createFundamentalStateInstance} from './ReactFiberFundamental.old';
133
-import {OffscreenLane, SomeRetryLane} from './ReactFiberLane.old';
146
+import {
147
+ OffscreenLane,
148
+ SomeRetryLane,
149
+ NoLanes,
150
+ includesSomeLane,
151
+ mergeLanes,
152
+} from './ReactFiberLane.old';
153
import {resetChildFibers} from './ReactChildFiber.old';
154
import {createScopeInstance} from './ReactFiberScope.old';
155
import {transferActualDuration} from './ReactProfilerTimer.old';
@@ -640,6 +659,126 @@ function cutOffTailIfNeeded(
659
}
660
}
661
662
+function bubbleProperties(completedWork: Fiber) {
663
+ const didBailout =
664
+ completedWork.alternate !== null &&
665
+ completedWork.alternate.child === completedWork.child;
666
+
667
+ let newChildLanes = NoLanes;
668
+ let subtreeFlags = NoFlags;
669
+
670
+ if (!didBailout) {
671
+ // Bubble up the earliest expiration time.
672
+ if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
673
+ // In profiling mode, resetChildExpirationTime is also used to reset
674
+ // profiler durations.
675
+ let actualDuration = completedWork.actualDuration;
676
+ let treeBaseDuration = ((completedWork.selfBaseDuration: any): number);
677
+
678
+ let child = completedWork.child;
679
+ while (child !== null) {
680
+ newChildLanes = mergeLanes(
681
+ newChildLanes,
682
+ mergeLanes(child.lanes, child.childLanes),
683
+ );
684
+
685
+ subtreeFlags |= child.subtreeFlags;
686
+ subtreeFlags |= child.flags;
687
+
688
+ // When a fiber is cloned, its actualDuration is reset to 0. This value will
689
+ // only be updated if work is done on the fiber (i.e. it doesn't bailout).
690
+ // When work is done, it should bubble to the parent's actualDuration. If
691
+ // the fiber has not been cloned though, (meaning no work was done), then
692
+ // this value will reflect the amount of time spent working on a previous
693
+ // render. In that case it should not bubble. We determine whether it was
694
+ // cloned by comparing the child pointer.
695
+ actualDuration += child.actualDuration;
696
+
697
+ treeBaseDuration += child.treeBaseDuration;
698
+ child = child.sibling;
699
+ }
700
+
701
+ completedWork.actualDuration = actualDuration;
702
+ completedWork.treeBaseDuration = treeBaseDuration;
703
+ } else {
704
+ let child = completedWork.child;
705
+ while (child !== null) {
706
+ newChildLanes = mergeLanes(
707
+ newChildLanes,
708
+ mergeLanes(child.lanes, child.childLanes),
709
+ );
710
+
711
+ subtreeFlags |= child.subtreeFlags;
712
+ subtreeFlags |= child.flags;
713
+
714
+ // Update the return pointer so the tree is consistent. This is a code
715
+ // smell because it assumes the commit phase is never concurrent with
716
+ // the render phase. Will address during refactor to alternate model.
717
+ child.return = completedWork;
718
+
719
+ child = child.sibling;
720
+ }
721
+ }
722
+
723
+ completedWork.subtreeFlags |= subtreeFlags;
724
+ } else {
725
+ // Bubble up the earliest expiration time.
726
+ if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
727
+ // In profiling mode, resetChildExpirationTime is also used to reset
728
+ // profiler durations.
729
+ let treeBaseDuration = ((completedWork.selfBaseDuration: any): number);
730
+
731
+ let child = completedWork.child;
732
+ while (child !== null) {
733
+ newChildLanes = mergeLanes(
734
+ newChildLanes,
735
+ mergeLanes(child.lanes, child.childLanes),
736
+ );
737
+
738
+ // "Static" flags share the lifetime of the fiber/hook they belong to,
739
+ // so we should bubble those up even during a bailout. All the other
740
+ // flags have a lifetime only of a single render + commit, so we should
741
+ // ignore them.
742
+ subtreeFlags |= child.subtreeFlags & StaticMask;
743
+ subtreeFlags |= child.flags & StaticMask;
744
+
745
+ treeBaseDuration += child.treeBaseDuration;
746
+ child = child.sibling;
747
+ }
748
+
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
+ // "Static" flags share the lifetime of the fiber/hook they belong to,
759
+ // so we should bubble those up even during a bailout. All the other
760
+ // flags have a lifetime only of a single render + commit, so we should
761
+ // ignore them.
762
+ subtreeFlags |= child.subtreeFlags & StaticMask;
763
+ subtreeFlags |= child.flags & StaticMask;
764
+
765
+ // Update the return pointer so the tree is consistent. This is a code
766
+ // smell because it assumes the commit phase is never concurrent with
767
+ // the render phase. Will address during refactor to alternate model.
768
+ child.return = completedWork;
769
+
770
+ child = child.sibling;
771
+ }
772
+ }
773
+
774
+ completedWork.subtreeFlags |= subtreeFlags;
775
+ }
776
+
777
+ completedWork.childLanes = newChildLanes;
778
+
779
+ return didBailout;
780
+}
781
+
782
function completeWork(
783
current: Fiber | null,
784
workInProgress: Fiber,
@@ -658,12 +797,14 @@ function completeWork(
797
case Profiler:
798
case ContextConsumer:
799
case MemoComponent:
800
+ bubbleProperties(workInProgress);
801
return null;
802
case ClassComponent: {
803
const Component = workInProgress.type;
804
if (isLegacyContextProvider(Component)) {
805
popLegacyContext(workInProgress);
806
}
807
+ bubbleProperties(workInProgress);
808
return null;
809
}
810
case HostRoot: {
@@ -692,6 +833,7 @@ function completeWork(
833
}
834
}
835
updateHostContainer(workInProgress);
836
+ bubbleProperties(workInProgress);
837
return null;
838
}
839
case HostComponent: {
@@ -718,6 +860,7 @@ function completeWork(
860
'caused by a bug in React. Please file an issue.',
861
);
862
// This can happen when we abort work.
863
+ bubbleProperties(workInProgress);
864
return null;
865
}
866
@@ -775,6 +918,7 @@ function completeWork(
918
markRef(workInProgress);
919
}
920
}
921
+ bubbleProperties(workInProgress);
922
return null;
923
}
924
case HostText: {
@@ -809,6 +953,7 @@ function completeWork(
953
);
954
}
955
}
956
+ bubbleProperties(workInProgress);
957
return null;
958
}
959
case SuspenseComponent: {
@@ -828,6 +973,20 @@ function completeWork(
973
if (enableSchedulerTracing) {
974
markSpawnedWork(OffscreenLane);
975
}
976
+ bubbleProperties(workInProgress);
977
+ if (enableProfilerTimer) {
978
+ if ((workInProgress.mode & ProfileMode) !== NoMode) {
979
+ const isTimedOutSuspense = nextState !== null;
980
+ if (isTimedOutSuspense) {
981
+ // Don't count time spent in a timed out Suspense subtree as part of the base duration.
982
+ const primaryChildFragment = workInProgress.child;
983
+ if (primaryChildFragment !== null) {
984
+ // $FlowFixMe Flow doens't support type casting in combiation with the -= operator
985
+ workInProgress.treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number);
986
+ }
987
+ }
988
+ }
989
+ }
990
return null;
991
} else {
992
// We should never have been in a hydration state if we didn't have a current.
@@ -844,6 +1003,20 @@ function completeWork(
1003
// If something suspended, schedule an effect to attach retry listeners.
1004
// So we might as well always mark this.
1005
workInProgress.flags |= Update;
1006
+ bubbleProperties(workInProgress);
1007
+ if (enableProfilerTimer) {
1008
+ if ((workInProgress.mode & ProfileMode) !== NoMode) {
1009
+ const isTimedOutSuspense = nextState !== null;
1010
+ if (isTimedOutSuspense) {
1011
+ // Don't count time spent in a timed out Suspense subtree as part of the base duration.
1012
+ const primaryChildFragment = workInProgress.child;
1013
+ if (primaryChildFragment !== null) {
1014
+ // $FlowFixMe Flow doens't support type casting in combiation with the -= operator
1015
+ workInProgress.treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number);
1016
+ }
1017
+ }
1018
+ }
1019
+ }
1020
return null;
1021
}
1022
}
@@ -859,6 +1032,7 @@ function completeWork(
1032
) {
1033
transferActualDuration(workInProgress);
1034
}
1035
+ // Don't bubble properties in this case.
1036
return workInProgress;
1037
}
1038
@@ -936,6 +1110,19 @@ function completeWork(
1110
// Always notify the callback
1111
workInProgress.flags |= Update;
1112
}
1113
+ bubbleProperties(workInProgress);
1114
+ if (enableProfilerTimer) {
1115
+ if ((workInProgress.mode & ProfileMode) !== NoMode) {
1116
+ if (nextDidTimeout) {
1117
+ // Don't count time spent in a timed out Suspense subtree as part of the base duration.
1118
+ const primaryChildFragment = workInProgress.child;
1119
+ if (primaryChildFragment !== null) {
1120
+ // $FlowFixMe Flow doens't support type casting in combiation with the -= operator
1121
+ workInProgress.treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number);
1122
+ }
1123
+ }
1124
+ }
1125
+ }
1126
return null;
1127
}
1128
case HostPortal:
@@ -944,10 +1131,12 @@ function completeWork(
1131
if (current === null) {
1132
preparePortalMount(workInProgress.stateNode.containerInfo);
1133
}
1134
+ bubbleProperties(workInProgress);
1135
return null;
1136
case ContextProvider:
1137
// Pop provider fiber
1138
popProvider(workInProgress);
1139
+ bubbleProperties(workInProgress);
1140
return null;
1141
case IncompleteClassComponent: {
1142
// Same as class component case. I put it down here so that the tags are
@@ -956,6 +1145,7 @@ function completeWork(
1145
if (isLegacyContextProvider(Component)) {
1146
popLegacyContext(workInProgress);
1147
}
1148
+ bubbleProperties(workInProgress);
1149
return null;
1150
}
1151
case SuspenseListComponent: {
@@ -967,6 +1157,7 @@ function completeWork(
1157
if (renderState === null) {
1158
// We're running in the default, "independent" mode.
1159
// We don't do anything in this mode.
1160
+ bubbleProperties(workInProgress);
1161
return null;
1162
}
1163
@@ -1025,6 +1216,7 @@ function completeWork(
1216
}
1217
workInProgress.lastEffect = renderState.lastEffect;
1218
// Reset the child fibers to their original state.
1219
+ workInProgress.subtreeFlags = NoFlags;
1220
resetChildFibers(workInProgress, renderLanes);
1221
1222
// Set up the Suspense Context to force suspense and immediately
@@ -1036,6 +1228,7 @@ function completeWork(
1228
ForceSuspenseFallback,
1229
),
1230
);
1231
+ // Don't bubble properties in this case.
1232
return workInProgress.child;
1233
}
1234
row = row.sibling;
@@ -1102,6 +1295,7 @@ function completeWork(
1295
lastEffect.nextEffect = null;
1296
}
1297
// We're done.
1298
+ bubbleProperties(workInProgress);
1299
return null;
1300
}
1301
} else if (
@@ -1177,8 +1371,10 @@ function completeWork(
1371
}
1372
pushSuspenseContext(workInProgress, suspenseContext);
1373
// Do a pass over the next row.
1374
+ // Don't bubble properties in this case.
1375
return next;
1376
}
1377
+ bubbleProperties(workInProgress);
1378
return null;
1379
}
1380
case FundamentalComponent: {
@@ -1206,6 +1402,7 @@ function completeWork(
1402
): any): Instance);
1403
fundamentalInstance.instance = instance;
1404
if (fundamentalImpl.reconcileChildren === false) {
1405
+ bubbleProperties(workInProgress);
1406
return null;
1407
}
1408
appendAllChildren(instance, workInProgress, false, false);
@@ -1228,6 +1425,7 @@ function completeWork(
1425
markUpdate(workInProgress);
1426
}
1427
}
1428
+ bubbleProperties(workInProgress);
1429
return null;
1430
}
1431
break;
@@ -1250,6 +1448,7 @@ function completeWork(
1448
markRef(workInProgress);
1449
}
1450
}
1451
+ bubbleProperties(workInProgress);
1452
return null;
1453
}
1454
break;
@@ -1257,12 +1456,12 @@ function completeWork(
1456
case OffscreenComponent:
1457
case LegacyHiddenComponent: {
1458
popRenderLanes(workInProgress);
1459
+ const nextState: OffscreenState | null = workInProgress.memoizedState;
1460
+ const nextIsHidden = nextState !== null;
1461
+
1462
if (current !== null) {
1261
- const nextState: OffscreenState | null = workInProgress.memoizedState;
1463
const prevState: OffscreenState | null = current.memoizedState;
1263
-
1464
const prevIsHidden = prevState !== null;
1265
- const nextIsHidden = nextState !== null;
1465
if (
1466
prevIsHidden !== nextIsHidden &&
1467
newProps.mode !== 'unstable-defer-without-hiding'
@@ -1270,6 +1469,16 @@ function completeWork(
1469
workInProgress.flags |= Update;
1470
}
1471
}
1472
+
1473
+ // Don't bubble properties for hidden children.
1474
+ if (
1475
+ !nextIsHidden ||
1476
+ includesSomeLane(subtreeRenderLanes, (OffscreenLane: Lane)) ||
1477
+ (workInProgress.mode & ConcurrentMode) === NoMode
1478
+ ) {
1479
+ bubbleProperties(workInProgress);
1480
+ }
1481
+
1482
return null;
1483
}
1484
}
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+2
-81
@@ -110,8 +110,6 @@ import {
110
ForwardRef,
111
MemoComponent,
112
SimpleMemoComponent,
113
- OffscreenComponent,
114
- LegacyHiddenComponent,
113
ScopeComponent,
114
Profiler,
115
} from './ReactWorkTags';
@@ -144,7 +142,6 @@ import {
142
NoLane,
143
SyncLane,
144
SyncBatchedLane,
147
- OffscreenLane,
145
NoTimestamp,
146
findUpdateLane,
147
findTransitionLane,
@@ -285,7 +282,7 @@ let workInProgressRootRenderLanes: Lanes = NoLanes;
282
//
283
// Most things in the work loop should deal with workInProgressRootRenderLanes.
284
// Most things in begin/complete phases should deal with subtreeRenderLanes.
288
-let subtreeRenderLanes: Lanes = NoLanes;
285
+export let subtreeRenderLanes: Lanes = NoLanes;
286
const subtreeRenderLanesCursor: StackCursor<Lanes> = createCursor(NoLanes);
287
288
// Whether to root completed, errored, suspended, etc.
@@ -1742,8 +1739,6 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
1739
return;
1740
}
1741
1745
- resetChildLanes(completedWork);
1746
-
1742
if (
1743
returnFiber !== null &&
1744
// Do not append effects to parents if a sibling failed to complete
@@ -1821,6 +1816,7 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
1816
// Mark the parent fiber as incomplete and clear its effect list.
1817
returnFiber.firstEffect = returnFiber.lastEffect = null;
1818
returnFiber.flags |= Incomplete;
1819
+ returnFiber.subtreeFlags = NoFlags;
1820
returnFiber.deletions = null;
1821
}
1822
}
@@ -1843,81 +1839,6 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
1839
}
1840
}
1841
1846
-function resetChildLanes(completedWork: Fiber) {
1847
- if (
1848
- // TODO: Move this check out of the hot path by moving `resetChildLanes`
1849
- // to switch statement in `completeWork`.
1850
- (completedWork.tag === LegacyHiddenComponent ||
1851
- completedWork.tag === OffscreenComponent) &&
1852
- completedWork.memoizedState !== null &&
1853
- !includesSomeLane(subtreeRenderLanes, (OffscreenLane: Lane)) &&
1854
- (completedWork.mode & ConcurrentMode) !== NoLanes
1855
- ) {
1856
- // The children of this component are hidden. Don't bubble their
1857
- // expiration times.
1858
- return;
1859
- }
1860
-
1861
- let newChildLanes = NoLanes;
1862
-
1863
- // Bubble up the earliest expiration time.
1864
- if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
1865
- // In profiling mode, resetChildExpirationTime is also used to reset
1866
- // profiler durations.
1867
- let actualDuration = completedWork.actualDuration;
1868
- let treeBaseDuration = ((completedWork.selfBaseDuration: any): number);
1869
-
1870
- // When a fiber is cloned, its actualDuration is reset to 0. This value will
1871
- // only be updated if work is done on the fiber (i.e. it doesn't bailout).
1872
- // When work is done, it should bubble to the parent's actualDuration. If
1873
- // the fiber has not been cloned though, (meaning no work was done), then
1874
- // this value will reflect the amount of time spent working on a previous
1875
- // render. In that case it should not bubble. We determine whether it was
1876
- // cloned by comparing the child pointer.
1877
- const shouldBubbleActualDurations =
1878
- completedWork.alternate === null ||
1879
- completedWork.child !== completedWork.alternate.child;
1880
-
1881
- let child = completedWork.child;
1882
- while (child !== null) {
1883
- newChildLanes = mergeLanes(
1884
- newChildLanes,
1885
- mergeLanes(child.lanes, child.childLanes),
1886
- );
1887
- if (shouldBubbleActualDurations) {
1888
- actualDuration += child.actualDuration;
1889
- }
1890
- treeBaseDuration += child.treeBaseDuration;
1891
- child = child.sibling;
1892
- }
1893
-
1894
- const isTimedOutSuspense =
1895
- completedWork.tag === SuspenseComponent &&
1896
- completedWork.memoizedState !== null;
1897
- if (isTimedOutSuspense) {
1898
- // Don't count time spent in a timed out Suspense subtree as part of the base duration.
1899
- const primaryChildFragment = completedWork.child;
1900
- if (primaryChildFragment !== null) {
1901
- treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number);
1902
- }
1903
- }
1904
-
1905
- completedWork.actualDuration = actualDuration;
1906
- completedWork.treeBaseDuration = treeBaseDuration;
1907
- } else {
1908
- let child = completedWork.child;
1909
- while (child !== null) {
1910
- newChildLanes = mergeLanes(
1911
- newChildLanes,
1912
- mergeLanes(child.lanes, child.childLanes),
1913
- );
1914
- child = child.sibling;
1915
- }
1916
- }
1917
-
1918
- completedWork.childLanes = newChildLanes;
1919
-}
1920
-
1842
function commitRoot(root) {
1843
const renderPriorityLevel = getCurrentPriorityLevel();
1844
runWithPriority(
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+2
-81
@@ -110,8 +110,6 @@ import {
110
ForwardRef,
111
MemoComponent,
112
SimpleMemoComponent,
113
- OffscreenComponent,
114
- LegacyHiddenComponent,
113
ScopeComponent,
114
Profiler,
115
} from './ReactWorkTags';
@@ -144,7 +142,6 @@ import {
142
NoLane,
143
SyncLane,
144
SyncBatchedLane,
147
- OffscreenLane,
145
NoTimestamp,
146
findUpdateLane,
147
findTransitionLane,
@@ -285,7 +282,7 @@ let workInProgressRootRenderLanes: Lanes = NoLanes;
282
//
283
// Most things in the work loop should deal with workInProgressRootRenderLanes.
284
// Most things in begin/complete phases should deal with subtreeRenderLanes.
288
-let subtreeRenderLanes: Lanes = NoLanes;
285
+export let subtreeRenderLanes: Lanes = NoLanes;
286
const subtreeRenderLanesCursor: StackCursor<Lanes> = createCursor(NoLanes);
287
288
// Whether to root completed, errored, suspended, etc.
@@ -1742,8 +1739,6 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
1739
return;
1740
}
1741
1745
- resetChildLanes(completedWork);
1746
-
1742
if (
1743
returnFiber !== null &&
1744
// Do not append effects to parents if a sibling failed to complete
@@ -1821,6 +1816,7 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
1816
// Mark the parent fiber as incomplete and clear its effect list.
1817
returnFiber.firstEffect = returnFiber.lastEffect = null;
1818
returnFiber.flags |= Incomplete;
1819
+ returnFiber.subtreeFlags = NoFlags;
1820
returnFiber.deletions = null;
1821
}
1822
}
@@ -1843,81 +1839,6 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
1839
}
1840
}
1841
1846
-function resetChildLanes(completedWork: Fiber) {
1847
- if (
1848
- // TODO: Move this check out of the hot path by moving `resetChildLanes`
1849
- // to switch statement in `completeWork`.
1850
- (completedWork.tag === LegacyHiddenComponent ||
1851
- completedWork.tag === OffscreenComponent) &&
1852
- completedWork.memoizedState !== null &&
1853
- !includesSomeLane(subtreeRenderLanes, (OffscreenLane: Lane)) &&
1854
- (completedWork.mode & ConcurrentMode) !== NoLanes
1855
- ) {
1856
- // The children of this component are hidden. Don't bubble their
1857
- // expiration times.
1858
- return;
1859
- }
1860
-
1861
- let newChildLanes = NoLanes;
1862
-
1863
- // Bubble up the earliest expiration time.
1864
- if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
1865
- // In profiling mode, resetChildExpirationTime is also used to reset
1866
- // profiler durations.
1867
- let actualDuration = completedWork.actualDuration;
1868
- let treeBaseDuration = ((completedWork.selfBaseDuration: any): number);
1869
-
1870
- // When a fiber is cloned, its actualDuration is reset to 0. This value will
1871
- // only be updated if work is done on the fiber (i.e. it doesn't bailout).
1872
- // When work is done, it should bubble to the parent's actualDuration. If
1873
- // the fiber has not been cloned though, (meaning no work was done), then
1874
- // this value will reflect the amount of time spent working on a previous
1875
- // render. In that case it should not bubble. We determine whether it was
1876
- // cloned by comparing the child pointer.
1877
- const shouldBubbleActualDurations =
1878
- completedWork.alternate === null ||
1879
- completedWork.child !== completedWork.alternate.child;
1880
-
1881
- let child = completedWork.child;
1882
- while (child !== null) {
1883
- newChildLanes = mergeLanes(
1884
- newChildLanes,
1885
- mergeLanes(child.lanes, child.childLanes),
1886
- );
1887
- if (shouldBubbleActualDurations) {
1888
- actualDuration += child.actualDuration;
1889
- }
1890
- treeBaseDuration += child.treeBaseDuration;
1891
- child = child.sibling;
1892
- }
1893
-
1894
- const isTimedOutSuspense =
1895
- completedWork.tag === SuspenseComponent &&
1896
- completedWork.memoizedState !== null;
1897
- if (isTimedOutSuspense) {
1898
- // Don't count time spent in a timed out Suspense subtree as part of the base duration.
1899
- const primaryChildFragment = completedWork.child;
1900
- if (primaryChildFragment !== null) {
1901
- treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number);
1902
- }
1903
- }
1904
-
1905
- completedWork.actualDuration = actualDuration;
1906
- completedWork.treeBaseDuration = treeBaseDuration;
1907
- } else {
1908
- let child = completedWork.child;
1909
- while (child !== null) {
1910
- newChildLanes = mergeLanes(
1911
- newChildLanes,
1912
- mergeLanes(child.lanes, child.childLanes),
1913
- );
1914
- child = child.sibling;
1915
- }
1916
- }
1917
-
1918
- completedWork.childLanes = newChildLanes;
1919
-}
1920
-
1842
function commitRoot(root) {
1843
const renderPriorityLevel = getCurrentPriorityLevel();
1844
runWithPriority(