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. import type {Fiber, FiberRoot} from './ReactInternalTypes';
    
  10. import type {Lanes} from './ReactFiberLane';
    
  11. import type {StackCursor} from './ReactFiberStack';
    
  12. import type {Cache, SpawnedCachePool} from './ReactFiberCacheComponent';
    
  13. import type {Transition} from './ReactFiberTracingMarkerComponent';
    
  14. 
    
  15. import {enableCache, enableTransitionTracing} from 'shared/ReactFeatureFlags';
    
  16. import {isPrimaryRenderer} from './ReactFiberConfig';
    
  17. import {createCursor, push, pop} from './ReactFiberStack';
    
  18. import {
    
  19.   getWorkInProgressRoot,
    
  20.   getWorkInProgressTransitions,
    
  21. } from './ReactFiberWorkLoop';
    
  22. import {
    
  23.   createCache,
    
  24.   retainCache,
    
  25.   CacheContext,
    
  26. } from './ReactFiberCacheComponent';
    
  27. 
    
  28. import ReactSharedInternals from 'shared/ReactSharedInternals';
    
  29. 
    
  30. const {ReactCurrentBatchConfig} = ReactSharedInternals;
    
  31. 
    
  32. export const NoTransition = null;
    
  33. 
    
  34. export function requestCurrentTransition(): Transition | null {
    
  35.   return ReactCurrentBatchConfig.transition;
    
  36. }
    
  37. 
    
  38. // When retrying a Suspense/Offscreen boundary, we restore the cache that was
    
  39. // used during the previous render by placing it here, on the stack.
    
  40. const resumedCache: StackCursor<Cache | null> = createCursor(null);
    
  41. 
    
  42. // During the render/synchronous commit phase, we don't actually process the
    
  43. // transitions. Therefore, we want to lazily combine transitions. Instead of
    
  44. // comparing the arrays of transitions when we combine them and storing them
    
  45. // and filtering out the duplicates, we will instead store the unprocessed transitions
    
  46. // in an array and actually filter them in the passive phase.
    
  47. const transitionStack: StackCursor<Array<Transition> | null> =
    
  48.   createCursor(null);
    
  49. 
    
  50. function peekCacheFromPool(): Cache | null {
    
  51.   if (!enableCache) {
    
  52.     return (null: any);
    
  53.   }
    
  54. 
    
  55.   // Check if the cache pool already has a cache we can use.
    
  56. 
    
  57.   // If we're rendering inside a Suspense boundary that is currently hidden,
    
  58.   // we should use the same cache that we used during the previous render, if
    
  59.   // one exists.
    
  60.   const cacheResumedFromPreviousRender = resumedCache.current;
    
  61.   if (cacheResumedFromPreviousRender !== null) {
    
  62.     return cacheResumedFromPreviousRender;
    
  63.   }
    
  64. 
    
  65.   // Otherwise, check the root's cache pool.
    
  66.   const root = (getWorkInProgressRoot(): any);
    
  67.   const cacheFromRootCachePool = root.pooledCache;
    
  68. 
    
  69.   return cacheFromRootCachePool;
    
  70. }
    
  71. 
    
  72. export function requestCacheFromPool(renderLanes: Lanes): Cache {
    
  73.   // Similar to previous function, except if there's not already a cache in the
    
  74.   // pool, we allocate a new one.
    
  75.   const cacheFromPool = peekCacheFromPool();
    
  76.   if (cacheFromPool !== null) {
    
  77.     return cacheFromPool;
    
  78.   }
    
  79. 
    
  80.   // Create a fresh cache and add it to the root cache pool. A cache can have
    
  81.   // multiple owners:
    
  82.   // - A cache pool that lives on the FiberRoot. This is where all fresh caches
    
  83.   //   are originally created (TODO: except during refreshes, until we implement
    
  84.   //   this correctly). The root takes ownership immediately when the cache is
    
  85.   //   created. Conceptually, root.pooledCache is an Option<Arc<Cache>> (owned),
    
  86.   //   and the return value of this function is a &Arc<Cache> (borrowed).
    
  87.   // - One of several fiber types: host root, cache boundary, suspense
    
  88.   //   component. These retain and release in the commit phase.
    
  89. 
    
  90.   const root = (getWorkInProgressRoot(): any);
    
  91.   const freshCache = createCache();
    
  92.   root.pooledCache = freshCache;
    
  93.   retainCache(freshCache);
    
  94.   if (freshCache !== null) {
    
  95.     root.pooledCacheLanes |= renderLanes;
    
  96.   }
    
  97.   return freshCache;
    
  98. }
    
  99. 
    
  100. export function pushRootTransition(
    
  101.   workInProgress: Fiber,
    
  102.   root: FiberRoot,
    
  103.   renderLanes: Lanes,
    
  104. ) {
    
  105.   if (enableTransitionTracing) {
    
  106.     const rootTransitions = getWorkInProgressTransitions();
    
  107.     push(transitionStack, rootTransitions, workInProgress);
    
  108.   }
    
  109. }
    
  110. 
    
  111. export function popRootTransition(
    
  112.   workInProgress: Fiber,
    
  113.   root: FiberRoot,
    
  114.   renderLanes: Lanes,
    
  115. ) {
    
  116.   if (enableTransitionTracing) {
    
  117.     pop(transitionStack, workInProgress);
    
  118.   }
    
  119. }
    
  120. 
    
  121. export function pushTransition(
    
  122.   offscreenWorkInProgress: Fiber,
    
  123.   prevCachePool: SpawnedCachePool | null,
    
  124.   newTransitions: Array<Transition> | null,
    
  125. ): void {
    
  126.   if (enableCache) {
    
  127.     if (prevCachePool === null) {
    
  128.       push(resumedCache, resumedCache.current, offscreenWorkInProgress);
    
  129.     } else {
    
  130.       push(resumedCache, prevCachePool.pool, offscreenWorkInProgress);
    
  131.     }
    
  132.   }
    
  133. 
    
  134.   if (enableTransitionTracing) {
    
  135.     if (transitionStack.current === null) {
    
  136.       push(transitionStack, newTransitions, offscreenWorkInProgress);
    
  137.     } else if (newTransitions === null) {
    
  138.       push(transitionStack, transitionStack.current, offscreenWorkInProgress);
    
  139.     } else {
    
  140.       push(
    
  141.         transitionStack,
    
  142.         transitionStack.current.concat(newTransitions),
    
  143.         offscreenWorkInProgress,
    
  144.       );
    
  145.     }
    
  146.   }
    
  147. }
    
  148. 
    
  149. export function popTransition(workInProgress: Fiber, current: Fiber | null) {
    
  150.   if (current !== null) {
    
  151.     if (enableTransitionTracing) {
    
  152.       pop(transitionStack, workInProgress);
    
  153.     }
    
  154. 
    
  155.     if (enableCache) {
    
  156.       pop(resumedCache, workInProgress);
    
  157.     }
    
  158.   }
    
  159. }
    
  160. 
    
  161. export function getPendingTransitions(): Array<Transition> | null {
    
  162.   if (!enableTransitionTracing) {
    
  163.     return null;
    
  164.   }
    
  165. 
    
  166.   return transitionStack.current;
    
  167. }
    
  168. 
    
  169. export function getSuspendedCache(): SpawnedCachePool | null {
    
  170.   if (!enableCache) {
    
  171.     return null;
    
  172.   }
    
  173.   // This function is called when a Suspense boundary suspends. It returns the
    
  174.   // cache that would have been used to render fresh data during this render,
    
  175.   // if there was any, so that we can resume rendering with the same cache when
    
  176.   // we receive more data.
    
  177.   const cacheFromPool = peekCacheFromPool();
    
  178.   if (cacheFromPool === null) {
    
  179.     return null;
    
  180.   }
    
  181. 
    
  182.   return {
    
  183.     // We must also save the parent, so that when we resume we can detect
    
  184.     // a refresh.
    
  185.     parent: isPrimaryRenderer
    
  186.       ? CacheContext._currentValue
    
  187.       : CacheContext._currentValue2,
    
  188.     pool: cacheFromPool,
    
  189.   };
    
  190. }
    
  191. 
    
  192. export function getOffscreenDeferredCache(): SpawnedCachePool | null {
    
  193.   if (!enableCache) {
    
  194.     return null;
    
  195.   }
    
  196. 
    
  197.   const cacheFromPool = peekCacheFromPool();
    
  198.   if (cacheFromPool === null) {
    
  199.     return null;
    
  200.   }
    
  201. 
    
  202.   return {
    
  203.     // We must also store the parent, so that when we resume we can detect
    
  204.     // a refresh.
    
  205.     parent: isPrimaryRenderer
    
  206.       ? CacheContext._currentValue
    
  207.       : CacheContext._currentValue2,
    
  208.     pool: cacheFromPool,
    
  209.   };
    
  210. }