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. /* eslint-disable no-var */
    
  11. 
    
  12. import type {PriorityLevel} from '../SchedulerPriorities';
    
  13. 
    
  14. import {
    
  15.   enableSchedulerDebugging,
    
  16.   enableProfiling,
    
  17.   enableIsInputPending,
    
  18.   enableIsInputPendingContinuous,
    
  19.   frameYieldMs,
    
  20.   continuousYieldMs,
    
  21.   maxYieldMs,
    
  22. } from '../SchedulerFeatureFlags';
    
  23. 
    
  24. import {push, pop, peek} from '../SchedulerMinHeap';
    
  25. 
    
  26. // TODO: Use symbols?
    
  27. import {
    
  28.   ImmediatePriority,
    
  29.   UserBlockingPriority,
    
  30.   NormalPriority,
    
  31.   LowPriority,
    
  32.   IdlePriority,
    
  33. } from '../SchedulerPriorities';
    
  34. import {
    
  35.   markTaskRun,
    
  36.   markTaskYield,
    
  37.   markTaskCompleted,
    
  38.   markTaskCanceled,
    
  39.   markTaskErrored,
    
  40.   markSchedulerSuspended,
    
  41.   markSchedulerUnsuspended,
    
  42.   markTaskStart,
    
  43.   stopLoggingProfilingEvents,
    
  44.   startLoggingProfilingEvents,
    
  45. } from '../SchedulerProfiling';
    
  46. 
    
  47. export type Callback = boolean => ?Callback;
    
  48. 
    
  49. export opaque type Task = {
    
  50.   id: number,
    
  51.   callback: Callback | null,
    
  52.   priorityLevel: PriorityLevel,
    
  53.   startTime: number,
    
  54.   expirationTime: number,
    
  55.   sortIndex: number,
    
  56.   isQueued?: boolean,
    
  57. };
    
  58. 
    
  59. let getCurrentTime: () => number | DOMHighResTimeStamp;
    
  60. const hasPerformanceNow =
    
  61.   // $FlowFixMe[method-unbinding]
    
  62.   typeof performance === 'object' && typeof performance.now === 'function';
    
  63. 
    
  64. if (hasPerformanceNow) {
    
  65.   const localPerformance = performance;
    
  66.   getCurrentTime = () => localPerformance.now();
    
  67. } else {
    
  68.   const localDate = Date;
    
  69.   const initialTime = localDate.now();
    
  70.   getCurrentTime = () => localDate.now() - initialTime;
    
  71. }
    
  72. 
    
  73. // Max 31 bit integer. The max integer size in V8 for 32-bit systems.
    
  74. // Math.pow(2, 30) - 1
    
  75. // 0b111111111111111111111111111111
    
  76. var maxSigned31BitInt = 1073741823;
    
  77. 
    
  78. // Times out immediately
    
  79. var IMMEDIATE_PRIORITY_TIMEOUT = -1;
    
  80. // Eventually times out
    
  81. var USER_BLOCKING_PRIORITY_TIMEOUT = 250;
    
  82. var NORMAL_PRIORITY_TIMEOUT = 5000;
    
  83. var LOW_PRIORITY_TIMEOUT = 10000;
    
  84. // Never times out
    
  85. var IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt;
    
  86. 
    
  87. // Tasks are stored on a min heap
    
  88. var taskQueue: Array<Task> = [];
    
  89. var timerQueue: Array<Task> = [];
    
  90. 
    
  91. // Incrementing id counter. Used to maintain insertion order.
    
  92. var taskIdCounter = 1;
    
  93. 
    
  94. // Pausing the scheduler is useful for debugging.
    
  95. var isSchedulerPaused = false;
    
  96. 
    
  97. var currentTask = null;
    
  98. var currentPriorityLevel = NormalPriority;
    
  99. 
    
  100. // This is set while performing work, to prevent re-entrance.
    
  101. var isPerformingWork = false;
    
  102. 
    
  103. var isHostCallbackScheduled = false;
    
  104. var isHostTimeoutScheduled = false;
    
  105. 
    
  106. // Capture local references to native APIs, in case a polyfill overrides them.
    
  107. const localSetTimeout = typeof setTimeout === 'function' ? setTimeout : null;
    
  108. const localClearTimeout =
    
  109.   typeof clearTimeout === 'function' ? clearTimeout : null;
    
  110. const localSetImmediate =
    
  111.   typeof setImmediate !== 'undefined' ? setImmediate : null; // IE and Node.js + jsdom
    
  112. 
    
  113. const isInputPending =
    
  114.   typeof navigator !== 'undefined' &&
    
  115.   // $FlowFixMe[prop-missing]
    
  116.   navigator.scheduling !== undefined &&
    
  117.   // $FlowFixMe[incompatible-type]
    
  118.   navigator.scheduling.isInputPending !== undefined
    
  119.     ? navigator.scheduling.isInputPending.bind(navigator.scheduling)
    
  120.     : null;
    
  121. 
    
  122. const continuousOptions = {includeContinuous: enableIsInputPendingContinuous};
    
  123. 
    
  124. function advanceTimers(currentTime: number) {
    
  125.   // Check for tasks that are no longer delayed and add them to the queue.
    
  126.   let timer = peek(timerQueue);
    
  127.   while (timer !== null) {
    
  128.     if (timer.callback === null) {
    
  129.       // Timer was cancelled.
    
  130.       pop(timerQueue);
    
  131.     } else if (timer.startTime <= currentTime) {
    
  132.       // Timer fired. Transfer to the task queue.
    
  133.       pop(timerQueue);
    
  134.       timer.sortIndex = timer.expirationTime;
    
  135.       push(taskQueue, timer);
    
  136.       if (enableProfiling) {
    
  137.         markTaskStart(timer, currentTime);
    
  138.         timer.isQueued = true;
    
  139.       }
    
  140.     } else {
    
  141.       // Remaining timers are pending.
    
  142.       return;
    
  143.     }
    
  144.     timer = peek(timerQueue);
    
  145.   }
    
  146. }
    
  147. 
    
  148. function handleTimeout(currentTime: number) {
    
  149.   isHostTimeoutScheduled = false;
    
  150.   advanceTimers(currentTime);
    
  151. 
    
  152.   if (!isHostCallbackScheduled) {
    
  153.     if (peek(taskQueue) !== null) {
    
  154.       isHostCallbackScheduled = true;
    
  155.       requestHostCallback();
    
  156.     } else {
    
  157.       const firstTimer = peek(timerQueue);
    
  158.       if (firstTimer !== null) {
    
  159.         requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
    
  160.       }
    
  161.     }
    
  162.   }
    
  163. }
    
  164. 
    
  165. function flushWork(initialTime: number) {
    
  166.   if (enableProfiling) {
    
  167.     markSchedulerUnsuspended(initialTime);
    
  168.   }
    
  169. 
    
  170.   // We'll need a host callback the next time work is scheduled.
    
  171.   isHostCallbackScheduled = false;
    
  172.   if (isHostTimeoutScheduled) {
    
  173.     // We scheduled a timeout but it's no longer needed. Cancel it.
    
  174.     isHostTimeoutScheduled = false;
    
  175.     cancelHostTimeout();
    
  176.   }
    
  177. 
    
  178.   isPerformingWork = true;
    
  179.   const previousPriorityLevel = currentPriorityLevel;
    
  180.   try {
    
  181.     if (enableProfiling) {
    
  182.       try {
    
  183.         return workLoop(initialTime);
    
  184.       } catch (error) {
    
  185.         if (currentTask !== null) {
    
  186.           const currentTime = getCurrentTime();
    
  187.           // $FlowFixMe[incompatible-call] found when upgrading Flow
    
  188.           markTaskErrored(currentTask, currentTime);
    
  189.           // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  190.           currentTask.isQueued = false;
    
  191.         }
    
  192.         throw error;
    
  193.       }
    
  194.     } else {
    
  195.       // No catch in prod code path.
    
  196.       return workLoop(initialTime);
    
  197.     }
    
  198.   } finally {
    
  199.     currentTask = null;
    
  200.     currentPriorityLevel = previousPriorityLevel;
    
  201.     isPerformingWork = false;
    
  202.     if (enableProfiling) {
    
  203.       const currentTime = getCurrentTime();
    
  204.       markSchedulerSuspended(currentTime);
    
  205.     }
    
  206.   }
    
  207. }
    
  208. 
    
  209. function workLoop(initialTime: number) {
    
  210.   let currentTime = initialTime;
    
  211.   advanceTimers(currentTime);
    
  212.   currentTask = peek(taskQueue);
    
  213.   while (
    
  214.     currentTask !== null &&
    
  215.     !(enableSchedulerDebugging && isSchedulerPaused)
    
  216.   ) {
    
  217.     if (currentTask.expirationTime > currentTime && shouldYieldToHost()) {
    
  218.       // This currentTask hasn't expired, and we've reached the deadline.
    
  219.       break;
    
  220.     }
    
  221.     // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  222.     const callback = currentTask.callback;
    
  223.     if (typeof callback === 'function') {
    
  224.       // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  225.       currentTask.callback = null;
    
  226.       // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  227.       currentPriorityLevel = currentTask.priorityLevel;
    
  228.       // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  229.       const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
    
  230.       if (enableProfiling) {
    
  231.         // $FlowFixMe[incompatible-call] found when upgrading Flow
    
  232.         markTaskRun(currentTask, currentTime);
    
  233.       }
    
  234.       const continuationCallback = callback(didUserCallbackTimeout);
    
  235.       currentTime = getCurrentTime();
    
  236.       if (typeof continuationCallback === 'function') {
    
  237.         // If a continuation is returned, immediately yield to the main thread
    
  238.         // regardless of how much time is left in the current time slice.
    
  239.         // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  240.         currentTask.callback = continuationCallback;
    
  241.         if (enableProfiling) {
    
  242.           // $FlowFixMe[incompatible-call] found when upgrading Flow
    
  243.           markTaskYield(currentTask, currentTime);
    
  244.         }
    
  245.         advanceTimers(currentTime);
    
  246.         return true;
    
  247.       } else {
    
  248.         if (enableProfiling) {
    
  249.           // $FlowFixMe[incompatible-call] found when upgrading Flow
    
  250.           markTaskCompleted(currentTask, currentTime);
    
  251.           // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  252.           currentTask.isQueued = false;
    
  253.         }
    
  254.         if (currentTask === peek(taskQueue)) {
    
  255.           pop(taskQueue);
    
  256.         }
    
  257.         advanceTimers(currentTime);
    
  258.       }
    
  259.     } else {
    
  260.       pop(taskQueue);
    
  261.     }
    
  262.     currentTask = peek(taskQueue);
    
  263.   }
    
  264.   // Return whether there's additional work
    
  265.   if (currentTask !== null) {
    
  266.     return true;
    
  267.   } else {
    
  268.     const firstTimer = peek(timerQueue);
    
  269.     if (firstTimer !== null) {
    
  270.       requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
    
  271.     }
    
  272.     return false;
    
  273.   }
    
  274. }
    
  275. 
    
  276. function unstable_runWithPriority<T>(
    
  277.   priorityLevel: PriorityLevel,
    
  278.   eventHandler: () => T,
    
  279. ): T {
    
  280.   switch (priorityLevel) {
    
  281.     case ImmediatePriority:
    
  282.     case UserBlockingPriority:
    
  283.     case NormalPriority:
    
  284.     case LowPriority:
    
  285.     case IdlePriority:
    
  286.       break;
    
  287.     default:
    
  288.       priorityLevel = NormalPriority;
    
  289.   }
    
  290. 
    
  291.   var previousPriorityLevel = currentPriorityLevel;
    
  292.   currentPriorityLevel = priorityLevel;
    
  293. 
    
  294.   try {
    
  295.     return eventHandler();
    
  296.   } finally {
    
  297.     currentPriorityLevel = previousPriorityLevel;
    
  298.   }
    
  299. }
    
  300. 
    
  301. function unstable_next<T>(eventHandler: () => T): T {
    
  302.   var priorityLevel;
    
  303.   switch (currentPriorityLevel) {
    
  304.     case ImmediatePriority:
    
  305.     case UserBlockingPriority:
    
  306.     case NormalPriority:
    
  307.       // Shift down to normal priority
    
  308.       priorityLevel = NormalPriority;
    
  309.       break;
    
  310.     default:
    
  311.       // Anything lower than normal priority should remain at the current level.
    
  312.       priorityLevel = currentPriorityLevel;
    
  313.       break;
    
  314.   }
    
  315. 
    
  316.   var previousPriorityLevel = currentPriorityLevel;
    
  317.   currentPriorityLevel = priorityLevel;
    
  318. 
    
  319.   try {
    
  320.     return eventHandler();
    
  321.   } finally {
    
  322.     currentPriorityLevel = previousPriorityLevel;
    
  323.   }
    
  324. }
    
  325. 
    
  326. function unstable_wrapCallback<T: (...Array<mixed>) => mixed>(callback: T): T {
    
  327.   var parentPriorityLevel = currentPriorityLevel;
    
  328.   // $FlowFixMe[incompatible-return]
    
  329.   // $FlowFixMe[missing-this-annot]
    
  330.   return function () {
    
  331.     // This is a fork of runWithPriority, inlined for performance.
    
  332.     var previousPriorityLevel = currentPriorityLevel;
    
  333.     currentPriorityLevel = parentPriorityLevel;
    
  334. 
    
  335.     try {
    
  336.       return callback.apply(this, arguments);
    
  337.     } finally {
    
  338.       currentPriorityLevel = previousPriorityLevel;
    
  339.     }
    
  340.   };
    
  341. }
    
  342. 
    
  343. function unstable_scheduleCallback(
    
  344.   priorityLevel: PriorityLevel,
    
  345.   callback: Callback,
    
  346.   options?: {delay: number},
    
  347. ): Task {
    
  348.   var currentTime = getCurrentTime();
    
  349. 
    
  350.   var startTime;
    
  351.   if (typeof options === 'object' && options !== null) {
    
  352.     var delay = options.delay;
    
  353.     if (typeof delay === 'number' && delay > 0) {
    
  354.       startTime = currentTime + delay;
    
  355.     } else {
    
  356.       startTime = currentTime;
    
  357.     }
    
  358.   } else {
    
  359.     startTime = currentTime;
    
  360.   }
    
  361. 
    
  362.   var timeout;
    
  363.   switch (priorityLevel) {
    
  364.     case ImmediatePriority:
    
  365.       timeout = IMMEDIATE_PRIORITY_TIMEOUT;
    
  366.       break;
    
  367.     case UserBlockingPriority:
    
  368.       timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
    
  369.       break;
    
  370.     case IdlePriority:
    
  371.       timeout = IDLE_PRIORITY_TIMEOUT;
    
  372.       break;
    
  373.     case LowPriority:
    
  374.       timeout = LOW_PRIORITY_TIMEOUT;
    
  375.       break;
    
  376.     case NormalPriority:
    
  377.     default:
    
  378.       timeout = NORMAL_PRIORITY_TIMEOUT;
    
  379.       break;
    
  380.   }
    
  381. 
    
  382.   var expirationTime = startTime + timeout;
    
  383. 
    
  384.   var newTask: Task = {
    
  385.     id: taskIdCounter++,
    
  386.     callback,
    
  387.     priorityLevel,
    
  388.     startTime,
    
  389.     expirationTime,
    
  390.     sortIndex: -1,
    
  391.   };
    
  392.   if (enableProfiling) {
    
  393.     newTask.isQueued = false;
    
  394.   }
    
  395. 
    
  396.   if (startTime > currentTime) {
    
  397.     // This is a delayed task.
    
  398.     newTask.sortIndex = startTime;
    
  399.     push(timerQueue, newTask);
    
  400.     if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
    
  401.       // All tasks are delayed, and this is the task with the earliest delay.
    
  402.       if (isHostTimeoutScheduled) {
    
  403.         // Cancel an existing timeout.
    
  404.         cancelHostTimeout();
    
  405.       } else {
    
  406.         isHostTimeoutScheduled = true;
    
  407.       }
    
  408.       // Schedule a timeout.
    
  409.       requestHostTimeout(handleTimeout, startTime - currentTime);
    
  410.     }
    
  411.   } else {
    
  412.     newTask.sortIndex = expirationTime;
    
  413.     push(taskQueue, newTask);
    
  414.     if (enableProfiling) {
    
  415.       markTaskStart(newTask, currentTime);
    
  416.       newTask.isQueued = true;
    
  417.     }
    
  418.     // Schedule a host callback, if needed. If we're already performing work,
    
  419.     // wait until the next time we yield.
    
  420.     if (!isHostCallbackScheduled && !isPerformingWork) {
    
  421.       isHostCallbackScheduled = true;
    
  422.       requestHostCallback();
    
  423.     }
    
  424.   }
    
  425. 
    
  426.   return newTask;
    
  427. }
    
  428. 
    
  429. function unstable_pauseExecution() {
    
  430.   isSchedulerPaused = true;
    
  431. }
    
  432. 
    
  433. function unstable_continueExecution() {
    
  434.   isSchedulerPaused = false;
    
  435.   if (!isHostCallbackScheduled && !isPerformingWork) {
    
  436.     isHostCallbackScheduled = true;
    
  437.     requestHostCallback();
    
  438.   }
    
  439. }
    
  440. 
    
  441. function unstable_getFirstCallbackNode(): Task | null {
    
  442.   return peek(taskQueue);
    
  443. }
    
  444. 
    
  445. function unstable_cancelCallback(task: Task) {
    
  446.   if (enableProfiling) {
    
  447.     if (task.isQueued) {
    
  448.       const currentTime = getCurrentTime();
    
  449.       markTaskCanceled(task, currentTime);
    
  450.       task.isQueued = false;
    
  451.     }
    
  452.   }
    
  453. 
    
  454.   // Null out the callback to indicate the task has been canceled. (Can't
    
  455.   // remove from the queue because you can't remove arbitrary nodes from an
    
  456.   // array based heap, only the first one.)
    
  457.   task.callback = null;
    
  458. }
    
  459. 
    
  460. function unstable_getCurrentPriorityLevel(): PriorityLevel {
    
  461.   return currentPriorityLevel;
    
  462. }
    
  463. 
    
  464. let isMessageLoopRunning = false;
    
  465. let taskTimeoutID: TimeoutID = (-1: any);
    
  466. 
    
  467. // Scheduler periodically yields in case there is other work on the main
    
  468. // thread, like user events. By default, it yields multiple times per frame.
    
  469. // It does not attempt to align with frame boundaries, since most tasks don't
    
  470. // need to be frame aligned; for those that do, use requestAnimationFrame.
    
  471. let frameInterval = frameYieldMs;
    
  472. const continuousInputInterval = continuousYieldMs;
    
  473. const maxInterval = maxYieldMs;
    
  474. let startTime = -1;
    
  475. 
    
  476. let needsPaint = false;
    
  477. 
    
  478. function shouldYieldToHost(): boolean {
    
  479.   const timeElapsed = getCurrentTime() - startTime;
    
  480.   if (timeElapsed < frameInterval) {
    
  481.     // The main thread has only been blocked for a really short amount of time;
    
  482.     // smaller than a single frame. Don't yield yet.
    
  483.     return false;
    
  484.   }
    
  485. 
    
  486.   // The main thread has been blocked for a non-negligible amount of time. We
    
  487.   // may want to yield control of the main thread, so the browser can perform
    
  488.   // high priority tasks. The main ones are painting and user input. If there's
    
  489.   // a pending paint or a pending input, then we should yield. But if there's
    
  490.   // neither, then we can yield less often while remaining responsive. We'll
    
  491.   // eventually yield regardless, since there could be a pending paint that
    
  492.   // wasn't accompanied by a call to `requestPaint`, or other main thread tasks
    
  493.   // like network events.
    
  494.   if (enableIsInputPending) {
    
  495.     if (needsPaint) {
    
  496.       // There's a pending paint (signaled by `requestPaint`). Yield now.
    
  497.       return true;
    
  498.     }
    
  499.     if (timeElapsed < continuousInputInterval) {
    
  500.       // We haven't blocked the thread for that long. Only yield if there's a
    
  501.       // pending discrete input (e.g. click). It's OK if there's pending
    
  502.       // continuous input (e.g. mouseover).
    
  503.       if (isInputPending !== null) {
    
  504.         return isInputPending();
    
  505.       }
    
  506.     } else if (timeElapsed < maxInterval) {
    
  507.       // Yield if there's either a pending discrete or continuous input.
    
  508.       if (isInputPending !== null) {
    
  509.         return isInputPending(continuousOptions);
    
  510.       }
    
  511.     } else {
    
  512.       // We've blocked the thread for a long time. Even if there's no pending
    
  513.       // input, there may be some other scheduled work that we don't know about,
    
  514.       // like a network event. Yield now.
    
  515.       return true;
    
  516.     }
    
  517.   }
    
  518. 
    
  519.   // `isInputPending` isn't available. Yield now.
    
  520.   return true;
    
  521. }
    
  522. 
    
  523. function requestPaint() {
    
  524.   if (
    
  525.     enableIsInputPending &&
    
  526.     navigator !== undefined &&
    
  527.     // $FlowFixMe[prop-missing]
    
  528.     navigator.scheduling !== undefined &&
    
  529.     // $FlowFixMe[incompatible-type]
    
  530.     navigator.scheduling.isInputPending !== undefined
    
  531.   ) {
    
  532.     needsPaint = true;
    
  533.   }
    
  534. 
    
  535.   // Since we yield every frame regardless, `requestPaint` has no effect.
    
  536. }
    
  537. 
    
  538. function forceFrameRate(fps: number) {
    
  539.   if (fps < 0 || fps > 125) {
    
  540.     // Using console['error'] to evade Babel and ESLint
    
  541.     console['error'](
    
  542.       'forceFrameRate takes a positive int between 0 and 125, ' +
    
  543.         'forcing frame rates higher than 125 fps is not supported',
    
  544.     );
    
  545.     return;
    
  546.   }
    
  547.   if (fps > 0) {
    
  548.     frameInterval = Math.floor(1000 / fps);
    
  549.   } else {
    
  550.     // reset the framerate
    
  551.     frameInterval = frameYieldMs;
    
  552.   }
    
  553. }
    
  554. 
    
  555. const performWorkUntilDeadline = () => {
    
  556.   if (isMessageLoopRunning) {
    
  557.     const currentTime = getCurrentTime();
    
  558.     // Keep track of the start time so we can measure how long the main thread
    
  559.     // has been blocked.
    
  560.     startTime = currentTime;
    
  561. 
    
  562.     // If a scheduler task throws, exit the current browser task so the
    
  563.     // error can be observed.
    
  564.     //
    
  565.     // Intentionally not using a try-catch, since that makes some debugging
    
  566.     // techniques harder. Instead, if `flushWork` errors, then `hasMoreWork` will
    
  567.     // remain true, and we'll continue the work loop.
    
  568.     let hasMoreWork = true;
    
  569.     try {
    
  570.       hasMoreWork = flushWork(currentTime);
    
  571.     } finally {
    
  572.       if (hasMoreWork) {
    
  573.         // If there's more work, schedule the next message event at the end
    
  574.         // of the preceding one.
    
  575.         schedulePerformWorkUntilDeadline();
    
  576.       } else {
    
  577.         isMessageLoopRunning = false;
    
  578.       }
    
  579.     }
    
  580.   }
    
  581.   // Yielding to the browser will give it a chance to paint, so we can
    
  582.   // reset this.
    
  583.   needsPaint = false;
    
  584. };
    
  585. 
    
  586. let schedulePerformWorkUntilDeadline;
    
  587. if (typeof localSetImmediate === 'function') {
    
  588.   // Node.js and old IE.
    
  589.   // There's a few reasons for why we prefer setImmediate.
    
  590.   //
    
  591.   // Unlike MessageChannel, it doesn't prevent a Node.js process from exiting.
    
  592.   // (Even though this is a DOM fork of the Scheduler, you could get here
    
  593.   // with a mix of Node.js 15+, which has a MessageChannel, and jsdom.)
    
  594.   // https://github.com/facebook/react/issues/20756
    
  595.   //
    
  596.   // But also, it runs earlier which is the semantic we want.
    
  597.   // If other browsers ever implement it, it's better to use it.
    
  598.   // Although both of these would be inferior to native scheduling.
    
  599.   schedulePerformWorkUntilDeadline = () => {
    
  600.     localSetImmediate(performWorkUntilDeadline);
    
  601.   };
    
  602. } else if (typeof MessageChannel !== 'undefined') {
    
  603.   // DOM and Worker environments.
    
  604.   // We prefer MessageChannel because of the 4ms setTimeout clamping.
    
  605.   const channel = new MessageChannel();
    
  606.   const port = channel.port2;
    
  607.   channel.port1.onmessage = performWorkUntilDeadline;
    
  608.   schedulePerformWorkUntilDeadline = () => {
    
  609.     port.postMessage(null);
    
  610.   };
    
  611. } else {
    
  612.   // We should only fallback here in non-browser environments.
    
  613.   schedulePerformWorkUntilDeadline = () => {
    
  614.     // $FlowFixMe[not-a-function] nullable value
    
  615.     localSetTimeout(performWorkUntilDeadline, 0);
    
  616.   };
    
  617. }
    
  618. 
    
  619. function requestHostCallback() {
    
  620.   if (!isMessageLoopRunning) {
    
  621.     isMessageLoopRunning = true;
    
  622.     schedulePerformWorkUntilDeadline();
    
  623.   }
    
  624. }
    
  625. 
    
  626. function requestHostTimeout(
    
  627.   callback: (currentTime: number) => void,
    
  628.   ms: number,
    
  629. ) {
    
  630.   // $FlowFixMe[not-a-function] nullable value
    
  631.   taskTimeoutID = localSetTimeout(() => {
    
  632.     callback(getCurrentTime());
    
  633.   }, ms);
    
  634. }
    
  635. 
    
  636. function cancelHostTimeout() {
    
  637.   // $FlowFixMe[not-a-function] nullable value
    
  638.   localClearTimeout(taskTimeoutID);
    
  639.   taskTimeoutID = ((-1: any): TimeoutID);
    
  640. }
    
  641. 
    
  642. export {
    
  643.   ImmediatePriority as unstable_ImmediatePriority,
    
  644.   UserBlockingPriority as unstable_UserBlockingPriority,
    
  645.   NormalPriority as unstable_NormalPriority,
    
  646.   IdlePriority as unstable_IdlePriority,
    
  647.   LowPriority as unstable_LowPriority,
    
  648.   unstable_runWithPriority,
    
  649.   unstable_next,
    
  650.   unstable_scheduleCallback,
    
  651.   unstable_cancelCallback,
    
  652.   unstable_wrapCallback,
    
  653.   unstable_getCurrentPriorityLevel,
    
  654.   shouldYieldToHost as unstable_shouldYield,
    
  655.   requestPaint as unstable_requestPaint,
    
  656.   unstable_continueExecution,
    
  657.   unstable_pauseExecution,
    
  658.   unstable_getFirstCallbackNode,
    
  659.   getCurrentTime as unstable_now,
    
  660.   forceFrameRate as unstable_forceFrameRate,
    
  661. };
    
  662. 
    
  663. export const unstable_Profiling: {
    
  664.   startLoggingProfilingEvents(): void,
    
  665.   stopLoggingProfilingEvents(): ArrayBuffer | null,
    
  666. } | null = enableProfiling
    
  667.   ? {
    
  668.       startLoggingProfilingEvents,
    
  669.       stopLoggingProfilingEvents,
    
  670.     }
    
  671.   : null;