@samitouri / QOS-React-2 / commits / b66ae09b6e

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(