1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @flow
    
  8.  */
    
  9. 
    
  10. import type {FiberRoot} from './ReactInternalTypes';
    
  11. import type {Lane} from './ReactFiberLane';
    
  12. import type {PriorityLevel} from 'scheduler/src/SchedulerPriorities';
    
  13. 
    
  14. import {enableDeferRootSchedulingToMicrotask} from 'shared/ReactFeatureFlags';
    
  15. import {
    
  16.   NoLane,
    
  17.   NoLanes,
    
  18.   SyncLane,
    
  19.   getHighestPriorityLane,
    
  20.   getNextLanes,
    
  21.   includesSyncLane,
    
  22.   markStarvedLanesAsExpired,
    
  23.   upgradePendingLaneToSync,
    
  24.   claimNextTransitionLane,
    
  25. } from './ReactFiberLane';
    
  26. import {
    
  27.   CommitContext,
    
  28.   NoContext,
    
  29.   RenderContext,
    
  30.   getExecutionContext,
    
  31.   getWorkInProgressRoot,
    
  32.   getWorkInProgressRootRenderLanes,
    
  33.   isWorkLoopSuspendedOnData,
    
  34.   performConcurrentWorkOnRoot,
    
  35.   performSyncWorkOnRoot,
    
  36. } from './ReactFiberWorkLoop';
    
  37. import {LegacyRoot} from './ReactRootTags';
    
  38. import {
    
  39.   ImmediatePriority as ImmediateSchedulerPriority,
    
  40.   UserBlockingPriority as UserBlockingSchedulerPriority,
    
  41.   NormalPriority as NormalSchedulerPriority,
    
  42.   IdlePriority as IdleSchedulerPriority,
    
  43.   cancelCallback as Scheduler_cancelCallback,
    
  44.   scheduleCallback as Scheduler_scheduleCallback,
    
  45.   now,
    
  46. } from './Scheduler';
    
  47. import {
    
  48.   DiscreteEventPriority,
    
  49.   ContinuousEventPriority,
    
  50.   DefaultEventPriority,
    
  51.   IdleEventPriority,
    
  52.   lanesToEventPriority,
    
  53. } from './ReactEventPriorities';
    
  54. import {
    
  55.   supportsMicrotasks,
    
  56.   scheduleMicrotask,
    
  57.   shouldAttemptEagerTransition,
    
  58. } from './ReactFiberConfig';
    
  59. 
    
  60. import ReactSharedInternals from 'shared/ReactSharedInternals';
    
  61. const {ReactCurrentActQueue} = ReactSharedInternals;
    
  62. 
    
  63. // A linked list of all the roots with pending work. In an idiomatic app,
    
  64. // there's only a single root, but we do support multi root apps, hence this
    
  65. // extra complexity. But this module is optimized for the single root case.
    
  66. let firstScheduledRoot: FiberRoot | null = null;
    
  67. let lastScheduledRoot: FiberRoot | null = null;
    
  68. 
    
  69. // Used to prevent redundant mircotasks from being scheduled.
    
  70. let didScheduleMicrotask: boolean = false;
    
  71. // `act` "microtasks" are scheduled on the `act` queue instead of an actual
    
  72. // microtask, so we have to dedupe those separately. This wouldn't be an issue
    
  73. // if we required all `act` calls to be awaited, which we might in the future.
    
  74. let didScheduleMicrotask_act: boolean = false;
    
  75. 
    
  76. // Used to quickly bail out of flushSync if there's no sync work to do.
    
  77. let mightHavePendingSyncWork: boolean = false;
    
  78. 
    
  79. let isFlushingWork: boolean = false;
    
  80. 
    
  81. let currentEventTransitionLane: Lane = NoLane;
    
  82. 
    
  83. export function ensureRootIsScheduled(root: FiberRoot): void {
    
  84.   // This function is called whenever a root receives an update. It does two
    
  85.   // things 1) it ensures the root is in the root schedule, and 2) it ensures
    
  86.   // there's a pending microtask to process the root schedule.
    
  87.   //
    
  88.   // Most of the actual scheduling logic does not happen until
    
  89.   // `scheduleTaskForRootDuringMicrotask` runs.
    
  90. 
    
  91.   // Add the root to the schedule
    
  92.   if (root === lastScheduledRoot || root.next !== null) {
    
  93.     // Fast path. This root is already scheduled.
    
  94.   } else {
    
  95.     if (lastScheduledRoot === null) {
    
  96.       firstScheduledRoot = lastScheduledRoot = root;
    
  97.     } else {
    
  98.       lastScheduledRoot.next = root;
    
  99.       lastScheduledRoot = root;
    
  100.     }
    
  101.   }
    
  102. 
    
  103.   // Any time a root received an update, we set this to true until the next time
    
  104.   // we process the schedule. If it's false, then we can quickly exit flushSync
    
  105.   // without consulting the schedule.
    
  106.   mightHavePendingSyncWork = true;
    
  107. 
    
  108.   // At the end of the current event, go through each of the roots and ensure
    
  109.   // there's a task scheduled for each one at the correct priority.
    
  110.   if (__DEV__ && ReactCurrentActQueue.current !== null) {
    
  111.     // We're inside an `act` scope.
    
  112.     if (!didScheduleMicrotask_act) {
    
  113.       didScheduleMicrotask_act = true;
    
  114.       scheduleImmediateTask(processRootScheduleInMicrotask);
    
  115.     }
    
  116.   } else {
    
  117.     if (!didScheduleMicrotask) {
    
  118.       didScheduleMicrotask = true;
    
  119.       scheduleImmediateTask(processRootScheduleInMicrotask);
    
  120.     }
    
  121.   }
    
  122. 
    
  123.   if (!enableDeferRootSchedulingToMicrotask) {
    
  124.     // While this flag is disabled, we schedule the render task immediately
    
  125.     // instead of waiting a microtask.
    
  126.     // TODO: We need to land enableDeferRootSchedulingToMicrotask ASAP to
    
  127.     // unblock additional features we have planned.
    
  128.     scheduleTaskForRootDuringMicrotask(root, now());
    
  129.   }
    
  130. 
    
  131.   if (
    
  132.     __DEV__ &&
    
  133.     ReactCurrentActQueue.isBatchingLegacy &&
    
  134.     root.tag === LegacyRoot
    
  135.   ) {
    
  136.     // Special `act` case: Record whenever a legacy update is scheduled.
    
  137.     ReactCurrentActQueue.didScheduleLegacyUpdate = true;
    
  138.   }
    
  139. }
    
  140. 
    
  141. export function flushSyncWorkOnAllRoots() {
    
  142.   // This is allowed to be called synchronously, but the caller should check
    
  143.   // the execution context first.
    
  144.   flushSyncWorkAcrossRoots_impl(false);
    
  145. }
    
  146. 
    
  147. export function flushSyncWorkOnLegacyRootsOnly() {
    
  148.   // This is allowed to be called synchronously, but the caller should check
    
  149.   // the execution context first.
    
  150.   flushSyncWorkAcrossRoots_impl(true);
    
  151. }
    
  152. 
    
  153. function flushSyncWorkAcrossRoots_impl(onlyLegacy: boolean) {
    
  154.   if (isFlushingWork) {
    
  155.     // Prevent reentrancy.
    
  156.     // TODO: Is this overly defensive? The callers must check the execution
    
  157.     // context first regardless.
    
  158.     return;
    
  159.   }
    
  160. 
    
  161.   if (!mightHavePendingSyncWork) {
    
  162.     // Fast path. There's no sync work to do.
    
  163.     return;
    
  164.   }
    
  165. 
    
  166.   // There may or may not be synchronous work scheduled. Let's check.
    
  167.   let didPerformSomeWork;
    
  168.   let errors: Array<mixed> | null = null;
    
  169.   isFlushingWork = true;
    
  170.   do {
    
  171.     didPerformSomeWork = false;
    
  172.     let root = firstScheduledRoot;
    
  173.     while (root !== null) {
    
  174.       if (onlyLegacy && root.tag !== LegacyRoot) {
    
  175.         // Skip non-legacy roots.
    
  176.       } else {
    
  177.         const workInProgressRoot = getWorkInProgressRoot();
    
  178.         const workInProgressRootRenderLanes =
    
  179.           getWorkInProgressRootRenderLanes();
    
  180.         const nextLanes = getNextLanes(
    
  181.           root,
    
  182.           root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
    
  183.         );
    
  184.         if (includesSyncLane(nextLanes)) {
    
  185.           // This root has pending sync work. Flush it now.
    
  186.           try {
    
  187.             didPerformSomeWork = true;
    
  188.             performSyncWorkOnRoot(root, nextLanes);
    
  189.           } catch (error) {
    
  190.             // Collect errors so we can rethrow them at the end
    
  191.             if (errors === null) {
    
  192.               errors = [error];
    
  193.             } else {
    
  194.               errors.push(error);
    
  195.             }
    
  196.           }
    
  197.         }
    
  198.       }
    
  199.       root = root.next;
    
  200.     }
    
  201.   } while (didPerformSomeWork);
    
  202.   isFlushingWork = false;
    
  203. 
    
  204.   // If any errors were thrown, rethrow them right before exiting.
    
  205.   // TODO: Consider returning these to the caller, to allow them to decide
    
  206.   // how/when to rethrow.
    
  207.   if (errors !== null) {
    
  208.     if (errors.length > 1) {
    
  209.       if (typeof AggregateError === 'function') {
    
  210.         // eslint-disable-next-line no-undef
    
  211.         throw new AggregateError(errors);
    
  212.       } else {
    
  213.         for (let i = 1; i < errors.length; i++) {
    
  214.           scheduleImmediateTask(throwError.bind(null, errors[i]));
    
  215.         }
    
  216.         const firstError = errors[0];
    
  217.         throw firstError;
    
  218.       }
    
  219.     } else {
    
  220.       const error = errors[0];
    
  221.       throw error;
    
  222.     }
    
  223.   }
    
  224. }
    
  225. 
    
  226. function throwError(error: mixed) {
    
  227.   throw error;
    
  228. }
    
  229. 
    
  230. function processRootScheduleInMicrotask() {
    
  231.   // This function is always called inside a microtask. It should never be
    
  232.   // called synchronously.
    
  233.   didScheduleMicrotask = false;
    
  234.   if (__DEV__) {
    
  235.     didScheduleMicrotask_act = false;
    
  236.   }
    
  237. 
    
  238.   // We'll recompute this as we iterate through all the roots and schedule them.
    
  239.   mightHavePendingSyncWork = false;
    
  240. 
    
  241.   const currentTime = now();
    
  242. 
    
  243.   let prev = null;
    
  244.   let root = firstScheduledRoot;
    
  245.   while (root !== null) {
    
  246.     const next = root.next;
    
  247. 
    
  248.     if (
    
  249.       currentEventTransitionLane !== NoLane &&
    
  250.       shouldAttemptEagerTransition()
    
  251.     ) {
    
  252.       // A transition was scheduled during an event, but we're going to try to
    
  253.       // render it synchronously anyway. We do this during a popstate event to
    
  254.       // preserve the scroll position of the previous page.
    
  255.       upgradePendingLaneToSync(root, currentEventTransitionLane);
    
  256.     }
    
  257. 
    
  258.     const nextLanes = scheduleTaskForRootDuringMicrotask(root, currentTime);
    
  259.     if (nextLanes === NoLane) {
    
  260.       // This root has no more pending work. Remove it from the schedule. To
    
  261.       // guard against subtle reentrancy bugs, this microtask is the only place
    
  262.       // we do this — you can add roots to the schedule whenever, but you can
    
  263.       // only remove them here.
    
  264. 
    
  265.       // Null this out so we know it's been removed from the schedule.
    
  266.       root.next = null;
    
  267.       if (prev === null) {
    
  268.         // This is the new head of the list
    
  269.         firstScheduledRoot = next;
    
  270.       } else {
    
  271.         prev.next = next;
    
  272.       }
    
  273.       if (next === null) {
    
  274.         // This is the new tail of the list
    
  275.         lastScheduledRoot = prev;
    
  276.       }
    
  277.     } else {
    
  278.       // This root still has work. Keep it in the list.
    
  279.       prev = root;
    
  280.       if (includesSyncLane(nextLanes)) {
    
  281.         mightHavePendingSyncWork = true;
    
  282.       }
    
  283.     }
    
  284.     root = next;
    
  285.   }
    
  286. 
    
  287.   currentEventTransitionLane = NoLane;
    
  288. 
    
  289.   // At the end of the microtask, flush any pending synchronous work. This has
    
  290.   // to come at the end, because it does actual rendering work that might throw.
    
  291.   flushSyncWorkOnAllRoots();
    
  292. }
    
  293. 
    
  294. function scheduleTaskForRootDuringMicrotask(
    
  295.   root: FiberRoot,
    
  296.   currentTime: number,
    
  297. ): Lane {
    
  298.   // This function is always called inside a microtask, or at the very end of a
    
  299.   // rendering task right before we yield to the main thread. It should never be
    
  300.   // called synchronously.
    
  301.   //
    
  302.   // TODO: Unless enableDeferRootSchedulingToMicrotask is off. We need to land
    
  303.   // that ASAP to unblock additional features we have planned.
    
  304.   //
    
  305.   // This function also never performs React work synchronously; it should
    
  306.   // only schedule work to be performed later, in a separate task or microtask.
    
  307. 
    
  308.   // Check if any lanes are being starved by other work. If so, mark them as
    
  309.   // expired so we know to work on those next.
    
  310.   markStarvedLanesAsExpired(root, currentTime);
    
  311. 
    
  312.   // Determine the next lanes to work on, and their priority.
    
  313.   const workInProgressRoot = getWorkInProgressRoot();
    
  314.   const workInProgressRootRenderLanes = getWorkInProgressRootRenderLanes();
    
  315.   const nextLanes = getNextLanes(
    
  316.     root,
    
  317.     root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
    
  318.   );
    
  319. 
    
  320.   const existingCallbackNode = root.callbackNode;
    
  321.   if (
    
  322.     // Check if there's nothing to work on
    
  323.     nextLanes === NoLanes ||
    
  324.     // If this root is currently suspended and waiting for data to resolve, don't
    
  325.     // schedule a task to render it. We'll either wait for a ping, or wait to
    
  326.     // receive an update.
    
  327.     //
    
  328.     // Suspended render phase
    
  329.     (root === workInProgressRoot && isWorkLoopSuspendedOnData()) ||
    
  330.     // Suspended commit phase
    
  331.     root.cancelPendingCommit !== null
    
  332.   ) {
    
  333.     // Fast path: There's nothing to work on.
    
  334.     if (existingCallbackNode !== null) {
    
  335.       cancelCallback(existingCallbackNode);
    
  336.     }
    
  337.     root.callbackNode = null;
    
  338.     root.callbackPriority = NoLane;
    
  339.     return NoLane;
    
  340.   }
    
  341. 
    
  342.   // Schedule a new callback in the host environment.
    
  343.   if (includesSyncLane(nextLanes)) {
    
  344.     // Synchronous work is always flushed at the end of the microtask, so we
    
  345.     // don't need to schedule an additional task.
    
  346.     if (existingCallbackNode !== null) {
    
  347.       cancelCallback(existingCallbackNode);
    
  348.     }
    
  349.     root.callbackPriority = SyncLane;
    
  350.     root.callbackNode = null;
    
  351.     return SyncLane;
    
  352.   } else {
    
  353.     // We use the highest priority lane to represent the priority of the callback.
    
  354.     const existingCallbackPriority = root.callbackPriority;
    
  355.     const newCallbackPriority = getHighestPriorityLane(nextLanes);
    
  356. 
    
  357.     if (
    
  358.       newCallbackPriority === existingCallbackPriority &&
    
  359.       // Special case related to `act`. If the currently scheduled task is a
    
  360.       // Scheduler task, rather than an `act` task, cancel it and re-schedule
    
  361.       // on the `act` queue.
    
  362.       !(
    
  363.         __DEV__ &&
    
  364.         ReactCurrentActQueue.current !== null &&
    
  365.         existingCallbackNode !== fakeActCallbackNode
    
  366.       )
    
  367.     ) {
    
  368.       // The priority hasn't changed. We can reuse the existing task.
    
  369.       return newCallbackPriority;
    
  370.     } else {
    
  371.       // Cancel the existing callback. We'll schedule a new one below.
    
  372.       cancelCallback(existingCallbackNode);
    
  373.     }
    
  374. 
    
  375.     let schedulerPriorityLevel;
    
  376.     switch (lanesToEventPriority(nextLanes)) {
    
  377.       case DiscreteEventPriority:
    
  378.         schedulerPriorityLevel = ImmediateSchedulerPriority;
    
  379.         break;
    
  380.       case ContinuousEventPriority:
    
  381.         schedulerPriorityLevel = UserBlockingSchedulerPriority;
    
  382.         break;
    
  383.       case DefaultEventPriority:
    
  384.         schedulerPriorityLevel = NormalSchedulerPriority;
    
  385.         break;
    
  386.       case IdleEventPriority:
    
  387.         schedulerPriorityLevel = IdleSchedulerPriority;
    
  388.         break;
    
  389.       default:
    
  390.         schedulerPriorityLevel = NormalSchedulerPriority;
    
  391.         break;
    
  392.     }
    
  393. 
    
  394.     const newCallbackNode = scheduleCallback(
    
  395.       schedulerPriorityLevel,
    
  396.       performConcurrentWorkOnRoot.bind(null, root),
    
  397.     );
    
  398. 
    
  399.     root.callbackPriority = newCallbackPriority;
    
  400.     root.callbackNode = newCallbackNode;
    
  401.     return newCallbackPriority;
    
  402.   }
    
  403. }
    
  404. 
    
  405. export type RenderTaskFn = (didTimeout: boolean) => RenderTaskFn | null;
    
  406. 
    
  407. export function getContinuationForRoot(
    
  408.   root: FiberRoot,
    
  409.   originalCallbackNode: mixed,
    
  410. ): RenderTaskFn | null {
    
  411.   // This is called at the end of `performConcurrentWorkOnRoot` to determine
    
  412.   // if we need to schedule a continuation task.
    
  413.   //
    
  414.   // Usually `scheduleTaskForRootDuringMicrotask` only runs inside a microtask;
    
  415.   // however, since most of the logic for determining if we need a continuation
    
  416.   // versus a new task is the same, we cheat a bit and call it here. This is
    
  417.   // only safe to do because we know we're at the end of the browser task.
    
  418.   // So although it's not an actual microtask, it might as well be.
    
  419.   scheduleTaskForRootDuringMicrotask(root, now());
    
  420.   if (root.callbackNode === originalCallbackNode) {
    
  421.     // The task node scheduled for this root is the same one that's
    
  422.     // currently executed. Need to return a continuation.
    
  423.     return performConcurrentWorkOnRoot.bind(null, root);
    
  424.   }
    
  425.   return null;
    
  426. }
    
  427. 
    
  428. const fakeActCallbackNode = {};
    
  429. 
    
  430. function scheduleCallback(
    
  431.   priorityLevel: PriorityLevel,
    
  432.   callback: RenderTaskFn,
    
  433. ) {
    
  434.   if (__DEV__ && ReactCurrentActQueue.current !== null) {
    
  435.     // Special case: We're inside an `act` scope (a testing utility).
    
  436.     // Instead of scheduling work in the host environment, add it to a
    
  437.     // fake internal queue that's managed by the `act` implementation.
    
  438.     ReactCurrentActQueue.current.push(callback);
    
  439.     return fakeActCallbackNode;
    
  440.   } else {
    
  441.     return Scheduler_scheduleCallback(priorityLevel, callback);
    
  442.   }
    
  443. }
    
  444. 
    
  445. function cancelCallback(callbackNode: mixed) {
    
  446.   if (__DEV__ && callbackNode === fakeActCallbackNode) {
    
  447.     // Special `act` case: check if this is the fake callback node used by
    
  448.     // the `act` implementation.
    
  449.   } else if (callbackNode !== null) {
    
  450.     Scheduler_cancelCallback(callbackNode);
    
  451.   }
    
  452. }
    
  453. 
    
  454. function scheduleImmediateTask(cb: () => mixed) {
    
  455.   if (__DEV__ && ReactCurrentActQueue.current !== null) {
    
  456.     // Special case: Inside an `act` scope, we push microtasks to the fake `act`
    
  457.     // callback queue. This is because we currently support calling `act`
    
  458.     // without awaiting the result. The plan is to deprecate that, and require
    
  459.     // that you always await the result so that the microtasks have a chance to
    
  460.     // run. But it hasn't happened yet.
    
  461.     ReactCurrentActQueue.current.push(() => {
    
  462.       cb();
    
  463.       return null;
    
  464.     });
    
  465.   }
    
  466. 
    
  467.   // TODO: Can we land supportsMicrotasks? Which environments don't support it?
    
  468.   // Alternatively, can we move this check to the host config?
    
  469.   if (supportsMicrotasks) {
    
  470.     scheduleMicrotask(() => {
    
  471.       // In Safari, appending an iframe forces microtasks to run.
    
  472.       // https://github.com/facebook/react/issues/22459
    
  473.       // We don't support running callbacks in the middle of render
    
  474.       // or commit so we need to check against that.
    
  475.       const executionContext = getExecutionContext();
    
  476.       if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
    
  477.         // Note that this would still prematurely flush the callbacks
    
  478.         // if this happens outside render or commit phase (e.g. in an event).
    
  479. 
    
  480.         // Intentionally using a macrotask instead of a microtask here. This is
    
  481.         // wrong semantically but it prevents an infinite loop. The bug is
    
  482.         // Safari's, not ours, so we just do our best to not crash even though
    
  483.         // the behavior isn't completely correct.
    
  484.         Scheduler_scheduleCallback(ImmediateSchedulerPriority, cb);
    
  485.         return;
    
  486.       }
    
  487.       cb();
    
  488.     });
    
  489.   } else {
    
  490.     // If microtasks are not supported, use Scheduler.
    
  491.     Scheduler_scheduleCallback(ImmediateSchedulerPriority, cb);
    
  492.   }
    
  493. }
    
  494. 
    
  495. export function requestTransitionLane(): Lane {
    
  496.   // The algorithm for assigning an update to a lane should be stable for all
    
  497.   // updates at the same priority within the same event. To do this, the
    
  498.   // inputs to the algorithm must be the same.
    
  499.   //
    
  500.   // The trick we use is to cache the first of each of these inputs within an
    
  501.   // event. Then reset the cached values once we can be sure the event is
    
  502.   // over. Our heuristic for that is whenever we enter a concurrent work loop.
    
  503.   if (currentEventTransitionLane === NoLane) {
    
  504.     // All transitions within the same event are assigned the same lane.
    
  505.     currentEventTransitionLane = claimNextTransitionLane();
    
  506.   }
    
  507.   return currentEventTransitionLane;
    
  508. }