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