exportfunctionensureRootIsScheduled(root: FiberRoot): void { // This function is called whenever a root receives an update. It does two // things 1) it ensures the root is in the root schedule, and 2) it ensures // there's a pending microtask to process the root schedule. // // Most of the actual scheduling logic does not happen until // `scheduleTaskForRootDuringMicrotask` runs.
// Add the root to the schedule if (root === lastScheduledRoot || root.next !== null) { // Fast path. This root is already scheduled. } else { if (lastScheduledRoot === null) { firstScheduledRoot = lastScheduledRoot = root; } else { lastScheduledRoot.next = root; lastScheduledRoot = root; } }
// Any time a root received an update, we set this to true until the next time // we process the schedule. If it's false, then we can quickly exit flushSync // without consulting the schedule. mightHavePendingSyncWork = true;
if (!didScheduleMicrotask) { didScheduleMicrotask = true; scheduleImmediateRootScheduleTask(); } }
functionscheduleImmediateRootScheduleTask() { // TODO: Can we land supportsMicrotasks? Which environments don't support it? // Alternatively, can we move this check to the host config? if (supportsMicrotasks) { scheduleMicrotask(() => { // In Safari, appending an iframe forces microtasks to run. // https://github.com/facebook/react/issues/22459 // We don't support running callbacks in the middle of render // or commit so we need to check against that. const executionContext = getExecutionContext(); if ((executionContext & (RenderContext | CommitContext)) !== NoContext) { // Note that this would still prematurely flush the callbacks // if this happens outside render or commit phase (e.g. in an event).
// Intentionally using a macrotask instead of a microtask here. This is // wrong semantically but it prevents an infinite loop. The bug is // Safari's, not ours, so we just do our best to not crash even though // the behavior isn't completely correct. Scheduler_scheduleCallback( ImmediateSchedulerPriority, processRootScheduleInImmediateTask, ); return; } processRootScheduleInMicrotask(); }); } else { // If microtasks are not supported, use Scheduler. Scheduler_scheduleCallback( ImmediateSchedulerPriority, processRootScheduleInImmediateTask, ); } }
// We'll recompute this as we iterate through all the roots and schedule them. mightHavePendingSyncWork = false;
let syncTransitionLanes = NoLanes; const currentTime = now();
let prev = null; let root = firstScheduledRoot; while (root !== null) { const next = root.next; const nextLanes = scheduleTaskForRootDuringMicrotask(root, currentTime); if (nextLanes === NoLane) { // This root has no more pending work. Remove it from the schedule. To // guard against subtle reentrancy bugs, this microtask is the only place // we do this — you can add roots to the schedule whenever, but you can // only remove them here.
// Null this out so we know it's been removed from the schedule. root.next = null; if (prev === null) { // This is the new head of the list firstScheduledRoot = next; } else { prev.next = next; } if (next === null) { // This is the new tail of the list lastScheduledRoot = prev; } } else { // This root still has work. Keep it in the list. prev = root;
// This is a fast-path optimization to early exit from // flushSyncWorkOnAllRoots if we can be certain that there is no remaining // synchronous work to perform. Set this to true if there might be sync // work left. if ( // Common case: we're not treating any extra lanes as synchronous, so we // can just check if the next lanes are sync. includesSyncLane(nextLanes) ) { mightHavePendingSyncWork = true; } } root = next; }
// At the end of the microtask, flush any pending synchronous work. This has // to come at the end, because it does actual rendering work that might throw. flushSyncWorkAcrossRoots_impl(syncTransitionLanes, false); }