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 {ReactContext} from 'shared/ReactTypes';
    
  11. import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
    
  12. 
    
  13. import {enableCache} from 'shared/ReactFeatureFlags';
    
  14. import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
    
  15. 
    
  16. import {pushProvider, popProvider} from './ReactFiberNewContext';
    
  17. import * as Scheduler from 'scheduler';
    
  18. 
    
  19. // In environments without AbortController (e.g. tests)
    
  20. // replace it with a lightweight shim that only has the features we use.
    
  21. const AbortControllerLocal: typeof AbortController = enableCache
    
  22.   ? typeof AbortController !== 'undefined'
    
  23.     ? AbortController
    
  24.     : // $FlowFixMe[missing-this-annot]
    
  25.       // $FlowFixMe[prop-missing]
    
  26.       function AbortControllerShim() {
    
  27.         const listeners = [];
    
  28.         const signal = (this.signal = {
    
  29.           aborted: false,
    
  30.           addEventListener: (type, listener) => {
    
  31.             listeners.push(listener);
    
  32.           },
    
  33.         });
    
  34. 
    
  35.         this.abort = () => {
    
  36.           signal.aborted = true;
    
  37.           listeners.forEach(listener => listener());
    
  38.         };
    
  39.       }
    
  40.   : // $FlowFixMe[incompatible-type]
    
  41.     null;
    
  42. 
    
  43. export type Cache = {
    
  44.   controller: AbortController,
    
  45.   data: Map<() => mixed, mixed>,
    
  46.   refCount: number,
    
  47. };
    
  48. 
    
  49. export type CacheComponentState = {
    
  50.   +parent: Cache,
    
  51.   +cache: Cache,
    
  52. };
    
  53. 
    
  54. export type SpawnedCachePool = {
    
  55.   +parent: Cache,
    
  56.   +pool: Cache,
    
  57. };
    
  58. 
    
  59. // Intentionally not named imports because Rollup would
    
  60. // use dynamic dispatch for CommonJS interop named imports.
    
  61. const {
    
  62.   unstable_scheduleCallback: scheduleCallback,
    
  63.   unstable_NormalPriority: NormalPriority,
    
  64. } = Scheduler;
    
  65. 
    
  66. export const CacheContext: ReactContext<Cache> = enableCache
    
  67.   ? {
    
  68.       $$typeof: REACT_CONTEXT_TYPE,
    
  69.       // We don't use Consumer/Provider for Cache components. So we'll cheat.
    
  70.       Consumer: (null: any),
    
  71.       Provider: (null: any),
    
  72.       // We'll initialize these at the root.
    
  73.       _currentValue: (null: any),
    
  74.       _currentValue2: (null: any),
    
  75.       _threadCount: 0,
    
  76.       _defaultValue: (null: any),
    
  77.       _globalName: (null: any),
    
  78.     }
    
  79.   : (null: any);
    
  80. 
    
  81. if (__DEV__ && enableCache) {
    
  82.   CacheContext._currentRenderer = null;
    
  83.   CacheContext._currentRenderer2 = null;
    
  84. }
    
  85. 
    
  86. // Creates a new empty Cache instance with a ref-count of 0. The caller is responsible
    
  87. // for retaining the cache once it is in use (retainCache), and releasing the cache
    
  88. // once it is no longer needed (releaseCache).
    
  89. export function createCache(): Cache {
    
  90.   if (!enableCache) {
    
  91.     return (null: any);
    
  92.   }
    
  93.   const cache: Cache = {
    
  94.     controller: new AbortControllerLocal(),
    
  95.     data: new Map(),
    
  96.     refCount: 0,
    
  97.   };
    
  98. 
    
  99.   return cache;
    
  100. }
    
  101. 
    
  102. export function retainCache(cache: Cache) {
    
  103.   if (!enableCache) {
    
  104.     return;
    
  105.   }
    
  106.   if (__DEV__) {
    
  107.     if (cache.controller.signal.aborted) {
    
  108.       console.warn(
    
  109.         'A cache instance was retained after it was already freed. ' +
    
  110.           'This likely indicates a bug in React.',
    
  111.       );
    
  112.     }
    
  113.   }
    
  114.   cache.refCount++;
    
  115. }
    
  116. 
    
  117. // Cleanup a cache instance, potentially freeing it if there are no more references
    
  118. export function releaseCache(cache: Cache) {
    
  119.   if (!enableCache) {
    
  120.     return;
    
  121.   }
    
  122.   cache.refCount--;
    
  123.   if (__DEV__) {
    
  124.     if (cache.refCount < 0) {
    
  125.       console.warn(
    
  126.         'A cache instance was released after it was already freed. ' +
    
  127.           'This likely indicates a bug in React.',
    
  128.       );
    
  129.     }
    
  130.   }
    
  131.   if (cache.refCount === 0) {
    
  132.     scheduleCallback(NormalPriority, () => {
    
  133.       cache.controller.abort();
    
  134.     });
    
  135.   }
    
  136. }
    
  137. 
    
  138. export function pushCacheProvider(workInProgress: Fiber, cache: Cache) {
    
  139.   if (!enableCache) {
    
  140.     return;
    
  141.   }
    
  142.   pushProvider(workInProgress, CacheContext, cache);
    
  143. }
    
  144. 
    
  145. export function popCacheProvider(workInProgress: Fiber, cache: Cache) {
    
  146.   if (!enableCache) {
    
  147.     return;
    
  148.   }
    
  149.   popProvider(CacheContext, workInProgress);
    
  150. }