main
js 332 lines 10.7 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, FiberRoot} from './ReactInternalTypes';
12 import type {Lanes} from './ReactFiberLane';
13 import type {ActivityState} from './ReactFiberActivityComponent';
14 import type {
15 SuspenseState,
16 SuspenseListRenderState,
17 } from './ReactFiberSuspenseComponent';
18 import type {Cache} from './ReactFiberCacheComponent';
19 import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent';
20
21 import {
22 ClassComponent,
23 HostRoot,
24 HostComponent,
25 HostHoistable,
26 HostSingleton,
27 HostPortal,
28 ContextProvider,
29 ActivityComponent,
30 SuspenseComponent,
31 SuspenseListComponent,
32 OffscreenComponent,
33 LegacyHiddenComponent,
34 CacheComponent,
35 TracingMarkerComponent,
36 } from './ReactWorkTags';
37 import {DidCapture, NoFlags, ShouldCapture, Update} from './ReactFiberFlags';
38 import {NoMode, ProfileMode} from './ReactTypeOfMode';
39 import {
40 enableProfilerTimer,
41 enableTransitionTracing,
42 } from 'shared/ReactFeatureFlags';
43
44 import {popHostContainer, popHostContext} from './ReactFiberHostContext';
45 import {
46 popSuspenseListContext,
47 popSuspenseHandler,
48 } from './ReactFiberSuspenseContext';
49 import {popHiddenContext} from './ReactFiberHiddenContext';
50 import {resetHydrationState} from './ReactFiberHydrationContext';
51 import {
52 isContextProvider as isLegacyContextProvider,
53 popContext as popLegacyContext,
54 popTopLevelContextObject as popTopLevelLegacyContextObject,
55 } from './ReactFiberLegacyContext';
56 import {popProvider} from './ReactFiberNewContext';
57 import {popCacheProvider} from './ReactFiberCacheComponent';
58 import {transferActualDuration} from './ReactProfilerTimer';
59 import {popTreeContext} from './ReactFiberTreeContext';
60 import {popRootTransition, popTransition} from './ReactFiberTransition';
61 import {
62 popMarkerInstance,
63 popRootMarkerInstance,
64 } from './ReactFiberTracingMarkerComponent';
65
66 function unwindWork(
67 current: Fiber | null,
68 workInProgress: Fiber,
69 renderLanes: Lanes,
70 ): Fiber | null {
71 // Note: This intentionally doesn't check if we're hydrating because comparing
72 // to the current tree provider fiber is just as fast and less error-prone.
73 // Ideally we would have a special version of the work loop only
74 // for hydration.
75 popTreeContext(workInProgress);
76 switch (workInProgress.tag) {
77 case ClassComponent: {
78 const Component = workInProgress.type;
79 if (isLegacyContextProvider(Component)) {
80 popLegacyContext(workInProgress);
81 }
82 const flags = workInProgress.flags;
83 if (flags & ShouldCapture) {
84 workInProgress.flags = (flags & ~ShouldCapture) | DidCapture;
85 if (
86 enableProfilerTimer &&
87 (workInProgress.mode & ProfileMode) !== NoMode
88 ) {
89 transferActualDuration(workInProgress);
90 }
91 return workInProgress;
92 }
93 return null;
94 }
95 case HostRoot: {
96 const root: FiberRoot = workInProgress.stateNode;
97 const cache: Cache = workInProgress.memoizedState.cache;
98 popCacheProvider(workInProgress, cache);
99
100 if (enableTransitionTracing) {
101 popRootMarkerInstance(workInProgress);
102 }
103
104 popRootTransition(workInProgress, root, renderLanes);
105 popHostContainer(workInProgress);
106 popTopLevelLegacyContextObject(workInProgress);
107 const flags = workInProgress.flags;
108 if (
109 (flags & ShouldCapture) !== NoFlags &&
110 (flags & DidCapture) === NoFlags
111 ) {
112 // There was an error during render that wasn't captured by a suspense
113 // boundary. Do a second pass on the root to unmount the children.
114 workInProgress.flags = (flags & ~ShouldCapture) | DidCapture;
115 return workInProgress;
116 }
117 // We unwound to the root without completing it. Exit.
118 return null;
119 }
120 case HostHoistable:
121 case HostSingleton:
122 case HostComponent: {
123 // TODO: popHydrationState
124 popHostContext(workInProgress);
125 return null;
126 }
127 case ActivityComponent: {
128 const activityState: null | ActivityState = workInProgress.memoizedState;
129 if (activityState !== null) {
130 popSuspenseHandler(workInProgress);
131
132 if (workInProgress.alternate === null) {
133 throw new Error(
134 'Threw in newly mounted dehydrated component. This is likely a bug in ' +
135 'React. Please file an issue.',
136 );
137 }
138
139 resetHydrationState();
140 }
141
142 const flags = workInProgress.flags;
143 if (flags & ShouldCapture) {
144 workInProgress.flags = (flags & ~ShouldCapture) | DidCapture;
145 // Captured a suspense effect. Re-render the boundary.
146 if (
147 enableProfilerTimer &&
148 (workInProgress.mode & ProfileMode) !== NoMode
149 ) {
150 transferActualDuration(workInProgress);
151 }
152 return workInProgress;
153 }
154 return null;
155 }
156 case SuspenseComponent: {
157 popSuspenseHandler(workInProgress);
158 const suspenseState: null | SuspenseState = workInProgress.memoizedState;
159 if (suspenseState !== null && suspenseState.dehydrated !== null) {
160 if (workInProgress.alternate === null) {
161 throw new Error(
162 'Threw in newly mounted dehydrated component. This is likely a bug in ' +
163 'React. Please file an issue.',
164 );
165 }
166
167 resetHydrationState();
168 }
169
170 const flags = workInProgress.flags;
171 if (flags & ShouldCapture) {
172 workInProgress.flags = (flags & ~ShouldCapture) | DidCapture;
173 // Captured a suspense effect. Re-render the boundary.
174 if (
175 enableProfilerTimer &&
176 (workInProgress.mode & ProfileMode) !== NoMode
177 ) {
178 transferActualDuration(workInProgress);
179 }
180 return workInProgress;
181 }
182 return null;
183 }
184 case SuspenseListComponent: {
185 popSuspenseListContext(workInProgress);
186 // SuspenseList doesn't normally catch anything. It should've been
187 // caught by a nested boundary. If not, it should bubble through.
188 const flags = workInProgress.flags;
189 if (flags & ShouldCapture) {
190 workInProgress.flags = (flags & ~ShouldCapture) | DidCapture;
191 // If we caught something on the SuspenseList itself it's because
192 // we want to ignore something. Re-enter the cycle and handle it
193 // in the complete phase.
194 const renderState: null | SuspenseListRenderState =
195 workInProgress.memoizedState;
196 if (renderState !== null) {
197 // Cut off any remaining tail work and don't commit the rendering one.
198 // This assumes that we have already confirmed that none of these are
199 // already mounted.
200 renderState.rendering = null;
201 renderState.tail = null;
202 }
203 // Schedule the commit phase to attach retry listeners.
204 workInProgress.flags |= Update;
205 return workInProgress;
206 }
207 return null;
208 }
209 case HostPortal:
210 popHostContainer(workInProgress);
211 return null;
212 case ContextProvider:
213 const context: ReactContext<any> = workInProgress.type;
214 popProvider(context, workInProgress);
215 return null;
216 case OffscreenComponent:
217 case LegacyHiddenComponent: {
218 popSuspenseHandler(workInProgress);
219 popHiddenContext(workInProgress);
220 popTransition(workInProgress, current);
221 const flags = workInProgress.flags;
222 if (flags & ShouldCapture) {
223 workInProgress.flags = (flags & ~ShouldCapture) | DidCapture;
224 // Captured a suspense effect. Re-render the boundary.
225 if (
226 enableProfilerTimer &&
227 (workInProgress.mode & ProfileMode) !== NoMode
228 ) {
229 transferActualDuration(workInProgress);
230 }
231 return workInProgress;
232 }
233 return null;
234 }
235 case CacheComponent:
236 const cache: Cache = workInProgress.memoizedState.cache;
237 popCacheProvider(workInProgress, cache);
238 return null;
239 case TracingMarkerComponent:
240 if (enableTransitionTracing) {
241 if (workInProgress.stateNode !== null) {
242 popMarkerInstance(workInProgress);
243 }
244 }
245 return null;
246 default:
247 return null;
248 }
249 }
250
251 function unwindInterruptedWork(
252 current: Fiber | null,
253 interruptedWork: Fiber,
254 renderLanes: Lanes,
255 ) {
256 // Note: This intentionally doesn't check if we're hydrating because comparing
257 // to the current tree provider fiber is just as fast and less error-prone.
258 // Ideally we would have a special version of the work loop only
259 // for hydration.
260 popTreeContext(interruptedWork);
261 switch (interruptedWork.tag) {
262 case ClassComponent: {
263 const childContextTypes = interruptedWork.type.childContextTypes;
264 if (childContextTypes !== null && childContextTypes !== undefined) {
265 popLegacyContext(interruptedWork);
266 }
267 break;
268 }
269 case HostRoot: {
270 const root: FiberRoot = interruptedWork.stateNode;
271 const cache: Cache = interruptedWork.memoizedState.cache;
272 popCacheProvider(interruptedWork, cache);
273
274 if (enableTransitionTracing) {
275 popRootMarkerInstance(interruptedWork);
276 }
277
278 popRootTransition(interruptedWork, root, renderLanes);
279 popHostContainer(interruptedWork);
280 popTopLevelLegacyContextObject(interruptedWork);
281 break;
282 }
283 case HostHoistable:
284 case HostSingleton:
285 case HostComponent: {
286 popHostContext(interruptedWork);
287 break;
288 }
289 case HostPortal:
290 popHostContainer(interruptedWork);
291 break;
292 case ActivityComponent: {
293 if (interruptedWork.memoizedState !== null) {
294 popSuspenseHandler(interruptedWork);
295 }
296 break;
297 }
298 case SuspenseComponent:
299 popSuspenseHandler(interruptedWork);
300 break;
301 case SuspenseListComponent:
302 popSuspenseListContext(interruptedWork);
303 break;
304 case ContextProvider:
305 const context: ReactContext<any> = interruptedWork.type;
306 popProvider(context, interruptedWork);
307 break;
308 case OffscreenComponent:
309 case LegacyHiddenComponent:
310 popSuspenseHandler(interruptedWork);
311 popHiddenContext(interruptedWork);
312 popTransition(interruptedWork, current);
313 break;
314 case CacheComponent:
315 const cache: Cache = interruptedWork.memoizedState.cache;
316 popCacheProvider(interruptedWork, cache);
317 break;
318 case TracingMarkerComponent:
319 if (enableTransitionTracing) {
320 const instance: TracingMarkerInstance | null =
321 interruptedWork.stateNode;
322 if (instance !== null) {
323 popMarkerInstance(interruptedWork);
324 }
325 }
326 break;
327 default:
328 break;
329 }
330 }
331
332 export {unwindWork, unwindInterruptedWork};