@samitouri / QOS-React / commits / 4ebaeae40d

moved mutation code to passive (#24251)

This PR moves the code for transition tracing in the mutation phase that adds transitions to the pending callbacks object (to be called sometime later after paint) from the mutation to the passive phase. Things to think about: Passive effects can be flushed before or after paint. How do we make sure that we get the correct end time for the interaction?

Luna Ruan committed Apr 8, 2022 at 11:28 UTC 4ebaeae40dec52c84c968a25f1f21f4d0f571336
6 files changed +176 -104
packages/react-reconciler/src/ReactFiberCommitWork.new.js
+33 -30
@@ -2227,32 +2227,6 @@ function commitMutationEffectsOnFiber(
2227 // because of the shared reconciliation logic below.
2228 const flags = finishedWork.flags;
2229
2230 - if (enableTransitionTracing) {
2231 - switch (finishedWork.tag) {
2232 - case HostRoot: {
2233 - const state = finishedWork.memoizedState;
2234 - const transitions = state.transitions;
2235 - if (transitions !== null) {
2236 - transitions.forEach(transition => {
2237 - // TODO(luna) Do we want to log TransitionStart in the startTransition callback instead?
2238 - addTransitionStartCallbackToPendingTransition({
2239 - transitionName: transition.name,
2240 - startTime: transition.startTime,
2241 - });
2242 -
2243 - addTransitionCompleteCallbackToPendingTransition({
2244 - transitionName: transition.name,
2245 - startTime: transition.startTime,
2246 - });
2247 - });
2248 -
2249 - clearTransitionsForLanes(root, lanes);
2250 - state.transitions = null;
2251 - }
2252 - }
2253 - }
2254 - }
2255 -
2230 if (flags & ContentReset) {
2231 commitResetTextContent(finishedWork);
2232 }
@@ -2639,12 +2613,17 @@ function reappearLayoutEffects_complete(subtreeRoot: Fiber) {
2613 export function commitPassiveMountEffects(
2614 root: FiberRoot,
2615 finishedWork: Fiber,
2616 + committedLanes: Lanes,
2617 ): void {
2618 nextEffect = finishedWork;
2644 - commitPassiveMountEffects_begin(finishedWork, root);
2619 + commitPassiveMountEffects_begin(finishedWork, root, committedLanes);
2620 }
2621
2647 -function commitPassiveMountEffects_begin(subtreeRoot: Fiber, root: FiberRoot) {
2622 +function commitPassiveMountEffects_begin(
2623 + subtreeRoot: Fiber,
2624 + root: FiberRoot,
2625 + committedLanes: Lanes,
2626 +) {
2627 while (nextEffect !== null) {
2628 const fiber = nextEffect;
2629 const firstChild = fiber.child;
@@ -2652,7 +2631,7 @@ function commitPassiveMountEffects_begin(subtreeRoot: Fiber, root: FiberRoot) {
2631 ensureCorrectReturnPointer(firstChild, fiber);
2632 nextEffect = firstChild;
2633 } else {
2655 - commitPassiveMountEffects_complete(subtreeRoot, root);
2634 + commitPassiveMountEffects_complete(subtreeRoot, root, committedLanes);
2635 }
2636 }
2637 }
@@ -2660,13 +2639,15 @@ function commitPassiveMountEffects_begin(subtreeRoot: Fiber, root: FiberRoot) {
2639 function commitPassiveMountEffects_complete(
2640 subtreeRoot: Fiber,
2641 root: FiberRoot,
2642 + committedLanes: Lanes,
2643 ) {
2644 while (nextEffect !== null) {
2645 const fiber = nextEffect;
2646 +
2647 if ((fiber.flags & Passive) !== NoFlags) {
2648 setCurrentDebugFiberInDEV(fiber);
2649 try {
2669 - commitPassiveMountOnFiber(root, fiber);
2650 + commitPassiveMountOnFiber(root, fiber, committedLanes);
2651 } catch (error) {
2652 reportUncaughtErrorInDEV(error);
2653 captureCommitPhaseError(fiber, fiber.return, error);
@@ -2693,6 +2674,7 @@ function commitPassiveMountEffects_complete(
2674 function commitPassiveMountOnFiber(
2675 finishedRoot: FiberRoot,
2676 finishedWork: Fiber,
2677 + committedLanes: Lanes,
2678 ): void {
2679 switch (finishedWork.tag) {
2680 case FunctionComponent:
@@ -2734,6 +2716,27 @@ function commitPassiveMountOnFiber(
2716 }
2717 }
2718 }
2719 +
2720 + if (enableTransitionTracing) {
2721 + const transitions = finishedWork.memoizedState.transitions;
2722 + if (transitions !== null) {
2723 + transitions.forEach(transition => {
2724 + // TODO(luna) Do we want to log TransitionStart in the startTransition callback instead?
2725 + addTransitionStartCallbackToPendingTransition({
2726 + transitionName: transition.name,
2727 + startTime: transition.startTime,
2728 + });
2729 +
2730 + addTransitionCompleteCallbackToPendingTransition({
2731 + transitionName: transition.name,
2732 + startTime: transition.startTime,
2733 + });
2734 + });
2735 +
2736 + clearTransitionsForLanes(finishedRoot, committedLanes);
2737 + finishedWork.memoizedState.transitions = null;
2738 + }
2739 + }
2740 break;
2741 }
2742 case LegacyHiddenComponent:
packages/react-reconciler/src/ReactFiberCommitWork.old.js
+33 -30
@@ -2227,32 +2227,6 @@ function commitMutationEffectsOnFiber(
2227 // because of the shared reconciliation logic below.
2228 const flags = finishedWork.flags;
2229
2230 - if (enableTransitionTracing) {
2231 - switch (finishedWork.tag) {
2232 - case HostRoot: {
2233 - const state = finishedWork.memoizedState;
2234 - const transitions = state.transitions;
2235 - if (transitions !== null) {
2236 - transitions.forEach(transition => {
2237 - // TODO(luna) Do we want to log TransitionStart in the startTransition callback instead?
2238 - addTransitionStartCallbackToPendingTransition({
2239 - transitionName: transition.name,
2240 - startTime: transition.startTime,
2241 - });
2242 -
2243 - addTransitionCompleteCallbackToPendingTransition({
2244 - transitionName: transition.name,
2245 - startTime: transition.startTime,
2246 - });
2247 - });
2248 -
2249 - clearTransitionsForLanes(root, lanes);
2250 - state.transitions = null;
2251 - }
2252 - }
2253 - }
2254 - }
2255 -
2230 if (flags & ContentReset) {
2231 commitResetTextContent(finishedWork);
2232 }
@@ -2639,12 +2613,17 @@ function reappearLayoutEffects_complete(subtreeRoot: Fiber) {
2613 export function commitPassiveMountEffects(
2614 root: FiberRoot,
2615 finishedWork: Fiber,
2616 + committedLanes: Lanes,
2617 ): void {
2618 nextEffect = finishedWork;
2644 - commitPassiveMountEffects_begin(finishedWork, root);
2619 + commitPassiveMountEffects_begin(finishedWork, root, committedLanes);
2620 }
2621
2647 -function commitPassiveMountEffects_begin(subtreeRoot: Fiber, root: FiberRoot) {
2622 +function commitPassiveMountEffects_begin(
2623 + subtreeRoot: Fiber,
2624 + root: FiberRoot,
2625 + committedLanes: Lanes,
2626 +) {
2627 while (nextEffect !== null) {
2628 const fiber = nextEffect;
2629 const firstChild = fiber.child;
@@ -2652,7 +2631,7 @@ function commitPassiveMountEffects_begin(subtreeRoot: Fiber, root: FiberRoot) {
2631 ensureCorrectReturnPointer(firstChild, fiber);
2632 nextEffect = firstChild;
2633 } else {
2655 - commitPassiveMountEffects_complete(subtreeRoot, root);
2634 + commitPassiveMountEffects_complete(subtreeRoot, root, committedLanes);
2635 }
2636 }
2637 }
@@ -2660,13 +2639,15 @@ function commitPassiveMountEffects_begin(subtreeRoot: Fiber, root: FiberRoot) {
2639 function commitPassiveMountEffects_complete(
2640 subtreeRoot: Fiber,
2641 root: FiberRoot,
2642 + committedLanes: Lanes,
2643 ) {
2644 while (nextEffect !== null) {
2645 const fiber = nextEffect;
2646 +
2647 if ((fiber.flags & Passive) !== NoFlags) {
2648 setCurrentDebugFiberInDEV(fiber);
2649 try {
2669 - commitPassiveMountOnFiber(root, fiber);
2650 + commitPassiveMountOnFiber(root, fiber, committedLanes);
2651 } catch (error) {
2652 reportUncaughtErrorInDEV(error);
2653 captureCommitPhaseError(fiber, fiber.return, error);
@@ -2693,6 +2674,7 @@ function commitPassiveMountEffects_complete(
2674 function commitPassiveMountOnFiber(
2675 finishedRoot: FiberRoot,
2676 finishedWork: Fiber,
2677 + committedLanes: Lanes,
2678 ): void {
2679 switch (finishedWork.tag) {
2680 case FunctionComponent:
@@ -2734,6 +2716,27 @@ function commitPassiveMountOnFiber(
2716 }
2717 }
2718 }
2719 +
2720 + if (enableTransitionTracing) {
2721 + const transitions = finishedWork.memoizedState.transitions;
2722 + if (transitions !== null) {
2723 + transitions.forEach(transition => {
2724 + // TODO(luna) Do we want to log TransitionStart in the startTransition callback instead?
2725 + addTransitionStartCallbackToPendingTransition({
2726 + transitionName: transition.name,
2727 + startTime: transition.startTime,
2728 + });
2729 +
2730 + addTransitionCompleteCallbackToPendingTransition({
2731 + transitionName: transition.name,
2732 + startTime: transition.startTime,
2733 + });
2734 + });
2735 +
2736 + clearTransitionsForLanes(finishedRoot, committedLanes);
2737 + finishedWork.memoizedState.transitions = null;
2738 + }
2739 + }
2740 break;
2741 }
2742 case LegacyHiddenComponent:
packages/react-reconciler/src/ReactFiberCompleteWork.new.js
+26
@@ -153,6 +153,7 @@ import {
153 popRenderLanes,
154 getRenderTargetTime,
155 subtreeRenderLanes,
156 + getWorkInProgressTransitions,
157 } from './ReactFiberWorkLoop.new';
158 import {
159 OffscreenLane,
@@ -862,6 +863,17 @@ function completeWork(
863 }
864 case HostRoot: {
865 const fiberRoot = (workInProgress.stateNode: FiberRoot);
866 +
867 + if (enableTransitionTracing) {
868 + const transitions = getWorkInProgressTransitions();
869 + // We set the Passive flag here because if there are new transitions,
870 + // we will need to schedule callbacks and process the transitions,
871 + // which we do in the passive phase
872 + if (transitions !== null) {
873 + workInProgress.flags |= Passive;
874 + }
875 + }
876 +
877 if (enableCache) {
878 popRootTransition(fiberRoot, renderLanes);
879
@@ -918,6 +930,14 @@ function completeWork(
930 }
931 updateHostContainer(current, workInProgress);
932 bubbleProperties(workInProgress);
933 + if (enableTransitionTracing) {
934 + if ((workInProgress.subtreeFlags & Visibility) !== NoFlags) {
935 + // If any of our suspense children toggle visibility, this means that
936 + // the pending boundaries array needs to be updated, which we only
937 + // do in the passive phase.
938 + workInProgress.flags |= Passive;
939 + }
940 + }
941 return null;
942 }
943 case HostComponent: {
@@ -1187,6 +1207,12 @@ function completeWork(
1207 const offscreenFiber: Fiber = (workInProgress.child: any);
1208 offscreenFiber.flags |= Visibility;
1209
1210 + // If the suspended state of the boundary changes, we need to schedule
1211 + // a passive effect, which is when we process the transitions
1212 + if (enableTransitionTracing) {
1213 + offscreenFiber.flags |= Passive;
1214 + }
1215 +
1216 // TODO: This will still suspend a synchronous tree if anything
1217 // in the concurrent tree already suspended during this render.
1218 // This is a known bug.
packages/react-reconciler/src/ReactFiberCompleteWork.old.js
+26
@@ -153,6 +153,7 @@ import {
153 popRenderLanes,
154 getRenderTargetTime,
155 subtreeRenderLanes,
156 + getWorkInProgressTransitions,
157 } from './ReactFiberWorkLoop.old';
158 import {
159 OffscreenLane,
@@ -862,6 +863,17 @@ function completeWork(
863 }
864 case HostRoot: {
865 const fiberRoot = (workInProgress.stateNode: FiberRoot);
866 +
867 + if (enableTransitionTracing) {
868 + const transitions = getWorkInProgressTransitions();
869 + // We set the Passive flag here because if there are new transitions,
870 + // we will need to schedule callbacks and process the transitions,
871 + // which we do in the passive phase
872 + if (transitions !== null) {
873 + workInProgress.flags |= Passive;
874 + }
875 + }
876 +
877 if (enableCache) {
878 popRootTransition(fiberRoot, renderLanes);
879
@@ -918,6 +930,14 @@ function completeWork(
930 }
931 updateHostContainer(current, workInProgress);
932 bubbleProperties(workInProgress);
933 + if (enableTransitionTracing) {
934 + if ((workInProgress.subtreeFlags & Visibility) !== NoFlags) {
935 + // If any of our suspense children toggle visibility, this means that
936 + // the pending boundaries array needs to be updated, which we only
937 + // do in the passive phase.
938 + workInProgress.flags |= Passive;
939 + }
940 + }
941 return null;
942 }
943 case HostComponent: {
@@ -1187,6 +1207,12 @@ function completeWork(
1207 const offscreenFiber: Fiber = (workInProgress.child: any);
1208 offscreenFiber.flags |= Visibility;
1209
1210 + // If the suspended state of the boundary changes, we need to schedule
1211 + // a passive effect, which is when we process the transitions
1212 + if (enableTransitionTracing) {
1213 + offscreenFiber.flags |= Passive;
1214 + }
1215 +
1216 // TODO: This will still suspend a synchronous tree if anything
1217 // in the concurrent tree already suspended during this render.
1218 // This is a known bug.
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+29 -22
@@ -2322,27 +2322,6 @@ function commitRootImpl(
2322 // If layout work was scheduled, flush it now.
2323 flushSyncCallbacks();
2324
2325 - if (enableTransitionTracing) {
2326 - const prevPendingTransitionCallbacks = currentPendingTransitionCallbacks;
2327 - const prevRootTransitionCallbacks = root.transitionCallbacks;
2328 - if (
2329 - prevPendingTransitionCallbacks !== null &&
2330 - prevRootTransitionCallbacks !== null
2331 - ) {
2332 - // TODO(luna) Refactor this code into the Host Config
2333 - const endTime = now();
2334 - currentPendingTransitionCallbacks = null;
2335 -
2336 - scheduleCallback(IdleSchedulerPriority, () =>
2337 - processTransitionCallbacks(
2338 - prevPendingTransitionCallbacks,
2339 - endTime,
2340 - prevRootTransitionCallbacks,
2341 - ),
2342 - );
2343 - }
2344 - }
2345 -
2325 if (__DEV__) {
2326 if (enableDebugTracing) {
2327 logCommitStopped();
@@ -2457,7 +2436,7 @@ function flushPassiveEffectsImpl() {
2436 executionContext |= CommitContext;
2437
2438 commitPassiveUnmountEffects(root.current);
2460 - commitPassiveMountEffects(root, root.current);
2439 + commitPassiveMountEffects(root, root.current, lanes);
2440
2441 // TODO: Move to commitPassiveMountEffects
2442 if (enableProfilerTimer && enableProfilerCommitHooks) {
@@ -2487,6 +2466,34 @@ function flushPassiveEffectsImpl() {
2466
2467 flushSyncCallbacks();
2468
2469 + if (enableTransitionTracing) {
2470 + const prevPendingTransitionCallbacks = currentPendingTransitionCallbacks;
2471 + const prevRootTransitionCallbacks = root.transitionCallbacks;
2472 + if (
2473 + prevPendingTransitionCallbacks !== null &&
2474 + prevRootTransitionCallbacks !== null
2475 + ) {
2476 + // TODO(luna) Refactor this code into the Host Config
2477 + // TODO(luna) The end time here is not necessarily accurate
2478 + // because passive effects could be called before paint
2479 + // (synchronously) or after paint (normally). We need
2480 + // to come up with a way to get the correct end time for both cases.
2481 + // One solution is in the host config, if the passive effects
2482 + // have not yet been run, make a call to flush the passive effects
2483 + // right after paint.
2484 + const endTime = now();
2485 + currentPendingTransitionCallbacks = null;
2486 +
2487 + scheduleCallback(IdleSchedulerPriority, () =>
2488 + processTransitionCallbacks(
2489 + prevPendingTransitionCallbacks,
2490 + endTime,
2491 + prevRootTransitionCallbacks,
2492 + ),
2493 + );
2494 + }
2495 + }
2496 +
2497 if (__DEV__) {
2498 // If additional passive effects were scheduled, increment a counter. If this
2499 // exceeds the limit, we'll fire a warning.
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+29 -22
@@ -2322,27 +2322,6 @@ function commitRootImpl(
2322 // If layout work was scheduled, flush it now.
2323 flushSyncCallbacks();
2324
2325 - if (enableTransitionTracing) {
2326 - const prevPendingTransitionCallbacks = currentPendingTransitionCallbacks;
2327 - const prevRootTransitionCallbacks = root.transitionCallbacks;
2328 - if (
2329 - prevPendingTransitionCallbacks !== null &&
2330 - prevRootTransitionCallbacks !== null
2331 - ) {
2332 - // TODO(luna) Refactor this code into the Host Config
2333 - const endTime = now();
2334 - currentPendingTransitionCallbacks = null;
2335 -
2336 - scheduleCallback(IdleSchedulerPriority, () =>
2337 - processTransitionCallbacks(
2338 - prevPendingTransitionCallbacks,
2339 - endTime,
2340 - prevRootTransitionCallbacks,
2341 - ),
2342 - );
2343 - }
2344 - }
2345 -
2325 if (__DEV__) {
2326 if (enableDebugTracing) {
2327 logCommitStopped();
@@ -2457,7 +2436,7 @@ function flushPassiveEffectsImpl() {
2436 executionContext |= CommitContext;
2437
2438 commitPassiveUnmountEffects(root.current);
2460 - commitPassiveMountEffects(root, root.current);
2439 + commitPassiveMountEffects(root, root.current, lanes);
2440
2441 // TODO: Move to commitPassiveMountEffects
2442 if (enableProfilerTimer && enableProfilerCommitHooks) {
@@ -2487,6 +2466,34 @@ function flushPassiveEffectsImpl() {
2466
2467 flushSyncCallbacks();
2468
2469 + if (enableTransitionTracing) {
2470 + const prevPendingTransitionCallbacks = currentPendingTransitionCallbacks;
2471 + const prevRootTransitionCallbacks = root.transitionCallbacks;
2472 + if (
2473 + prevPendingTransitionCallbacks !== null &&
2474 + prevRootTransitionCallbacks !== null
2475 + ) {
2476 + // TODO(luna) Refactor this code into the Host Config
2477 + // TODO(luna) The end time here is not necessarily accurate
2478 + // because passive effects could be called before paint
2479 + // (synchronously) or after paint (normally). We need
2480 + // to come up with a way to get the correct end time for both cases.
2481 + // One solution is in the host config, if the passive effects
2482 + // have not yet been run, make a call to flush the passive effects
2483 + // right after paint.
2484 + const endTime = now();
2485 + currentPendingTransitionCallbacks = null;
2486 +
2487 + scheduleCallback(IdleSchedulerPriority, () =>
2488 + processTransitionCallbacks(
2489 + prevPendingTransitionCallbacks,
2490 + endTime,
2491 + prevRootTransitionCallbacks,
2492 + ),
2493 + );
2494 + }
2495 + }
2496 +
2497 if (__DEV__) {
2498 // If additional passive effects were scheduled, increment a counter. If this
2499 // exceeds the limit, we'll fire a warning.