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
8
-function notYetImplemented() {
9
- throw new Error('Not yet implemented.');
10
-}
11
-
12
-export const requestCurrentTime = notYetImplemented;
13
-export const computeExpirationForFiber = notYetImplemented;
14
-export const captureCommitPhaseError = notYetImplemented;
15
-export const onUncaughtError = notYetImplemented;
16
-export const renderDidSuspend = notYetImplemented;
17
-export const renderDidError = notYetImplemented;
18
-export const pingSuspendedRoot = notYetImplemented;
19
-export const retryTimedOutBoundary = notYetImplemented;
20
-export const resolveRetryThenable = notYetImplemented;
21
-export const markLegacyErrorBoundaryAsFailed = notYetImplemented;
22
-export const isAlreadyFailedLegacyErrorBoundary = notYetImplemented;
23
-export const scheduleWork = notYetImplemented;
24
-export const requestWork = notYetImplemented;
25
-export const flushRoot = notYetImplemented;
26
-export const batchedUpdates = notYetImplemented;
27
-export const unbatchedUpdates = notYetImplemented;
28
-export const flushSync = notYetImplemented;
29
-export const flushControlled = notYetImplemented;
30
-export const deferredUpdates = notYetImplemented;
31
-export const syncUpdates = notYetImplemented;
32
-export const interactiveUpdates = notYetImplemented;
33
-export const flushInteractiveUpdates = notYetImplemented;
34
-export const computeUniqueAsyncExpiration = notYetImplemented;
35
-export const flushPassiveEffects = notYetImplemented;
36
-export const warnIfNotCurrentlyActingUpdatesInDev = notYetImplemented;
10
+import type {Fiber} from './ReactFiber';
11
+import type {FiberRoot} from './ReactFiberRoot';
12
+import type {ExpirationTime} from './ReactFiberExpirationTime';
13
+import type {
14
+ ReactPriorityLevel,
15
+ SchedulerCallback,
16
+} from './SchedulerWithReactIntegration';
17
+import type {Interaction} from 'scheduler/src/Tracing';
18
+
19
+import {
20
+ warnAboutDeprecatedLifecycles,
21
+ enableUserTimingAPI,
22
+ enableSuspenseServerRenderer,
23
+ replayFailedUnitOfWorkWithInvokeGuardedCallback,
24
+ enableProfilerTimer,
25
+ disableYielding,
26
+ enableSchedulerTracing,
27
+} from 'shared/ReactFeatureFlags';
28
+import ReactSharedInternals from 'shared/ReactSharedInternals';
29
+import invariant from 'shared/invariant';
30
+import warning from 'shared/warning';
31
+
32
+import {
33
+ scheduleCallback,
34
+ cancelCallback,
35
+ getCurrentPriorityLevel,
36
+ runWithPriority,
37
+ shouldYield,
38
+ now,
39
+ ImmediatePriority,
40
+ UserBlockingPriority,
41
+ NormalPriority,
42
+ LowPriority,
43
+ IdlePriority,
44
+ flushImmediateQueue,
45
+} from './SchedulerWithReactIntegration';
46
+
47
+import {__interactionsRef, __subscriberRef} from 'scheduler/tracing';
48
+
49
+import {
50
+ prepareForCommit,
51
+ resetAfterCommit,
52
+ scheduleTimeout,
53
+ cancelTimeout,
54
+ noTimeout,
55
+} from './ReactFiberHostConfig';
56
+
57
+import {createWorkInProgress, assignFiberPropertiesInDEV} from './ReactFiber';
58
+import {NoContext, ConcurrentMode, ProfileMode} from './ReactTypeOfMode';
59
+import {
60
+ HostRoot,
61
+ ClassComponent,
62
+ SuspenseComponent,
63
+ DehydratedSuspenseComponent,
64
+ FunctionComponent,
65
+ ForwardRef,
66
+ MemoComponent,
67
+ SimpleMemoComponent,
68
+} from 'shared/ReactWorkTags';
69
+import {
70
+ NoEffect,
71
+ PerformedWork,
72
+ Placement,
73
+ Update,
74
+ PlacementAndUpdate,
75
+ Deletion,
76
+ Ref,
77
+ ContentReset,
78
+ Snapshot,
79
+ Callback,
80
+ Passive,
81
+ Incomplete,
82
+ HostEffectMask,
83
+} from 'shared/ReactSideEffectTags';
84
+import {
85
+ NoWork,
86
+ Sync,
87
+ Never,
88
+ msToExpirationTime,
89
+ expirationTimeToMs,
90
+ computeInteractiveExpiration,
91
+ computeAsyncExpiration,
92
+ inferPriorityFromExpirationTime,
93
+ LOW_PRIORITY_EXPIRATION,
94
+} from './ReactFiberExpirationTime';
95
+import {beginWork as originalBeginWork} from './ReactFiberBeginWork';
96
+import {completeWork} from './ReactFiberCompleteWork';
97
+import {
98
+ throwException,
99
+ unwindWork,
100
+ unwindInterruptedWork,
101
+ createRootErrorUpdate,
102
+ createClassErrorUpdate,
103
+} from './ReactFiberUnwindWork';
104
+import {
105
+ commitBeforeMutationLifeCycles as commitBeforeMutationEffectOnFiber,
106
+ commitLifeCycles as commitLayoutEffectOnFiber,
107
+ commitPassiveHookEffects,
108
+ commitPlacement,
109
+ commitWork,
110
+ commitDeletion,
111
+ commitDetachRef,
112
+ commitAttachRef,
113
+ commitResetTextContent,
114
+} from './ReactFiberCommitWork';
115
+import {enqueueUpdate} from './ReactUpdateQueue';
116
+// TODO: Ahaha Andrew is bad at spellling
117
+import {resetContextDependences as resetContextDependencies} from './ReactFiberNewContext';
118
+import {resetHooks, ContextOnlyDispatcher} from './ReactFiberHooks';
119
+import {createCapturedValue} from './ReactCapturedValue';
120
+
121
+import {
122
+ recordCommitTime,
123
+ startProfilerTimer,
124
+ stopProfilerTimerIfRunningAndRecordDelta,
125
+} from './ReactProfilerTimer';
126
+
127
+// DEV stuff
128
+import warningWithoutStack from 'shared/warningWithoutStack';
129
+import getComponentName from 'shared/getComponentName';
130
+import ReactStrictModeWarnings from './ReactStrictModeWarnings';
131
+import {
132
+ phase as ReactCurrentDebugFiberPhaseInDEV,
133
+ resetCurrentFiber as resetCurrentDebugFiberInDEV,
134
+ setCurrentFiber as setCurrentDebugFiberInDEV,
135
+ getStackByFiberInDevAndProd,
136
+} from './ReactCurrentFiber';
137
+import {
138
+ recordEffect,
139
+ recordScheduleUpdate,
140
+ startRequestCallbackTimer,
141
+ stopRequestCallbackTimer,
142
+ startWorkTimer,
143
+ stopWorkTimer,
144
+ stopFailedWorkTimer,
145
+ startWorkLoopTimer,
146
+ stopWorkLoopTimer,
147
+ startCommitTimer,
148
+ stopCommitTimer,
149
+ startCommitSnapshotEffectsTimer,
150
+ stopCommitSnapshotEffectsTimer,
151
+ startCommitHostEffectsTimer,
152
+ stopCommitHostEffectsTimer,
153
+ startCommitLifeCyclesTimer,
154
+ stopCommitLifeCyclesTimer,
155
+} from './ReactDebugFiberPerf';
156
+import {
157
+ invokeGuardedCallback,
158
+ hasCaughtError,
159
+ clearCaughtError,
160
+} from 'shared/ReactErrorUtils';
161
+import {onCommitRoot} from './ReactFiberDevToolsHook';
162
+
163
+const {
164
+ ReactCurrentDispatcher,
165
+ ReactCurrentOwner,
166
+ ReactShouldWarnActingUpdates,
167
+} = ReactSharedInternals;
168
+
169
+type WorkPhase = 0 | 1 | 2 | 3 | 4 | 5;
170
+const NotWorking = 0;
171
+const BatchedPhase = 1;
172
+const LegacyUnbatchedPhase = 2;
173
+const FlushSyncPhase = 3;
174
+const RenderPhase = 4;
175
+const CommitPhase = 5;
176
+
177
+type RootExitStatus = 0 | 1 | 2 | 3;
178
+const RootIncomplete = 0;
179
+const RootErrored = 1;
180
+const RootSuspended = 2;
181
+const RootCompleted = 3;
182
+
183
+export type Thenable = {
184
+ then(resolve: () => mixed, reject?: () => mixed): Thenable | void,
185
+};
186
+
187
+// The phase of work we're currently in
188
+let workPhase: WorkPhase = NotWorking;
189
+// The root we're working on
190
+let workInProgressRoot: FiberRoot | null = null;
191
+// The fiber we're working on
192
+let workInProgress: Fiber | null = null;
193
+// The expiration time we're rendering
194
+let renderExpirationTime: ExpirationTime = NoWork;
195
+// Whether to root completed, errored, suspended, etc.
196
+let workInProgressRootExitStatus: RootExitStatus = RootIncomplete;
197
+let workInProgressRootAbsoluteTimeoutMs: number = -1;
198
+
199
+let nextEffect: Fiber | null = null;
200
+let hasUncaughtError = false;
201
+let firstUncaughtError = null;
202
+let legacyErrorBoundariesThatAlreadyFailed: Set<mixed> | null = null;
203
+
204
+let rootDoesHavePassiveEffects: boolean = false;
205
+let rootWithPendingPassiveEffects: FiberRoot | null = null;
206
+let pendingPassiveEffectsExpirationTime: ExpirationTime = NoWork;
207
+
208
+let rootsWithPendingDiscreteUpdates: Map<
209
+ FiberRoot,
210
+ ExpirationTime,
211
+> | null = null;
212
+
213
+// Use these to prevent an infinite loop of nested updates
214
+const NESTED_UPDATE_LIMIT = 50;
215
+let nestedUpdateCount: number = 0;
216
+let rootWithNestedUpdates: FiberRoot | null = null;
217
+
218
+const NESTED_PASSIVE_UPDATE_LIMIT = 50;
219
+let nestedPassiveUpdateCount: number = 0;
220
+
221
+let interruptedBy: Fiber | null = null;
222
+
223
+// Expiration times are computed by adding to the current time (the start
224
+// time). However, if two updates are scheduled within the same event, we
225
+// should treat their start times as simultaneous, even if the actual clock
226
+// time has advanced between the first and second call.
227
+
228
+// In other words, because expiration times determine how updates are batched,
229
+// we want all updates of like priority that occur within the same event to
230
+// receive the same expiration time. Otherwise we get tearing.
231
+let currentEventTime: ExpirationTime = NoWork;
232
+
233
+export function requestCurrentTime() {
234
+ if (workPhase === RenderPhase || workPhase === CommitPhase) {
235
+ // We're inside React, so it's fine to read the actual time.
236
+ return msToExpirationTime(now());
237
+ }
238
+ // We're not inside React, so we may be in the middle of a browser event.
239
+ if (currentEventTime !== NoWork) {
240
+ // Use the same start time for all updates until we enter React again.
241
+ return currentEventTime;
242
+ }
243
+ // This is the first update since React yielded. Compute a new start time.
244
+ currentEventTime = msToExpirationTime(now());
245
+ return currentEventTime;
246
+}
247
+
248
+export function computeExpirationForFiber(
249
+ currentTime: ExpirationTime,
250
+ fiber: Fiber,
251
+): ExpirationTime {
252
+ if ((fiber.mode & ConcurrentMode) === NoContext) {
253
+ return Sync;
254
+ }
255
+
256
+ if (workPhase === RenderPhase) {
257
+ // Use whatever time we're already rendering
258
+ return renderExpirationTime;
259
+ }
260
+
261
+ // Compute an expiration time based on the Scheduler priority.
262
+ let expirationTime;
263
+ const priorityLevel = getCurrentPriorityLevel();
264
+ switch (priorityLevel) {
265
+ case ImmediatePriority:
266
+ expirationTime = Sync;
267
+ break;
268
+ case UserBlockingPriority:
269
+ // TODO: Rename this to computeUserBlockingExpiration
270
+ expirationTime = computeInteractiveExpiration(currentTime);
271
+ break;
272
+ case NormalPriority:
273
+ case LowPriority: // TODO: Handle LowPriority
274
+ // TODO: Rename this to... something better.
275
+ expirationTime = computeAsyncExpiration(currentTime);
276
+ break;
277
+ case IdlePriority:
278
+ expirationTime = Never;
279
+ break;
280
+ default:
281
+ invariant(false, 'Expected a valid priority level');
282
+ }
283
+
284
+ // If we're in the middle of rendering a tree, do not update at the same
285
+ // expiration time that is already rendering.
286
+ if (workInProgressRoot !== null && expirationTime === renderExpirationTime) {
287
+ // This is a trick to move this update into a separate batch
288
+ expirationTime -= 1;
289
+ }
290
+
291
+ return expirationTime;
292
+}
293
+
294
+let lastUniqueAsyncExpiration = NoWork;
295
+export function computeUniqueAsyncExpiration(): ExpirationTime {
296
+ const currentTime = requestCurrentTime();
297
+ let result = computeAsyncExpiration(currentTime);
298
+ if (result <= lastUniqueAsyncExpiration) {
299
+ // Since we assume the current time monotonically increases, we only hit
300
+ // this branch when computeUniqueAsyncExpiration is fired multiple times
301
+ // within a 200ms window (or whatever the async bucket size is).
302
+ result -= 1;
303
+ }
304
+ lastUniqueAsyncExpiration = result;
305
+ return result;
306
+}
307
+
308
+export function scheduleUpdateOnFiber(
309
+ fiber: Fiber,
310
+ expirationTime: ExpirationTime,
311
+) {
312
+ checkForNestedUpdates();
313
+ warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber);
314
+
315
+ const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);
316
+ if (root === null) {
317
+ warnAboutUpdateOnUnmountedFiberInDEV(fiber);
318
+ return;
319
+ }
320
+
321
+ root.pingTime = NoWork;
322
+
323
+ checkForInterruption(fiber, expirationTime);
324
+ recordScheduleUpdate();
325
+
326
+ if (expirationTime === Sync) {
327
+ if (workPhase === LegacyUnbatchedPhase) {
328
+ // This is a legacy edge case. The initial mount of a ReactDOM.render-ed
329
+ // root inside of batchedUpdates should be synchronous, but layout updates
330
+ // should be deferred until the end of the batch.
331
+ let callback = renderRoot(root, Sync, true);
332
+ while (callback !== null) {
333
+ callback = callback(true);
334
+ }
335
+ } else {
336
+ scheduleCallbackForRoot(root, ImmediatePriority, Sync);
337
+ if (workPhase === NotWorking) {
338
+ // Flush the synchronous work now, wnless we're already working or inside
339
+ // a batch. This is intentionally inside scheduleUpdateOnFiber instead of
340
+ // scheduleCallbackForFiber to preserve the ability to schedule a callback
341
+ // without immediately flushing it. We only do this for user-initated
342
+ // updates, to preserve historical behavior of sync mode.
343
+ flushImmediateQueue();
344
+ }
345
+ }
346
+ } else {
347
+ // TODO: computeExpirationForFiber also reads the priority. Pass the
348
+ // priority as an argument to that function and this one.
349
+ const priorityLevel = getCurrentPriorityLevel();
350
+ if (priorityLevel === UserBlockingPriority) {
351
+ // This is the result of a discrete event. Track the lowest priority
352
+ // discrete update per root so we can flush them early, if needed.
353
+ if (rootsWithPendingDiscreteUpdates === null) {
354
+ rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);
355
+ } else {
356
+ const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
357
+ if (
358
+ lastDiscreteTime === undefined ||
359
+ lastDiscreteTime > expirationTime
360
+ ) {
361
+ rootsWithPendingDiscreteUpdates.set(root, expirationTime);
362
+ }
363
+ }
364
+ }
365
+ scheduleCallbackForRoot(root, priorityLevel, expirationTime);
366
+ }
367
+}
368
+export const scheduleWork = scheduleUpdateOnFiber;
369
+
370
+// This is split into a separate function so we can mark a fiber with pending
371
+// work without treating it as a typical update that originates from an event;
372
+// e.g. retrying a Suspense boundary isn't an update, but it does schedule work
373
+// on a fiber.
374
+function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {
375
+ // Update the source fiber's expiration time
376
+ if (fiber.expirationTime < expirationTime) {
377
+ fiber.expirationTime = expirationTime;
378
+ }
379
+ let alternate = fiber.alternate;
380
+ if (alternate !== null && alternate.expirationTime < expirationTime) {
381
+ alternate.expirationTime = expirationTime;
382
+ }
383
+ // Walk the parent path to the root and update the child expiration time.
384
+ let node = fiber.return;
385
+ let root = null;
386
+ if (node === null && fiber.tag === HostRoot) {
387
+ root = fiber.stateNode;
388
+ } else {
389
+ while (node !== null) {
390
+ alternate = node.alternate;
391
+ if (node.childExpirationTime < expirationTime) {
392
+ node.childExpirationTime = expirationTime;
393
+ if (
394
+ alternate !== null &&
395
+ alternate.childExpirationTime < expirationTime
396
+ ) {
397
+ alternate.childExpirationTime = expirationTime;
398
+ }
399
+ } else if (
400
+ alternate !== null &&
401
+ alternate.childExpirationTime < expirationTime
402
+ ) {
403
+ alternate.childExpirationTime = expirationTime;
404
+ }
405
+ if (node.return === null && node.tag === HostRoot) {
406
+ root = node.stateNode;
407
+ break;
408
+ }
409
+ node = node.return;
410
+ }
411
+ }
412
+
413
+ if (root !== null) {
414
+ // Update the first and last pending expiration times in this root
415
+ const firstPendingTime = root.firstPendingTime;
416
+ if (expirationTime > firstPendingTime) {
417
+ root.firstPendingTime = expirationTime;
418
+ }
419
+ const lastPendingTime = root.lastPendingTime;
420
+ if (lastPendingTime === NoWork || expirationTime < lastPendingTime) {
421
+ root.lastPendingTime = expirationTime;
422
+ }
423
+ }
424
+
425
+ return root;
426
+}
427
+
428
+// Use this function, along with runRootCallback, to ensure that only a single
429
+// callback per root is scheduled. It's still possible to call renderRoot
430
+// directly, but scheduling via this function helps avoid excessive callbacks.
431
+// It works by storing the callback node and expiration time on the root. When a
432
+// new callback comes in, it compares the expiration time to determine if it
433
+// should cancel the previous one. It also relies on commitRoot scheduling a
434
+// callback to render the next level, because that means we don't need a
435
+// separate callback per expiration time.
436
+function scheduleCallbackForRoot(
437
+ root: FiberRoot,
438
+ priorityLevel: ReactPriorityLevel,
439
+ expirationTime: ExpirationTime,
440
+) {
441
+ const existingCallbackExpirationTime = root.callbackExpirationTime;
442
+ if (existingCallbackExpirationTime < expirationTime) {
443
+ // New callback has higher priority than the existing one.
444
+ const existingCallbackNode = root.callbackNode;
445
+ if (existingCallbackNode !== null) {
446
+ cancelCallback(existingCallbackNode);
447
+ }
448
+ root.callbackExpirationTime = expirationTime;
449
+ const options =
450
+ expirationTime === Sync
451
+ ? null
452
+ : {timeout: expirationTimeToMs(expirationTime)};
453
+ root.callbackNode = scheduleCallback(
454
+ priorityLevel,
455
+ runRootCallback.bind(
456
+ null,
457
+ root,
458
+ renderRoot.bind(null, root, expirationTime),
459
+ ),
460
+ options,
461
+ );
462
+ if (
463
+ enableUserTimingAPI &&
464
+ expirationTime !== Sync &&
465
+ workPhase !== RenderPhase &&
466
+ workPhase !== CommitPhase
467
+ ) {
468
+ // Scheduled an async callback, and we're not already working. Add an
469
+ // entry to the flamegraph that shows we're waiting for a callback
470
+ // to fire.
471
+ startRequestCallbackTimer();
472
+ }
473
+ }
474
+
475
+ const timeoutHandle = root.timeoutHandle;
476
+ if (timeoutHandle !== noTimeout) {
477
+ // The root previous suspended and scheduled a timeout to commit a fallback
478
+ // state. Now that we have additional work, cancel the timeout.
479
+ root.timeoutHandle = noTimeout;
480
+ // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
481
+ cancelTimeout(timeoutHandle);
482
+ }
483
+
484
+ // Add the current set of interactions to the pending set associated with
485
+ // this root.
486
+ schedulePendingInteraction(root, expirationTime);
487
+}
488
+
489
+function runRootCallback(root, callback, isSync) {
490
+ const prevCallbackNode = root.callbackNode;
491
+ let continuation = null;
492
+ try {
493
+ continuation = callback(isSync);
494
+ if (continuation !== null) {
495
+ return runRootCallback.bind(null, root, continuation);
496
+ } else {
497
+ return null;
498
+ }
499
+ } finally {
500
+ // If the callback exits without returning a continuation, remove the
501
+ // corresponding callback node from the root. Unless the callback node
502
+ // has changed, which implies that it was already cancelled by a high
503
+ // priority update.
504
+ if (continuation === null && prevCallbackNode === root.callbackNode) {
505
+ root.callbackNode = null;
506
+ root.callbackExpirationTime = NoWork;
507
+ }
508
+ }
509
+}
510
+
511
+export function flushRoot(root: FiberRoot, expirationTime: ExpirationTime) {
512
+ if (workPhase === RenderPhase || workPhase === CommitPhase) {
513
+ invariant(
514
+ false,
515
+ 'work.commit(): Cannot commit while already rendering. This likely ' +
516
+ 'means you attempted to commit from inside a lifecycle method.',
517
+ );
518
+ }
519
+ scheduleCallback(
520
+ ImmediatePriority,
521
+ renderRoot.bind(null, root, expirationTime),
522
+ );
523
+ flushImmediateQueue();
524
+}
525
+
526
+export function flushInteractiveUpdates() {
527
+ if (workPhase === RenderPhase || workPhase === CommitPhase) {
528
+ // Can't synchronously flush interactive updates if React is already
529
+ // working. This is currently a no-op.
530
+ // TODO: Should we fire a warning? This happens if you synchronously invoke
531
+ // an input event inside an effect, like with `element.click()`.
532
+ return;
533
+ }
534
+ flushPendingDiscreteUpdates();
535
+}
536
+
537
+function resolveLocksOnRoot(root: FiberRoot, expirationTime: ExpirationTime) {
538
+ const firstBatch = root.firstBatch;
539
+ if (
540
+ firstBatch !== null &&
541
+ firstBatch._defer &&
542
+ firstBatch._expirationTime >= expirationTime
543
+ ) {
544
+ root.finishedWork = root.current.alternate;
545
+ root.pendingCommitExpirationTime = expirationTime;
546
+ scheduleCallback(NormalPriority, () => {
547
+ firstBatch._onComplete();
548
+ return null;
549
+ });
550
+ return true;
551
+ } else {
552
+ return false;
553
+ }
554
+}
555
+
556
+export function deferredUpdates<A>(fn: () => A): A {
557
+ // TODO: Remove in favor of Scheduler.next
558
+ return runWithPriority(NormalPriority, fn);
559
+}
560
+
561
+export function interactiveUpdates<A, B, C, R>(
562
+ fn: (A, B, C) => R,
563
+ a: A,
564
+ b: B,
565
+ c: C,
566
+): R {
567
+ if (workPhase === NotWorking) {
568
+ // TODO: Remove this call. Instead of doing this automatically, the caller
569
+ // should explicitly call flushInteractiveUpdates.
570
+ flushPendingDiscreteUpdates();
571
+ }
572
+ return runWithPriority(UserBlockingPriority, fn.bind(null, a, b, c));
573
+}
574
+
575
+export function syncUpdates<A, B, C, R>(
576
+ fn: (A, B, C) => R,
577
+ a: A,
578
+ b: B,
579
+ c: C,
580
+): R {
581
+ return runWithPriority(ImmediatePriority, fn.bind(null, a, b, c));
582
+}
583
+
584
+function flushPendingDiscreteUpdates() {
585
+ if (rootsWithPendingDiscreteUpdates !== null) {
586
+ // For each root with pending discrete updates, schedule a callback to
587
+ // immediately flush them.
588
+ const roots = rootsWithPendingDiscreteUpdates;
589
+ rootsWithPendingDiscreteUpdates = null;
590
+ roots.forEach((expirationTime, root) => {
591
+ scheduleCallback(
592
+ ImmediatePriority,
593
+ renderRoot.bind(null, root, expirationTime),
594
+ );
595
+ });
596
+ // Now flush the immediate queue.
597
+ flushImmediateQueue();
598
+ }
599
+}
600
+
601
+export function batchedUpdates<A, R>(fn: A => R, a: A): R {
602
+ if (workPhase !== NotWorking) {
603
+ // We're already working, or inside a batch, so batchedUpdates is a no-op.
604
+ return fn(a);
605
+ }
606
+ workPhase = BatchedPhase;
607
+ try {
608
+ return fn(a);
609
+ } finally {
610
+ workPhase = NotWorking;
611
+ // Flush the immediate callbacks that were scheduled during this batch
612
+ flushImmediateQueue();
613
+ }
614
+}
615
+
616
+export function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
617
+ if (workPhase !== BatchedPhase && workPhase !== FlushSyncPhase) {
618
+ // We're not inside batchedUpdates or flushSync, so unbatchedUpdates is
619
+ // a no-op.
620
+ return fn(a);
621
+ }
622
+ const prevWorkPhase = workPhase;
623
+ workPhase = LegacyUnbatchedPhase;
624
+ try {
625
+ return fn(a);
626
+ } finally {
627
+ workPhase = prevWorkPhase;
628
+ }
629
+}
630
+
631
+export function flushSync<A, R>(fn: A => R, a: A): R {
632
+ if (workPhase === RenderPhase || workPhase === CommitPhase) {
633
+ invariant(
634
+ false,
635
+ 'flushSync was called from inside a lifecycle method. It cannot be ' +
636
+ 'called when React is already rendering.',
637
+ );
638
+ }
639
+ const prevWorkPhase = workPhase;
640
+ workPhase = FlushSyncPhase;
641
+ try {
642
+ return runWithPriority(ImmediatePriority, fn.bind(null, a));
643
+ } finally {
644
+ workPhase = prevWorkPhase;
645
+ // Flush the immediate callbacks that were scheduled during this batch.
646
+ // Note that this will happen even if batchedUpdates is higher up
647
+ // the stack.
648
+ flushImmediateQueue();
649
+ }
650
+}
651
+
652
+export function flushControlled(fn: () => mixed): void {
653
+ const prevWorkPhase = workPhase;
654
+ workPhase = BatchedPhase;
655
+ try {
656
+ runWithPriority(ImmediatePriority, fn);
657
+ } finally {
658
+ workPhase = prevWorkPhase;
659
+ if (workPhase === NotWorking) {
660
+ // Flush the immediate callbacks that were scheduled during this batch
661
+ flushImmediateQueue();
662
+ }
663
+ }
664
+}
665
+
666
+function prepareFreshStack(root, expirationTime) {
667
+ root.pendingCommitExpirationTime = NoWork;
668
+
669
+ if (workInProgress !== null) {
670
+ let interruptedWork = workInProgress.return;
671
+ while (interruptedWork !== null) {
672
+ unwindInterruptedWork(interruptedWork);
673
+ interruptedWork = interruptedWork.return;
674
+ }
675
+ }
676
+ workInProgressRoot = root;
677
+ workInProgress = createWorkInProgress(root.current, null, expirationTime);
678
+ renderExpirationTime = expirationTime;
679
+ workInProgressRootExitStatus = RootIncomplete;
680
+ workInProgressRootAbsoluteTimeoutMs = -1;
681
+
682
+ if (__DEV__) {
683
+ ReactStrictModeWarnings.discardPendingWarnings();
684
+ }
685
+}
686
+
687
+function renderRoot(
688
+ root: FiberRoot,
689
+ expirationTime: ExpirationTime,
690
+ isSync: boolean,
691
+): SchedulerCallback | null {
692
+ invariant(
693
+ workPhase !== RenderPhase && workPhase !== CommitPhase,
694
+ 'Should not already be working.',
695
+ );
696
+
697
+ if (enableUserTimingAPI && expirationTime !== Sync) {
698
+ const didExpire = isSync;
699
+ const timeoutMs = expirationTimeToMs(expirationTime);
700
+ stopRequestCallbackTimer(didExpire, timeoutMs);
701
+ }
702
+
703
+ if (root.firstPendingTime < expirationTime) {
704
+ // If there's no work left at this expiration time, exit immediately. This
705
+ // happens when multiple callbacks are scheduled for a single root, but an
706
+ // earlier callback flushes the work of a later one.
707
+ return null;
708
+ }
709
+
710
+ if (root.pendingCommitExpirationTime === expirationTime) {
711
+ // There's already a pending commit at this expiration time.
712
+ root.pendingCommitExpirationTime = NoWork;
713
+ return commitRoot.bind(null, root, expirationTime);
714
+ }
715
+
716
+ flushPassiveEffects();
717
+
718
+ // If the root or expiration time have changed, throw out the existing stack
719
+ // and prepare a fresh one. Otherwise we'll continue where we left off.
720
+ if (root !== workInProgressRoot || expirationTime !== renderExpirationTime) {
721
+ prepareFreshStack(root, expirationTime);
722
+ startWorkOnPendingInteraction(root, expirationTime);
723
+ }
724
+
725
+ // If we have a work-in-progress fiber, it means there's still work to do
726
+ // in this root.
727
+ if (workInProgress !== null) {
728
+ const prevWorkPhase = workPhase;
729
+ workPhase = RenderPhase;
730
+ let prevDispatcher = ReactCurrentDispatcher.current;
731
+ if (prevDispatcher === null) {
732
+ // The React isomorphic package does not include a default dispatcher.
733
+ // Instead the first renderer will lazily attach one, in order to give
734
+ // nicer error messages.
735
+ prevDispatcher = ContextOnlyDispatcher;
736
+ }
737
+ ReactCurrentDispatcher.current = ContextOnlyDispatcher;
738
+ let prevInteractions: Set<Interaction> | null = null;
739
+ if (enableSchedulerTracing) {
740
+ prevInteractions = __interactionsRef.current;
741
+ __interactionsRef.current = root.memoizedInteractions;
742
+ }
743
+
744
+ startWorkLoopTimer(workInProgress);
745
+ do {
746
+ try {
747
+ if (isSync) {
748
+ if (expirationTime !== Sync) {
749
+ // An async update expired. There may be other expired updates on
750
+ // this root. We should render all the expired work in a
751
+ // single batch.
752
+ const currentTime = requestCurrentTime();
753
+ if (currentTime < expirationTime) {
754
+ // Restart at the current time.
755
+ workPhase = prevWorkPhase;
756
+ ReactCurrentDispatcher.current = prevDispatcher;
757
+ return renderRoot.bind(null, root, currentTime);
758
+ }
759
+ }
760
+ workLoopSync();
761
+ } else {
762
+ // Since we know we're in a React event, we can clear the current
763
+ // event time. The next update will compute a new event time.
764
+ currentEventTime = NoWork;
765
+ workLoop();
766
+ }
767
+ break;
768
+ } catch (thrownValue) {
769
+ // Reset module-level state that was set during the render phase.
770
+ resetContextDependencies();
771
+ resetHooks();
772
+
773
+ const sourceFiber = workInProgress;
774
+ if (sourceFiber === null || sourceFiber.return === null) {
775
+ // Expected to be working on a non-root fiber. This is a fatal error
776
+ // because there's no ancestor that can handle it; the root is
777
+ // supposed to capture all errors that weren't caught by an error
778
+ // boundary.
779
+ prepareFreshStack(root, expirationTime);
780
+ workPhase = prevWorkPhase;
781
+ throw thrownValue;
782
+ }
783
+
784
+ if (enableProfilerTimer && sourceFiber.mode & ProfileMode) {
785
+ // Record the time spent rendering before an error was thrown. This
786
+ // avoids inaccurate Profiler durations in the case of a
787
+ // suspended render.
788
+ stopProfilerTimerIfRunningAndRecordDelta(sourceFiber, true);
789
+ }
790
+
791
+ const returnFiber = sourceFiber.return;
792
+ throwException(
793
+ root,
794
+ returnFiber,
795
+ sourceFiber,
796
+ thrownValue,
797
+ renderExpirationTime,
798
+ );
799
+ workInProgress = completeUnitOfWork(sourceFiber);
800
+ }
801
+ } while (true);
802
+
803
+ workPhase = prevWorkPhase;
804
+ resetContextDependencies();
805
+ ReactCurrentDispatcher.current = prevDispatcher;
806
+ if (enableSchedulerTracing) {
807
+ __interactionsRef.current = ((prevInteractions: any): Set<Interaction>);
808
+ }
809
+
810
+ if (workInProgress !== null) {
811
+ // There's still work left over. Return a continuation.
812
+ stopInterruptedWorkLoopTimer();
813
+ if (expirationTime !== Sync) {
814
+ startRequestCallbackTimer();
815
+ }
816
+ return renderRoot.bind(null, root, expirationTime);
817
+ }
818
+ }
819
+
820
+ // We now have a consistent tree. The next step is either to commit it, or, if
821
+ // something suspended, wait to commit it after a timeout.
822
+ stopFinishedWorkLoopTimer();
823
+
824
+ const isLocked = resolveLocksOnRoot(root, expirationTime);
825
+ if (isLocked) {
826
+ // This root has a lock that prevents it from committing. Exit. If we begin
827
+ // work on the root again, without any intervening updates, it will finish
828
+ // without doing additional work.
829
+ return null;
830
+ }
831
+
832
+ // Set this to null to indicate there's no in-progress render.
833
+ workInProgressRoot = null;
834
+
835
+ switch (workInProgressRootExitStatus) {
836
+ case RootIncomplete: {
837
+ invariant(false, 'Should have a work-in-progress.');
838
+ }
839
+ // Flow knows about invariant, so it compains if I add a break statement,
840
+ // but eslint doesn't know about invariant, so it complains if I do.
841
+ // eslint-disable-next-line no-fallthrough
842
+ case RootErrored: {
843
+ // An error was thrown. First check if there is lower priority work
844
+ // scheduled on this root.
845
+ const lastPendingTime = root.lastPendingTime;
846
+ if (root.lastPendingTime < expirationTime) {
847
+ // There's lower priority work. Before raising the error, try rendering
848
+ // at the lower priority to see if it fixes it. Use a continuation to
849
+ // maintain the existing priority and position in the queue.
850
+ return renderRoot.bind(null, root, lastPendingTime);
851
+ }
852
+ if (!isSync) {
853
+ // If we're rendering asynchronously, it's possible the error was
854
+ // caused by tearing due to a mutation during an event. Try rendering
855
+ // one more time without yiedling to events.
856
+ prepareFreshStack(root, expirationTime);
857
+ scheduleCallback(
858
+ ImmediatePriority,
859
+ renderRoot.bind(null, root, expirationTime),
860
+ );
861
+ return null;
862
+ }
863
+ // If we're already rendering synchronously, commit the root in its
864
+ // errored state.
865
+ return commitRoot.bind(null, root, expirationTime);
866
+ }
867
+ case RootSuspended: {
868
+ const lastPendingTime = root.lastPendingTime;
869
+ if (root.lastPendingTime < expirationTime) {
870
+ // There's lower priority work. It might be unsuspended. Try rendering
871
+ // at that level.
872
+ return renderRoot.bind(null, root, lastPendingTime);
873
+ }
874
+ if (!isSync) {
875
+ const msUntilTimeout = computeMsUntilTimeout(
876
+ root,
877
+ workInProgressRootAbsoluteTimeoutMs,
878
+ );
879
+ if (msUntilTimeout > 0) {
880
+ // The render is suspended, it hasn't timed out, and there's no lower
881
+ // priority work to do. Instead of committing the fallback
882
+ // immediately, wait for more data to arrive.
883
+ root.timeoutHandle = scheduleTimeout(
884
+ commitRoot.bind(null, root, expirationTime),
885
+ msUntilTimeout,
886
+ );
887
+ return null;
888
+ }
889
+ }
890
+ // The work expired. Commit immediately.
891
+ return commitRoot.bind(null, root, expirationTime);
892
+ }
893
+ case RootCompleted: {
894
+ // The work completed. Ready to commit.
895
+ return commitRoot.bind(null, root, expirationTime);
896
+ }
897
+ default: {
898
+ invariant(false, 'Unknown root exit status.');
899
+ }
900
+ }
901
+}
902
+
903
+export function renderDidSuspend(
904
+ root: FiberRoot,
905
+ absoluteTimeoutMs: number,
906
+ // TODO: Don't need this argument anymore
907
+ suspendedTime: ExpirationTime,
908
+) {
909
+ if (
910
+ absoluteTimeoutMs >= 0 &&
911
+ workInProgressRootAbsoluteTimeoutMs < absoluteTimeoutMs
912
+ ) {
913
+ workInProgressRootAbsoluteTimeoutMs = absoluteTimeoutMs;
914
+ if (workInProgressRootExitStatus === RootIncomplete) {
915
+ workInProgressRootExitStatus = RootSuspended;
916
+ }
917
+ }
918
+}
919
+
920
+export function renderDidError() {
921
+ if (
922
+ workInProgressRootExitStatus === RootIncomplete ||
923
+ workInProgressRootExitStatus === RootSuspended
924
+ ) {
925
+ workInProgressRootExitStatus = RootErrored;
926
+ }
927
+}
928
+
929
+function workLoopSync() {
930
+ // Already timed out, so perform work without checking if we need to yield.
931
+ while (workInProgress !== null) {
932
+ workInProgress = performUnitOfWork(workInProgress);
933
+ }
934
+}
935
+
936
+function workLoop() {
937
+ // Perform work until Scheduler asks us to yield
938
+ while (workInProgress !== null && !shouldYield()) {
939
+ workInProgress = performUnitOfWork(workInProgress);
940
+ }
941
+}
942
+
943
+function performUnitOfWork(unitOfWork: Fiber): Fiber | null {
944
+ // The current, flushed, state of this fiber is the alternate. Ideally
945
+ // nothing should rely on this, but relying on it here means that we don't
946
+ // need an additional field on the work in progress.
947
+ const current = unitOfWork.alternate;
948
+
949
+ startWorkTimer(unitOfWork);
950
+ setCurrentDebugFiberInDEV(unitOfWork);
951
+
952
+ let next;
953
+ if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoContext) {
954
+ startProfilerTimer(unitOfWork);
955
+ next = beginWork(current, unitOfWork, renderExpirationTime);
956
+ stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
957
+ } else {
958
+ next = beginWork(current, unitOfWork, renderExpirationTime);
959
+ }
960
+
961
+ resetCurrentDebugFiberInDEV();
962
+ unitOfWork.memoizedProps = unitOfWork.pendingProps;
963
+ if (next === null) {
964
+ // If this doesn't spawn new work, complete the current work.
965
+ next = completeUnitOfWork(unitOfWork);
966
+ }
967
+
968
+ ReactCurrentOwner.current = null;
969
+ return next;
970
+}
971
+
972
+function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {
973
+ // Attempt to complete the current unit of work, then move to the next
974
+ // sibling. If there are no more siblings, return to the parent fiber.
975
+ workInProgress = unitOfWork;
976
+ do {
977
+ // The current, flushed, state of this fiber is the alternate. Ideally
978
+ // nothing should rely on this, but relying on it here means that we don't
979
+ // need an additional field on the work in progress.
980
+ const current = workInProgress.alternate;
981
+ const returnFiber = workInProgress.return;
982
+
983
+ // Check if the work completed or if something threw.
984
+ if ((workInProgress.effectTag & Incomplete) === NoEffect) {
985
+ setCurrentDebugFiberInDEV(workInProgress);
986
+ let next;
987
+ if (
988
+ !enableProfilerTimer ||
989
+ (workInProgress.mode & ProfileMode) === NoContext
990
+ ) {
991
+ next = completeWork(current, workInProgress, renderExpirationTime);
992
+ } else {
993
+ startProfilerTimer(workInProgress);
994
+ next = completeWork(current, workInProgress, renderExpirationTime);
995
+ // Update render duration assuming we didn't error.
996
+ stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
997
+ }
998
+ stopWorkTimer(workInProgress);
999
+ resetCurrentDebugFiberInDEV();
1000
+ resetChildExpirationTime(workInProgress);
1001
+
1002
+ if (next !== null) {
1003
+ // Completing this fiber spawned new work. Work on that next.
1004
+ return next;
1005
+ }
1006
+
1007
+ if (
1008
+ returnFiber !== null &&
1009
+ // Do not append effects to parents if a sibling failed to complete
1010
+ (returnFiber.effectTag & Incomplete) === NoEffect
1011
+ ) {
1012
+ // Append all the effects of the subtree and this fiber onto the effect
1013
+ // list of the parent. The completion order of the children affects the
1014
+ // side-effect order.
1015
+ if (returnFiber.firstEffect === null) {
1016
+ returnFiber.firstEffect = workInProgress.firstEffect;
1017
+ }
1018
+ if (workInProgress.lastEffect !== null) {
1019
+ if (returnFiber.lastEffect !== null) {
1020
+ returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
1021
+ }
1022
+ returnFiber.lastEffect = workInProgress.lastEffect;
1023
+ }
1024
+
1025
+ // If this fiber had side-effects, we append it AFTER the children's
1026
+ // side-effects. We can perform certain side-effects earlier if needed,
1027
+ // by doing multiple passes over the effect list. We don't want to
1028
+ // schedule our own side-effect on our own list because if end up
1029
+ // reusing children we'll schedule this effect onto itself since we're
1030
+ // at the end.
1031
+ const effectTag = workInProgress.effectTag;
1032
+
1033
+ // Skip both NoWork and PerformedWork tags when creating the effect
1034
+ // list. PerformedWork effect is read by React DevTools but shouldn't be
1035
+ // committed.
1036
+ if (effectTag > PerformedWork) {
1037
+ if (returnFiber.lastEffect !== null) {
1038
+ returnFiber.lastEffect.nextEffect = workInProgress;
1039
+ } else {
1040
+ returnFiber.firstEffect = workInProgress;
1041
+ }
1042
+ returnFiber.lastEffect = workInProgress;
1043
+ }
1044
+ }
1045
+ } else {
1046
+ // This fiber did not complete because something threw. Pop values off
1047
+ // the stack without entering the complete phase. If this is a boundary,
1048
+ // capture values if possible.
1049
+ const next = unwindWork(workInProgress, renderExpirationTime);
1050
+
1051
+ // Because this fiber did not complete, don't reset its expiration time.
1052
+
1053
+ if (
1054
+ enableProfilerTimer &&
1055
+ (workInProgress.mode & ProfileMode) !== NoContext
1056
+ ) {
1057
+ // Record the render duration for the fiber that errored.
1058
+ stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
1059
+
1060
+ // Include the time spent working on failed children before continuing.
1061
+ let actualDuration = workInProgress.actualDuration;
1062
+ let child = workInProgress.child;
1063
+ while (child !== null) {
1064
+ actualDuration += child.actualDuration;
1065
+ child = child.sibling;
1066
+ }
1067
+ workInProgress.actualDuration = actualDuration;
1068
+ }
1069
+
1070
+ if (next !== null) {
1071
+ // If completing this work spawned new work, do that next. We'll come
1072
+ // back here again.
1073
+ // Since we're restarting, remove anything that is not a host effect
1074
+ // from the effect tag.
1075
+ // TODO: The name stopFailedWorkTimer is misleading because Suspense
1076
+ // also captures and restarts.
1077
+ stopFailedWorkTimer(workInProgress);
1078
+ next.effectTag &= HostEffectMask;
1079
+ return next;
1080
+ }
1081
+ stopWorkTimer(workInProgress);
1082
+
1083
+ if (returnFiber !== null) {
1084
+ // Mark the parent fiber as incomplete and clear its effect list.
1085
+ returnFiber.firstEffect = returnFiber.lastEffect = null;
1086
+ returnFiber.effectTag |= Incomplete;
1087
+ }
1088
+ }
1089
+
1090
+ const siblingFiber = workInProgress.sibling;
1091
+ if (siblingFiber !== null) {
1092
+ // If there is more work to do in this returnFiber, do that next.
1093
+ return siblingFiber;
1094
+ }
1095
+ // Otherwise, return to the parent
1096
+ workInProgress = returnFiber;
1097
+ } while (workInProgress !== null);
1098
+
1099
+ // We've reached the root.
1100
+ if (workInProgressRootExitStatus === RootIncomplete) {
1101
+ workInProgressRootExitStatus = RootCompleted;
1102
+ }
1103
+ return null;
1104
+}
1105
+
1106
+function resetChildExpirationTime(completedWork: Fiber) {
1107
+ if (
1108
+ renderExpirationTime !== Never &&
1109
+ completedWork.childExpirationTime === Never
1110
+ ) {
1111
+ // The children of this component are hidden. Don't bubble their
1112
+ // expiration times.
1113
+ return;
1114
+ }
1115
+
1116
+ let newChildExpirationTime = NoWork;
1117
+
1118
+ // Bubble up the earliest expiration time.
1119
+ if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoContext) {
1120
+ // In profiling mode, resetChildExpirationTime is also used to reset
1121
+ // profiler durations.
1122
+ let actualDuration = completedWork.actualDuration;
1123
+ let treeBaseDuration = completedWork.selfBaseDuration;
1124
+
1125
+ // When a fiber is cloned, its actualDuration is reset to 0. This value will
1126
+ // only be updated if work is done on the fiber (i.e. it doesn't bailout).
1127
+ // When work is done, it should bubble to the parent's actualDuration. If
1128
+ // the fiber has not been cloned though, (meaning no work was done), then
1129
+ // this value will reflect the amount of time spent working on a previous
1130
+ // render. In that case it should not bubble. We determine whether it was
1131
+ // cloned by comparing the child pointer.
1132
+ const shouldBubbleActualDurations =
1133
+ completedWork.alternate === null ||
1134
+ completedWork.child !== completedWork.alternate.child;
1135
+
1136
+ let child = completedWork.child;
1137
+ while (child !== null) {
1138
+ const childUpdateExpirationTime = child.expirationTime;
1139
+ const childChildExpirationTime = child.childExpirationTime;
1140
+ if (childUpdateExpirationTime > newChildExpirationTime) {
1141
+ newChildExpirationTime = childUpdateExpirationTime;
1142
+ }
1143
+ if (childChildExpirationTime > newChildExpirationTime) {
1144
+ newChildExpirationTime = childChildExpirationTime;
1145
+ }
1146
+ if (shouldBubbleActualDurations) {
1147
+ actualDuration += child.actualDuration;
1148
+ }
1149
+ treeBaseDuration += child.treeBaseDuration;
1150
+ child = child.sibling;
1151
+ }
1152
+ completedWork.actualDuration = actualDuration;
1153
+ completedWork.treeBaseDuration = treeBaseDuration;
1154
+ } else {
1155
+ let child = completedWork.child;
1156
+ while (child !== null) {
1157
+ const childUpdateExpirationTime = child.expirationTime;
1158
+ const childChildExpirationTime = child.childExpirationTime;
1159
+ if (childUpdateExpirationTime > newChildExpirationTime) {
1160
+ newChildExpirationTime = childUpdateExpirationTime;
1161
+ }
1162
+ if (childChildExpirationTime > newChildExpirationTime) {
1163
+ newChildExpirationTime = childChildExpirationTime;
1164
+ }
1165
+ child = child.sibling;
1166
+ }
1167
+ }
1168
+
1169
+ completedWork.childExpirationTime = newChildExpirationTime;
1170
+}
1171
+
1172
+function commitRoot(root, expirationTime) {
1173
+ runWithPriority(
1174
+ ImmediatePriority,
1175
+ commitRootImpl.bind(null, root, expirationTime),
1176
+ );
1177
+ // If there are passive effects, schedule a callback to flush them. This goes
1178
+ // outside commitRootImpl so that it inherits the priority of the render.
1179
+ if (rootWithPendingPassiveEffects !== null) {
1180
+ const priorityLevel = getCurrentPriorityLevel();
1181
+ scheduleCallback(priorityLevel, () => {
1182
+ flushPassiveEffects();
1183
+ return null;
1184
+ });
1185
+ }
1186
+ return null;
1187
+}
1188
+
1189
+function commitRootImpl(root, expirationTime) {
1190
+ flushPassiveEffects();
1191
+ flushRenderPhaseStrictModeWarningsInDEV();
1192
+
1193
+ invariant(
1194
+ workPhase !== RenderPhase && workPhase !== CommitPhase,
1195
+ 'Should not already be working.',
1196
+ );
1197
+ const finishedWork = root.current.alternate;
1198
+ invariant(finishedWork !== null, 'Should have a work-in-progress root.');
1199
+
1200
+ // commitRoot never returns a continuation; it always finishes synchronously.
1201
+ // So we can clear these now to allow a new callback to be scheduled.
1202
+ root.callbackNode = null;
1203
+ root.callbackExpirationTime = NoWork;
1204
+
1205
+ startCommitTimer();
1206
+
1207
+ // Update the first and last pending times on this root. The new first
1208
+ // pending time is whatever is left on the root fiber.
1209
+ const updateExpirationTimeBeforeCommit = finishedWork.expirationTime;
1210
+ const childExpirationTimeBeforeCommit = finishedWork.childExpirationTime;
1211
+ const firstPendingTimeBeforeCommit =
1212
+ childExpirationTimeBeforeCommit > updateExpirationTimeBeforeCommit
1213
+ ? childExpirationTimeBeforeCommit
1214
+ : updateExpirationTimeBeforeCommit;
1215
+ root.firstPendingTime = firstPendingTimeBeforeCommit;
1216
+ if (firstPendingTimeBeforeCommit < root.lastPendingTime) {
1217
+ // This usually means we've finished all the work, but it can also happen
1218
+ // when something gets downprioritized during render, like a hidden tree.
1219
+ root.lastPendingTime = firstPendingTimeBeforeCommit;
1220
+ }
1221
+
1222
+ if (root === workInProgressRoot) {
1223
+ // We can reset these now that they are finished.
1224
+ workInProgressRoot = null;
1225
+ workInProgress = null;
1226
+ renderExpirationTime = NoWork;
1227
+ } else {
1228
+ // This indicates that the last root we worked on is not the same one that
1229
+ // we're committing now. This most commonly happens when a suspended root
1230
+ // times out.
1231
+ }
1232
+
1233
+ // Get the list of effects.
1234
+ let firstEffect;
1235
+ if (finishedWork.effectTag > PerformedWork) {
1236
+ // A fiber's effect list consists only of its children, not itself. So if
1237
+ // the root has an effect, we need to add it to the end of the list. The
1238
+ // resulting list is the set that would belong to the root's parent, if it
1239
+ // had one; that is, all the effects in the tree including the root.
1240
+ if (finishedWork.lastEffect !== null) {
1241
+ finishedWork.lastEffect.nextEffect = finishedWork;
1242
+ firstEffect = finishedWork.firstEffect;
1243
+ } else {
1244
+ firstEffect = finishedWork;
1245
+ }
1246
+ } else {
1247
+ // There is no effect on the root.
1248
+ firstEffect = finishedWork.firstEffect;
1249
+ }
1250
+
1251
+ if (firstEffect !== null) {
1252
+ const prevWorkPhase = workPhase;
1253
+ workPhase = CommitPhase;
1254
+ let prevInteractions: Set<Interaction> | null = null;
1255
+ if (enableSchedulerTracing) {
1256
+ prevInteractions = __interactionsRef.current;
1257
+ __interactionsRef.current = root.memoizedInteractions;
1258
+ }
1259
+
1260
+ // Reset this to null before calling lifecycles
1261
+ ReactCurrentOwner.current = null;
1262
+
1263
+ // The commit phase is broken into several sub-phases. We do a separate pass
1264
+ // of the effect list for each phase: all mutation effects come before all
1265
+ // layout effects, and so on.
1266
+
1267
+ // The first phase a "before mutation" phase. We use this phase to read the
1268
+ // state of the host tree right before we mutate it. This is where
1269
+ // getSnapshotBeforeUpdate is called.
1270
+ startCommitSnapshotEffectsTimer();
1271
+ prepareForCommit(root.containerInfo);
1272
+ nextEffect = firstEffect;
1273
+ do {
1274
+ if (__DEV__) {
1275
+ invokeGuardedCallback(null, commitBeforeMutationEffects, null);
1276
+ if (hasCaughtError()) {
1277
+ invariant(nextEffect !== null, 'Should be working on an effect.');
1278
+ const error = clearCaughtError();
1279
+ captureCommitPhaseError(nextEffect, error);
1280
+ nextEffect = nextEffect.nextEffect;
1281
+ }
1282
+ } else {
1283
+ try {
1284
+ commitBeforeMutationEffects();
1285
+ } catch (error) {
1286
+ invariant(nextEffect !== null, 'Should be working on an effect.');
1287
+ captureCommitPhaseError(nextEffect, error);
1288
+ nextEffect = nextEffect.nextEffect;
1289
+ }
1290
+ }
1291
+ } while (nextEffect !== null);
1292
+ stopCommitSnapshotEffectsTimer();
1293
+
1294
+ if (enableProfilerTimer) {
1295
+ // Mark the current commit time to be shared by all Profilers in this
1296
+ // batch. This enables them to be grouped later.
1297
+ recordCommitTime();
1298
+ }
1299
+
1300
+ // The next phase is the mutation phase, where we mutate the host tree.
1301
+ startCommitHostEffectsTimer();
1302
+ nextEffect = firstEffect;
1303
+ do {
1304
+ if (__DEV__) {
1305
+ invokeGuardedCallback(null, commitMutationEffects, null);
1306
+ if (hasCaughtError()) {
1307
+ invariant(nextEffect !== null, 'Should be working on an effect.');
1308
+ const error = clearCaughtError();
1309
+ captureCommitPhaseError(nextEffect, error);
1310
+ nextEffect = nextEffect.nextEffect;
1311
+ }
1312
+ } else {
1313
+ try {
1314
+ commitMutationEffects();
1315
+ } catch (error) {
1316
+ invariant(nextEffect !== null, 'Should be working on an effect.');
1317
+ captureCommitPhaseError(nextEffect, error);
1318
+ nextEffect = nextEffect.nextEffect;
1319
+ }
1320
+ }
1321
+ } while (nextEffect !== null);
1322
+ stopCommitHostEffectsTimer();
1323
+ resetAfterCommit(root.containerInfo);
1324
+
1325
+ // The work-in-progress tree is now the current tree. This must come after
1326
+ // the mutation phase, so that the previous tree is still current during
1327
+ // componentWillUnmount, but before the layout phase, so that the finished
1328
+ // work is current during componentDidMount/Update.
1329
+ root.current = finishedWork;
1330
+
1331
+ // The next phase is the layout phase, where we call effects that read
1332
+ // the host tree after it's been mutated. The idiomatic use case for this is
1333
+ // layout, but class component lifecycles also fire here for legacy reasons.
1334
+ startCommitLifeCyclesTimer();
1335
+ nextEffect = firstEffect;
1336
+ do {
1337
+ if (__DEV__) {
1338
+ invokeGuardedCallback(
1339
+ null,
1340
+ commitLayoutEffects,
1341
+ null,
1342
+ root,
1343
+ expirationTime,
1344
+ );
1345
+ if (hasCaughtError()) {
1346
+ invariant(nextEffect !== null, 'Should be working on an effect.');
1347
+ const error = clearCaughtError();
1348
+ captureCommitPhaseError(nextEffect, error);
1349
+ nextEffect = nextEffect.nextEffect;
1350
+ }
1351
+ } else {
1352
+ try {
1353
+ commitLayoutEffects(root, expirationTime);
1354
+ } catch (error) {
1355
+ invariant(nextEffect !== null, 'Should be working on an effect.');
1356
+ captureCommitPhaseError(nextEffect, error);
1357
+ nextEffect = nextEffect.nextEffect;
1358
+ }
1359
+ }
1360
+ } while (nextEffect !== null);
1361
+ stopCommitLifeCyclesTimer();
1362
+
1363
+ nextEffect = null;
1364
+
1365
+ if (enableSchedulerTracing) {
1366
+ __interactionsRef.current = ((prevInteractions: any): Set<Interaction>);
1367
+ }
1368
+ workPhase = prevWorkPhase;
1369
+ } else {
1370
+ // No effects.
1371
+ root.current = finishedWork;
1372
+ // Measure these anyway so the flamegraph explicitly shows that there were
1373
+ // no effects.
1374
+ // TODO: Maybe there's a better way to report this.
1375
+ startCommitSnapshotEffectsTimer();
1376
+ stopCommitSnapshotEffectsTimer();
1377
+ if (enableProfilerTimer) {
1378
+ recordCommitTime();
1379
+ }
1380
+ startCommitHostEffectsTimer();
1381
+ stopCommitHostEffectsTimer();
1382
+ startCommitLifeCyclesTimer();
1383
+ stopCommitLifeCyclesTimer();
1384
+ }
1385
+
1386
+ stopCommitTimer();
1387
+
1388
+ if (rootDoesHavePassiveEffects) {
1389
+ // This commit has passive effects. Stash a reference to them. But don't
1390
+ // schedule a callback until after flushing layout work.
1391
+ rootDoesHavePassiveEffects = false;
1392
+ rootWithPendingPassiveEffects = root;
1393
+ pendingPassiveEffectsExpirationTime = expirationTime;
1394
+ } else {
1395
+ if (enableSchedulerTracing) {
1396
+ // If there are no passive effects, then we can complete the pending
1397
+ // interactions. Otherwise, we'll wait until after the passive effects
1398
+ // are flushed.
1399
+ finishPendingInteractions(root, expirationTime);
1400
+ }
1401
+ }
1402
+
1403
+ // Check if there's remaining work on this root
1404
+ const remainingExpirationTime = root.firstPendingTime;
1405
+ if (remainingExpirationTime !== NoWork) {
1406
+ const currentTime = requestCurrentTime();
1407
+ const priorityLevel = inferPriorityFromExpirationTime(
1408
+ currentTime,
1409
+ remainingExpirationTime,
1410
+ );
1411
+ scheduleCallbackForRoot(root, priorityLevel, remainingExpirationTime);
1412
+ } else {
1413
+ // If there's no remaining work, we can clear the set of already failed
1414
+ // error boundaries.
1415
+ legacyErrorBoundariesThatAlreadyFailed = null;
1416
+ }
1417
+
1418
+ onCommitRoot(finishedWork.stateNode);
1419
+
1420
+ if (remainingExpirationTime === Sync) {
1421
+ // Count the number of times the root synchronously re-renders without
1422
+ // finishing. If there are too many, it indicates an infinite update loop.
1423
+ if (root === rootWithNestedUpdates) {
1424
+ nestedUpdateCount++;
1425
+ } else {
1426
+ nestedUpdateCount = 0;
1427
+ rootWithNestedUpdates = root;
1428
+ }
1429
+ } else {
1430
+ nestedUpdateCount = 0;
1431
+ }
1432
+
1433
+ if (hasUncaughtError) {
1434
+ hasUncaughtError = false;
1435
+ const error = firstUncaughtError;
1436
+ firstUncaughtError = null;
1437
+ throw error;
1438
+ }
1439
+
1440
+ if (workPhase === LegacyUnbatchedPhase) {
1441
+ // This is a legacy edge case. We just committed the initial mount of
1442
+ // a ReactDOM.render-ed root inside of batchedUpdates. The commit fired
1443
+ // synchronously, but layout updates should be deferred until the end
1444
+ // of the batch.
1445
+ return null;
1446
+ }
1447
+
1448
+ // If layout work was scheduled, flush it now.
1449
+ flushImmediateQueue();
1450
+ return null;
1451
+}
1452
+
1453
+function commitBeforeMutationEffects() {
1454
+ while (nextEffect !== null) {
1455
+ if ((nextEffect.effectTag & Snapshot) !== NoEffect) {
1456
+ setCurrentDebugFiberInDEV(nextEffect);
1457
+ recordEffect();
1458
+
1459
+ const current = nextEffect.alternate;
1460
+ commitBeforeMutationEffectOnFiber(current, nextEffect);
1461
+
1462
+ resetCurrentDebugFiberInDEV();
1463
+ }
1464
+ nextEffect = nextEffect.nextEffect;
1465
+ }
1466
+}
1467
+
1468
+function commitMutationEffects() {
1469
+ // TODO: Should probably move the bulk of this function to commitWork.
1470
+ while (nextEffect !== null) {
1471
+ setCurrentDebugFiberInDEV(nextEffect);
1472
+
1473
+ const effectTag = nextEffect.effectTag;
1474
+
1475
+ if (effectTag & ContentReset) {
1476
+ commitResetTextContent(nextEffect);
1477
+ }
1478
+
1479
+ if (effectTag & Ref) {
1480
+ const current = nextEffect.alternate;
1481
+ if (current !== null) {
1482
+ commitDetachRef(current);
1483
+ }
1484
+ }
1485
+
1486
+ // The following switch statement is only concerned about placement,
1487
+ // updates, and deletions. To avoid needing to add a case for every possible
1488
+ // bitmap value, we remove the secondary effects from the effect tag and
1489
+ // switch on that value.
1490
+ let primaryEffectTag = effectTag & (Placement | Update | Deletion);
1491
+ switch (primaryEffectTag) {
1492
+ case Placement: {
1493
+ commitPlacement(nextEffect);
1494
+ // Clear the "placement" from effect tag so that we know that this is
1495
+ // inserted, before any life-cycles like componentDidMount gets called.
1496
+ // TODO: findDOMNode doesn't rely on this any more but isMounted does
1497
+ // and isMounted is deprecated anyway so we should be able to kill this.
1498
+ nextEffect.effectTag &= ~Placement;
1499
+ break;
1500
+ }
1501
+ case PlacementAndUpdate: {
1502
+ // Placement
1503
+ commitPlacement(nextEffect);
1504
+ // Clear the "placement" from effect tag so that we know that this is
1505
+ // inserted, before any life-cycles like componentDidMount gets called.
1506
+ nextEffect.effectTag &= ~Placement;
1507
+
1508
+ // Update
1509
+ const current = nextEffect.alternate;
1510
+ commitWork(current, nextEffect);
1511
+ break;
1512
+ }
1513
+ case Update: {
1514
+ const current = nextEffect.alternate;
1515
+ commitWork(current, nextEffect);
1516
+ break;
1517
+ }
1518
+ case Deletion: {
1519
+ commitDeletion(nextEffect);
1520
+ break;
1521
+ }
1522
+ }
1523
+
1524
+ // TODO: Only record a mutation effect if primaryEffectTag is non-zero.
1525
+ recordEffect();
1526
+
1527
+ resetCurrentDebugFiberInDEV();
1528
+ nextEffect = nextEffect.nextEffect;
1529
+ }
1530
+}
1531
+
1532
+function commitLayoutEffects(
1533
+ root: FiberRoot,
1534
+ committedExpirationTime: ExpirationTime,
1535
+) {
1536
+ // TODO: Should probably move the bulk of this function to commitWork.
1537
+ while (nextEffect !== null) {
1538
+ setCurrentDebugFiberInDEV(nextEffect);
1539
+
1540
+ const effectTag = nextEffect.effectTag;
1541
+
1542
+ if (effectTag & (Update | Callback)) {
1543
+ recordEffect();
1544
+ const current = nextEffect.alternate;
1545
+ commitLayoutEffectOnFiber(
1546
+ root,
1547
+ current,
1548
+ nextEffect,
1549
+ committedExpirationTime,
1550
+ );
1551
+ }
1552
+
1553
+ if (effectTag & Ref) {
1554
+ recordEffect();
1555
+ commitAttachRef(nextEffect);
1556
+ }
1557
+
1558
+ if (effectTag & Passive) {
1559
+ rootDoesHavePassiveEffects = true;
1560
+ }
1561
+
1562
+ resetCurrentDebugFiberInDEV();
1563
+ nextEffect = nextEffect.nextEffect;
1564
+ }
1565
+}
1566
+
1567
+export function flushPassiveEffects() {
1568
+ if (rootWithPendingPassiveEffects === null) {
1569
+ return false;
1570
+ }
1571
+ const root = rootWithPendingPassiveEffects;
1572
+ const expirationTime = pendingPassiveEffectsExpirationTime;
1573
+ rootWithPendingPassiveEffects = null;
1574
+ pendingPassiveEffectsExpirationTime = NoWork;
1575
+
1576
+ let prevInteractions: Set<Interaction> | null = null;
1577
+ if (enableSchedulerTracing) {
1578
+ prevInteractions = __interactionsRef.current;
1579
+ __interactionsRef.current = root.memoizedInteractions;
1580
+ }
1581
+
1582
+ invariant(
1583
+ workPhase !== RenderPhase && workPhase !== CommitPhase,
1584
+ 'Cannot flush passive effects while already rendering.',
1585
+ );
1586
+ const prevWorkPhase = workPhase;
1587
+ workPhase = CommitPhase;
1588
+
1589
+ // Note: This currently assumes there are no passive effects on the root
1590
+ // fiber, because the root is not part of its own effect list. This could
1591
+ // change in the future.
1592
+ let effect = root.current.firstEffect;
1593
+ while (effect !== null) {
1594
+ if (__DEV__) {
1595
+ setCurrentDebugFiberInDEV(effect);
1596
+ invokeGuardedCallback(null, commitPassiveHookEffects, null, effect);
1597
+ if (hasCaughtError()) {
1598
+ invariant(effect !== null, 'Should be working on an effect.');
1599
+ const error = clearCaughtError();
1600
+ captureCommitPhaseError(effect, error);
1601
+ }
1602
+ resetCurrentDebugFiberInDEV();
1603
+ } else {
1604
+ try {
1605
+ commitPassiveHookEffects(effect);
1606
+ } catch (error) {
1607
+ invariant(effect !== null, 'Should be working on an effect.');
1608
+ captureCommitPhaseError(effect, error);
1609
+ }
1610
+ }
1611
+ effect = effect.nextEffect;
1612
+ }
1613
+
1614
+ if (enableSchedulerTracing) {
1615
+ __interactionsRef.current = ((prevInteractions: any): Set<Interaction>);
1616
+ finishPendingInteractions(root, expirationTime);
1617
+ }
1618
+
1619
+ workPhase = prevWorkPhase;
1620
+ flushImmediateQueue();
1621
+
1622
+ // If additional passive effects were scheduled, increment a counter. If this
1623
+ // exceeds the limit, we'll fire a warning.
1624
+ nestedPassiveUpdateCount =
1625
+ rootWithPendingPassiveEffects === null ? 0 : nestedPassiveUpdateCount + 1;
1626
+
1627
+ return true;
1628
+}
1629
+
1630
+export function isAlreadyFailedLegacyErrorBoundary(instance: mixed): boolean {
1631
+ return (
1632
+ legacyErrorBoundariesThatAlreadyFailed !== null &&
1633
+ legacyErrorBoundariesThatAlreadyFailed.has(instance)
1634
+ );
1635
+}
1636
+
1637
+export function markLegacyErrorBoundaryAsFailed(instance: mixed) {
1638
+ if (legacyErrorBoundariesThatAlreadyFailed === null) {
1639
+ legacyErrorBoundariesThatAlreadyFailed = new Set([instance]);
1640
+ } else {
1641
+ legacyErrorBoundariesThatAlreadyFailed.add(instance);
1642
+ }
1643
+}
1644
+
1645
+function prepareToThrowUncaughtError(error: mixed) {
1646
+ if (!hasUncaughtError) {
1647
+ hasUncaughtError = true;
1648
+ firstUncaughtError = error;
1649
+ }
1650
+}
1651
+export const onUncaughtError = prepareToThrowUncaughtError;
1652
+
1653
+function captureCommitPhaseErrorOnRoot(
1654
+ rootFiber: Fiber,
1655
+ sourceFiber: Fiber,
1656
+ error: mixed,
1657
+) {
1658
+ const errorInfo = createCapturedValue(error, sourceFiber);
1659
+ const update = createRootErrorUpdate(rootFiber, errorInfo, Sync);
1660
+ enqueueUpdate(rootFiber, update);
1661
+ const root = markUpdateTimeFromFiberToRoot(rootFiber, Sync);
1662
+ if (root !== null) {
1663
+ scheduleCallbackForRoot(root, ImmediatePriority, Sync);
1664
+ }
1665
+}
1666
+
1667
+export function captureCommitPhaseError(sourceFiber: Fiber, error: mixed) {
1668
+ if (sourceFiber.tag === HostRoot) {
1669
+ // Error was thrown at the root. There is no parent, so the root
1670
+ // itself should capture it.
1671
+ captureCommitPhaseErrorOnRoot(sourceFiber, sourceFiber, error);
1672
+ return;
1673
+ }
1674
+
1675
+ let fiber = sourceFiber.return;
1676
+ while (fiber !== null) {
1677
+ if (fiber.tag === HostRoot) {
1678
+ captureCommitPhaseErrorOnRoot(fiber, sourceFiber, error);
1679
+ return;
1680
+ } else if (fiber.tag === ClassComponent) {
1681
+ const ctor = fiber.type;
1682
+ const instance = fiber.stateNode;
1683
+ if (
1684
+ typeof ctor.getDerivedStateFromError === 'function' ||
1685
+ (typeof instance.componentDidCatch === 'function' &&
1686
+ !isAlreadyFailedLegacyErrorBoundary(instance))
1687
+ ) {
1688
+ const errorInfo = createCapturedValue(error, sourceFiber);
1689
+ const update = createClassErrorUpdate(
1690
+ fiber,
1691
+ errorInfo,
1692
+ // TODO: This is always sync
1693
+ Sync,
1694
+ );
1695
+ enqueueUpdate(fiber, update);
1696
+ const root = markUpdateTimeFromFiberToRoot(fiber, Sync);
1697
+ if (root !== null) {
1698
+ scheduleCallbackForRoot(root, ImmediatePriority, Sync);
1699
+ }
1700
+ return;
1701
+ }
1702
+ }
1703
+ fiber = fiber.return;
1704
+ }
1705
+}
1706
+
1707
+export function pingSuspendedRoot(
1708
+ root: FiberRoot,
1709
+ thenable: Thenable,
1710
+ suspendedTime: ExpirationTime,
1711
+) {
1712
+ const pingCache = root.pingCache;
1713
+ if (pingCache !== null) {
1714
+ // The thenable resolved, so we no longer need to memoize, because it will
1715
+ // never be thrown again.
1716
+ pingCache.delete(thenable);
1717
+ }
1718
+
1719
+ if (workInProgressRoot === root && renderExpirationTime === suspendedTime) {
1720
+ // Received a ping at the same priority level at which we're currently
1721
+ // rendering. Restart from the root. Don't need to schedule a ping because
1722
+ // we're already working on this tree.
1723
+ prepareFreshStack(root, renderExpirationTime);
1724
+ return;
1725
+ }
1726
+
1727
+ const lastPendingTime = root.lastPendingTime;
1728
+ if (lastPendingTime < suspendedTime) {
1729
+ // The root is no longer suspended at this time.
1730
+ return;
1731
+ }
1732
+
1733
+ const pingTime = root.pingTime;
1734
+ if (pingTime !== NoWork && pingTime < suspendedTime) {
1735
+ // There's already a lower priority ping scheduled.
1736
+ return;
1737
+ }
1738
+
1739
+ // Mark the time at which this ping was scheduled.
1740
+ root.pingTime = suspendedTime;
1741
+
1742
+ const currentTime = requestCurrentTime();
1743
+ const priorityLevel = inferPriorityFromExpirationTime(
1744
+ currentTime,
1745
+ suspendedTime,
1746
+ );
1747
+ scheduleCallbackForRoot(root, priorityLevel, suspendedTime);
1748
+}
1749
+
1750
+export function retryTimedOutBoundary(boundaryFiber: Fiber) {
1751
+ // The boundary fiber (a Suspense component) previously timed out and was
1752
+ // rendered in its fallback state. One of the promises that suspended it has
1753
+ // resolved, which means at least part of the tree was likely unblocked. Try
1754
+ // rendering again, at a new expiration time.
1755
+ const currentTime = requestCurrentTime();
1756
+ const retryTime = computeExpirationForFiber(currentTime, boundaryFiber);
1757
+ // TODO: Special case idle priority?
1758
+ const priorityLevel = inferPriorityFromExpirationTime(currentTime, retryTime);
1759
+ const root = markUpdateTimeFromFiberToRoot(boundaryFiber, retryTime);
1760
+ if (root !== null) {
1761
+ scheduleCallbackForRoot(root, priorityLevel, retryTime);
1762
+ }
1763
+}
1764
+
1765
+export function resolveRetryThenable(boundaryFiber: Fiber, thenable: Thenable) {
1766
+ let retryCache: WeakSet<Thenable> | Set<Thenable> | null;
1767
+ if (enableSuspenseServerRenderer) {
1768
+ switch (boundaryFiber.tag) {
1769
+ case SuspenseComponent:
1770
+ retryCache = boundaryFiber.stateNode;
1771
+ break;
1772
+ case DehydratedSuspenseComponent:
1773
+ retryCache = boundaryFiber.memoizedState;
1774
+ break;
1775
+ default:
1776
+ invariant(
1777
+ false,
1778
+ 'Pinged unknown suspense boundary type. ' +
1779
+ 'This is probably a bug in React.',
1780
+ );
1781
+ }
1782
+ } else {
1783
+ retryCache = boundaryFiber.stateNode;
1784
+ }
1785
+
1786
+ if (retryCache !== null) {
1787
+ // The thenable resolved, so we no longer need to memoize, because it will
1788
+ // never be thrown again.
1789
+ retryCache.delete(thenable);
1790
+ }
1791
+
1792
+ retryTimedOutBoundary(boundaryFiber);
1793
+}
1794
+
1795
+export function inferStartTimeFromExpirationTime(
1796
+ root: FiberRoot,
1797
+ expirationTime: ExpirationTime,
1798
+) {
1799
+ // We don't know exactly when the update was scheduled, but we can infer an
1800
+ // approximate start time from the expiration time.
1801
+ const earliestExpirationTimeMs = expirationTimeToMs(root.firstPendingTime);
1802
+ // TODO: Track this on the root instead. It's more accurate, doesn't rely on
1803
+ // assumptions about priority, and isn't coupled to Scheduler details.
1804
+ return earliestExpirationTimeMs - LOW_PRIORITY_EXPIRATION;
1805
+}
1806
+
1807
+function computeMsUntilTimeout(root, absoluteTimeoutMs) {
1808
+ if (disableYielding) {
1809
+ // Timeout immediately when yielding is disabled.
1810
+ return 0;
1811
+ }
1812
+
1813
+ // Find the earliest uncommitted expiration time in the tree, including
1814
+ // work that is suspended. The timeout threshold cannot be longer than
1815
+ // the overall expiration.
1816
+ const earliestExpirationTimeMs = expirationTimeToMs(root.firstPendingTime);
1817
+ if (earliestExpirationTimeMs < absoluteTimeoutMs) {
1818
+ absoluteTimeoutMs = earliestExpirationTimeMs;
1819
+ }
1820
+
1821
+ // Subtract the current time from the absolute timeout to get the number
1822
+ // of milliseconds until the timeout. In other words, convert an absolute
1823
+ // timestamp to a relative time. This is the value that is passed
1824
+ // to `setTimeout`.
1825
+ let msUntilTimeout = absoluteTimeoutMs - now();
1826
+ return msUntilTimeout < 0 ? 0 : msUntilTimeout;
1827
+}
1828
+
1829
+function checkForNestedUpdates() {
1830
+ if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
1831
+ nestedUpdateCount = 0;
1832
+ rootWithNestedUpdates = null;
1833
+ invariant(
1834
+ false,
1835
+ 'Maximum update depth exceeded. This can happen when a component ' +
1836
+ 'repeatedly calls setState inside componentWillUpdate or ' +
1837
+ 'componentDidUpdate. React limits the number of nested updates to ' +
1838
+ 'prevent infinite loops.',
1839
+ );
1840
+ }
1841
+
1842
+ if (__DEV__) {
1843
+ if (nestedPassiveUpdateCount > NESTED_PASSIVE_UPDATE_LIMIT) {
1844
+ nestedPassiveUpdateCount = 0;
1845
+ warning(
1846
+ false,
1847
+ 'Maximum update depth exceeded. This can happen when a component ' +
1848
+ "calls setState inside useEffect, but useEffect either doesn't " +
1849
+ 'have a dependency array, or one of the dependencies changes on ' +
1850
+ 'every render.',
1851
+ );
1852
+ }
1853
+ }
1854
+}
1855
+
1856
+function flushRenderPhaseStrictModeWarningsInDEV() {
1857
+ if (__DEV__) {
1858
+ ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings();
1859
+ ReactStrictModeWarnings.flushLegacyContextWarning();
1860
+
1861
+ if (warnAboutDeprecatedLifecycles) {
1862
+ ReactStrictModeWarnings.flushPendingDeprecationWarnings();
1863
+ }
1864
+ }
1865
+}
1866
+
1867
+function stopFinishedWorkLoopTimer() {
1868
+ const didCompleteRoot = true;
1869
+ stopWorkLoopTimer(interruptedBy, didCompleteRoot);
1870
+ interruptedBy = null;
1871
+}
1872
+
1873
+function stopInterruptedWorkLoopTimer() {
1874
+ // TODO: Track which fiber caused the interruption.
1875
+ const didCompleteRoot = false;
1876
+ stopWorkLoopTimer(interruptedBy, didCompleteRoot);
1877
+ interruptedBy = null;
1878
+}
1879
+
1880
+function checkForInterruption(
1881
+ fiberThatReceivedUpdate: Fiber,
1882
+ updateExpirationTime: ExpirationTime,
1883
+) {
1884
+ if (
1885
+ enableUserTimingAPI &&
1886
+ workInProgressRoot !== null &&
1887
+ updateExpirationTime > renderExpirationTime
1888
+ ) {
1889
+ interruptedBy = fiberThatReceivedUpdate;
1890
+ }
1891
+}
1892
+
1893
+let didWarnStateUpdateForUnmountedComponent: Set<string> | null = null;
1894
+function warnAboutUpdateOnUnmountedFiberInDEV(fiber) {
1895
+ if (__DEV__) {
1896
+ const tag = fiber.tag;
1897
+ if (
1898
+ tag !== HostRoot &&
1899
+ tag !== ClassComponent &&
1900
+ tag !== FunctionComponent &&
1901
+ tag !== ForwardRef &&
1902
+ tag !== MemoComponent &&
1903
+ tag !== SimpleMemoComponent
1904
+ ) {
1905
+ // Only warn for user-defined components, not internal ones like Suspense.
1906
+ return;
1907
+ }
1908
+ // We show the whole stack but dedupe on the top component's name because
1909
+ // the problematic code almost always lies inside that component.
1910
+ const componentName = getComponentName(fiber.type) || 'ReactComponent';
1911
+ if (didWarnStateUpdateForUnmountedComponent !== null) {
1912
+ if (didWarnStateUpdateForUnmountedComponent.has(componentName)) {
1913
+ return;
1914
+ }
1915
+ didWarnStateUpdateForUnmountedComponent.add(componentName);
1916
+ } else {
1917
+ didWarnStateUpdateForUnmountedComponent = new Set([componentName]);
1918
+ }
1919
+ warningWithoutStack(
1920
+ false,
1921
+ "Can't perform a React state update on an unmounted component. This " +
1922
+ 'is a no-op, but it indicates a memory leak in your application. To ' +
1923
+ 'fix, cancel all subscriptions and asynchronous tasks in %s.%s',
1924
+ tag === ClassComponent
1925
+ ? 'the componentWillUnmount method'
1926
+ : 'a useEffect cleanup function',
1927
+ getStackByFiberInDevAndProd(fiber),
1928
+ );
1929
+ }
1930
+}
1931
+
1932
+let beginWork;
1933
+if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
1934
+ let dummyFiber = null;
1935
+ beginWork = (current, unitOfWork, expirationTime) => {
1936
+ // If a component throws an error, we replay it again in a synchronously
1937
+ // dispatched event, so that the debugger will treat it as an uncaught
1938
+ // error See ReactErrorUtils for more information.
1939
+
1940
+ // Before entering the begin phase, copy the work-in-progress onto a dummy
1941
+ // fiber. If beginWork throws, we'll use this to reset the state.
1942
+ const originalWorkInProgressCopy = assignFiberPropertiesInDEV(
1943
+ dummyFiber,
1944
+ unitOfWork,
1945
+ );
1946
+ try {
1947
+ return originalBeginWork(current, unitOfWork, expirationTime);
1948
+ } catch (originalError) {
1949
+ if (
1950
+ originalError !== null &&
1951
+ typeof originalError === 'object' &&
1952
+ typeof originalError.then === 'function'
1953
+ ) {
1954
+ // Don't replay promises. Treat everything else like an error.
1955
+ throw originalError;
1956
+ }
1957
+
1958
+ // Keep this code in sync with renderRoot; any changes here must have
1959
+ // corresponding changes there.
1960
+ resetContextDependencies();
1961
+ resetHooks();
1962
+
1963
+ // Unwind the failed stack frame
1964
+ unwindInterruptedWork(unitOfWork);
1965
+
1966
+ // Restore the original properties of the fiber.
1967
+ assignFiberPropertiesInDEV(unitOfWork, originalWorkInProgressCopy);
1968
+
1969
+ if (enableProfilerTimer && unitOfWork.mode & ProfileMode) {
1970
+ // Reset the profiler timer.
1971
+ startProfilerTimer(unitOfWork);
1972
+ }
1973
+
1974
+ // Run beginWork again.
1975
+ invokeGuardedCallback(
1976
+ null,
1977
+ originalBeginWork,
1978
+ null,
1979
+ current,
1980
+ unitOfWork,
1981
+ expirationTime,
1982
+ );
1983
+
1984
+ if (hasCaughtError()) {
1985
+ const replayError = clearCaughtError();
1986
+ // `invokeGuardedCallback` sometimes sets an expando `_suppressLogging`.
1987
+ // Rethrow this error instead of the original one.
1988
+ throw replayError;
1989
+ } else {
1990
+ // This branch is reachable if the render phase is impure.
1991
+ throw originalError;
1992
+ }
1993
+ }
1994
+ };
1995
+} else {
1996
+ beginWork = originalBeginWork;
1997
+}
1998
+
1999
+let didWarnAboutUpdateInRender = false;
2000
+let didWarnAboutUpdateInGetChildContext = false;
2001
+function warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber) {
2002
+ if (__DEV__) {
2003
+ if (fiber.tag === ClassComponent) {
2004
+ switch (ReactCurrentDebugFiberPhaseInDEV) {
2005
+ case 'getChildContext':
2006
+ if (didWarnAboutUpdateInGetChildContext) {
2007
+ return;
2008
+ }
2009
+ warningWithoutStack(
2010
+ false,
2011
+ 'setState(...): Cannot call setState() inside getChildContext()',
2012
+ );
2013
+ didWarnAboutUpdateInGetChildContext = true;
2014
+ break;
2015
+ case 'render':
2016
+ if (didWarnAboutUpdateInRender) {
2017
+ return;
2018
+ }
2019
+ warningWithoutStack(
2020
+ false,
2021
+ 'Cannot update during an existing state transition (such as ' +
2022
+ 'within `render`). Render methods should be a pure function of ' +
2023
+ 'props and state.',
2024
+ );
2025
+ didWarnAboutUpdateInRender = true;
2026
+ break;
2027
+ }
2028
+ }
2029
+ }
2030
+}
2031
+
2032
+function warnIfNotCurrentlyActingUpdatesInDEV(fiber: Fiber): void {
2033
+ if (__DEV__) {
2034
+ if (
2035
+ workPhase === NotWorking &&
2036
+ ReactShouldWarnActingUpdates.current === false
2037
+ ) {
2038
+ warningWithoutStack(
2039
+ false,
2040
+ 'An update to %s inside a test was not wrapped in act(...).\n\n' +
2041
+ 'When testing, code that causes React state updates should be ' +
2042
+ 'wrapped into act(...):\n\n' +
2043
+ 'act(() => {\n' +
2044
+ ' /* fire events that update state */\n' +
2045
+ '});\n' +
2046
+ '/* assert on the output */\n\n' +
2047
+ "This ensures that you're testing the behavior the user would see " +
2048
+ 'in the browser.' +
2049
+ ' Learn more at https://fb.me/react-wrap-tests-with-act' +
2050
+ '%s',
2051
+ getComponentName(fiber.type),
2052
+ getStackByFiberInDevAndProd(fiber),
2053
+ );
2054
+ }
2055
+ }
2056
+}
2057
+
2058
+export const warnIfNotCurrentlyActingUpdatesInDev = warnIfNotCurrentlyActingUpdatesInDEV;
2059
+
2060
+function computeThreadID(root, expirationTime) {
2061
+ // Interaction threads are unique per root and expiration time.
2062
+ return expirationTime * 1000 + root.interactionThreadID;
2063
+}
2064
+
2065
+function schedulePendingInteraction(root, expirationTime) {
2066
+ // This is called when work is scheduled on a root. It sets up a pending
2067
+ // interaction, which is completed once the work commits.
2068
+ if (!enableSchedulerTracing) {
2069
+ return;
2070
+ }
2071
+
2072
+ const interactions = __interactionsRef.current;
2073
+ if (interactions.size > 0) {
2074
+ const pendingInteractionMap = root.pendingInteractionMap;
2075
+ const pendingInteractions = pendingInteractionMap.get(expirationTime);
2076
+ if (pendingInteractions != null) {
2077
+ interactions.forEach(interaction => {
2078
+ if (!pendingInteractions.has(interaction)) {
2079
+ // Update the pending async work count for previously unscheduled interaction.
2080
+ interaction.__count++;
2081
+ }
2082
+
2083
+ pendingInteractions.add(interaction);
2084
+ });
2085
+ } else {
2086
+ pendingInteractionMap.set(expirationTime, new Set(interactions));
2087
+
2088
+ // Update the pending async work count for the current interactions.
2089
+ interactions.forEach(interaction => {
2090
+ interaction.__count++;
2091
+ });
2092
+ }
2093
+
2094
+ const subscriber = __subscriberRef.current;
2095
+ if (subscriber !== null) {
2096
+ const threadID = computeThreadID(root, expirationTime);
2097
+ subscriber.onWorkScheduled(interactions, threadID);
2098
+ }
2099
+ }
2100
+}
2101
+
2102
+function startWorkOnPendingInteraction(root, expirationTime) {
2103
+ // This is called when new work is started on a root.
2104
+ if (!enableSchedulerTracing) {
2105
+ return;
2106
+ }
2107
+
2108
+ // Determine which interactions this batch of work currently includes, So that
2109
+ // we can accurately attribute time spent working on it, And so that cascading
2110
+ // work triggered during the render phase will be associated with it.
2111
+ const interactions: Set<Interaction> = new Set();
2112
+ root.pendingInteractionMap.forEach(
2113
+ (scheduledInteractions, scheduledExpirationTime) => {
2114
+ if (scheduledExpirationTime >= expirationTime) {
2115
+ scheduledInteractions.forEach(interaction =>
2116
+ interactions.add(interaction),
2117
+ );
2118
+ }
2119
+ },
2120
+ );
2121
+
2122
+ // Store the current set of interactions on the FiberRoot for a few reasons:
2123
+ // We can re-use it in hot functions like renderRoot() without having to
2124
+ // recalculate it. We will also use it in commitWork() to pass to any Profiler
2125
+ // onRender() hooks. This also provides DevTools with a way to access it when
2126
+ // the onCommitRoot() hook is called.
2127
+ root.memoizedInteractions = interactions;
2128
+
2129
+ if (interactions.size > 0) {
2130
+ const subscriber = __subscriberRef.current;
2131
+ if (subscriber !== null) {
2132
+ const threadID = computeThreadID(root, expirationTime);
2133
+ try {
2134
+ subscriber.onWorkStarted(interactions, threadID);
2135
+ } catch (error) {
2136
+ // If the subscriber throws, rethrow it in a separate task
2137
+ scheduleCallback(ImmediatePriority, () => {
2138
+ throw error;
2139
+ });
2140
+ }
2141
+ }
2142
+ }
2143
+}
2144
+
2145
+function finishPendingInteractions(root, committedExpirationTime) {
2146
+ if (!enableSchedulerTracing) {
2147
+ return;
2148
+ }
2149
+
2150
+ const earliestRemainingTimeAfterCommit = root.firstPendingTime;
2151
+
2152
+ let subscriber;
2153
+
2154
+ try {
2155
+ subscriber = __subscriberRef.current;
2156
+ if (subscriber !== null && root.memoizedInteractions.size > 0) {
2157
+ const threadID = computeThreadID(root, committedExpirationTime);
2158
+ subscriber.onWorkStopped(root.memoizedInteractions, threadID);
2159
+ }
2160
+ } catch (error) {
2161
+ // If the subscriber throws, rethrow it in a separate task
2162
+ scheduleCallback(ImmediatePriority, () => {
2163
+ throw error;
2164
+ });
2165
+ } finally {
2166
+ // Clear completed interactions from the pending Map.
2167
+ // Unless the render was suspended or cascading work was scheduled,
2168
+ // In which case– leave pending interactions until the subsequent render.
2169
+ const pendingInteractionMap = root.pendingInteractionMap;
2170
+ pendingInteractionMap.forEach(
2171
+ (scheduledInteractions, scheduledExpirationTime) => {
2172
+ // Only decrement the pending interaction count if we're done.
2173
+ // If there's still work at the current priority,
2174
+ // That indicates that we are waiting for suspense data.
2175
+ if (scheduledExpirationTime > earliestRemainingTimeAfterCommit) {
2176
+ pendingInteractionMap.delete(scheduledExpirationTime);
2177
+
2178
+ scheduledInteractions.forEach(interaction => {
2179
+ interaction.__count--;
2180
+
2181
+ if (subscriber !== null && interaction.__count === 0) {
2182
+ try {
2183
+ subscriber.onInteractionScheduledWorkCompleted(interaction);
2184
+ } catch (error) {
2185
+ // If the subscriber throws, rethrow it in a separate task
2186
+ scheduleCallback(ImmediatePriority, () => {
2187
+ throw error;
2188
+ });
2189
+ }
2190
+ }
2191
+ });
2192
+ }
2193
+ },
2194
+ );
2195
+ }
2196
+}