1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @flow
    
  8.  */
    
  9. 
    
  10. import type {
    
  11.   TransitionTracingCallbacks,
    
  12.   Fiber,
    
  13.   FiberRoot,
    
  14. } from './ReactInternalTypes';
    
  15. import type {OffscreenInstance} from './ReactFiberActivityComponent';
    
  16. import type {StackCursor} from './ReactFiberStack';
    
  17. 
    
  18. import {enableTransitionTracing} from 'shared/ReactFeatureFlags';
    
  19. import {createCursor, push, pop} from './ReactFiberStack';
    
  20. import {getWorkInProgressTransitions} from './ReactFiberWorkLoop';
    
  21. 
    
  22. export type SuspenseInfo = {name: string | null};
    
  23. 
    
  24. export type PendingTransitionCallbacks = {
    
  25.   transitionStart: Array<Transition> | null,
    
  26.   transitionProgress: Map<Transition, PendingBoundaries> | null,
    
  27.   transitionComplete: Array<Transition> | null,
    
  28.   markerProgress: Map<
    
  29.     string,
    
  30.     {pendingBoundaries: PendingBoundaries, transitions: Set<Transition>},
    
  31.   > | null,
    
  32.   markerIncomplete: Map<
    
  33.     string,
    
  34.     {aborts: Array<TransitionAbort>, transitions: Set<Transition>},
    
  35.   > | null,
    
  36.   markerComplete: Map<string, Set<Transition>> | null,
    
  37. };
    
  38. 
    
  39. export type Transition = {
    
  40.   name: string,
    
  41.   startTime: number,
    
  42. };
    
  43. 
    
  44. export type BatchConfigTransition = {
    
  45.   name?: string,
    
  46.   startTime?: number,
    
  47.   _updatedFibers?: Set<Fiber>,
    
  48. };
    
  49. 
    
  50. // TODO: Is there a way to not include the tag or name here?
    
  51. export type TracingMarkerInstance = {
    
  52.   tag?: TracingMarkerTag,
    
  53.   transitions: Set<Transition> | null,
    
  54.   pendingBoundaries: PendingBoundaries | null,
    
  55.   aborts: Array<TransitionAbort> | null,
    
  56.   name: string | null,
    
  57. };
    
  58. 
    
  59. export type TransitionAbort = {
    
  60.   reason: 'error' | 'unknown' | 'marker' | 'suspense',
    
  61.   name?: string | null,
    
  62. };
    
  63. 
    
  64. export const TransitionRoot = 0;
    
  65. export const TransitionTracingMarker = 1;
    
  66. export type TracingMarkerTag = 0 | 1;
    
  67. 
    
  68. export type PendingBoundaries = Map<OffscreenInstance, SuspenseInfo>;
    
  69. 
    
  70. export function processTransitionCallbacks(
    
  71.   pendingTransitions: PendingTransitionCallbacks,
    
  72.   endTime: number,
    
  73.   callbacks: TransitionTracingCallbacks,
    
  74. ): void {
    
  75.   if (enableTransitionTracing) {
    
  76.     if (pendingTransitions !== null) {
    
  77.       const transitionStart = pendingTransitions.transitionStart;
    
  78.       const onTransitionStart = callbacks.onTransitionStart;
    
  79.       if (transitionStart !== null && onTransitionStart != null) {
    
  80.         transitionStart.forEach(transition =>
    
  81.           onTransitionStart(transition.name, transition.startTime),
    
  82.         );
    
  83.       }
    
  84. 
    
  85.       const markerProgress = pendingTransitions.markerProgress;
    
  86.       const onMarkerProgress = callbacks.onMarkerProgress;
    
  87.       if (onMarkerProgress != null && markerProgress !== null) {
    
  88.         markerProgress.forEach((markerInstance, markerName) => {
    
  89.           if (markerInstance.transitions !== null) {
    
  90.             // TODO: Clone the suspense object so users can't modify it
    
  91.             const pending =
    
  92.               markerInstance.pendingBoundaries !== null
    
  93.                 ? Array.from(markerInstance.pendingBoundaries.values())
    
  94.                 : [];
    
  95.             markerInstance.transitions.forEach(transition => {
    
  96.               onMarkerProgress(
    
  97.                 transition.name,
    
  98.                 markerName,
    
  99.                 transition.startTime,
    
  100.                 endTime,
    
  101.                 pending,
    
  102.               );
    
  103.             });
    
  104.           }
    
  105.         });
    
  106.       }
    
  107. 
    
  108.       const markerComplete = pendingTransitions.markerComplete;
    
  109.       const onMarkerComplete = callbacks.onMarkerComplete;
    
  110.       if (markerComplete !== null && onMarkerComplete != null) {
    
  111.         markerComplete.forEach((transitions, markerName) => {
    
  112.           transitions.forEach(transition => {
    
  113.             onMarkerComplete(
    
  114.               transition.name,
    
  115.               markerName,
    
  116.               transition.startTime,
    
  117.               endTime,
    
  118.             );
    
  119.           });
    
  120.         });
    
  121.       }
    
  122. 
    
  123.       const markerIncomplete = pendingTransitions.markerIncomplete;
    
  124.       const onMarkerIncomplete = callbacks.onMarkerIncomplete;
    
  125.       if (onMarkerIncomplete != null && markerIncomplete !== null) {
    
  126.         markerIncomplete.forEach(({transitions, aborts}, markerName) => {
    
  127.           transitions.forEach(transition => {
    
  128.             const filteredAborts = [];
    
  129.             aborts.forEach(abort => {
    
  130.               switch (abort.reason) {
    
  131.                 case 'marker': {
    
  132.                   filteredAborts.push({
    
  133.                     type: 'marker',
    
  134.                     name: abort.name,
    
  135.                     endTime,
    
  136.                   });
    
  137.                   break;
    
  138.                 }
    
  139.                 case 'suspense': {
    
  140.                   filteredAborts.push({
    
  141.                     type: 'suspense',
    
  142.                     name: abort.name,
    
  143.                     endTime,
    
  144.                   });
    
  145.                   break;
    
  146.                 }
    
  147.                 default: {
    
  148.                   break;
    
  149.                 }
    
  150.               }
    
  151.             });
    
  152. 
    
  153.             if (filteredAborts.length > 0) {
    
  154.               onMarkerIncomplete(
    
  155.                 transition.name,
    
  156.                 markerName,
    
  157.                 transition.startTime,
    
  158.                 filteredAborts,
    
  159.               );
    
  160.             }
    
  161.           });
    
  162.         });
    
  163.       }
    
  164. 
    
  165.       const transitionProgress = pendingTransitions.transitionProgress;
    
  166.       const onTransitionProgress = callbacks.onTransitionProgress;
    
  167.       if (onTransitionProgress != null && transitionProgress !== null) {
    
  168.         transitionProgress.forEach((pending, transition) => {
    
  169.           onTransitionProgress(
    
  170.             transition.name,
    
  171.             transition.startTime,
    
  172.             endTime,
    
  173.             Array.from(pending.values()),
    
  174.           );
    
  175.         });
    
  176.       }
    
  177. 
    
  178.       const transitionComplete = pendingTransitions.transitionComplete;
    
  179.       const onTransitionComplete = callbacks.onTransitionComplete;
    
  180.       if (transitionComplete !== null && onTransitionComplete != null) {
    
  181.         transitionComplete.forEach(transition =>
    
  182.           onTransitionComplete(transition.name, transition.startTime, endTime),
    
  183.         );
    
  184.       }
    
  185.     }
    
  186.   }
    
  187. }
    
  188. 
    
  189. // For every tracing marker, store a pointer to it. We will later access it
    
  190. // to get the set of suspense boundaries that need to resolve before the
    
  191. // tracing marker can be logged as complete
    
  192. // This code lives separate from the ReactFiberTransition code because
    
  193. // we push and pop on the tracing marker, not the suspense boundary
    
  194. const markerInstanceStack: StackCursor<Array<TracingMarkerInstance> | null> =
    
  195.   createCursor(null);
    
  196. 
    
  197. export function pushRootMarkerInstance(workInProgress: Fiber): void {
    
  198.   if (enableTransitionTracing) {
    
  199.     // On the root, every transition gets mapped to it's own map of
    
  200.     // suspense boundaries. The transition is marked as complete when
    
  201.     // the suspense boundaries map is empty. We do this because every
    
  202.     // transition completes at different times and depends on different
    
  203.     // suspense boundaries to complete. We store all the transitions
    
  204.     // along with its map of suspense boundaries in the root incomplete
    
  205.     // transitions map. Each entry in this map functions like a tracing
    
  206.     // marker does, so we can push it onto the marker instance stack
    
  207.     const transitions = getWorkInProgressTransitions();
    
  208.     const root: FiberRoot = workInProgress.stateNode;
    
  209. 
    
  210.     if (transitions !== null) {
    
  211.       transitions.forEach(transition => {
    
  212.         if (!root.incompleteTransitions.has(transition)) {
    
  213.           const markerInstance: TracingMarkerInstance = {
    
  214.             tag: TransitionRoot,
    
  215.             transitions: new Set([transition]),
    
  216.             pendingBoundaries: null,
    
  217.             aborts: null,
    
  218.             name: null,
    
  219.           };
    
  220.           root.incompleteTransitions.set(transition, markerInstance);
    
  221.         }
    
  222.       });
    
  223.     }
    
  224. 
    
  225.     const markerInstances = [];
    
  226.     // For ever transition on the suspense boundary, we push the transition
    
  227.     // along with its map of pending suspense boundaries onto the marker
    
  228.     // instance stack.
    
  229.     root.incompleteTransitions.forEach(markerInstance => {
    
  230.       markerInstances.push(markerInstance);
    
  231.     });
    
  232.     push(markerInstanceStack, markerInstances, workInProgress);
    
  233.   }
    
  234. }
    
  235. 
    
  236. export function popRootMarkerInstance(workInProgress: Fiber) {
    
  237.   if (enableTransitionTracing) {
    
  238.     pop(markerInstanceStack, workInProgress);
    
  239.   }
    
  240. }
    
  241. 
    
  242. export function pushMarkerInstance(
    
  243.   workInProgress: Fiber,
    
  244.   markerInstance: TracingMarkerInstance,
    
  245. ): void {
    
  246.   if (enableTransitionTracing) {
    
  247.     if (markerInstanceStack.current === null) {
    
  248.       push(markerInstanceStack, [markerInstance], workInProgress);
    
  249.     } else {
    
  250.       push(
    
  251.         markerInstanceStack,
    
  252.         markerInstanceStack.current.concat(markerInstance),
    
  253.         workInProgress,
    
  254.       );
    
  255.     }
    
  256.   }
    
  257. }
    
  258. 
    
  259. export function popMarkerInstance(workInProgress: Fiber): void {
    
  260.   if (enableTransitionTracing) {
    
  261.     pop(markerInstanceStack, workInProgress);
    
  262.   }
    
  263. }
    
  264. 
    
  265. export function getMarkerInstances(): Array<TracingMarkerInstance> | null {
    
  266.   if (enableTransitionTracing) {
    
  267.     return markerInstanceStack.current;
    
  268.   }
    
  269.   return null;
    
  270. }