| 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 {ReactNodeList, ReactFormState} from 'shared/ReactTypes'; |
| 11 | import type { |
| 12 | FiberRoot, |
| 13 | SuspenseHydrationCallbacks, |
| 14 | TransitionTracingCallbacks, |
| 15 | } from './ReactInternalTypes'; |
| 16 | import type {RootTag} from './ReactRootTags'; |
| 17 | import type {Cache} from './ReactFiberCacheComponent'; |
| 18 | import type {Container} from './ReactFiberConfig'; |
| 19 | |
| 20 | import {noTimeout} from './ReactFiberConfig'; |
| 21 | import {createHostRootFiber} from './ReactFiber'; |
| 22 | import { |
| 23 | NoLane, |
| 24 | NoLanes, |
| 25 | NoTimestamp, |
| 26 | TotalLanes, |
| 27 | createLaneMap, |
| 28 | } from './ReactFiberLane'; |
| 29 | import { |
| 30 | enableSuspenseCallback, |
| 31 | enableProfilerCommitHooks, |
| 32 | enableProfilerTimer, |
| 33 | enableUpdaterTracking, |
| 34 | enableTransitionTracing, |
| 35 | disableLegacyMode, |
| 36 | enableViewTransition, |
| 37 | enableGestureTransition, |
| 38 | enableDefaultTransitionIndicator, |
| 39 | } from 'shared/ReactFeatureFlags'; |
| 40 | import {initializeUpdateQueue} from './ReactFiberClassUpdateQueue'; |
| 41 | import {LegacyRoot, ConcurrentRoot} from './ReactRootTags'; |
| 42 | import {createCache, retainCache} from './ReactFiberCacheComponent'; |
| 43 | |
| 44 | export type RootState = { |
| 45 | element: any, |
| 46 | isDehydrated: boolean, |
| 47 | cache: Cache, |
| 48 | }; |
| 49 | |
| 50 | function FiberRootNode( |
| 51 | this: $FlowFixMe, |
| 52 | containerInfo: any, |
| 53 | // $FlowFixMe[missing-local-annot] |
| 54 | tag, |
| 55 | hydrate: any, |
| 56 | identifierPrefix: any, |
| 57 | onUncaughtError: any, |
| 58 | onCaughtError: any, |
| 59 | onRecoverableError: any, |
| 60 | onDefaultTransitionIndicator: any, |
| 61 | formState: ReactFormState<any, any> | null, |
| 62 | ) { |
| 63 | this.tag = disableLegacyMode ? ConcurrentRoot : tag; |
| 64 | this.containerInfo = containerInfo; |
| 65 | this.pendingChildren = null; |
| 66 | this.current = null; |
| 67 | this.pingCache = null; |
| 68 | this.timeoutHandle = noTimeout; |
| 69 | this.cancelPendingCommit = null; |
| 70 | this.context = null; |
| 71 | this.pendingContext = null; |
| 72 | this.next = null; |
| 73 | this.callbackNode = null; |
| 74 | this.callbackPriority = NoLane; |
| 75 | this.expirationTimes = createLaneMap(NoTimestamp); |
| 76 | |
| 77 | this.pendingLanes = NoLanes; |
| 78 | this.suspendedLanes = NoLanes; |
| 79 | this.pingedLanes = NoLanes; |
| 80 | this.warmLanes = NoLanes; |
| 81 | this.expiredLanes = NoLanes; |
| 82 | if (enableDefaultTransitionIndicator) { |
| 83 | this.indicatorLanes = NoLanes; |
| 84 | } |
| 85 | this.errorRecoveryDisabledLanes = NoLanes; |
| 86 | this.shellSuspendCounter = 0; |
| 87 | |
| 88 | this.entangledLanes = NoLanes; |
| 89 | this.entanglements = createLaneMap(NoLanes); |
| 90 | |
| 91 | this.hiddenUpdates = createLaneMap(null); |
| 92 | |
| 93 | this.identifierPrefix = identifierPrefix; |
| 94 | this.onUncaughtError = onUncaughtError; |
| 95 | this.onCaughtError = onCaughtError; |
| 96 | this.onRecoverableError = onRecoverableError; |
| 97 | |
| 98 | if (enableDefaultTransitionIndicator) { |
| 99 | this.onDefaultTransitionIndicator = onDefaultTransitionIndicator; |
| 100 | this.pendingIndicator = null; |
| 101 | } |
| 102 | |
| 103 | this.pooledCache = null; |
| 104 | this.pooledCacheLanes = NoLanes; |
| 105 | |
| 106 | if (enableSuspenseCallback) { |
| 107 | this.hydrationCallbacks = null; |
| 108 | } |
| 109 | |
| 110 | this.formState = formState; |
| 111 | |
| 112 | if (enableViewTransition) { |
| 113 | this.transitionTypes = null; |
| 114 | } |
| 115 | |
| 116 | if (enableGestureTransition) { |
| 117 | this.pendingGestures = null; |
| 118 | this.gestureClone = null; |
| 119 | } |
| 120 | |
| 121 | this.incompleteTransitions = new Map(); |
| 122 | if (enableTransitionTracing) { |
| 123 | this.transitionCallbacks = null; |
| 124 | this.transitionLanes = createLaneMap(null); |
| 125 | } |
| 126 | |
| 127 | if (enableProfilerTimer && enableProfilerCommitHooks) { |
| 128 | this.effectDuration = -0; |
| 129 | this.passiveEffectDuration = -0; |
| 130 | } |
| 131 | |
| 132 | if (enableUpdaterTracking) { |
| 133 | this.memoizedUpdaters = new Set(); |
| 134 | const pendingUpdatersLaneMap = (this.pendingUpdatersLaneMap = []); |
| 135 | for (let i = 0; i < TotalLanes; i++) { |
| 136 | pendingUpdatersLaneMap.push(new Set()); |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | if (__DEV__) { |
| 141 | if (disableLegacyMode) { |
| 142 | // TODO: This varies by each renderer. |
| 143 | this._debugRootType = hydrate ? 'hydrateRoot()' : 'createRoot()'; |
| 144 | } else { |
| 145 | switch (tag) { |
| 146 | case ConcurrentRoot: |
| 147 | this._debugRootType = hydrate ? 'hydrateRoot()' : 'createRoot()'; |
| 148 | break; |
| 149 | case LegacyRoot: |
| 150 | this._debugRootType = hydrate ? 'hydrate()' : 'render()'; |
| 151 | break; |
| 152 | } |
| 153 | } |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | export function createFiberRoot( |
| 158 | containerInfo: Container, |
| 159 | tag: RootTag, |
| 160 | hydrate: boolean, |
| 161 | initialChildren: ReactNodeList, |
| 162 | hydrationCallbacks: null | SuspenseHydrationCallbacks, |
| 163 | isStrictMode: boolean, |
| 164 | // TODO: We have several of these arguments that are conceptually part of the |
| 165 | // host config, but because they are passed in at runtime, we have to thread |
| 166 | // them through the root constructor. Perhaps we should put them all into a |
| 167 | // single type, like a DynamicHostConfig that is defined by the renderer. |
| 168 | identifierPrefix: string, |
| 169 | formState: ReactFormState<any, any> | null, |
| 170 | onUncaughtError: ( |
| 171 | error: mixed, |
| 172 | errorInfo: {+componentStack?: ?string}, |
| 173 | ) => void, |
| 174 | onCaughtError: ( |
| 175 | error: mixed, |
| 176 | errorInfo: { |
| 177 | +componentStack?: ?string, |
| 178 | +errorBoundary?: ?component(...props: any), |
| 179 | }, |
| 180 | ) => void, |
| 181 | onRecoverableError: ( |
| 182 | error: mixed, |
| 183 | errorInfo: {+componentStack?: ?string}, |
| 184 | ) => void, |
| 185 | onDefaultTransitionIndicator: () => void | (() => void), |
| 186 | transitionCallbacks: null | TransitionTracingCallbacks, |
| 187 | ): FiberRoot { |
| 188 | // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions |
| 189 | const root: FiberRoot = new FiberRootNode( |
| 190 | containerInfo, |
| 191 | tag, |
| 192 | hydrate, |
| 193 | identifierPrefix, |
| 194 | onUncaughtError, |
| 195 | onCaughtError, |
| 196 | onRecoverableError, |
| 197 | onDefaultTransitionIndicator, |
| 198 | formState, |
| 199 | ) as any; |
| 200 | if (enableSuspenseCallback) { |
| 201 | root.hydrationCallbacks = hydrationCallbacks; |
| 202 | } |
| 203 | |
| 204 | if (enableTransitionTracing) { |
| 205 | root.transitionCallbacks = transitionCallbacks; |
| 206 | } |
| 207 | |
| 208 | // Cyclic construction. This cheats the type system right now because |
| 209 | // stateNode is any. |
| 210 | const uninitializedFiber = createHostRootFiber(tag, isStrictMode); |
| 211 | root.current = uninitializedFiber; |
| 212 | uninitializedFiber.stateNode = root; |
| 213 | |
| 214 | const initialCache = createCache(); |
| 215 | retainCache(initialCache); |
| 216 | |
| 217 | // The pooledCache is a fresh cache instance that is used temporarily |
| 218 | // for newly mounted boundaries during a render. In general, the |
| 219 | // pooledCache is always cleared from the root at the end of a render: |
| 220 | // it is either released when render commits, or moved to an Offscreen |
| 221 | // component if rendering suspends. Because the lifetime of the pooled |
| 222 | // cache is distinct from the main memoizedState.cache, it must be |
| 223 | // retained separately. |
| 224 | root.pooledCache = initialCache; |
| 225 | retainCache(initialCache); |
| 226 | const initialState: RootState = { |
| 227 | element: initialChildren, |
| 228 | isDehydrated: hydrate, |
| 229 | cache: initialCache, |
| 230 | }; |
| 231 | uninitializedFiber.memoizedState = initialState; |
| 232 | |
| 233 | initializeUpdateQueue(uninitializedFiber); |
| 234 | |
| 235 | return root; |
| 236 | } |