How to use captureCommitPhaseErrorOnRoot method in Playwright Internal

Best JavaScript code snippet using playwright-internal

Run Playwright Internal automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

ReactFiberWorkLoop.new.js

Source: ReactFiberWorkLoop.new.js Github

copy
1/**
2 * Copyright (c) Facebook, Inc. and its 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
10import type {Thenable, Wakeable} from 'shared/ReactTypes';
11import type {Fiber, FiberRoot} from './ReactInternalTypes';
12import type {Lanes, Lane} from './ReactFiberLane';
13import type {ReactPriorityLevel} from './ReactInternalTypes';
14import type {Interaction} from 'scheduler/src/Tracing';
15import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
16import type {StackCursor} from './ReactFiberStack.new';
17import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new';
18import type {Flags} from './ReactFiberFlags';
19
20import {
21  warnAboutDeprecatedLifecycles,
22  enableSuspenseServerRenderer,
23  replayFailedUnitOfWorkWithInvokeGuardedCallback,
24  enableProfilerTimer,
25  enableProfilerCommitHooks,
26  enableSchedulerTracing,
27  warnAboutUnmockedScheduler,
28  deferRenderPhaseUpdateToNextBatch,
29  decoupleUpdatePriorityFromScheduler,
30  enableDebugTracing,
31  enableSchedulingProfiler,
32  enableScopeAPI,
33  skipUnmountedBoundaries,
34  enableDoubleInvokingEffects,
35} from 'shared/ReactFeatureFlags';
36import ReactSharedInternals from 'shared/ReactSharedInternals';
37import invariant from 'shared/invariant';
38
39import {
40  scheduleCallback,
41  cancelCallback,
42  getCurrentPriorityLevel,
43  runWithPriority,
44  shouldYield,
45  requestPaint,
46  now,
47  NoPriority as NoSchedulerPriority,
48  ImmediatePriority as ImmediateSchedulerPriority,
49  UserBlockingPriority as UserBlockingSchedulerPriority,
50  NormalPriority as NormalSchedulerPriority,
51  flushSyncCallbackQueue,
52  scheduleSyncCallback,
53} from './SchedulerWithReactIntegration.new';
54import {
55  NoFlags as NoHookEffect,
56  Passive as HookPassive,
57} from './ReactHookEffectTags';
58import {
59  logCommitStarted,
60  logCommitStopped,
61  logLayoutEffectsStarted,
62  logLayoutEffectsStopped,
63  logPassiveEffectsStarted,
64  logPassiveEffectsStopped,
65  logRenderStarted,
66  logRenderStopped,
67} from './DebugTracing';
68import {
69  markCommitStarted,
70  markCommitStopped,
71  markLayoutEffectsStarted,
72  markLayoutEffectsStopped,
73  markPassiveEffectsStarted,
74  markPassiveEffectsStopped,
75  markRenderStarted,
76  markRenderYielded,
77  markRenderStopped,
78} from './SchedulingProfiler';
79
80// The scheduler is imported here *only* to detect whether it's been mocked
81import * as Scheduler from 'scheduler';
82
83import {__interactionsRef, __subscriberRef} from 'scheduler/tracing';
84
85import {
86  prepareForCommit,
87  resetAfterCommit,
88  scheduleTimeout,
89  cancelTimeout,
90  noTimeout,
91  warnsIfNotActing,
92  beforeActiveInstanceBlur,
93  afterActiveInstanceBlur,
94  clearContainer,
95} from './ReactFiberHostConfig';
96
97import {
98  createWorkInProgress,
99  assignFiberPropertiesInDEV,
100} from './ReactFiber.new';
101import {
102  NoMode,
103  StrictMode,
104  ProfileMode,
105  BlockingMode,
106  ConcurrentMode,
107} from './ReactTypeOfMode';
108import {
109  HostRoot,
110  IndeterminateComponent,
111  ClassComponent,
112  SuspenseComponent,
113  SuspenseListComponent,
114  FunctionComponent,
115  ForwardRef,
116  MemoComponent,
117  SimpleMemoComponent,
118  Block,
119  ScopeComponent,
120  Profiler,
121} from './ReactWorkTags';
122import {LegacyRoot} from './ReactRootTags';
123import {
124  NoFlags,
125  Placement,
126  Update,
127  PlacementAndUpdate,
128  Ref,
129  ContentReset,
130  Snapshot,
131  Passive,
132  PassiveStatic,
133  Incomplete,
134  HostEffectMask,
135  Hydrating,
136  HydratingAndUpdate,
137  BeforeMutationMask,
138  MutationMask,
139  LayoutMask,
140  PassiveMask,
141  MountPassiveDev,
142  MountLayoutDev,
143} from './ReactFiberFlags';
144import {
145  NoLanePriority,
146  SyncLanePriority,
147  SyncBatchedLanePriority,
148  InputDiscreteLanePriority,
149  DefaultLanePriority,
150  NoLanes,
151  NoLane,
152  SyncLane,
153  SyncBatchedLane,
154  NoTimestamp,
155  findUpdateLane,
156  findTransitionLane,
157  findRetryLane,
158  includesSomeLane,
159  isSubsetOfLanes,
160  mergeLanes,
161  removeLanes,
162  pickArbitraryLane,
163  hasDiscreteLanes,
164  includesNonIdleWork,
165  includesOnlyRetries,
166  includesOnlyTransitions,
167  getNextLanes,
168  returnNextLanesPriority,
169  setCurrentUpdateLanePriority,
170  getCurrentUpdateLanePriority,
171  markStarvedLanesAsExpired,
172  getLanesToRetrySynchronouslyOnError,
173  getMostRecentEventTime,
174  markRootUpdated,
175  markRootSuspended as markRootSuspended_dontCallThisOneDirectly,
176  markRootPinged,
177  markRootExpired,
178  markDiscreteUpdatesExpired,
179  markRootFinished,
180  schedulerPriorityToLanePriority,
181  lanePriorityToSchedulerPriority,
182} from './ReactFiberLane';
183import {requestCurrentTransition, NoTransition} from './ReactFiberTransition';
184import {beginWork as originalBeginWork} from './ReactFiberBeginWork.new';
185import {completeWork} from './ReactFiberCompleteWork.new';
186import {unwindWork, unwindInterruptedWork} from './ReactFiberUnwindWork.new';
187import {
188  throwException,
189  createRootErrorUpdate,
190  createClassErrorUpdate,
191} from './ReactFiberThrow.new';
192import {
193  commitBeforeMutationLifeCycles as commitBeforeMutationEffectOnFiber,
194  commitPlacement,
195  commitWork,
196  commitDeletion,
197  commitPassiveUnmount as commitPassiveUnmountOnFiber,
198  commitPassiveUnmountInsideDeletedTree as commitPassiveUnmountInsideDeletedTreeOnFiber,
199  commitPassiveMount as commitPassiveMountOnFiber,
200  commitDetachRef,
201  commitAttachRef,
202  commitResetTextContent,
203  isSuspenseBoundaryBeingHidden,
204  invokeLayoutEffectMountInDEV,
205  invokePassiveEffectMountInDEV,
206  invokeLayoutEffectUnmountInDEV,
207  invokePassiveEffectUnmountInDEV,
208  recursivelyCommitLayoutEffects,
209} from './ReactFiberCommitWork.new';
210import {enqueueUpdate} from './ReactUpdateQueue.new';
211import {resetContextDependencies} from './ReactFiberNewContext.new';
212import {
213  resetHooksAfterThrow,
214  ContextOnlyDispatcher,
215  getIsUpdatingOpaqueValueInRenderPhaseInDEV,
216} from './ReactFiberHooks.new';
217import {createCapturedValue} from './ReactCapturedValue';
218import {
219  push as pushToStack,
220  pop as popFromStack,
221  createCursor,
222} from './ReactFiberStack.new';
223
224import {
225  recordCommitTime,
226  startProfilerTimer,
227  stopProfilerTimerIfRunningAndRecordDelta,
228} from './ReactProfilerTimer.new';
229
230// DEV stuff
231import getComponentName from 'shared/getComponentName';
232import ReactStrictModeWarnings from './ReactStrictModeWarnings.new';
233import {
234  isRendering as ReactCurrentDebugFiberIsRenderingInDEV,
235  current as ReactCurrentFiberCurrent,
236  resetCurrentFiber as resetCurrentDebugFiberInDEV,
237  setCurrentFiber as setCurrentDebugFiberInDEV,
238} from './ReactCurrentFiber';
239import {
240  invokeGuardedCallback,
241  hasCaughtError,
242  clearCaughtError,
243} from 'shared/ReactErrorUtils';
244import {onCommitRoot as onCommitRootDevTools} from './ReactFiberDevToolsHook.new';
245import {onCommitRoot as onCommitRootTestSelector} from './ReactTestSelectors';
246
247// Used by `act`
248import enqueueTask from 'shared/enqueueTask';
249import {doesFiberContain} from './ReactFiberTreeReflection';
250
251const ceil = Math.ceil;
252
253const {
254  ReactCurrentDispatcher,
255  ReactCurrentOwner,
256  IsSomeRendererActing,
257} = ReactSharedInternals;
258
259type ExecutionContext = number;
260
261export const NoContext = /*             */ 0b0000000;
262const BatchedContext = /*               */ 0b0000001;
263const EventContext = /*                 */ 0b0000010;
264const DiscreteEventContext = /*         */ 0b0000100;
265const LegacyUnbatchedContext = /*       */ 0b0001000;
266const RenderContext = /*                */ 0b0010000;
267const CommitContext = /*                */ 0b0100000;
268export const RetryAfterError = /*       */ 0b1000000;
269
270type RootExitStatus = 0 | 1 | 2 | 3 | 4 | 5;
271const RootIncomplete = 0;
272const RootFatalErrored = 1;
273const RootErrored = 2;
274const RootSuspended = 3;
275const RootSuspendedWithDelay = 4;
276const RootCompleted = 5;
277
278// Describes where we are in the React execution stack
279let executionContext: ExecutionContext = NoContext;
280// The root we're working on
281let workInProgressRoot: FiberRoot | null = null;
282// The fiber we're working on
283let workInProgress: Fiber | null = null;
284// The lanes we're rendering
285let workInProgressRootRenderLanes: Lanes = NoLanes;
286
287// Stack that allows components to change the render lanes for its subtree
288// This is a superset of the lanes we started working on at the root. The only
289// case where it's different from `workInProgressRootRenderLanes` is when we
290// enter a subtree that is hidden and needs to be unhidden: Suspense and
291// Offscreen component.
292//
293// Most things in the work loop should deal with workInProgressRootRenderLanes.
294// Most things in begin/complete phases should deal with subtreeRenderLanes.
295export let subtreeRenderLanes: Lanes = NoLanes;
296const subtreeRenderLanesCursor: StackCursor<Lanes> = createCursor(NoLanes);
297
298// Whether to root completed, errored, suspended, etc.
299let workInProgressRootExitStatus: RootExitStatus = RootIncomplete;
300// A fatal error, if one is thrown
301let workInProgressRootFatalError: mixed = null;
302// "Included" lanes refer to lanes that were worked on during this render. It's
303// slightly different than `renderLanes` because `renderLanes` can change as you
304// enter and exit an Offscreen tree. This value is the combination of all render
305// lanes for the entire render phase.
306let workInProgressRootIncludedLanes: Lanes = NoLanes;
307// The work left over by components that were visited during this render. Only
308// includes unprocessed updates, not work in bailed out children.
309let workInProgressRootSkippedLanes: Lanes = NoLanes;
310// Lanes that were updated (in an interleaved event) during this render.
311let workInProgressRootUpdatedLanes: Lanes = NoLanes;
312// Lanes that were pinged (in an interleaved event) during this render.
313let workInProgressRootPingedLanes: Lanes = NoLanes;
314
315let mostRecentlyUpdatedRoot: FiberRoot | null = null;
316
317// The most recent time we committed a fallback. This lets us ensure a train
318// model where we don't commit new loading states in too quick succession.
319let globalMostRecentFallbackTime: number = 0;
320const FALLBACK_THROTTLE_MS: number = 500;
321
322// The absolute time for when we should start giving up on rendering
323// more and prefer CPU suspense heuristics instead.
324let workInProgressRootRenderTargetTime: number = Infinity;
325// How long a render is supposed to take before we start following CPU
326// suspense heuristics and opt out of rendering more content.
327const RENDER_TIMEOUT_MS = 500;
328
329// Used to avoid traversing the return path to find the nearest Profiler ancestor during commit.
330let nearestProfilerOnStack: Fiber | null = null;
331
332function resetRenderTimer() {
333  workInProgressRootRenderTargetTime = now() + RENDER_TIMEOUT_MS;
334}
335
336export function getRenderTargetTime(): number {
337  return workInProgressRootRenderTargetTime;
338}
339
340let hasUncaughtError = false;
341let firstUncaughtError = null;
342let legacyErrorBoundariesThatAlreadyFailed: Set<mixed> | null = null;
343
344let rootDoesHavePassiveEffects: boolean = false;
345let rootWithPendingPassiveEffects: FiberRoot | null = null;
346let pendingPassiveEffectsRenderPriority: ReactPriorityLevel = NoSchedulerPriority;
347let pendingPassiveEffectsLanes: Lanes = NoLanes;
348
349let rootsWithPendingDiscreteUpdates: Set<FiberRoot> | null = null;
350
351// Use these to prevent an infinite loop of nested updates
352const NESTED_UPDATE_LIMIT = 50;
353let nestedUpdateCount: number = 0;
354let rootWithNestedUpdates: FiberRoot | null = null;
355
356const NESTED_PASSIVE_UPDATE_LIMIT = 50;
357let nestedPassiveUpdateCount: number = 0;
358
359// Marks the need to reschedule pending interactions at these lanes
360// during the commit phase. This enables them to be traced across components
361// that spawn new work during render. E.g. hidden boundaries, suspended SSR
362// hydration or SuspenseList.
363// TODO: Can use a bitmask instead of an array
364let spawnedWorkDuringRender: null | Array<Lane | Lanes> = null;
365
366// If two updates are scheduled within the same event, we should treat their
367// event times as simultaneous, even if the actual clock time has advanced
368// between the first and second call.
369let currentEventTime: number = NoTimestamp;
370let currentEventWipLanes: Lanes = NoLanes;
371let currentEventPendingLanes: Lanes = NoLanes;
372
373// Dev only flag that tracks if passive effects are currently being flushed.
374// We warn about state updates for unmounted components differently in this case.
375let isFlushingPassiveEffects = false;
376
377let focusedInstanceHandle: null | Fiber = null;
378let shouldFireAfterActiveInstanceBlur: boolean = false;
379
380export function getWorkInProgressRoot(): FiberRoot | null {
381  return workInProgressRoot;
382}
383
384export function requestEventTime() {
385  if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
386    // We're inside React, so it's fine to read the actual time.
387    return now();
388  }
389  // We're not inside React, so we may be in the middle of a browser event.
390  if (currentEventTime !== NoTimestamp) {
391    // Use the same start time for all updates until we enter React again.
392    return currentEventTime;
393  }
394  // This is the first update since React yielded. Compute a new start time.
395  currentEventTime = now();
396  return currentEventTime;
397}
398
399export function getCurrentTime() {
400  return now();
401}
402
403export function requestUpdateLane(fiber: Fiber): Lane {
404  // Special cases
405  const mode = fiber.mode;
406  if ((mode & BlockingMode) === NoMode) {
407    return (SyncLane: Lane);
408  } else if ((mode & ConcurrentMode) === NoMode) {
409    return getCurrentPriorityLevel() === ImmediateSchedulerPriority
410      ? (SyncLane: Lane)
411      : (SyncBatchedLane: Lane);
412  } else if (
413    !deferRenderPhaseUpdateToNextBatch &&
414    (executionContext & RenderContext) !== NoContext &&
415    workInProgressRootRenderLanes !== NoLanes
416  ) {
417    // This is a render phase update. These are not officially supported. The
418    // old behavior is to give this the same "thread" (expiration time) as
419    // whatever is currently rendering. So if you call `setState` on a component
420    // that happens later in the same render, it will flush. Ideally, we want to
421    // remove the special case and treat them as if they came from an
422    // interleaved event. Regardless, this pattern is not officially supported.
423    // This behavior is only a fallback. The flag only exists until we can roll
424    // out the setState warning, since existing code might accidentally rely on
425    // the current behavior.
426    return pickArbitraryLane(workInProgressRootRenderLanes);
427  }
428
429  // The algorithm for assigning an update to a lane should be stable for all
430  // updates at the same priority within the same event. To do this, the inputs
431  // to the algorithm must be the same. For example, we use the `renderLanes`
432  // to avoid choosing a lane that is already in the middle of rendering.
433  //
434  // However, the "included" lanes could be mutated in between updates in the
435  // same event, like if you perform an update inside `flushSync`. Or any other
436  // code path that might call `prepareFreshStack`.
437  //
438  // The trick we use is to cache the first of each of these inputs within an
439  // event. Then reset the cached values once we can be sure the event is over.
440  // Our heuristic for that is whenever we enter a concurrent work loop.
441  //
442  // We'll do the same for `currentEventPendingLanes` below.
443  if (currentEventWipLanes === NoLanes) {
444    currentEventWipLanes = workInProgressRootIncludedLanes;
445  }
446
447  const isTransition = requestCurrentTransition() !== NoTransition;
448  if (isTransition) {
449    if (currentEventPendingLanes !== NoLanes) {
450      currentEventPendingLanes =
451        mostRecentlyUpdatedRoot !== null
452          ? mostRecentlyUpdatedRoot.pendingLanes
453          : NoLanes;
454    }
455    return findTransitionLane(currentEventWipLanes, currentEventPendingLanes);
456  }
457
458  // TODO: Remove this dependency on the Scheduler priority.
459  // To do that, we're replacing it with an update lane priority.
460  const schedulerPriority = getCurrentPriorityLevel();
461
462  // The old behavior was using the priority level of the Scheduler.
463  // This couples React to the Scheduler internals, so we're replacing it
464  // with the currentUpdateLanePriority above. As an example of how this
465  // could be problematic, if we're not inside `Scheduler.runWithPriority`,
466  // then we'll get the priority of the current running Scheduler task,
467  // which is probably not what we want.
468  let lane;
469  if (
470    // TODO: Temporary. We're removing the concept of discrete updates.
471    (executionContext & DiscreteEventContext) !== NoContext &&
472    schedulerPriority === UserBlockingSchedulerPriority
473  ) {
474    lane = findUpdateLane(InputDiscreteLanePriority, currentEventWipLanes);
475  } else {
476    const schedulerLanePriority = schedulerPriorityToLanePriority(
477      schedulerPriority,
478    );
479
480    if (decoupleUpdatePriorityFromScheduler) {
481      // In the new strategy, we will track the current update lane priority
482      // inside React and use that priority to select a lane for this update.
483      // For now, we're just logging when they're different so we can assess.
484      const currentUpdateLanePriority = getCurrentUpdateLanePriority();
485
486      if (
487        schedulerLanePriority !== currentUpdateLanePriority &&
488        currentUpdateLanePriority !== NoLanePriority
489      ) {
490        if (__DEV__) {
491          console.error(
492            'Expected current scheduler lane priority %s to match current update lane priority %s',
493            schedulerLanePriority,
494            currentUpdateLanePriority,
495          );
496        }
497      }
498    }
499
500    lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
501  }
502
503  return lane;
504}
505
506function requestRetryLane(fiber: Fiber) {
507  // This is a fork of `requestUpdateLane` designed specifically for Suspense
508  // "retries" — a special update that attempts to flip a Suspense boundary
509  // from its placeholder state to its primary/resolved state.
510
511  // Special cases
512  const mode = fiber.mode;
513  if ((mode & BlockingMode) === NoMode) {
514    return (SyncLane: Lane);
515  } else if ((mode & ConcurrentMode) === NoMode) {
516    return getCurrentPriorityLevel() === ImmediateSchedulerPriority
517      ? (SyncLane: Lane)
518      : (SyncBatchedLane: Lane);
519  }
520
521  // See `requestUpdateLane` for explanation of `currentEventWipLanes`
522  if (currentEventWipLanes === NoLanes) {
523    currentEventWipLanes = workInProgressRootIncludedLanes;
524  }
525  return findRetryLane(currentEventWipLanes);
526}
527
528export function scheduleUpdateOnFiber(
529  fiber: Fiber,
530  lane: Lane,
531  eventTime: number,
532) {
533  checkForNestedUpdates();
534  warnAboutRenderPhaseUpdatesInDEV(fiber);
535
536  const root = markUpdateLaneFromFiberToRoot(fiber, lane);
537  if (root === null) {
538    warnAboutUpdateOnUnmountedFiberInDEV(fiber);
539    return null;
540  }
541
542  // Mark that the root has a pending update.
543  markRootUpdated(root, lane, eventTime);
544
545  if (root === workInProgressRoot) {
546    // Received an update to a tree that's in the middle of rendering. Mark
547    // that there was an interleaved update work on this root. Unless the
548    // `deferRenderPhaseUpdateToNextBatch` flag is off and this is a render
549    // phase update. In that case, we don't treat render phase updates as if
550    // they were interleaved, for backwards compat reasons.
551    if (
552      deferRenderPhaseUpdateToNextBatch ||
553      (executionContext & RenderContext) === NoContext
554    ) {
555      workInProgressRootUpdatedLanes = mergeLanes(
556        workInProgressRootUpdatedLanes,
557        lane,
558      );
559    }
560    if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
561      // The root already suspended with a delay, which means this render
562      // definitely won't finish. Since we have a new update, let's mark it as
563      // suspended now, right before marking the incoming update. This has the
564      // effect of interrupting the current render and switching to the update.
565      // TODO: Make sure this doesn't override pings that happen while we've
566      // already started rendering.
567      markRootSuspended(root, workInProgressRootRenderLanes);
568    }
569  }
570
571  // TODO: requestUpdateLanePriority also reads the priority. Pass the
572  // priority as an argument to that function and this one.
573  const priorityLevel = getCurrentPriorityLevel();
574
575  if (lane === SyncLane) {
576    if (
577      // Check if we're inside unbatchedUpdates
578      (executionContext & LegacyUnbatchedContext) !== NoContext &&
579      // Check if we're not already rendering
580      (executionContext & (RenderContext | CommitContext)) === NoContext
581    ) {
582      // Register pending interactions on the root to avoid losing traced interaction data.
583      schedulePendingInteractions(root, lane);
584
585      // This is a legacy edge case. The initial mount of a ReactDOM.render-ed
586      // root inside of batchedUpdates should be synchronous, but layout updates
587      // should be deferred until the end of the batch.
588      performSyncWorkOnRoot(root);
589    } else {
590      ensureRootIsScheduled(root, eventTime);
591      schedulePendingInteractions(root, lane);
592      if (executionContext === NoContext) {
593        // Flush the synchronous work now, unless we're already working or inside
594        // a batch. This is intentionally inside scheduleUpdateOnFiber instead of
595        // scheduleCallbackForFiber to preserve the ability to schedule a callback
596        // without immediately flushing it. We only do this for user-initiated
597        // updates, to preserve historical behavior of legacy mode.
598        resetRenderTimer();
599        flushSyncCallbackQueue();
600      }
601    }
602  } else {
603    // Schedule a discrete update but only if it's not Sync.
604    if (
605      (executionContext & DiscreteEventContext) !== NoContext &&
606      // Only updates at user-blocking priority or greater are considered
607      // discrete, even inside a discrete event.
608      (priorityLevel === UserBlockingSchedulerPriority ||
609        priorityLevel === ImmediateSchedulerPriority)
610    ) {
611      // This is the result of a discrete event. Track the lowest priority
612      // discrete update per root so we can flush them early, if needed.
613      if (rootsWithPendingDiscreteUpdates === null) {
614        rootsWithPendingDiscreteUpdates = new Set([root]);
615      } else {
616        rootsWithPendingDiscreteUpdates.add(root);
617      }
618    }
619    // Schedule other updates after in case the callback is sync.
620    ensureRootIsScheduled(root, eventTime);
621    schedulePendingInteractions(root, lane);
622  }
623
624  // We use this when assigning a lane for a transition inside
625  // `requestUpdateLane`. We assume it's the same as the root being updated,
626  // since in the common case of a single root app it probably is. If it's not
627  // the same root, then it's not a huge deal, we just might batch more stuff
628  // together more than necessary.
629  mostRecentlyUpdatedRoot = root;
630}
631
632// This is split into a separate function so we can mark a fiber with pending
633// work without treating it as a typical update that originates from an event;
634// e.g. retrying a Suspense boundary isn't an update, but it does schedule work
635// on a fiber.
636function markUpdateLaneFromFiberToRoot(
637  sourceFiber: Fiber,
638  lane: Lane,
639): FiberRoot | null {
640  // Update the source fiber's lanes
641  sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
642  let alternate = sourceFiber.alternate;
643  if (alternate !== null) {
644    alternate.lanes = mergeLanes(alternate.lanes, lane);
645  }
646  if (__DEV__) {
647    if (
648      alternate === null &&
649      (sourceFiber.flags & (Placement | Hydrating)) !== NoFlags
650    ) {
651      warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber);
652    }
653  }
654  // Walk the parent path to the root and update the child expiration time.
655  let node = sourceFiber;
656  let parent = sourceFiber.return;
657  while (parent !== null) {
658    parent.childLanes = mergeLanes(parent.childLanes, lane);
659    alternate = parent.alternate;
660    if (alternate !== null) {
661      alternate.childLanes = mergeLanes(alternate.childLanes, lane);
662    } else {
663      if (__DEV__) {
664        if ((parent.flags & (Placement | Hydrating)) !== NoFlags) {
665          warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber);
666        }
667      }
668    }
669    node = parent;
670    parent = parent.return;
671  }
672  if (node.tag === HostRoot) {
673    const root: FiberRoot = node.stateNode;
674    return root;
675  } else {
676    return null;
677  }
678}
679
680// Use this function to schedule a task for a root. There's only one task per
681// root; if a task was already scheduled, we'll check to make sure the priority
682// of the existing task is the same as the priority of the next level that the
683// root has work on. This function is called on every update, and right before
684// exiting a task.
685function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
686  const existingCallbackNode = root.callbackNode;
687
688  // Check if any lanes are being starved by other work. If so, mark them as
689  // expired so we know to work on those next.
690  markStarvedLanesAsExpired(root, currentTime);
691
692  // Determine the next lanes to work on, and their priority.
693  const nextLanes = getNextLanes(
694    root,
695    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
696  );
697  // This returns the priority level computed during the `getNextLanes` call.
698  const newCallbackPriority = returnNextLanesPriority();
699
700  if (nextLanes === NoLanes) {
701    // Special case: There's nothing to work on.
702    if (existingCallbackNode !== null) {
703      cancelCallback(existingCallbackNode);
704      root.callbackNode = null;
705      root.callbackPriority = NoLanePriority;
706    }
707    return;
708  }
709
710  // Check if there's an existing task. We may be able to reuse it.
711  if (existingCallbackNode !== null) {
712    const existingCallbackPriority = root.callbackPriority;
713    if (existingCallbackPriority === newCallbackPriority) {
714      // The priority hasn't changed. We can reuse the existing task. Exit.
715      return;
716    }
717    // The priority changed. Cancel the existing callback. We'll schedule a new
718    // one below.
719    cancelCallback(existingCallbackNode);
720  }
721
722  // Schedule a new callback.
723  let newCallbackNode;
724  if (newCallbackPriority === SyncLanePriority) {
725    // Special case: Sync React callbacks are scheduled on a special
726    // internal queue
727    newCallbackNode = scheduleSyncCallback(
728      performSyncWorkOnRoot.bind(null, root),
729    );
730  } else if (newCallbackPriority === SyncBatchedLanePriority) {
731    newCallbackNode = scheduleCallback(
732      ImmediateSchedulerPriority,
733      performSyncWorkOnRoot.bind(null, root),
734    );
735  } else {
736    const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
737      newCallbackPriority,
738    );
739    newCallbackNode = scheduleCallback(
740      schedulerPriorityLevel,
741      performConcurrentWorkOnRoot.bind(null, root),
742    );
743  }
744
745  root.callbackPriority = newCallbackPriority;
746  root.callbackNode = newCallbackNode;
747}
748
749// This is the entry point for every concurrent task, i.e. anything that
750// goes through Scheduler.
751function performConcurrentWorkOnRoot(root) {
752  // Since we know we're in a React event, we can clear the current
753  // event time. The next update will compute a new event time.
754  currentEventTime = NoTimestamp;
755  currentEventWipLanes = NoLanes;
756  currentEventPendingLanes = NoLanes;
757
758  invariant(
759    (executionContext & (RenderContext | CommitContext)) === NoContext,
760    'Should not already be working.',
761  );
762
763  // Flush any pending passive effects before deciding which lanes to work on,
764  // in case they schedule additional work.
765  const originalCallbackNode = root.callbackNode;
766  const didFlushPassiveEffects = flushPassiveEffects();
767  if (didFlushPassiveEffects) {
768    // Something in the passive effect phase may have canceled the current task.
769    // Check if the task node for this root was changed.
770    if (root.callbackNode !== originalCallbackNode) {
771      // The current task was canceled. Exit. We don't need to call
772      // `ensureRootIsScheduled` because the check above implies either that
773      // there's a new task, or that there's no remaining work on this root.
774      return null;
775    } else {
776      // Current task was not canceled. Continue.
777    }
778  }
779
780  // Determine the next expiration time to work on, using the fields stored
781  // on the root.
782  let lanes = getNextLanes(
783    root,
784    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
785  );
786  if (lanes === NoLanes) {
787    // Defensive coding. This is never expected to happen.
788    return null;
789  }
790
791  let exitStatus = renderRootConcurrent(root, lanes);
792
793  if (
794    includesSomeLane(
795      workInProgressRootIncludedLanes,
796      workInProgressRootUpdatedLanes,
797    )
798  ) {
799    // The render included lanes that were updated during the render phase.
800    // For example, when unhiding a hidden tree, we include all the lanes
801    // that were previously skipped when the tree was hidden. That set of
802    // lanes is a superset of the lanes we started rendering with.
803    //
804    // So we'll throw out the current work and restart.
805    prepareFreshStack(root, NoLanes);
806  } else if (exitStatus !== RootIncomplete) {
807    if (exitStatus === RootErrored) {
808      executionContext |= RetryAfterError;
809
810      // If an error occurred during hydration,
811      // discard server response and fall back to client side render.
812      if (root.hydrate) {
813        root.hydrate = false;
814        clearContainer(root.containerInfo);
815      }
816
817      // If something threw an error, try rendering one more time. We'll render
818      // synchronously to block concurrent data mutations, and we'll includes
819      // all pending updates are included. If it still fails after the second
820      // attempt, we'll give up and commit the resulting tree.
821      lanes = getLanesToRetrySynchronouslyOnError(root);
822      if (lanes !== NoLanes) {
823        exitStatus = renderRootSync(root, lanes);
824      }
825    }
826
827    if (exitStatus === RootFatalErrored) {
828      const fatalError = workInProgressRootFatalError;
829      prepareFreshStack(root, NoLanes);
830      markRootSuspended(root, lanes);
831      ensureRootIsScheduled(root, now());
832      throw fatalError;
833    }
834
835    // We now have a consistent tree. The next step is either to commit it,
836    // or, if something suspended, wait to commit it after a timeout.
837    const finishedWork: Fiber = (root.current.alternate: any);
838    root.finishedWork = finishedWork;
839    root.finishedLanes = lanes;
840    finishConcurrentRender(root, exitStatus, lanes);
841  }
842
843  ensureRootIsScheduled(root, now());
844  if (root.callbackNode === originalCallbackNode) {
845    // The task node scheduled for this root is the same one that's
846    // currently executed. Need to return a continuation.
847    return performConcurrentWorkOnRoot.bind(null, root);
848  }
849  return null;
850}
851
852function finishConcurrentRender(root, exitStatus, lanes) {
853  switch (exitStatus) {
854    case RootIncomplete:
855    case RootFatalErrored: {
856      invariant(false, 'Root did not complete. This is a bug in React.');
857    }
858    // Flow knows about invariant, so it complains if I add a break
859    // statement, but eslint doesn't know about invariant, so it complains
860    // if I do. eslint-disable-next-line no-fallthrough
861    case RootErrored: {
862      // We should have already attempted to retry this tree. If we reached
863      // this point, it errored again. Commit it.
864      commitRoot(root);
865      break;
866    }
867    case RootSuspended: {
868      markRootSuspended(root, lanes);
869
870      // We have an acceptable loading state. We need to figure out if we
871      // should immediately commit it or wait a bit.
872
873      if (
874        includesOnlyRetries(lanes) &&
875        // do not delay if we're inside an act() scope
876        !shouldForceFlushFallbacksInDEV()
877      ) {
878        // This render only included retries, no updates. Throttle committing
879        // retries so that we don't show too many loading states too quickly.
880        const msUntilTimeout =
881          globalMostRecentFallbackTime + FALLBACK_THROTTLE_MS - now();
882        // Don't bother with a very short suspense time.
883        if (msUntilTimeout > 10) {
884          const nextLanes = getNextLanes(root, NoLanes);
885          if (nextLanes !== NoLanes) {
886            // There's additional work on this root.
887            break;
888          }
889          const suspendedLanes = root.suspendedLanes;
890          if (!isSubsetOfLanes(suspendedLanes, lanes)) {
891            // We should prefer to render the fallback of at the last
892            // suspended level. Ping the last suspended level to try
893            // rendering it again.
894            // FIXME: What if the suspended lanes are Idle? Should not restart.
895            const eventTime = requestEventTime();
896            markRootPinged(root, suspendedLanes, eventTime);
897            break;
898          }
899
900          // The render is suspended, it hasn't timed out, and there's no
901          // lower priority work to do. Instead of committing the fallback
902          // immediately, wait for more data to arrive.
903          root.timeoutHandle = scheduleTimeout(
904            commitRoot.bind(null, root),
905            msUntilTimeout,
906          );
907          break;
908        }
909      }
910      // The work expired. Commit immediately.
911      commitRoot(root);
912      break;
913    }
914    case RootSuspendedWithDelay: {
915      markRootSuspended(root, lanes);
916
917      if (includesOnlyTransitions(lanes)) {
918        // This is a transition, so we should exit without committing a
919        // placeholder and without scheduling a timeout. Delay indefinitely
920        // until we receive more data.
921        break;
922      }
923
924      if (!shouldForceFlushFallbacksInDEV()) {
925        // This is not a transition, but we did trigger an avoided state.
926        // Schedule a placeholder to display after a short delay, using the Just
927        // Noticeable Difference.
928        // TODO: Is the JND optimization worth the added complexity? If this is
929        // the only reason we track the event time, then probably not.
930        // Consider removing.
931
932        const mostRecentEventTime = getMostRecentEventTime(root, lanes);
933        const eventTimeMs = mostRecentEventTime;
934        const timeElapsedMs = now() - eventTimeMs;
935        const msUntilTimeout = jnd(timeElapsedMs) - timeElapsedMs;
936
937        // Don't bother with a very short suspense time.
938        if (msUntilTimeout > 10) {
939          // Instead of committing the fallback immediately, wait for more data
940          // to arrive.
941          root.timeoutHandle = scheduleTimeout(
942            commitRoot.bind(null, root),
943            msUntilTimeout,
944          );
945          break;
946        }
947      }
948
949      // Commit the placeholder.
950      commitRoot(root);
951      break;
952    }
953    case RootCompleted: {
954      // The work completed. Ready to commit.
955      commitRoot(root);
956      break;
957    }
958    default: {
959      invariant(false, 'Unknown root exit status.');
960    }
961  }
962}
963
964function markRootSuspended(root, suspendedLanes) {
965  // When suspending, we should always exclude lanes that were pinged or (more
966  // rarely, since we try to avoid it) updated during the render phase.
967  // TODO: Lol maybe there's a better way to factor this besides this
968  // obnoxiously named function :)
969  suspendedLanes = removeLanes(suspendedLanes, workInProgressRootPingedLanes);
970  suspendedLanes = removeLanes(suspendedLanes, workInProgressRootUpdatedLanes);
971  markRootSuspended_dontCallThisOneDirectly(root, suspendedLanes);
972}
973
974// This is the entry point for synchronous tasks that don't go
975// through Scheduler
976function performSyncWorkOnRoot(root) {
977  invariant(
978    (executionContext & (RenderContext | CommitContext)) === NoContext,
979    'Should not already be working.',
980  );
981
982  flushPassiveEffects();
983
984  let lanes;
985  let exitStatus;
986  if (
987    root === workInProgressRoot &&
988    includesSomeLane(root.expiredLanes, workInProgressRootRenderLanes)
989  ) {
990    // There's a partial tree, and at least one of its lanes has expired. Finish
991    // rendering it before rendering the rest of the expired work.
992    lanes = workInProgressRootRenderLanes;
993    exitStatus = renderRootSync(root, lanes);
994    if (
995      includesSomeLane(
996        workInProgressRootIncludedLanes,
997        workInProgressRootUpdatedLanes,
998      )
999    ) {
1000      // The render included lanes that were updated during the render phase.
1001      // For example, when unhiding a hidden tree, we include all the lanes
1002      // that were previously skipped when the tree was hidden. That set of
1003      // lanes is a superset of the lanes we started rendering with.
1004      //
1005      // Note that this only happens when part of the tree is rendered
1006      // concurrently. If the whole tree is rendered synchronously, then there
1007      // are no interleaved events.
1008      lanes = getNextLanes(root, lanes);
1009      exitStatus = renderRootSync(root, lanes);
1010    }
1011  } else {
1012    lanes = getNextLanes(root, NoLanes);
1013    exitStatus = renderRootSync(root, lanes);
1014  }
1015
1016  if (root.tag !== LegacyRoot && exitStatus === RootErrored) {
1017    executionContext |= RetryAfterError;
1018
1019    // If an error occurred during hydration,
1020    // discard server response and fall back to client side render.
1021    if (root.hydrate) {
1022      root.hydrate = false;
1023      clearContainer(root.containerInfo);
1024    }
1025
1026    // If something threw an error, try rendering one more time. We'll render
1027    // synchronously to block concurrent data mutations, and we'll includes
1028    // all pending updates are included. If it still fails after the second
1029    // attempt, we'll give up and commit the resulting tree.
1030    lanes = getLanesToRetrySynchronouslyOnError(root);
1031    if (lanes !== NoLanes) {
1032      exitStatus = renderRootSync(root, lanes);
1033    }
1034  }
1035
1036  if (exitStatus === RootFatalErrored) {
1037    const fatalError = workInProgressRootFatalError;
1038    prepareFreshStack(root, NoLanes);
1039    markRootSuspended(root, lanes);
1040    ensureRootIsScheduled(root, now());
1041    throw fatalError;
1042  }
1043
1044  // We now have a consistent tree. Because this is a sync render, we
1045  // will commit it even if something suspended.
1046  const finishedWork: Fiber = (root.current.alternate: any);
1047  root.finishedWork = finishedWork;
1048  root.finishedLanes = lanes;
1049  commitRoot(root);
1050
1051  // Before exiting, make sure there's a callback scheduled for the next
1052  // pending level.
1053  ensureRootIsScheduled(root, now());
1054
1055  return null;
1056}
1057
1058export function flushRoot(root: FiberRoot, lanes: Lanes) {
1059  markRootExpired(root, lanes);
1060  ensureRootIsScheduled(root, now());
1061  if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
1062    resetRenderTimer();
1063    flushSyncCallbackQueue();
1064  }
1065}
1066
1067export function getExecutionContext(): ExecutionContext {
1068  return executionContext;
1069}
1070
1071export function flushDiscreteUpdates() {
1072  // TODO: Should be able to flush inside batchedUpdates, but not inside `act`.
1073  // However, `act` uses `batchedUpdates`, so there's no way to distinguish
1074  // those two cases. Need to fix this before exposing flushDiscreteUpdates
1075  // as a public API.
1076  if (
1077    (executionContext & (BatchedContext | RenderContext | CommitContext)) !==
1078    NoContext
1079  ) {
1080    if (__DEV__) {
1081      if ((executionContext & RenderContext) !== NoContext) {
1082        console.error(
1083          'unstable_flushDiscreteUpdates: Cannot flush updates when React is ' +
1084            'already rendering.',
1085        );
1086      }
1087    }
1088    // We're already rendering, so we can't synchronously flush pending work.
1089    // This is probably a nested event dispatch triggered by a lifecycle/effect,
1090    // like `el.focus()`. Exit.
1091    return;
1092  }
1093  flushPendingDiscreteUpdates();
1094  // If the discrete updates scheduled passive effects, flush them now so that
1095  // they fire before the next serial event.
1096  flushPassiveEffects();
1097}
1098
1099export function deferredUpdates<A>(fn: () => A): A {
1100  if (decoupleUpdatePriorityFromScheduler) {
1101    const previousLanePriority = getCurrentUpdateLanePriority();
1102    try {
1103      setCurrentUpdateLanePriority(DefaultLanePriority);
1104      return runWithPriority(NormalSchedulerPriority, fn);
1105    } finally {
1106      setCurrentUpdateLanePriority(previousLanePriority);
1107    }
1108  } else {
1109    return runWithPriority(NormalSchedulerPriority, fn);
1110  }
1111}
1112
1113function flushPendingDiscreteUpdates() {
1114  if (rootsWithPendingDiscreteUpdates !== null) {
1115    // For each root with pending discrete updates, schedule a callback to
1116    // immediately flush them.
1117    const roots = rootsWithPendingDiscreteUpdates;
1118    rootsWithPendingDiscreteUpdates = null;
1119    roots.forEach(root => {
1120      markDiscreteUpdatesExpired(root);
1121      ensureRootIsScheduled(root, now());
1122    });
1123  }
1124  // Now flush the immediate queue.
1125  flushSyncCallbackQueue();
1126}
1127
1128export function batchedUpdates<A, R>(fn: A => R, a: A): R {
1129  const prevExecutionContext = executionContext;
1130  executionContext |= BatchedContext;
1131  try {
1132    return fn(a);
1133  } finally {
1134    executionContext = prevExecutionContext;
1135    if (executionContext === NoContext) {
1136      // Flush the immediate callbacks that were scheduled during this batch
1137      resetRenderTimer();
1138      flushSyncCallbackQueue();
1139    }
1140  }
1141}
1142
1143export function batchedEventUpdates<A, R>(fn: A => R, a: A): R {
1144  const prevExecutionContext = executionContext;
1145  executionContext |= EventContext;
1146  try {
1147    return fn(a);
1148  } finally {
1149    executionContext = prevExecutionContext;
1150    if (executionContext === NoContext) {
1151      // Flush the immediate callbacks that were scheduled during this batch
1152      resetRenderTimer();
1153      flushSyncCallbackQueue();
1154    }
1155  }
1156}
1157
1158export function discreteUpdates<A, B, C, D, R>(
1159  fn: (A, B, C) => R,
1160  a: A,
1161  b: B,
1162  c: C,
1163  d: D,
1164): R {
1165  const prevExecutionContext = executionContext;
1166  executionContext |= DiscreteEventContext;
1167
1168  if (decoupleUpdatePriorityFromScheduler) {
1169    const previousLanePriority = getCurrentUpdateLanePriority();
1170    try {
1171      setCurrentUpdateLanePriority(InputDiscreteLanePriority);
1172      return runWithPriority(
1173        UserBlockingSchedulerPriority,
1174        fn.bind(null, a, b, c, d),
1175      );
1176    } finally {
1177      setCurrentUpdateLanePriority(previousLanePriority);
1178      executionContext = prevExecutionContext;
1179      if (executionContext === NoContext) {
1180        // Flush the immediate callbacks that were scheduled during this batch
1181        resetRenderTimer();
1182        flushSyncCallbackQueue();
1183      }
1184    }
1185  } else {
1186    try {
1187      return runWithPriority(
1188        UserBlockingSchedulerPriority,
1189        fn.bind(null, a, b, c, d),
1190      );
1191    } finally {
1192      executionContext = prevExecutionContext;
1193      if (executionContext === NoContext) {
1194        // Flush the immediate callbacks that were scheduled during this batch
1195        resetRenderTimer();
1196        flushSyncCallbackQueue();
1197      }
1198    }
1199  }
1200}
1201
1202export function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
1203  const prevExecutionContext = executionContext;
1204  executionContext &= ~BatchedContext;
1205  executionContext |= LegacyUnbatchedContext;
1206  try {
1207    return fn(a);
1208  } finally {
1209    executionContext = prevExecutionContext;
1210    if (executionContext === NoContext) {
1211      // Flush the immediate callbacks that were scheduled during this batch
1212      resetRenderTimer();
1213      flushSyncCallbackQueue();
1214    }
1215  }
1216}
1217
1218export function flushSync<A, R>(fn: A => R, a: A): R {
1219  const prevExecutionContext = executionContext;
1220  if ((prevExecutionContext & (RenderContext | CommitContext)) !== NoContext) {
1221    if (__DEV__) {
1222      console.error(
1223        'flushSync was called from inside a lifecycle method. React cannot ' +
1224          'flush when React is already rendering. Consider moving this call to ' +
1225          'a scheduler task or micro task.',
1226      );
1227    }
1228    return fn(a);
1229  }
1230  executionContext |= BatchedContext;
1231
1232  if (decoupleUpdatePriorityFromScheduler) {
1233    const previousLanePriority = getCurrentUpdateLanePriority();
1234    try {
1235      setCurrentUpdateLanePriority(SyncLanePriority);
1236      if (fn) {
1237        return runWithPriority(ImmediateSchedulerPriority, fn.bind(null, a));
1238      } else {
1239        return (undefined: $FlowFixMe);
1240      }
1241    } finally {
1242      setCurrentUpdateLanePriority(previousLanePriority);
1243      executionContext = prevExecutionContext;
1244      // Flush the immediate callbacks that were scheduled during this batch.
1245      // Note that this will happen even if batchedUpdates is higher up
1246      // the stack.
1247      flushSyncCallbackQueue();
1248    }
1249  } else {
1250    try {
1251      if (fn) {
1252        return runWithPriority(ImmediateSchedulerPriority, fn.bind(null, a));
1253      } else {
1254        return (undefined: $FlowFixMe);
1255      }
1256    } finally {
1257      executionContext = prevExecutionContext;
1258      // Flush the immediate callbacks that were scheduled during this batch.
1259      // Note that this will happen even if batchedUpdates is higher up
1260      // the stack.
1261      flushSyncCallbackQueue();
1262    }
1263  }
1264}
1265
1266export function flushControlled(fn: () => mixed): void {
1267  const prevExecutionContext = executionContext;
1268  executionContext |= BatchedContext;
1269  if (decoupleUpdatePriorityFromScheduler) {
1270    const previousLanePriority = getCurrentUpdateLanePriority();
1271    try {
1272      setCurrentUpdateLanePriority(SyncLanePriority);
1273      runWithPriority(ImmediateSchedulerPriority, fn);
1274    } finally {
1275      setCurrentUpdateLanePriority(previousLanePriority);
1276
1277      executionContext = prevExecutionContext;
1278      if (executionContext === NoContext) {
1279        // Flush the immediate callbacks that were scheduled during this batch
1280        resetRenderTimer();
1281        flushSyncCallbackQueue();
1282      }
1283    }
1284  } else {
1285    try {
1286      runWithPriority(ImmediateSchedulerPriority, fn);
1287    } finally {
1288      executionContext = prevExecutionContext;
1289      if (executionContext === NoContext) {
1290        // Flush the immediate callbacks that were scheduled during this batch
1291        resetRenderTimer();
1292        flushSyncCallbackQueue();
1293      }
1294    }
1295  }
1296}
1297
1298export function pushRenderLanes(fiber: Fiber, lanes: Lanes) {
1299  pushToStack(subtreeRenderLanesCursor, subtreeRenderLanes, fiber);
1300  subtreeRenderLanes = mergeLanes(subtreeRenderLanes, lanes);
1301  workInProgressRootIncludedLanes = mergeLanes(
1302    workInProgressRootIncludedLanes,
1303    lanes,
1304  );
1305}
1306
1307export function popRenderLanes(fiber: Fiber) {
1308  subtreeRenderLanes = subtreeRenderLanesCursor.current;
1309  popFromStack(subtreeRenderLanesCursor, fiber);
1310}
1311
1312function prepareFreshStack(root: FiberRoot, lanes: Lanes) {
1313  root.finishedWork = null;
1314  root.finishedLanes = NoLanes;
1315
1316  const timeoutHandle = root.timeoutHandle;
1317  if (timeoutHandle !== noTimeout) {
1318    // The root previous suspended and scheduled a timeout to commit a fallback
1319    // state. Now that we have additional work, cancel the timeout.
1320    root.timeoutHandle = noTimeout;
1321    // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
1322    cancelTimeout(timeoutHandle);
1323  }
1324
1325  if (workInProgress !== null) {
1326    let interruptedWork = workInProgress.return;
1327    while (interruptedWork !== null) {
1328      unwindInterruptedWork(interruptedWork);
1329      interruptedWork = interruptedWork.return;
1330    }
1331  }
1332  workInProgressRoot = root;
1333  workInProgress = createWorkInProgress(root.current, null);
1334  workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes;
1335  workInProgressRootExitStatus = RootIncomplete;
1336  workInProgressRootFatalError = null;
1337  workInProgressRootSkippedLanes = NoLanes;
1338  workInProgressRootUpdatedLanes = NoLanes;
1339  workInProgressRootPingedLanes = NoLanes;
1340
1341  if (enableSchedulerTracing) {
1342    spawnedWorkDuringRender = null;
1343  }
1344
1345  if (__DEV__) {
1346    ReactStrictModeWarnings.discardPendingWarnings();
1347  }
1348}
1349
1350function handleError(root, thrownValue): void {
1351  do {
1352    let erroredWork = workInProgress;
1353    try {
1354      // Reset module-level state that was set during the render phase.
1355      resetContextDependencies();
1356      resetHooksAfterThrow();
1357      resetCurrentDebugFiberInDEV();
1358      // TODO: I found and added this missing line while investigating a
1359      // separate issue. Write a regression test using string refs.
1360      ReactCurrentOwner.current = null;
1361
1362      if (erroredWork === null || erroredWork.return === null) {
1363        // Expected to be working on a non-root fiber. This is a fatal error
1364        // because there's no ancestor that can handle it; the root is
1365        // supposed to capture all errors that weren't caught by an error
1366        // boundary.
1367        workInProgressRootExitStatus = RootFatalErrored;
1368        workInProgressRootFatalError = thrownValue;
1369        // Set `workInProgress` to null. This represents advancing to the next
1370        // sibling, or the parent if there are no siblings. But since the root
1371        // has no siblings nor a parent, we set it to null. Usually this is
1372        // handled by `completeUnitOfWork` or `unwindWork`, but since we're
1373        // intentionally not calling those, we need set it here.
1374        // TODO: Consider calling `unwindWork` to pop the contexts.
1375        workInProgress = null;
1376        return;
1377      }
1378
1379      if (enableProfilerTimer && erroredWork.mode & ProfileMode) {
1380        // Record the time spent rendering before an error was thrown. This
1381        // avoids inaccurate Profiler durations in the case of a
1382        // suspended render.
1383        stopProfilerTimerIfRunningAndRecordDelta(erroredWork, true);
1384      }
1385
1386      throwException(
1387        root,
1388        erroredWork.return,
1389        erroredWork,
1390        thrownValue,
1391        workInProgressRootRenderLanes,
1392      );
1393      completeUnitOfWork(erroredWork);
1394    } catch (yetAnotherThrownValue) {
1395      // Something in the return path also threw.
1396      thrownValue = yetAnotherThrownValue;
1397      if (workInProgress === erroredWork && erroredWork !== null) {
1398        // If this boundary has already errored, then we had trouble processing
1399        // the error. Bubble it to the next boundary.
1400        erroredWork = erroredWork.return;
1401        workInProgress = erroredWork;
1402      } else {
1403        erroredWork = workInProgress;
1404      }
1405      continue;
1406    }
1407    // Return to the normal work loop.
1408    return;
1409  } while (true);
1410}
1411
1412function pushDispatcher() {
1413  const prevDispatcher = ReactCurrentDispatcher.current;
1414  ReactCurrentDispatcher.current = ContextOnlyDispatcher;
1415  if (prevDispatcher === null) {
1416    // The React isomorphic package does not include a default dispatcher.
1417    // Instead the first renderer will lazily attach one, in order to give
1418    // nicer error messages.
1419    return ContextOnlyDispatcher;
1420  } else {
1421    return prevDispatcher;
1422  }
1423}
1424
1425function popDispatcher(prevDispatcher) {
1426  ReactCurrentDispatcher.current = prevDispatcher;
1427}
1428
1429function pushInteractions(root) {
1430  if (enableSchedulerTracing) {
1431    const prevInteractions: Set<Interaction> | null = __interactionsRef.current;
1432    __interactionsRef.current = root.memoizedInteractions;
1433    return prevInteractions;
1434  }
1435  return null;
1436}
1437
1438function popInteractions(prevInteractions) {
1439  if (enableSchedulerTracing) {
1440    __interactionsRef.current = prevInteractions;
1441  }
1442}
1443
1444export function markCommitTimeOfFallback() {
1445  globalMostRecentFallbackTime = now();
1446}
1447
1448export function markSkippedUpdateLanes(lane: Lane | Lanes): void {
1449  workInProgressRootSkippedLanes = mergeLanes(
1450    lane,
1451    workInProgressRootSkippedLanes,
1452  );
1453}
1454
1455export function renderDidSuspend(): void {
1456  if (workInProgressRootExitStatus === RootIncomplete) {
1457    workInProgressRootExitStatus = RootSuspended;
1458  }
1459}
1460
1461export function renderDidSuspendDelayIfPossible(): void {
1462  if (
1463    workInProgressRootExitStatus === RootIncomplete ||
1464    workInProgressRootExitStatus === RootSuspended
1465  ) {
1466    workInProgressRootExitStatus = RootSuspendedWithDelay;
1467  }
1468
1469  // Check if there are updates that we skipped tree that might have unblocked
1470  // this render.
1471  if (
1472    workInProgressRoot !== null &&
1473    (includesNonIdleWork(workInProgressRootSkippedLanes) ||
1474      includesNonIdleWork(workInProgressRootUpdatedLanes))
1475  ) {
1476    // Mark the current render as suspended so that we switch to working on
1477    // the updates that were skipped. Usually we only suspend at the end of
1478    // the render phase.
1479    // TODO: We should probably always mark the root as suspended immediately
1480    // (inside this function), since by suspending at the end of the render
1481    // phase introduces a potential mistake where we suspend lanes that were
1482    // pinged or updated while we were rendering.
1483    markRootSuspended(workInProgressRoot, workInProgressRootRenderLanes);
1484  }
1485}
1486
1487export function renderDidError() {
1488  if (workInProgressRootExitStatus !== RootCompleted) {
1489    workInProgressRootExitStatus = RootErrored;
1490  }
1491}
1492
1493// Called during render to determine if anything has suspended.
1494// Returns false if we're not sure.
1495export function renderHasNotSuspendedYet(): boolean {
1496  // If something errored or completed, we can't really be sure,
1497  // so those are false.
1498  return workInProgressRootExitStatus === RootIncomplete;
1499}
1500
1501function renderRootSync(root: FiberRoot, lanes: Lanes) {
1502  const prevExecutionContext = executionContext;
1503  executionContext |= RenderContext;
1504  const prevDispatcher = pushDispatcher();
1505
1506  // If the root or lanes have changed, throw out the existing stack
1507  // and prepare a fresh one. Otherwise we'll continue where we left off.
1508  if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
1509    prepareFreshStack(root, lanes);
1510    startWorkOnPendingInteractions(root, lanes);
1511  }
1512
1513  const prevInteractions = pushInteractions(root);
1514
1515  if (__DEV__) {
1516    if (enableDebugTracing) {
1517      logRenderStarted(lanes);
1518    }
1519  }
1520
1521  if (enableSchedulingProfiler) {
1522    markRenderStarted(lanes);
1523  }
1524
1525  do {
1526    try {
1527      workLoopSync();
1528      break;
1529    } catch (thrownValue) {
1530      handleError(root, thrownValue);
1531    }
1532  } while (true);
1533  resetContextDependencies();
1534  if (enableSchedulerTracing) {
1535    popInteractions(((prevInteractions: any): Set<Interaction>));
1536  }
1537
1538  executionContext = prevExecutionContext;
1539  popDispatcher(prevDispatcher);
1540
1541  if (workInProgress !== null) {
1542    // This is a sync render, so we should have finished the whole tree.
1543    invariant(
1544      false,
1545      'Cannot commit an incomplete root. This error is likely caused by a ' +
1546        'bug in React. Please file an issue.',
1547    );
1548  }
1549
1550  if (__DEV__) {
1551    if (enableDebugTracing) {
1552      logRenderStopped();
1553    }
1554  }
1555
1556  if (enableSchedulingProfiler) {
1557    markRenderStopped();
1558  }
1559
1560  // Set this to null to indicate there's no in-progress render.
1561  workInProgressRoot = null;
1562  workInProgressRootRenderLanes = NoLanes;
1563
1564  return workInProgressRootExitStatus;
1565}
1566
1567// The work loop is an extremely hot path. Tell Closure not to inline it.
1568/** @noinline */
1569function workLoopSync() {
1570  // Already timed out, so perform work without checking if we need to yield.
1571  while (workInProgress !== null) {
1572    performUnitOfWork(workInProgress);
1573  }
1574}
1575
1576function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
1577  const prevExecutionContext = executionContext;
1578  executionContext |= RenderContext;
1579  const prevDispatcher = pushDispatcher();
1580
1581  // If the root or lanes have changed, throw out the existing stack
1582  // and prepare a fresh one. Otherwise we'll continue where we left off.
1583  if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
1584    resetRenderTimer();
1585    prepareFreshStack(root, lanes);
1586    startWorkOnPendingInteractions(root, lanes);
1587  }
1588
1589  const prevInteractions = pushInteractions(root);
1590
1591  if (__DEV__) {
1592    if (enableDebugTracing) {
1593      logRenderStarted(lanes);
1594    }
1595  }
1596
1597  if (enableSchedulingProfiler) {
1598    markRenderStarted(lanes);
1599  }
1600
1601  do {
1602    try {
1603      workLoopConcurrent();
1604      break;
1605    } catch (thrownValue) {
1606      handleError(root, thrownValue);
1607    }
1608  } while (true);
1609  resetContextDependencies();
1610  if (enableSchedulerTracing) {
1611    popInteractions(((prevInteractions: any): Set<Interaction>));
1612  }
1613
1614  popDispatcher(prevDispatcher);
1615  executionContext = prevExecutionContext;
1616
1617  if (__DEV__) {
1618    if (enableDebugTracing) {
1619      logRenderStopped();
1620    }
1621  }
1622
1623  // Check if the tree has completed.
1624  if (workInProgress !== null) {
1625    // Still work remaining.
1626    if (enableSchedulingProfiler) {
1627      markRenderYielded();
1628    }
1629    return RootIncomplete;
1630  } else {
1631    // Completed the tree.
1632    if (enableSchedulingProfiler) {
1633      markRenderStopped();
1634    }
1635
1636    // Set this to null to indicate there's no in-progress render.
1637    workInProgressRoot = null;
1638    workInProgressRootRenderLanes = NoLanes;
1639
1640    // Return the final exit status.
1641    return workInProgressRootExitStatus;
1642  }
1643}
1644
1645/** @noinline */
1646function workLoopConcurrent() {
1647  // Perform work until Scheduler asks us to yield
1648  while (workInProgress !== null && !shouldYield()) {
1649    performUnitOfWork(workInProgress);
1650  }
1651}
1652
1653function performUnitOfWork(unitOfWork: Fiber): void {
1654  // The current, flushed, state of this fiber is the alternate. Ideally
1655  // nothing should rely on this, but relying on it here means that we don't
1656  // need an additional field on the work in progress.
1657  const current = unitOfWork.alternate;
1658  setCurrentDebugFiberInDEV(unitOfWork);
1659
1660  let next;
1661  if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
1662    startProfilerTimer(unitOfWork);
1663    next = beginWork(current, unitOfWork, subtreeRenderLanes);
1664    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
1665  } else {
1666    next = beginWork(current, unitOfWork, subtreeRenderLanes);
1667  }
1668
1669  resetCurrentDebugFiberInDEV();
1670  unitOfWork.memoizedProps = unitOfWork.pendingProps;
1671  if (next === null) {
1672    // If this doesn't spawn new work, complete the current work.
1673    completeUnitOfWork(unitOfWork);
1674  } else {
1675    workInProgress = next;
1676  }
1677
1678  ReactCurrentOwner.current = null;
1679}
1680
1681function completeUnitOfWork(unitOfWork: Fiber): void {
1682  // Attempt to complete the current unit of work, then move to the next
1683  // sibling. If there are no more siblings, return to the parent fiber.
1684  let completedWork = unitOfWork;
1685  do {
1686    // The current, flushed, state of this fiber is the alternate. Ideally
1687    // nothing should rely on this, but relying on it here means that we don't
1688    // need an additional field on the work in progress.
1689    const current = completedWork.alternate;
1690    const returnFiber = completedWork.return;
1691
1692    // Check if the work completed or if something threw.
1693    if ((completedWork.flags & Incomplete) === NoFlags) {
1694      setCurrentDebugFiberInDEV(completedWork);
1695      let next;
1696      if (
1697        !enableProfilerTimer ||
1698        (completedWork.mode & ProfileMode) === NoMode
1699      ) {
1700        next = completeWork(current, completedWork, subtreeRenderLanes);
1701      } else {
1702        startProfilerTimer(completedWork);
1703        next = completeWork(current, completedWork, subtreeRenderLanes);
1704        // Update render duration assuming we didn't error.
1705        stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
1706      }
1707      resetCurrentDebugFiberInDEV();
1708
1709      if (next !== null) {
1710        // Completing this fiber spawned new work. Work on that next.
1711        workInProgress = next;
1712        return;
1713      }
1714    } else {
1715      // This fiber did not complete because something threw. Pop values off
1716      // the stack without entering the complete phase. If this is a boundary,
1717      // capture values if possible.
1718      const next = unwindWork(completedWork, subtreeRenderLanes);
1719
1720      // Because this fiber did not complete, don't reset its expiration time.
1721
1722      if (next !== null) {
1723        // If completing this work spawned new work, do that next. We'll come
1724        // back here again.
1725        // Since we're restarting, remove anything that is not a host effect
1726        // from the effect tag.
1727        next.flags &= HostEffectMask;
1728        workInProgress = next;
1729        return;
1730      }
1731
1732      if (
1733        enableProfilerTimer &&
1734        (completedWork.mode & ProfileMode) !== NoMode
1735      ) {
1736        // Record the render duration for the fiber that errored.
1737        stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
1738
1739        // Include the time spent working on failed children before continuing.
1740        let actualDuration = completedWork.actualDuration;
1741        let child = completedWork.child;
1742        while (child !== null) {
1743          actualDuration += child.actualDuration;
1744          child = child.sibling;
1745        }
1746        completedWork.actualDuration = actualDuration;
1747      }
1748
1749      if (returnFiber !== null) {
1750        // Mark the parent fiber as incomplete
1751        returnFiber.flags |= Incomplete;
1752        returnFiber.subtreeFlags = NoFlags;
1753        returnFiber.deletions = null;
1754      }
1755    }
1756
1757    const siblingFiber = completedWork.sibling;
1758    if (siblingFiber !== null) {
1759      // If there is more work to do in this returnFiber, do that next.
1760      workInProgress = siblingFiber;
1761      return;
1762    }
1763    // Otherwise, return to the parent
1764    completedWork = returnFiber;
1765    // Update the next thing we're working on in case something throws.
1766    workInProgress = completedWork;
1767  } while (completedWork !== null);
1768
1769  // We've reached the root.
1770  if (workInProgressRootExitStatus === RootIncomplete) {
1771    workInProgressRootExitStatus = RootCompleted;
1772  }
1773}
1774
1775function commitRoot(root) {
1776  const renderPriorityLevel = getCurrentPriorityLevel();
1777  runWithPriority(
1778    ImmediateSchedulerPriority,
1779    commitRootImpl.bind(null, root, renderPriorityLevel),
1780  );
1781  return null;
1782}
1783
1784function commitRootImpl(root, renderPriorityLevel) {
1785  do {
1786    // `flushPassiveEffects` will call `flushSyncUpdateQueue` at the end, which
1787    // means `flushPassiveEffects` will sometimes result in additional
1788    // passive effects. So we need to keep flushing in a loop until there are
1789    // no more pending effects.
1790    // TODO: Might be better if `flushPassiveEffects` did not automatically
1791    // flush synchronous work at the end, to avoid factoring hazards like this.
1792    flushPassiveEffects();
1793  } while (rootWithPendingPassiveEffects !== null);
1794  flushRenderPhaseStrictModeWarningsInDEV();
1795
1796  invariant(
1797    (executionContext & (RenderContext | CommitContext)) === NoContext,
1798    'Should not already be working.',
1799  );
1800
1801  const finishedWork = root.finishedWork;
1802  const lanes = root.finishedLanes;
1803
1804  if (__DEV__) {
1805    if (enableDebugTracing) {
1806      logCommitStarted(lanes);
1807    }
1808  }
1809
1810  if (enableSchedulingProfiler) {
1811    markCommitStarted(lanes);
1812  }
1813
1814  // 没有需要更新的Fiber节点
1815  if (finishedWork === null) {
1816    if (__DEV__) {
1817      if (enableDebugTracing) {
1818        logCommitStopped();
1819      }
1820    }
1821
1822    if (enableSchedulingProfiler) {
1823      markCommitStopped();
1824    }
1825
1826    return null;
1827  }
1828  root.finishedWork = null;
1829  root.finishedLanes = NoLanes;
1830
1831  invariant(
1832    finishedWork !== root.current,
1833    'Cannot commit the same tree as before. This error is likely caused by ' +
1834      'a bug in React. Please file an issue.',
1835  );
1836
1837  // commitRoot never returns a continuation; it always finishes synchronously.
1838  // So we can clear these now to allow a new callback to be scheduled.
1839  root.callbackNode = null;
1840
1841  // Update the first and last pending times on this root. The new first
1842  // pending time is whatever is left on the root fiber.
1843  // 合并所有的通道
1844  let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);
1845  markRootFinished(root, remainingLanes);
1846
1847  // Clear already finished discrete updates in case that a later call of
1848  // `flushDiscreteUpdates` starts a useless render pass which may cancels
1849  // a scheduled timeout.
1850  if (rootsWithPendingDiscreteUpdates !== null) {
1851    if (
1852      !hasDiscreteLanes(remainingLanes) &&
1853      rootsWithPendingDiscreteUpdates.has(root)
1854    ) {
1855      rootsWithPendingDiscreteUpdates.delete(root);
1856    }
1857  }
1858
1859  // 说明本次更新已经完成了,将一些变量重置
1860  if (root === workInProgressRoot) {
1861    // We can reset these now that they are finished.
1862    workInProgressRoot = null;
1863    workInProgress = null;
1864    workInProgressRootRenderLanes = NoLanes;
1865  } else {
1866    // This indicates that the last root we worked on is not the same one that
1867    // we're committing now. This most commonly happens when a suspended root
1868    // times out.
1869  }
1870
1871  // Check if there are any effects in the whole tree.
1872  // TODO: This is left over from the effect list implementation, where we had
1873  // to check for the existence of `firstEffect` to satsify Flow. I think the
1874  // only other reason this optimization exists is because it affects profiling.
1875  // Reconsider whether this is necessary.
1876  const subtreeHasEffects =
1877    (finishedWork.subtreeFlags &
1878      (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
1879    NoFlags;
1880  const rootHasEffect =
1881    (finishedWork.flags &
1882      (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
1883    NoFlags;
1884
1885    // 有需要处理的副作用
1886  if (subtreeHasEffects || rootHasEffect) {
1887    let previousLanePriority;
1888    if (decoupleUpdatePriorityFromScheduler) {
1889      previousLanePriority = getCurrentUpdateLanePriority();
1890      setCurrentUpdateLanePriority(SyncLanePriority);
1891    }
1892    // 设置执行上下文
1893    const prevExecutionContext = executionContext;
1894    executionContext |= CommitContext;
1895    const prevInteractions = pushInteractions(root);
1896
1897    // Reset this to null before calling lifecycles
1898    ReactCurrentOwner.current = null;
1899
1900    // The commit phase is broken into several sub-phases. We do a separate pass
1901    // of the effect list for each phase: all mutation effects come before all
1902    // layout effects, and so on.
1903
1904    // The first phase a "before mutation" phase. We use this phase to read the
1905    // state of the host tree right before we mutate it. This is where
1906    // getSnapshotBeforeUpdate is called.
1907    focusedInstanceHandle = prepareForCommit(root.containerInfo);
1908    shouldFireAfterActiveInstanceBlur = false;
1909    commitBeforeMutationEffects(finishedWork);
1910
1911    // We no longer need to track the active instance fiber
1912    focusedInstanceHandle = null;
1913
1914    if (enableProfilerTimer) {
1915      // Mark the current commit time to be shared by all Profilers in this
1916      // batch. This enables them to be grouped later.
1917      recordCommitTime();
1918    }
1919    // 把DOM元素渲染到页面上
1920    // The next phase is the mutation phase, where we mutate the host tree.
1921    commitMutationEffects(finishedWork, root, renderPriorityLevel);
1922
1923    if (shouldFireAfterActiveInstanceBlur) {
1924      afterActiveInstanceBlur();
1925    }
1926    resetAfterCommit(root.containerInfo);
1927
1928    // The work-in-progress tree is now the current tree. This must come after
1929    // the mutation phase, so that the previous tree is still current during
1930    // componentWillUnmount, but before the layout phase, so that the finished
1931    // work is current during componentDidMount/Update.
1932    // 在此之后,root.current 指向了work-in-progress tree
1933    root.current = finishedWork;
1934
1935    // The next phase is the layout phase, where we call effects that read
1936    // the host tree after it's been mutated. The idiomatic use case for this is
1937    // layout, but class component lifecycles also fire here for legacy reasons.
1938
1939    if (__DEV__) {
1940      if (enableDebugTracing) {
1941        logLayoutEffectsStarted(lanes);
1942      }
1943    }
1944    if (enableSchedulingProfiler) {
1945      markLayoutEffectsStarted(lanes);
1946    }
1947
1948    if (__DEV__) {
1949      setCurrentDebugFiberInDEV(finishedWork);
1950      invokeGuardedCallback(
1951        null,
1952        recursivelyCommitLayoutEffects,
1953        null,
1954        finishedWork,
1955        root,
1956      );
1957      if (hasCaughtError()) {
1958        const error = clearCaughtError();
1959        captureCommitPhaseErrorOnRoot(finishedWork, finishedWork, error);
1960      }
1961      resetCurrentDebugFiberInDEV();
1962    } else {
1963      try {
1964        recursivelyCommitLayoutEffects(finishedWork, root);
1965      } catch (error) {
1966        captureCommitPhaseErrorOnRoot(finishedWork, finishedWork, error);
1967      }
1968    }
1969
1970    if (__DEV__) {
1971      if (enableDebugTracing) {
1972        logLayoutEffectsStopped();
1973      }
1974    }
1975    if (enableSchedulingProfiler) {
1976      markLayoutEffectsStopped();
1977    }
1978
1979    // If there are pending passive effects, schedule a callback to process them.
1980    if (
1981      (finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
1982      (finishedWork.flags & PassiveMask) !== NoFlags
1983    ) {
1984      if (!rootDoesHavePassiveEffects) {
1985        rootDoesHavePassiveEffects = true;
1986        scheduleCallback(NormalSchedulerPriority, () => {
1987          flushPassiveEffects();
1988          return null;
1989        });
1990      }
1991    }
1992
1993    // Tell Scheduler to yield at the end of the frame, so the browser has an
1994    // opportunity to paint.
1995    requestPaint();
1996
1997    if (enableSchedulerTracing) {
1998      popInteractions(((prevInteractions: any): Set<Interaction>));
1999    }
2000    executionContext = prevExecutionContext;
2001
2002    if (decoupleUpdatePriorityFromScheduler && previousLanePriority != null) {
2003      // Reset the priority to the previous non-sync value.
2004      setCurrentUpdateLanePriority(previousLanePriority);
2005    }
2006  } else {
2007    // No effects.
2008    root.current = finishedWork;
2009    // Measure these anyway so the flamegraph explicitly shows that there were
2010    // no effects.
2011    // TODO: Maybe there's a better way to report this.
2012    if (enableProfilerTimer) {
2013      recordCommitTime();
2014    }
2015  }
2016
2017  const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;
2018
2019  if (rootDoesHavePassiveEffects) {
2020    // This commit has passive effects. Stash a reference to them. But don't
2021    // schedule a callback until after flushing layout work.
2022    rootDoesHavePassiveEffects = false;
2023    rootWithPendingPassiveEffects = root;
2024    pendingPassiveEffectsLanes = lanes;
2025    pendingPassiveEffectsRenderPriority = renderPriorityLevel;
2026  }
2027
2028  // Read this again, since an effect might have updated it
2029  remainingLanes = root.pendingLanes;
2030
2031  // Check if there's remaining work on this root
2032  if (remainingLanes !== NoLanes) {
2033    if (enableSchedulerTracing) {
2034      if (spawnedWorkDuringRender !== null) {
2035        const expirationTimes = spawnedWorkDuringRender;
2036        spawnedWorkDuringRender = null;
2037        for (let i = 0; i < expirationTimes.length; i++) {
2038          scheduleInteractions(
2039            root,
2040            expirationTimes[i],
2041            root.memoizedInteractions,
2042          );
2043        }
2044      }
2045      schedulePendingInteractions(root, remainingLanes);
2046    }
2047  } else {
2048    // If there's no remaining work, we can clear the set of already failed
2049    // error boundaries.
2050    legacyErrorBoundariesThatAlreadyFailed = null;
2051  }
2052
2053  if (__DEV__ && enableDoubleInvokingEffects) {
2054    if (!rootDidHavePassiveEffects) {
2055      commitDoubleInvokeEffectsInDEV(root.current, false);
2056    }
2057  }
2058
2059  if (enableSchedulerTracing) {
2060    if (!rootDidHavePassiveEffects) {
2061      // If there are no passive effects, then we can complete the pending interactions.
2062      // Otherwise, we'll wait until after the passive effects are flushed.
2063      // Wait to do this until after remaining work has been scheduled,
2064      // so that we don't prematurely signal complete for interactions when there's e.g. hidden work.
2065      finishPendingInteractions(root, lanes);
2066    }
2067  }
2068
2069  if (remainingLanes === SyncLane) {
2070    // Count the number of times the root synchronously re-renders without
2071    // finishing. If there are too many, it indicates an infinite update loop.
2072    if (root === rootWithNestedUpdates) {
2073      nestedUpdateCount++;
2074    } else {
2075      nestedUpdateCount = 0;
2076      rootWithNestedUpdates = root;
2077    }
2078  } else {
2079    nestedUpdateCount = 0;
2080  }
2081
2082  onCommitRootDevTools(finishedWork.stateNode, renderPriorityLevel);
2083
2084  if (__DEV__) {
2085    onCommitRootTestSelector();
2086  }
2087
2088  // Always call this before exiting `commitRoot`, to ensure that any
2089  // additional work on this root is scheduled.
2090  ensureRootIsScheduled(root, now());
2091
2092  if (hasUncaughtError) {
2093    hasUncaughtError = false;
2094    const error = firstUncaughtError;
2095    firstUncaughtError = null;
2096    throw error;
2097  }
2098
2099  if ((executionContext & LegacyUnbatchedContext) !== NoContext) {
2100    if (__DEV__) {
2101      if (enableDebugTracing) {
2102        logCommitStopped();
2103      }
2104    }
2105
2106    if (enableSchedulingProfiler) {
2107      markCommitStopped();
2108    }
2109
2110    // This is a legacy edge case. We just committed the initial mount of
2111    // a ReactDOM.render-ed root inside of batchedUpdates. The commit fired
2112    // synchronously, but layout updates should be deferred until the end
2113    // of the batch.
2114    return null;
2115  }
2116
2117  // If layout work was scheduled, flush it now.
2118  flushSyncCallbackQueue();
2119
2120  if (__DEV__) {
2121    if (enableDebugTracing) {
2122      logCommitStopped();
2123    }
2124  }
2125
2126  if (enableSchedulingProfiler) {
2127    markCommitStopped();
2128  }
2129
2130  return null;
2131}
2132
2133function commitBeforeMutationEffects(firstChild: Fiber) {
2134  let fiber = firstChild;
2135  while (fiber !== null) {
2136    // 处理需要删除的fiber autoFocus、blur 逻辑。
2137    if (fiber.deletions !== null) {
2138      commitBeforeMutationEffectsDeletions(fiber.deletions);
2139    }
2140    // 递归调用处理子节点
2141    if (fiber.child !== null) {
2142      const primarySubtreeFlags = fiber.subtreeFlags & BeforeMutationMask;
2143      if (primarySubtreeFlags !== NoFlags) {
2144        commitBeforeMutationEffects(fiber.child);
2145      }
2146    }
2147
2148    if (__DEV__) {
2149      setCurrentDebugFiberInDEV(fiber);
2150      invokeGuardedCallback(null, commitBeforeMutationEffectsImpl, null, fiber);
2151      if (hasCaughtError()) {
2152        const error = clearCaughtError();
2153        captureCommitPhaseError(fiber, fiber.return, error);
2154      }
2155      resetCurrentDebugFiberInDEV();
2156    } else {
2157      try {
2158        // 调用 getSnapshotBeforeUpdate 生命周期
2159        // 异步调度useEffect
2160        commitBeforeMutationEffectsImpl(fiber);
2161      } catch (error) {
2162        captureCommitPhaseError(fiber, fiber.return, error);
2163      }
2164    }
2165    // 返回兄弟节点,接着循环
2166    fiber = fiber.sibling;
2167  }
2168}
2169
2170function commitBeforeMutationEffectsImpl(fiber: Fiber) {
2171  const current = fiber.alternate;
2172  const flags = fiber.flags;
2173
2174  if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {
2175    // Check to see if the focused element was inside of a hidden (Suspense) subtree.
2176    // TODO: Move this out of the hot path using a dedicated effect tag.
2177    if (
2178      fiber.tag === SuspenseComponent &&
2179      isSuspenseBoundaryBeingHidden(current, fiber) &&
2180      doesFiberContain(fiber, focusedInstanceHandle)
2181    ) {
2182      shouldFireAfterActiveInstanceBlur = true;
2183      beforeActiveInstanceBlur();
2184    }
2185  }
2186
2187  if ((flags & Snapshot) !== NoFlags) {
2188    setCurrentDebugFiberInDEV(fiber);
2189    // 调用 getSnapshotBeforeUpdate 生命周期
2190    commitBeforeMutationEffectOnFiber(current, fiber);
2191    resetCurrentDebugFiberInDEV();
2192  }
2193  // 调度useEffect
2194  if ((flags & Passive) !== NoFlags) {
2195    // If there are passive effects, schedule a callback to flush at
2196    // the earliest opportunity.
2197    if (!rootDoesHavePassiveEffects) {
2198      rootDoesHavePassiveEffects = true;
2199      scheduleCallback(NormalSchedulerPriority, () => {
2200        flushPassiveEffects();
2201        return null;
2202      });
2203    }
2204  }
2205}
2206
2207function commitBeforeMutationEffectsDeletions(deletions: Array<Fiber>) {
2208  for (let i = 0; i < deletions.length; i++) {
2209    const fiber = deletions[i];
2210
2211    // TODO (effects) It would be nice to avoid calling doesFiberContain()
2212    // Maybe we can repurpose one of the subtreeFlags positions for this instead?
2213    // Use it to store which part of the tree the focused instance is in?
2214    // This assumes we can safely determine that instance during the "render" phase.
2215
2216    if (doesFiberContain(fiber, ((focusedInstanceHandle: any): Fiber))) {
2217      shouldFireAfterActiveInstanceBlur = true;
2218      beforeActiveInstanceBlur();
2219    }
2220  }
2221}
2222
2223function commitMutationEffects(
2224  firstChild: Fiber,
2225  root: FiberRoot,
2226  renderPriorityLevel: ReactPriorityLevel,
2227) {
2228  let fiber = firstChild;
2229  while (fiber !== null) {
2230    const deletions = fiber.deletions;
2231    if (deletions !== null) {
2232      commitMutationEffectsDeletions(
2233        deletions,
2234        fiber,
2235        root,
2236        renderPriorityLevel,
2237      );
2238    }
2239
2240    if (fiber.child !== null) {
2241      const mutationFlags = fiber.subtreeFlags & MutationMask;
2242      if (mutationFlags !== NoFlags) {
2243        commitMutationEffects(fiber.child, root, renderPriorityLevel);
2244      }
2245    }
2246
2247    if (__DEV__) {
2248      setCurrentDebugFiberInDEV(fiber);
2249      invokeGuardedCallback(
2250        null,
2251        commitMutationEffectsImpl,
2252        null,
2253        fiber,
2254        root,
2255        renderPriorityLevel,
2256      );
2257      if (hasCaughtError()) {
2258        const error = clearCaughtError();
2259        captureCommitPhaseError(fiber, fiber.return, error);
2260      }
2261      resetCurrentDebugFiberInDEV();
2262    } else {
2263      try {
2264        commitMutationEffectsImpl(fiber, root, renderPriorityLevel);
2265      } catch (error) {
2266        captureCommitPhaseError(fiber, fiber.return, error);
2267      }
2268    }
2269    fiber = fiber.sibling;
2270  }
2271}
2272
2273function commitMutationEffectsImpl(
2274  fiber: Fiber,
2275  root: FiberRoot,
2276  renderPriorityLevel,
2277) {
2278  const flags = fiber.flags;
2279  if (flags & ContentReset) {
2280    commitResetTextContent(fiber);
2281  }
2282
2283  if (flags & Ref) {
2284    const current = fiber.alternate;
2285    if (current !== null) {
2286      commitDetachRef(current);
2287    }
2288    if (enableScopeAPI) {
2289      // TODO: This is a temporary solution that allowed us to transition away from React Flare on www.
2290      if (fiber.tag === ScopeComponent) {
2291        commitAttachRef(fiber);
2292      }
2293    }
2294  }
2295
2296  // The following switch statement is only concerned about placement,
2297  // updates, and deletions. To avoid needing to add a case for every possible
2298  // bitmap value, we remove the secondary effects from the effect tag and
2299  // switch on that value.
2300  const primaryFlags = flags & (Placement | Update | Hydrating);
2301  switch (primaryFlags) {
2302    case Placement: {
2303      commitPlacement(fiber);
2304      // Clear the "placement" from effect tag so that we know that this is
2305      // inserted, before any life-cycles like componentDidMount gets called.
2306      // TODO: findDOMNode doesn't rely on this any more but isMounted does
2307      // and isMounted is deprecated anyway so we should be able to kill this.
2308      fiber.flags &= ~Placement;
2309      break;
2310    }
2311    case PlacementAndUpdate: {
2312      // Placement
2313      commitPlacement(fiber);
2314      // Clear the "placement" from effect tag so that we know that this is
2315      // inserted, before any life-cycles like componentDidMount gets called.
2316      fiber.flags &= ~Placement;
2317
2318      // Update
2319      const current = fiber.alternate;
2320      commitWork(current, fiber);
2321      break;
2322    }
2323    case Hydrating: {
2324      fiber.flags &= ~Hydrating;
2325      break;
2326    }
2327    case HydratingAndUpdate: {
2328      fiber.flags &= ~Hydrating;
2329
2330      // Update
2331      const current = fiber.alternate;
2332      commitWork(current, fiber);
2333      break;
2334    }
2335    case Update: {
2336      const current = fiber.alternate;
2337      commitWork(current, fiber);
2338      break;
2339    }
2340  }
2341}
2342
2343function commitMutationEffectsDeletions(
2344  deletions: Array<Fiber>,
2345  nearestMountedAncestor: Fiber,
2346  root: FiberRoot,
2347  renderPriorityLevel,
2348) {
2349  for (let i = 0; i < deletions.length; i++) {
2350    const childToDelete = deletions[i];
2351    if (__DEV__) {
2352      invokeGuardedCallback(
2353        null,
2354        commitDeletion,
2355        null,
2356        root,
2357        childToDelete,
2358        nearestMountedAncestor,
2359        renderPriorityLevel,
2360      );
2361      if (hasCaughtError()) {
2362        const error = clearCaughtError();
2363        captureCommitPhaseError(childToDelete, nearestMountedAncestor, error);
2364      }
2365    } else {
2366      try {
2367        commitDeletion(
2368          root,
2369          childToDelete,
2370          nearestMountedAncestor,
2371          renderPriorityLevel,
2372        );
2373      } catch (error) {
2374        captureCommitPhaseError(childToDelete, nearestMountedAncestor, error);
2375      }
2376    }
2377  }
2378}
2379
2380export function schedulePassiveEffectCallback() {
2381  if (!rootDoesHavePassiveEffects) {
2382    rootDoesHavePassiveEffects = true;
2383    scheduleCallback(NormalSchedulerPriority, () => {
2384      flushPassiveEffects();
2385      return null;
2386    });
2387  }
2388}
2389
2390export function flushPassiveEffects(): boolean {
2391  // Returns whether passive effects were flushed.
2392  if (pendingPassiveEffectsRenderPriority !== NoSchedulerPriority) {
2393    const priorityLevel =
2394      pendingPassiveEffectsRenderPriority > NormalSchedulerPriority
2395        ? NormalSchedulerPriority
2396        : pendingPassiveEffectsRenderPriority;
2397    pendingPassiveEffectsRenderPriority = NoSchedulerPriority;
2398    if (decoupleUpdatePriorityFromScheduler) {
2399      const previousLanePriority = getCurrentUpdateLanePriority();
2400      try {
2401        setCurrentUpdateLanePriority(
2402          schedulerPriorityToLanePriority(priorityLevel),
2403        );
2404        return runWithPriority(priorityLevel, flushPassiveEffectsImpl);
2405      } finally {
2406        setCurrentUpdateLanePriority(previousLanePriority);
2407      }
2408    } else {
2409      return runWithPriority(priorityLevel, flushPassiveEffectsImpl);
2410    }
2411  }
2412  return false;
2413}
2414
2415function flushPassiveMountEffects(root, firstChild: Fiber): void {
2416  let fiber = firstChild;
2417  while (fiber !== null) {
2418    let prevProfilerOnStack = null;
2419    if (enableProfilerTimer && enableProfilerCommitHooks) {
2420      if (fiber.tag === Profiler) {
2421        prevProfilerOnStack = nearestProfilerOnStack;
2422        nearestProfilerOnStack = fiber;
2423      }
2424    }
2425
2426    const primarySubtreeFlags = fiber.subtreeFlags & PassiveMask;
2427
2428    if (fiber.child !== null && primarySubtreeFlags !== NoFlags) {
2429      flushPassiveMountEffects(root, fiber.child);
2430    }
2431
2432    if ((fiber.flags & Passive) !== NoFlags) {
2433      if (__DEV__) {
2434        setCurrentDebugFiberInDEV(fiber);
2435        invokeGuardedCallback(
2436          null,
2437          commitPassiveMountOnFiber,
2438          null,
2439          root,
2440          fiber,
2441        );
2442        if (hasCaughtError()) {
2443          const error = clearCaughtError();
2444          captureCommitPhaseError(fiber, fiber.return, error);
2445        }
2446        resetCurrentDebugFiberInDEV();
2447      } else {
2448        try {
2449          commitPassiveMountOnFiber(root, fiber);
2450        } catch (error) {
2451          captureCommitPhaseError(fiber, fiber.return, error);
2452        }
2453      }
2454    }
2455
2456    if (enableProfilerTimer && enableProfilerCommitHooks) {
2457      if (fiber.tag === Profiler) {
2458        // Bubble times to the next nearest ancestor Profiler.
2459        // After we process that Profiler, we'll bubble further up.
2460        if (prevProfilerOnStack !== null) {
2461          prevProfilerOnStack.stateNode.passiveEffectDuration +=
2462            fiber.stateNode.passiveEffectDuration;
2463        }
2464
2465        nearestProfilerOnStack = prevProfilerOnStack;
2466      }
2467    }
2468
2469    fiber = fiber.sibling;
2470  }
2471}
2472
2473function flushPassiveUnmountEffects(firstChild: Fiber): void {
2474  let fiber = firstChild;
2475  while (fiber !== null) {
2476    const deletions = fiber.deletions;
2477    if (deletions !== null) {
2478      for (let i = 0; i < deletions.length; i++) {
2479        const fiberToDelete = deletions[i];
2480        flushPassiveUnmountEffectsInsideOfDeletedTree(fiberToDelete, fiber);
2481
2482        // Now that passive effects have been processed, it's safe to detach lingering pointers.
2483        detachFiberAfterEffects(fiberToDelete);
2484      }
2485    }
2486
2487    const child = fiber.child;
2488    if (child !== null) {
2489      // If any children have passive effects then traverse the subtree.
2490      // Note that this requires checking subtreeFlags of the current Fiber,
2491      // rather than the subtreeFlags/effectsTag of the first child,
2492      // since that would not cover passive effects in siblings.
2493      const passiveFlags = fiber.subtreeFlags & PassiveMask;
2494      if (passiveFlags !== NoFlags) {
2495        flushPassiveUnmountEffects(child);
2496      }
2497    }
2498
2499    const primaryFlags = fiber.flags & Passive;
2500    if (primaryFlags !== NoFlags) {
2501      setCurrentDebugFiberInDEV(fiber);
2502      commitPassiveUnmountOnFiber(fiber);
2503      resetCurrentDebugFiberInDEV();
2504    }
2505
2506    fiber = fiber.sibling;
2507  }
2508}
2509
2510function flushPassiveUnmountEffectsInsideOfDeletedTree(
2511  fiberToDelete: Fiber,
2512  nearestMountedAncestor: Fiber,
2513): void {
2514  if ((fiberToDelete.subtreeFlags & PassiveStatic) !== NoFlags) {
2515    // If any children have passive effects then traverse the subtree.
2516    // Note that this requires checking subtreeFlags of the current Fiber,
2517    // rather than the subtreeFlags/effectsTag of the first child,
2518    // since that would not cover passive effects in siblings.
2519    let child = fiberToDelete.child;
2520    while (child !== null) {
2521      flushPassiveUnmountEffectsInsideOfDeletedTree(
2522        child,
2523        nearestMountedAncestor,
2524      );
2525      child = child.sibling;
2526    }
2527  }
2528
2529  if ((fiberToDelete.flags & PassiveStatic) !== NoFlags) {
2530    setCurrentDebugFiberInDEV(fiberToDelete);
2531    commitPassiveUnmountInsideDeletedTreeOnFiber(
2532      fiberToDelete,
2533      nearestMountedAncestor,
2534    );
2535    resetCurrentDebugFiberInDEV();
2536  }
2537}
2538
2539function flushPassiveEffectsImpl() {
2540  if (rootWithPendingPassiveEffects === null) {
2541    return false;
2542  }
2543
2544  const root = rootWithPendingPassiveEffects;
2545  const lanes = pendingPassiveEffectsLanes;
2546  rootWithPendingPassiveEffects = null;
2547  pendingPassiveEffectsLanes = NoLanes;
2548
2549  invariant(
2550    (executionContext & (RenderContext | CommitContext)) === NoContext,
2551    'Cannot flush passive effects while already rendering.',
2552  );
2553
2554  if (__DEV__) {
2555    if (enableDebugTracing) {
2556      logPassiveEffectsStarted(lanes);
2557    }
2558  }
2559
2560  if (enableSchedulingProfiler) {
2561    markPassiveEffectsStarted(lanes);
2562  }
2563
2564  if (__DEV__) {
2565    isFlushingPassiveEffects = true;
2566  }
2567
2568  const prevExecutionContext = executionContext;
2569  executionContext |= CommitContext;
2570  const prevInteractions = pushInteractions(root);
2571
2572  // It's important that ALL pending passive effect destroy functions are called
2573  // before ANY passive effect create functions are called.
2574  // Otherwise effects in sibling components might interfere with each other.
2575  // e.g. a destroy function in one component may unintentionally override a ref
2576  // value set by a create function in another component.
2577  // Layout effects have the same constraint.
2578  flushPassiveUnmountEffects(root.current);
2579  flushPassiveMountEffects(root, root.current);
2580
2581  if (__DEV__) {
2582    if (enableDebugTracing) {
2583      logPassiveEffectsStopped();
2584    }
2585  }
2586
2587  if (enableSchedulingProfiler) {
2588    markPassiveEffectsStopped();
2589  }
2590
2591  if (__DEV__ && enableDoubleInvokingEffects) {
2592    commitDoubleInvokeEffectsInDEV(root.current, true);
2593  }
2594
2595  if (__DEV__) {
2596    isFlushingPassiveEffects = false;
2597  }
2598
2599  if (enableSchedulerTracing) {
2600    popInteractions(((prevInteractions: any): Set<Interaction>));
2601    finishPendingInteractions(root, lanes);
2602  }
2603
2604  executionContext = prevExecutionContext;
2605
2606  flushSyncCallbackQueue();
2607
2608  // If additional passive effects were scheduled, increment a counter. If this
2609  // exceeds the limit, we'll fire a warning.
2610  nestedPassiveUpdateCount =
2611    rootWithPendingPassiveEffects === null ? 0 : nestedPassiveUpdateCount + 1;
2612
2613  return true;
2614}
2615
2616export function isAlreadyFailedLegacyErrorBoundary(instance: mixed): boolean {
2617  return (
2618    legacyErrorBoundariesThatAlreadyFailed !== null &&
2619    legacyErrorBoundariesThatAlreadyFailed.has(instance)
2620  );
2621}
2622
2623export function markLegacyErrorBoundaryAsFailed(instance: mixed) {
2624  if (legacyErrorBoundariesThatAlreadyFailed === null) {
2625    legacyErrorBoundariesThatAlreadyFailed = new Set([instance]);
2626  } else {
2627    legacyErrorBoundariesThatAlreadyFailed.add(instance);
2628  }
2629}
2630
2631function prepareToThrowUncaughtError(error: mixed) {
2632  if (!hasUncaughtError) {
2633    hasUncaughtError = true;
2634    firstUncaughtError = error;
2635  }
2636}
2637export const onUncaughtError = prepareToThrowUncaughtError;
2638
2639function captureCommitPhaseErrorOnRoot(
2640  rootFiber: Fiber,
2641  sourceFiber: Fiber,
2642  error: mixed,
2643) {
2644  const errorInfo = createCapturedValue(error, sourceFiber);
2645  const update = createRootErrorUpdate(rootFiber, errorInfo, (SyncLane: Lane));
2646  enqueueUpdate(rootFiber, update);
2647  const eventTime = requestEventTime();
2648  const root = markUpdateLaneFromFiberToRoot(rootFiber, (SyncLane: Lane));
2649  if (root !== null) {
2650    markRootUpdated(root, SyncLane, eventTime);
2651    ensureRootIsScheduled(root, eventTime);
2652    schedulePendingInteractions(root, SyncLane);
2653  }
2654}
2655
2656export function captureCommitPhaseError(
2657  sourceFiber: Fiber,
2658  nearestMountedAncestor: Fiber | null,
2659  error: mixed,
2660) {
2661  if (sourceFiber.tag === HostRoot) {
2662    // Error was thrown at the root. There is no parent, so the root
2663    // itself should capture it.
2664    captureCommitPhaseErrorOnRoot(sourceFiber, sourceFiber, error);
2665    return;
2666  }
2667
2668  let fiber = null;
2669  if (skipUnmountedBoundaries) {
2670    fiber = nearestMountedAncestor;
2671  } else {
2672    fiber = sourceFiber.return;
2673  }
2674
2675  while (fiber !== null) {
2676    if (fiber.tag === HostRoot) {
2677      captureCommitPhaseErrorOnRoot(fiber, sourceFiber, error);
2678      return;
2679    } else if (fiber.tag === ClassComponent) {
2680      const ctor = fiber.type;
2681      const instance = fiber.stateNode;
2682      if (
2683        typeof ctor.getDerivedStateFromError === 'function' ||
2684        (typeof instance.componentDidCatch === 'function' &&
2685          !isAlreadyFailedLegacyErrorBoundary(instance))
2686      ) {
2687        const errorInfo = createCapturedValue(error, sourceFiber);
2688        const update = createClassErrorUpdate(
2689          fiber,
2690          errorInfo,
2691          (SyncLane: Lane),
2692        );
2693        enqueueUpdate(fiber, update);
2694        const eventTime = requestEventTime();
2695        const root = markUpdateLaneFromFiberToRoot(fiber, (SyncLane: Lane));
2696        if (root !== null) {
2697          markRootUpdated(root, SyncLane, eventTime);
2698          ensureRootIsScheduled(root, eventTime);
2699          schedulePendingInteractions(root, SyncLane);
2700        }
2701        return;
2702      }
2703    }
2704    fiber = fiber.return;
2705  }
2706}
2707
2708export function pingSuspendedRoot(
2709  root: FiberRoot,
2710  wakeable: Wakeable,
2711  pingedLanes: Lanes,
2712) {
2713  const pingCache = root.pingCache;
2714  if (pingCache !== null) {
2715    // The wakeable resolved, so we no longer need to memoize, because it will
2716    // never be thrown again.
2717    pingCache.delete(wakeable);
2718  }
2719
2720  const eventTime = requestEventTime();
2721  markRootPinged(root, pingedLanes, eventTime);
2722
2723  if (
2724    workInProgressRoot === root &&
2725    isSubsetOfLanes(workInProgressRootRenderLanes, pingedLanes)
2726  ) {
2727    // Received a ping at the same priority level at which we're currently
2728    // rendering. We might want to restart this render. This should mirror
2729    // the logic of whether or not a root suspends once it completes.
2730
2731    // TODO: If we're rendering sync either due to Sync, Batched or expired,
2732    // we should probably never restart.
2733
2734    // If we're suspended with delay, or if it's a retry, we'll always suspend
2735    // so we can always restart.
2736    if (
2737      workInProgressRootExitStatus === RootSuspendedWithDelay ||
2738      (workInProgressRootExitStatus === RootSuspended &&
2739        includesOnlyRetries(workInProgressRootRenderLanes) &&
2740        now() - globalMostRecentFallbackTime < FALLBACK_THROTTLE_MS)
2741    ) {
2742      // Restart from the root.
2743      prepareFreshStack(root, NoLanes);
2744    } else {
2745      // Even though we can't restart right now, we might get an
2746      // opportunity later. So we mark this render as having a ping.
2747      workInProgressRootPingedLanes = mergeLanes(
2748        workInProgressRootPingedLanes,
2749        pingedLanes,
2750      );
2751    }
2752  }
2753
2754  ensureRootIsScheduled(root, eventTime);
2755  schedulePendingInteractions(root, pingedLanes);
2756}
2757
2758function retryTimedOutBoundary(boundaryFiber: Fiber, retryLane: Lane) {
2759  // The boundary fiber (a Suspense component or SuspenseList component)
2760  // previously was rendered in its fallback state. One of the promises that
2761  // suspended it has resolved, which means at least part of the tree was
2762  // likely unblocked. Try rendering again, at a new expiration time.
2763  if (retryLane === NoLane) {
2764    retryLane = requestRetryLane(boundaryFiber);
2765  }
2766  // TODO: Special case idle priority?
2767  const eventTime = requestEventTime();
2768  const root = markUpdateLaneFromFiberToRoot(boundaryFiber, retryLane);
2769  if (root !== null) {
2770    markRootUpdated(root, retryLane, eventTime);
2771    ensureRootIsScheduled(root, eventTime);
2772    schedulePendingInteractions(root, retryLane);
2773  }
2774}
2775
2776export function retryDehydratedSuspenseBoundary(boundaryFiber: Fiber) {
2777  const suspenseState: null | SuspenseState = boundaryFiber.memoizedState;
2778  let retryLane = NoLane;
2779  if (suspenseState !== null) {
2780    retryLane = suspenseState.retryLane;
2781  }
2782  retryTimedOutBoundary(boundaryFiber, retryLane);
2783}
2784
2785export function resolveRetryWakeable(boundaryFiber: Fiber, wakeable: Wakeable) {
2786  let retryLane = NoLane; // Default
2787  let retryCache: WeakSet<Wakeable> | Set<Wakeable> | null;
2788  if (enableSuspenseServerRenderer) {
2789    switch (boundaryFiber.tag) {
2790      case SuspenseComponent:
2791        retryCache = boundaryFiber.stateNode;
2792        const suspenseState: null | SuspenseState = boundaryFiber.memoizedState;
2793        if (suspenseState !== null) {
2794          retryLane = suspenseState.retryLane;
2795        }
2796        break;
2797      case SuspenseListComponent:
2798        retryCache = boundaryFiber.stateNode;
2799        break;
2800      default:
2801        invariant(
2802          false,
2803          'Pinged unknown suspense boundary type. ' +
2804            'This is probably a bug in React.',
2805        );
2806    }
2807  } else {
2808    retryCache = boundaryFiber.stateNode;
2809  }
2810
2811  if (retryCache !== null) {
2812    // The wakeable resolved, so we no longer need to memoize, because it will
2813    // never be thrown again.
2814    retryCache.delete(wakeable);
2815  }
2816
2817  retryTimedOutBoundary(boundaryFiber, retryLane);
2818}
2819
2820// Computes the next Just Noticeable Difference (JND) boundary.
2821// The theory is that a person can't tell the difference between small differences in time.
2822// Therefore, if we wait a bit longer than necessary that won't translate to a noticeable
2823// difference in the experience. However, waiting for longer might mean that we can avoid
2824// showing an intermediate loading state. The longer we have already waited, the harder it
2825// is to tell small differences in time. Therefore, the longer we've already waited,
2826// the longer we can wait additionally. At some point we have to give up though.
2827// We pick a train model where the next boundary commits at a consistent schedule.
2828// These particular numbers are vague estimates. We expect to adjust them based on research.
2829function jnd(timeElapsed: number) {
2830  return timeElapsed < 120
2831    ? 120
2832    : timeElapsed < 480
2833    ? 480
2834    : timeElapsed < 1080
2835    ? 1080
2836    : timeElapsed < 1920
2837    ? 1920
2838    : timeElapsed < 3000
2839    ? 3000
2840    : timeElapsed < 4320
2841    ? 4320
2842    : ceil(timeElapsed / 1960) * 1960;
2843}
2844
2845function checkForNestedUpdates() {
2846  if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
2847    nestedUpdateCount = 0;
2848    rootWithNestedUpdates = null;
2849    invariant(
2850      false,
2851      'Maximum update depth exceeded. This can happen when a component ' +
2852        'repeatedly calls setState inside componentWillUpdate or ' +
2853        'componentDidUpdate. React limits the number of nested updates to ' +
2854        'prevent infinite loops.',
2855    );
2856  }
2857
2858  if (__DEV__) {
2859    if (nestedPassiveUpdateCount > NESTED_PASSIVE_UPDATE_LIMIT) {
2860      nestedPassiveUpdateCount = 0;
2861      console.error(
2862        'Maximum update depth exceeded. This can happen when a component ' +
2863          "calls setState inside useEffect, but useEffect either doesn't " +
2864          'have a dependency array, or one of the dependencies changes on ' +
2865          'every render.',
2866      );
2867    }
2868  }
2869}
2870
2871function flushRenderPhaseStrictModeWarningsInDEV() {
2872  if (__DEV__) {
2873    ReactStrictModeWarnings.flushLegacyContextWarning();
2874
2875    if (warnAboutDeprecatedLifecycles) {
2876      ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings();
2877    }
2878  }
2879}
2880
2881function commitDoubleInvokeEffectsInDEV(
2882  fiber: Fiber,
2883  hasPassiveEffects: boolean,
2884) {
2885  if (__DEV__ && enableDoubleInvokingEffects) {
2886    setCurrentDebugFiberInDEV(fiber);
2887    invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectUnmountInDEV);
2888    if (hasPassiveEffects) {
2889      invokeEffectsInDev(
2890        fiber,
2891        MountPassiveDev,
2892        invokePassiveEffectUnmountInDEV,
2893      );
2894    }
2895
2896    invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectMountInDEV);
2897    if (hasPassiveEffects) {
2898      invokeEffectsInDev(fiber, MountPassiveDev, invokePassiveEffectMountInDEV);
2899    }
2900    resetCurrentDebugFiberInDEV();
2901  }
2902}
2903
2904function invokeEffectsInDev(
2905  firstChild: Fiber,
2906  fiberFlags: Flags,
2907  invokeEffectFn: (fiber: Fiber) => void,
2908): void {
2909  if (__DEV__ && enableDoubleInvokingEffects) {
2910    let fiber = firstChild;
2911    while (fiber !== null) {
2912      if (fiber.child !== null) {
2913        const primarySubtreeFlag = fiber.subtreeFlags & fiberFlags;
2914        if (primarySubtreeFlag !== NoFlags) {
2915          invokeEffectsInDev(fiber.child, fiberFlags, invokeEffectFn);
2916        }
2917      }
2918
2919      if ((fiber.flags & fiberFlags) !== NoFlags) {
2920        invokeEffectFn(fiber);
2921      }
2922      fiber = fiber.sibling;
2923    }
2924  }
2925}
2926
2927let didWarnStateUpdateForNotYetMountedComponent: Set<string> | null = null;
2928function warnAboutUpdateOnNotYetMountedFiberInDEV(fiber) {
2929  if (__DEV__) {
2930    if ((executionContext & RenderContext) !== NoContext) {
2931      // We let the other warning about render phase updates deal with this one.
2932      return;
2933    }
2934
2935    if (!(fiber.mode & (BlockingMode | ConcurrentMode))) {
2936      return;
2937    }
2938
2939    const tag = fiber.tag;
2940    if (
2941      tag !== IndeterminateComponent &&
2942      tag !== HostRoot &&
2943      tag !== ClassComponent &&
2944      tag !== FunctionComponent &&
2945      tag !== ForwardRef &&
2946      tag !== MemoComponent &&
2947      tag !== SimpleMemoComponent &&
2948      tag !== Block
2949    ) {
2950      // Only warn for user-defined components, not internal ones like Suspense.
2951      return;
2952    }
2953
2954    // We show the whole stack but dedupe on the top component's name because
2955    // the problematic code almost always lies inside that component.
2956    const componentName = getComponentName(fiber.type) || 'ReactComponent';
2957    if (didWarnStateUpdateForNotYetMountedComponent !== null) {
2958      if (didWarnStateUpdateForNotYetMountedComponent.has(componentName)) {
2959        return;
2960      }
2961      didWarnStateUpdateForNotYetMountedComponent.add(componentName);
2962    } else {
2963      didWarnStateUpdateForNotYetMountedComponent = new Set([componentName]);
2964    }
2965
2966    const previousFiber = ReactCurrentFiberCurrent;
2967    try {
2968      setCurrentDebugFiberInDEV(fiber);
2969      console.error(
2970        "Can't perform a React state update on a component that hasn't mounted yet. " +
2971          'This indicates that you have a side-effect in your render function that ' +
2972          'asynchronously later calls tries to update the component. Move this work to ' +
2973          'useEffect instead.',
2974      );
2975    } finally {
2976      if (previousFiber) {
2977        setCurrentDebugFiberInDEV(fiber);
2978      } else {
2979        resetCurrentDebugFiberInDEV();
2980      }
2981    }
2982  }
2983}
2984
2985let didWarnStateUpdateForUnmountedComponent: Set<string> | null = null;
2986function warnAboutUpdateOnUnmountedFiberInDEV(fiber) {
2987  if (__DEV__) {
2988    const tag = fiber.tag;
2989    if (
2990      tag !== HostRoot &&
2991      tag !== ClassComponent &&
2992      tag !== FunctionComponent &&
2993      tag !== ForwardRef &&
2994      tag !== MemoComponent &&
2995      tag !== SimpleMemoComponent &&
2996      tag !== Block
2997    ) {
2998      // Only warn for user-defined components, not internal ones like Suspense.
2999      return;
3000    }
3001
3002    if ((fiber.flags & PassiveStatic) !== NoFlags) {
3003      const updateQueue: FunctionComponentUpdateQueue | null = (fiber.updateQueue: any);
3004      if (updateQueue !== null) {
3005        const lastEffect = updateQueue.lastEffect;
3006        if (lastEffect !== null) {
3007          const firstEffect = lastEffect.next;
3008
3009          let effect = firstEffect;
3010          do {
3011            if (effect.destroy !== undefined) {
3012              if ((effect.tag & HookPassive) !== NoHookEffect) {
3013                return;
3014              }
3015            }
3016            effect = effect.next;
3017          } while (effect !== firstEffect);
3018        }
3019      }
3020    }
3021
3022    // We show the whole stack but dedupe on the top component's name because
3023    // the problematic code almost always lies inside that component.
3024    const componentName = getComponentName(fiber.type) || 'ReactComponent';
3025    if (didWarnStateUpdateForUnmountedComponent !== null) {
3026      if (didWarnStateUpdateForUnmountedComponent.has(componentName)) {
3027        return;
3028      }
3029      didWarnStateUpdateForUnmountedComponent.add(componentName);
3030    } else {
3031      didWarnStateUpdateForUnmountedComponent = new Set([componentName]);
3032    }
3033
3034    if (isFlushingPassiveEffects) {
3035      // Do not warn if we are currently flushing passive effects!
3036      //
3037      // React can't directly detect a memory leak, but there are some clues that warn about one.
3038      // One of these clues is when an unmounted React component tries to update its state.
3039      // For example, if a component forgets to remove an event listener when unmounting,
3040      // that listener may be called later and try to update state,
3041      // at which point React would warn about the potential leak.
3042      //
3043      // Warning signals are the most useful when they're strong.
3044      // (So we should avoid false positive warnings.)
3045      // Updating state from within an effect cleanup function is sometimes a necessary pattern, e.g.:
3046      // 1. Updating an ancestor that a component had registered itself with on mount.
3047      // 2. Resetting state when a component is hidden after going offscreen.
3048    } else {
3049      const previousFiber = ReactCurrentFiberCurrent;
3050      try {
3051        setCurrentDebugFiberInDEV(fiber);
3052        console.error(
3053          "Can't perform a React state update on an unmounted component. This " +
3054            'is a no-op, but it indicates a memory leak in your application. To ' +
3055            'fix, cancel all subscriptions and asynchronous tasks in %s.',
3056          tag === ClassComponent
3057            ? 'the componentWillUnmount method'
3058            : 'a useEffect cleanup function',
3059        );
3060      } finally {
3061        if (previousFiber) {
3062          setCurrentDebugFiberInDEV(fiber);
3063        } else {
3064          resetCurrentDebugFiberInDEV();
3065        }
3066      }
3067    }
3068  }
3069}
3070
3071let beginWork;
3072if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
3073  const dummyFiber = null;
3074  beginWork = (current, unitOfWork, lanes) => {
3075    // If a component throws an error, we replay it again in a synchronously
3076    // dispatched event, so that the debugger will treat it as an uncaught
3077    // error See ReactErrorUtils for more information.
3078
3079    // Before entering the begin phase, copy the work-in-progress onto a dummy
3080    // fiber. If beginWork throws, we'll use this to reset the state.
3081    const originalWorkInProgressCopy = assignFiberPropertiesInDEV(
3082      dummyFiber,
3083      unitOfWork,
3084    );
3085    try {
3086      return originalBeginWork(current, unitOfWork, lanes);
3087    } catch (originalError) {
3088      if (
3089        originalError !== null &&
3090        typeof originalError === 'object' &&
3091        typeof originalError.then === 'function'
3092      ) {
3093        // Don't replay promises. Treat everything else like an error.
3094        throw originalError;
3095      }
3096
3097      // Keep this code in sync with handleError; any changes here must have
3098      // corresponding changes there.
3099      resetContextDependencies();
3100      resetHooksAfterThrow();
3101      // Don't reset current debug fiber, since we're about to work on the
3102      // same fiber again.
3103
3104      // Unwind the failed stack frame
3105      unwindInterruptedWork(unitOfWork);
3106
3107      // Restore the original properties of the fiber.
3108      assignFiberPropertiesInDEV(unitOfWork, originalWorkInProgressCopy);
3109
3110      if (enableProfilerTimer && unitOfWork.mode & ProfileMode) {
3111        // Reset the profiler timer.
3112        startProfilerTimer(unitOfWork);
3113      }
3114
3115      // Run beginWork again.
3116      invokeGuardedCallback(
3117        null,
3118        originalBeginWork,
3119        null,
3120        current,
3121        unitOfWork,
3122        lanes,
3123      );
3124
3125      if (hasCaughtError()) {
3126        const replayError = clearCaughtError();
3127        // `invokeGuardedCallback` sometimes sets an expando `_suppressLogging`.
3128        // Rethrow this error instead of the original one.
3129        throw replayError;
3130      } else {
3131        // This branch is reachable if the render phase is impure.
3132        throw originalError;
3133      }
3134    }
3135  };
3136} else {
3137  beginWork = originalBeginWork;
3138}
3139
3140let didWarnAboutUpdateInRender = false;
3141let didWarnAboutUpdateInRenderForAnotherComponent;
3142if (__DEV__) {
3143  didWarnAboutUpdateInRenderForAnotherComponent = new Set();
3144}
3145
3146function warnAboutRenderPhaseUpdatesInDEV(fiber) {
3147  if (__DEV__) {
3148    if (
3149      ReactCurrentDebugFiberIsRenderingInDEV &&
3150      (executionContext & RenderContext) !== NoContext &&
3151      !getIsUpdatingOpaqueValueInRenderPhaseInDEV()
3152    ) {
3153      switch (fiber.tag) {
3154        case FunctionComponent:
3155        case ForwardRef:
3156        case SimpleMemoComponent: {
3157          const renderingComponentName =
3158            (workInProgress && getComponentName(workInProgress.type)) ||
3159            'Unknown';
3160          // Dedupe by the rendering component because it's the one that needs to be fixed.
3161          const dedupeKey = renderingComponentName;
3162          if (!didWarnAboutUpdateInRenderForAnotherComponent.has(dedupeKey)) {
3163            didWarnAboutUpdateInRenderForAnotherComponent.add(dedupeKey);
3164            const setStateComponentName =
3165              getComponentName(fiber.type) || 'Unknown';
3166            console.error(
3167              'Cannot update a component (`%s`) while rendering a ' +
3168                'different component (`%s`). To locate the bad setState() call inside `%s`, ' +
3169                'follow the stack trace as described in https://reactjs.org/link/setstate-in-render',
3170              setStateComponentName,
3171              renderingComponentName,
3172              renderingComponentName,
3173            );
3174          }
3175          break;
3176        }
3177        case ClassComponent: {
3178          if (!didWarnAboutUpdateInRender) {
3179            console.error(
3180              'Cannot update during an existing state transition (such as ' +
3181                'within `render`). Render methods should be a pure ' +
3182                'function of props and state.',
3183            );
3184            didWarnAboutUpdateInRender = true;
3185          }
3186          break;
3187        }
3188      }
3189    }
3190  }
3191}
3192
3193// a 'shared' variable that changes when act() opens/closes in tests.
3194export const IsThisRendererActing = {current: (false: boolean)};
3195
3196export function warnIfNotScopedWithMatchingAct(fiber: Fiber): void {
3197  if (__DEV__) {
3198    if (
3199      warnsIfNotActing === true &&
3200      IsSomeRendererActing.current === true &&
3201      IsThisRendererActing.current !== true
3202    ) {
3203      const previousFiber = ReactCurrentFiberCurrent;
3204      try {
3205        setCurrentDebugFiberInDEV(fiber);
3206        console.error(
3207          "It looks like you're using the wrong act() around your test interactions.\n" +
3208            'Be sure to use the matching version of act() corresponding to your renderer:\n\n' +
3209            '// for react-dom:\n' +
3210            // Break up imports to avoid accidentally parsing them as dependencies.
3211            'import {act} fr' +
3212            "om 'react-dom/test-utils';\n" +
3213            '// ...\n' +
3214            'act(() => ...);\n\n' +
3215            '// for react-test-renderer:\n' +
3216            // Break up imports to avoid accidentally parsing them as dependencies.
3217            'import TestRenderer fr' +
3218            "om react-test-renderer';\n" +
3219            'const {act} = TestRenderer;\n' +
3220            '// ...\n' +
3221            'act(() => ...);',
3222        );
3223      } finally {
3224        if (previousFiber) {
3225          setCurrentDebugFiberInDEV(fiber);
3226        } else {
3227          resetCurrentDebugFiberInDEV();
3228        }
3229      }
3230    }
3231  }
3232}
3233
3234export function warnIfNotCurrentlyActingEffectsInDEV(fiber: Fiber): void {
3235  if (__DEV__) {
3236    if (
3237      warnsIfNotActing === true &&
3238      (fiber.mode & StrictMode) !== NoMode &&
3239      IsSomeRendererActing.current === false &&
3240      IsThisRendererActing.current === false
3241    ) {
3242      console.error(
3243        'An update to %s ran an effect, but was not wrapped in act(...).\n\n' +
3244          'When testing, code that causes React state updates should be ' +
3245          'wrapped into act(...):\n\n' +
3246          'act(() => {\n' +
3247          '  /* fire events that update state */\n' +
3248          '});\n' +
3249          '/* assert on the output */\n\n' +
3250          "This ensures that you're testing the behavior the user would see " +
3251          'in the browser.' +
3252          ' Learn more at https://reactjs.org/link/wrap-tests-with-act',
3253        getComponentName(fiber.type),
3254      );
3255    }
3256  }
3257}
3258
3259function warnIfNotCurrentlyActingUpdatesInDEV(fiber: Fiber): void {
3260  if (__DEV__) {
3261    if (
3262      warnsIfNotActing === true &&
3263      executionContext === NoContext &&
3264      IsSomeRendererActing.current === false &&
3265      IsThisRendererActing.current === false
3266    ) {
3267      const previousFiber = ReactCurrentFiberCurrent;
3268      try {
3269        setCurrentDebugFiberInDEV(fiber);
3270        console.error(
3271          'An update to %s inside a test was not wrapped in act(...).\n\n' +
3272            'When testing, code that causes React state updates should be ' +
3273            'wrapped into act(...):\n\n' +
3274            'act(() => {\n' +
3275            '  /* fire events that update state */\n' +
3276            '});\n' +
3277            '/* assert on the output */\n\n' +
3278            "This ensures that you're testing the behavior the user would see " +
3279            'in the browser.' +
3280            ' Learn more at https://reactjs.org/link/wrap-tests-with-act',
3281          getComponentName(fiber.type),
3282        );
3283      } finally {
3284        if (previousFiber) {
3285          setCurrentDebugFiberInDEV(fiber);
3286        } else {
3287          resetCurrentDebugFiberInDEV();
3288        }
3289      }
3290    }
3291  }
3292}
3293
3294export const warnIfNotCurrentlyActingUpdatesInDev = warnIfNotCurrentlyActingUpdatesInDEV;
3295
3296// In tests, we want to enforce a mocked scheduler.
3297let didWarnAboutUnmockedScheduler = false;
3298// TODO Before we release concurrent mode, revisit this and decide whether a mocked
3299// scheduler is the actual recommendation. The alternative could be a testing build,
3300// a new lib, or whatever; we dunno just yet. This message is for early adopters
3301// to get their tests right.
3302
3303export function warnIfUnmockedScheduler(fiber: Fiber) {
3304  if (__DEV__) {
3305    if (
3306      didWarnAboutUnmockedScheduler === false &&
3307      Scheduler.unstable_flushAllWithoutAsserting === undefined
3308    ) {
3309      if (fiber.mode & BlockingMode || fiber.mode & ConcurrentMode) {
3310        didWarnAboutUnmockedScheduler = true;
3311        console.error(
3312          'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
3313            'to guarantee consistent behaviour across tests and browsers. ' +
3314            'For example, with jest: \n' +
3315            // Break up requires to avoid accidentally parsing them as dependencies.
3316            "jest.mock('scheduler', () => require" +
3317            "('scheduler/unstable_mock'));\n\n" +
3318            'For more info, visit https://reactjs.org/link/mock-scheduler',
3319        );
3320      } else if (warnAboutUnmockedScheduler === true) {
3321        didWarnAboutUnmockedScheduler = true;
3322        console.error(
3323          'Starting from React v18, the "scheduler" module will need to be mocked ' +
3324            'to guarantee consistent behaviour across tests and browsers. ' +
3325            'For example, with jest: \n' +
3326            // Break up requires to avoid accidentally parsing them as dependencies.
3327            "jest.mock('scheduler', () => require" +
3328            "('scheduler/unstable_mock'));\n\n" +
3329            'For more info, visit https://reactjs.org/link/mock-scheduler',
3330        );
3331      }
3332    }
3333  }
3334}
3335
3336function computeThreadID(root: FiberRoot, lane: Lane | Lanes) {
3337  // Interaction threads are unique per root and expiration time.
3338  // NOTE: Intentionally unsound cast. All that matters is that it's a number
3339  // and it represents a batch of work. Could make a helper function instead,
3340  // but meh this is fine for now.
3341  return (lane: any) * 1000 + root.interactionThreadID;
3342}
3343
3344export function markSpawnedWork(lane: Lane | Lanes) {
3345  if (!enableSchedulerTracing) {
3346    return;
3347  }
3348  if (spawnedWorkDuringRender === null) {
3349    spawnedWorkDuringRender = [lane];
3350  } else {
3351    spawnedWorkDuringRender.push(lane);
3352  }
3353}
3354
3355function scheduleInteractions(
3356  root: FiberRoot,
3357  lane: Lane | Lanes,
3358  interactions: Set<Interaction>,
3359) {
3360  if (!enableSchedulerTracing) {
3361    return;
3362  }
3363
3364  if (interactions.size > 0) {
3365    const pendingInteractionMap = root.pendingInteractionMap;
3366    const pendingInteractions = pendingInteractionMap.get(lane);
3367    if (pendingInteractions != null) {
3368      interactions.forEach(interaction => {
3369        if (!pendingInteractions.has(interaction)) {
3370          // Update the pending async work count for previously unscheduled interaction.
3371          interaction.__count++;
3372        }
3373
3374        pendingInteractions.add(interaction);
3375      });
3376    } else {
3377      pendingInteractionMap.set(lane, new Set(interactions));
3378
3379      // Update the pending async work count for the current interactions.
3380      interactions.forEach(interaction => {
3381        interaction.__count++;
3382      });
3383    }
3384
3385    const subscriber = __subscriberRef.current;
3386    if (subscriber !== null) {
3387      const threadID = computeThreadID(root, lane);
3388      subscriber.onWorkScheduled(interactions, threadID);
3389    }
3390  }
3391}
3392
3393function schedulePendingInteractions(root: FiberRoot, lane: Lane | Lanes) {
3394  // This is called when work is scheduled on a root.
3395  // It associates the current interactions with the newly-scheduled expiration.
3396  // They will be restored when that expiration is later committed.
3397  if (!enableSchedulerTracing) {
3398    return;
3399  }
3400
3401  scheduleInteractions(root, lane, __interactionsRef.current);
3402}
3403
3404function startWorkOnPendingInteractions(root: FiberRoot, lanes: Lanes) {
3405  // This is called when new work is started on a root.
3406  if (!enableSchedulerTracing) {
3407    return;
3408  }
3409
3410  // Determine which interactions this batch of work currently includes, So that
3411  // we can accurately attribute time spent working on it, And so that cascading
3412  // work triggered during the render phase will be associated with it.
3413  const interactions: Set<Interaction> = new Set();
3414  root.pendingInteractionMap.forEach((scheduledInteractions, scheduledLane) => {
3415    if (includesSomeLane(lanes, scheduledLane)) {
3416      scheduledInteractions.forEach(interaction =>
3417        interactions.add(interaction),
3418      );
3419    }
3420  });
3421
3422  // Store the current set of interactions on the FiberRoot for a few reasons:
3423  // We can re-use it in hot functions like performConcurrentWorkOnRoot()
3424  // without having to recalculate it. We will also use it in commitWork() to
3425  // pass to any Profiler onRender() hooks. This also provides DevTools with a
3426  // way to access it when the onCommitRoot() hook is called.
3427  root.memoizedInteractions = interactions;
3428
3429  if (interactions.size > 0) {
3430    const subscriber = __subscriberRef.current;
3431    if (subscriber !== null) {
3432      const threadID = computeThreadID(root, lanes);
3433      try {
3434        subscriber.onWorkStarted(interactions, threadID);
3435      } catch (error) {
3436        // If the subscriber throws, rethrow it in a separate task
3437        scheduleCallback(ImmediateSchedulerPriority, () => {
3438          throw error;
3439        });
3440      }
3441    }
3442  }
3443}
3444
3445function finishPendingInteractions(root, committedLanes) {
3446  if (!enableSchedulerTracing) {
3447    return;
3448  }
3449
3450  const remainingLanesAfterCommit = root.pendingLanes;
3451
3452  let subscriber;
3453
3454  try {
3455    subscriber = __subscriberRef.current;
3456    if (subscriber !== null && root.memoizedInteractions.size > 0) {
3457      // FIXME: More than one lane can finish in a single commit.
3458      const threadID = computeThreadID(root, committedLanes);
3459      subscriber.onWorkStopped(root.memoizedInteractions, threadID);
3460    }
3461  } catch (error) {
3462    // If the subscriber throws, rethrow it in a separate task
3463    scheduleCallback(ImmediateSchedulerPriority, () => {
3464      throw error;
3465    });
3466  } finally {
3467    // Clear completed interactions from the pending Map.
3468    // Unless the render was suspended or cascading work was scheduled,
3469    // In which case– leave pending interactions until the subsequent render.
3470    const pendingInteractionMap = root.pendingInteractionMap;
3471    pendingInteractionMap.forEach((scheduledInteractions, lane) => {
3472      // Only decrement the pending interaction count if we're done.
3473      // If there's still work at the current priority,
3474      // That indicates that we are waiting for suspense data.
3475      if (!includesSomeLane(remainingLanesAfterCommit, lane)) {
3476        pendingInteractionMap.delete(lane);
3477
3478        scheduledInteractions.forEach(interaction => {
3479          interaction.__count--;
3480
3481          if (subscriber !== null && interaction.__count === 0) {
3482            try {
3483              subscriber.onInteractionScheduledWorkCompleted(interaction);
3484            } catch (error) {
3485              // If the subscriber throws, rethrow it in a separate task
3486              scheduleCallback(ImmediateSchedulerPriority, () => {
3487                throw error;
3488              });
3489            }
3490          }
3491        });
3492      }
3493    });
3494  }
3495}
3496
3497// `act` testing API
3498//
3499// TODO: This is mostly a copy-paste from the legacy `act`, which does not have
3500// access to the same internals that we do here. Some trade offs in the
3501// implementation no longer make sense.
3502
3503let isFlushingAct = false;
3504let isInsideThisAct = false;
3505
3506function shouldForceFlushFallbacksInDEV() {
3507  // Never force flush in production. This function should get stripped out.
3508  return __DEV__ && actingUpdatesScopeDepth > 0;
3509}
3510
3511const flushMockScheduler = Scheduler.unstable_flushAllWithoutAsserting;
3512const isSchedulerMocked = typeof flushMockScheduler === 'function';
3513
3514// Returns whether additional work was scheduled. Caller should keep flushing
3515// until there's no work left.
3516function flushActWork(): boolean {
3517  if (flushMockScheduler !== undefined) {
3518    const prevIsFlushing = isFlushingAct;
3519    isFlushingAct = true;
3520    try {
3521      return flushMockScheduler();
3522    } finally {
3523      isFlushingAct = prevIsFlushing;
3524    }
3525  } else {
3526    // No mock scheduler available. However, the only type of pending work is
3527    // passive effects, which we control. So we can flush that.
3528    const prevIsFlushing = isFlushingAct;
3529    isFlushingAct = true;
3530    try {
3531      let didFlushWork = false;
3532      while (flushPassiveEffects()) {
3533        didFlushWork = true;
3534      }
3535      return didFlushWork;
3536    } finally {
3537      isFlushingAct = prevIsFlushing;
3538    }
3539  }
3540}
3541
3542function flushWorkAndMicroTasks(onDone: (err: ?Error) => void) {
3543  try {
3544    flushActWork();
3545    enqueueTask(() => {
3546      if (flushActWork()) {
3547        flushWorkAndMicroTasks(onDone);
3548      } else {
3549        onDone();
3550      }
3551    });
3552  } catch (err) {
3553    onDone(err);
3554  }
3555}
3556
3557// we track the 'depth' of the act() calls with this counter,
3558// so we can tell if any async act() calls try to run in parallel.
3559
3560let actingUpdatesScopeDepth = 0;
3561let didWarnAboutUsingActInProd = false;
3562
3563export function act(callback: () => Thenable<mixed>): Thenable<void> {
3564  if (!__DEV__) {
3565    if (didWarnAboutUsingActInProd === false) {
3566      didWarnAboutUsingActInProd = true;
3567      // eslint-disable-next-line react-internal/no-production-logging
3568      console.error(
3569        'act(...) is not supported in production builds of React, and might not behave as expected.',
3570      );
3571    }
3572  }
3573
3574  const previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
3575  actingUpdatesScopeDepth++;
3576
3577  const previousIsSomeRendererActing = IsSomeRendererActing.current;
3578  const previousIsThisRendererActing = IsThisRendererActing.current;
3579  const previousIsInsideThisAct = isInsideThisAct;
3580  IsSomeRendererActing.current = true;
3581  IsThisRendererActing.current = true;
3582  isInsideThisAct = true;
3583
3584  function onDone() {
3585    actingUpdatesScopeDepth--;
3586    IsSomeRendererActing.current = previousIsSomeRendererActing;
3587    IsThisRendererActing.current = previousIsThisRendererActing;
3588    isInsideThisAct = previousIsInsideThisAct;
3589    if (__DEV__) {
3590      if (actingUpdatesScopeDepth > previousActingUpdatesScopeDepth) {
3591        // if it's _less than_ previousActingUpdatesScopeDepth, then we can assume the 'other' one has warned
3592        console.error(
3593          'You seem to have overlapping act() calls, this is not supported. ' +
3594            'Be sure to await previous act() calls before making a new one. ',
3595        );
3596      }
3597    }
3598  }
3599
3600  let result;
3601  try {
3602    result = batchedUpdates(callback);
3603  } catch (error) {
3604    // on sync errors, we still want to 'cleanup' and decrement actingUpdatesScopeDepth
3605    onDone();
3606    throw error;
3607  }
3608
3609  if (
3610    result !== null &&
3611    typeof result === 'object' &&
3612    typeof result.then === 'function'
3613  ) {
3614    // setup a boolean that gets set to true only
3615    // once this act() call is await-ed
3616    let called = false;
3617    if (__DEV__) {
3618      if (typeof Promise !== 'undefined') {
3619        //eslint-disable-next-line no-undef
3620        Promise.resolve()
3621          .then(() => {})
3622          .then(() => {
3623            if (called === false) {
3624              console.error(
3625                'You called act(async () => ...) without await. ' +
3626                  'This could lead to unexpected testing behaviour, interleaving multiple act ' +
3627                  'calls and mixing their scopes. You should - await act(async () => ...);',
3628              );
3629            }
3630          });
3631      }
3632    }
3633
3634    // in the async case, the returned thenable runs the callback, flushes
3635    // effects and  microtasks in a loop until flushPassiveEffects() === false,
3636    // and cleans up
3637    return {
3638      then(resolve, reject) {
3639        called = true;
3640        result.then(
3641          () => {
3642            if (
3643              actingUpdatesScopeDepth > 1 ||
3644              (isSchedulerMocked === true &&
3645                previousIsSomeRendererActing === true)
3646            ) {
3647              onDone();
3648              resolve();
3649              return;
3650            }
3651            // we're about to exit the act() scope,
3652            // now's the time to flush tasks/effects
3653            flushWorkAndMicroTasks((err: ?Error) => {
3654              onDone();
3655              if (err) {
3656                reject(err);
3657              } else {
3658                resolve();
3659              }
3660            });
3661          },
3662          err => {
3663            onDone();
3664            reject(err);
3665          },
3666        );
3667      },
3668    };
3669  } else {
3670    if (__DEV__) {
3671      if (result !== undefined) {
3672        console.error(
3673          'The callback passed to act(...) function ' +
3674            'must return undefined, or a Promise. You returned %s',
3675          result,
3676        );
3677      }
3678    }
3679
3680    // flush effects until none remain, and cleanup
3681    try {
3682      if (
3683        actingUpdatesScopeDepth === 1 &&
3684        (isSchedulerMocked === false || previousIsSomeRendererActing === false)
3685      ) {
3686        // we're about to exit the act() scope,
3687        // now's the time to flush effects
3688        flushActWork();
3689      }
3690      onDone();
3691    } catch (err) {
3692      onDone();
3693      throw err;
3694    }
3695
3696    // in the sync case, the returned thenable only warns *if* await-ed
3697    return {
3698      then(resolve) {
3699        if (__DEV__) {
3700          console.error(
3701            'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
3702          );
3703        }
3704        resolve();
3705      },
3706    };
3707  }
3708}
3709
3710function detachFiberAfterEffects(fiber: Fiber): void {
3711  // Null out fields to improve GC for references that may be lingering (e.g. DevTools).
3712  // Note that we already cleared the return pointer in detachFiberMutation().
3713  fiber.child = null;
3714  fiber.deletions = null;
3715  fiber.dependencies = null;
3716  fiber.memoizedProps = null;
3717  fiber.memoizedState = null;
3718  fiber.pendingProps = null;
3719  fiber.sibling = null;
3720  fiber.stateNode = null;
3721  fiber.updateQueue = null;
3722
3723  if (__DEV__) {
3724    fiber._debugOwner = null;
3725  }
3726}
3727
Full Screen