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 {PriorityLevel} from '../SchedulerPriorities';
    
  11. 
    
  12. declare class TaskController {
    
  13.   constructor(options?: {priority?: string}): TaskController;
    
  14.   signal: mixed;
    
  15.   abort(): void;
    
  16. }
    
  17. 
    
  18. type PostTaskPriorityLevel = 'user-blocking' | 'user-visible' | 'background';
    
  19. 
    
  20. type CallbackNode = {
    
  21.   _controller: TaskController,
    
  22. };
    
  23. 
    
  24. import {
    
  25.   ImmediatePriority,
    
  26.   UserBlockingPriority,
    
  27.   NormalPriority,
    
  28.   LowPriority,
    
  29.   IdlePriority,
    
  30. } from '../SchedulerPriorities';
    
  31. 
    
  32. export {
    
  33.   ImmediatePriority as unstable_ImmediatePriority,
    
  34.   UserBlockingPriority as unstable_UserBlockingPriority,
    
  35.   NormalPriority as unstable_NormalPriority,
    
  36.   IdlePriority as unstable_IdlePriority,
    
  37.   LowPriority as unstable_LowPriority,
    
  38. };
    
  39. 
    
  40. // Capture local references to native APIs, in case a polyfill overrides them.
    
  41. const perf = window.performance;
    
  42. const setTimeout = window.setTimeout;
    
  43. 
    
  44. // Use experimental Chrome Scheduler postTask API.
    
  45. const scheduler = global.scheduler;
    
  46. 
    
  47. const getCurrentTime: () => DOMHighResTimeStamp = perf.now.bind(perf);
    
  48. 
    
  49. export const unstable_now = getCurrentTime;
    
  50. 
    
  51. // Scheduler periodically yields in case there is other work on the main
    
  52. // thread, like user events. By default, it yields multiple times per frame.
    
  53. // It does not attempt to align with frame boundaries, since most tasks don't
    
  54. // need to be frame aligned; for those that do, use requestAnimationFrame.
    
  55. const yieldInterval = 5;
    
  56. let deadline = 0;
    
  57. 
    
  58. let currentPriorityLevel_DEPRECATED = NormalPriority;
    
  59. 
    
  60. // `isInputPending` is not available. Since we have no way of knowing if
    
  61. // there's pending input, always yield at the end of the frame.
    
  62. export function unstable_shouldYield(): boolean {
    
  63.   return getCurrentTime() >= deadline;
    
  64. }
    
  65. 
    
  66. export function unstable_requestPaint() {
    
  67.   // Since we yield every frame regardless, `requestPaint` has no effect.
    
  68. }
    
  69. 
    
  70. type SchedulerCallback<T> = (didTimeout_DEPRECATED: boolean) =>
    
  71.   | T
    
  72.   // May return a continuation
    
  73.   | SchedulerCallback<T>;
    
  74. 
    
  75. export function unstable_scheduleCallback<T>(
    
  76.   priorityLevel: PriorityLevel,
    
  77.   callback: SchedulerCallback<T>,
    
  78.   options?: {delay?: number},
    
  79. ): CallbackNode {
    
  80.   let postTaskPriority;
    
  81.   switch (priorityLevel) {
    
  82.     case ImmediatePriority:
    
  83.     case UserBlockingPriority:
    
  84.       postTaskPriority = 'user-blocking';
    
  85.       break;
    
  86.     case LowPriority:
    
  87.     case NormalPriority:
    
  88.       postTaskPriority = 'user-visible';
    
  89.       break;
    
  90.     case IdlePriority:
    
  91.       postTaskPriority = 'background';
    
  92.       break;
    
  93.     default:
    
  94.       postTaskPriority = 'user-visible';
    
  95.       break;
    
  96.   }
    
  97. 
    
  98.   const controller = new TaskController({priority: postTaskPriority});
    
  99.   const postTaskOptions = {
    
  100.     delay: typeof options === 'object' && options !== null ? options.delay : 0,
    
  101.     signal: controller.signal,
    
  102.   };
    
  103. 
    
  104.   const node = {
    
  105.     _controller: controller,
    
  106.   };
    
  107. 
    
  108.   scheduler
    
  109.     .postTask(
    
  110.       runTask.bind(null, priorityLevel, postTaskPriority, node, callback),
    
  111.       postTaskOptions,
    
  112.     )
    
  113.     .catch(handleAbortError);
    
  114. 
    
  115.   return node;
    
  116. }
    
  117. 
    
  118. function runTask<T>(
    
  119.   priorityLevel: PriorityLevel,
    
  120.   postTaskPriority: PostTaskPriorityLevel,
    
  121.   node: CallbackNode,
    
  122.   callback: SchedulerCallback<T>,
    
  123. ) {
    
  124.   deadline = getCurrentTime() + yieldInterval;
    
  125.   try {
    
  126.     currentPriorityLevel_DEPRECATED = priorityLevel;
    
  127.     const didTimeout_DEPRECATED = false;
    
  128.     const result = callback(didTimeout_DEPRECATED);
    
  129.     if (typeof result === 'function') {
    
  130.       // Assume this is a continuation
    
  131.       const continuation: SchedulerCallback<T> = (result: any);
    
  132.       const continuationOptions = {
    
  133.         signal: node._controller.signal,
    
  134.       };
    
  135. 
    
  136.       const nextTask = runTask.bind(
    
  137.         null,
    
  138.         priorityLevel,
    
  139.         postTaskPriority,
    
  140.         node,
    
  141.         continuation,
    
  142.       );
    
  143. 
    
  144.       if (scheduler.yield !== undefined) {
    
  145.         scheduler
    
  146.           .yield(continuationOptions)
    
  147.           .then(nextTask)
    
  148.           .catch(handleAbortError);
    
  149.       } else {
    
  150.         scheduler
    
  151.           .postTask(nextTask, continuationOptions)
    
  152.           .catch(handleAbortError);
    
  153.       }
    
  154.     }
    
  155.   } catch (error) {
    
  156.     // We're inside a `postTask` promise. If we don't handle this error, then it
    
  157.     // will trigger an "Unhandled promise rejection" error. We don't want that,
    
  158.     // but we do want the default error reporting behavior that normal
    
  159.     // (non-Promise) tasks get for unhandled errors.
    
  160.     //
    
  161.     // So we'll re-throw the error inside a regular browser task.
    
  162.     setTimeout(() => {
    
  163.       throw error;
    
  164.     });
    
  165.   } finally {
    
  166.     currentPriorityLevel_DEPRECATED = NormalPriority;
    
  167.   }
    
  168. }
    
  169. 
    
  170. function handleAbortError(error: any) {
    
  171.   // Abort errors are an implementation detail. We don't expose the
    
  172.   // TaskController to the user, nor do we expose the promise that is returned
    
  173.   // from `postTask`. So we should suppress them, since there's no way for the
    
  174.   // user to handle them.
    
  175. }
    
  176. 
    
  177. export function unstable_cancelCallback(node: CallbackNode) {
    
  178.   const controller = node._controller;
    
  179.   controller.abort();
    
  180. }
    
  181. 
    
  182. export function unstable_runWithPriority<T>(
    
  183.   priorityLevel: PriorityLevel,
    
  184.   callback: () => T,
    
  185. ): T {
    
  186.   const previousPriorityLevel = currentPriorityLevel_DEPRECATED;
    
  187.   currentPriorityLevel_DEPRECATED = priorityLevel;
    
  188.   try {
    
  189.     return callback();
    
  190.   } finally {
    
  191.     currentPriorityLevel_DEPRECATED = previousPriorityLevel;
    
  192.   }
    
  193. }
    
  194. 
    
  195. export function unstable_getCurrentPriorityLevel(): PriorityLevel {
    
  196.   return currentPriorityLevel_DEPRECATED;
    
  197. }
    
  198. 
    
  199. export function unstable_next<T>(callback: () => T): T {
    
  200.   let priorityLevel;
    
  201.   switch (currentPriorityLevel_DEPRECATED) {
    
  202.     case ImmediatePriority:
    
  203.     case UserBlockingPriority:
    
  204.     case NormalPriority:
    
  205.       // Shift down to normal priority
    
  206.       priorityLevel = NormalPriority;
    
  207.       break;
    
  208.     default:
    
  209.       // Anything lower than normal priority should remain at the current level.
    
  210.       priorityLevel = currentPriorityLevel_DEPRECATED;
    
  211.       break;
    
  212.   }
    
  213. 
    
  214.   const previousPriorityLevel = currentPriorityLevel_DEPRECATED;
    
  215.   currentPriorityLevel_DEPRECATED = priorityLevel;
    
  216.   try {
    
  217.     return callback();
    
  218.   } finally {
    
  219.     currentPriorityLevel_DEPRECATED = previousPriorityLevel;
    
  220.   }
    
  221. }
    
  222. 
    
  223. export function unstable_wrapCallback<T>(callback: () => T): () => T {
    
  224.   const parentPriorityLevel = currentPriorityLevel_DEPRECATED;
    
  225.   return () => {
    
  226.     const previousPriorityLevel = currentPriorityLevel_DEPRECATED;
    
  227.     currentPriorityLevel_DEPRECATED = parentPriorityLevel;
    
  228.     try {
    
  229.       return callback();
    
  230.     } finally {
    
  231.       currentPriorityLevel_DEPRECATED = previousPriorityLevel;
    
  232.     }
    
  233.   };
    
  234. }
    
  235. 
    
  236. export function unstable_forceFrameRate() {}
    
  237. 
    
  238. export function unstable_pauseExecution() {}
    
  239. 
    
  240. export function unstable_continueExecution() {}
    
  241. 
    
  242. export function unstable_getFirstCallbackNode(): null {
    
  243.   return null;
    
  244. }
    
  245. 
    
  246. // Currently no profiling build
    
  247. export const unstable_Profiling = null;