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. /* eslint-disable react-internal/prod-error-codes */
    
  12. 
    
  13. import type {PriorityLevel} from '../SchedulerPriorities';
    
  14. 
    
  15. import {
    
  16.   enableSchedulerDebugging,
    
  17.   enableProfiling,
    
  18. } from '../SchedulerFeatureFlags';
    
  19. import {push, pop, peek} from '../SchedulerMinHeap';
    
  20. 
    
  21. // TODO: Use symbols?
    
  22. import {
    
  23.   ImmediatePriority,
    
  24.   UserBlockingPriority,
    
  25.   NormalPriority,
    
  26.   LowPriority,
    
  27.   IdlePriority,
    
  28. } from '../SchedulerPriorities';
    
  29. import {
    
  30.   markTaskRun,
    
  31.   markTaskYield,
    
  32.   markTaskCompleted,
    
  33.   markTaskCanceled,
    
  34.   markTaskErrored,
    
  35.   markSchedulerSuspended,
    
  36.   markSchedulerUnsuspended,
    
  37.   markTaskStart,
    
  38.   stopLoggingProfilingEvents,
    
  39.   startLoggingProfilingEvents,
    
  40. } from '../SchedulerProfiling';
    
  41. 
    
  42. type Callback = boolean => ?Callback;
    
  43. 
    
  44. type Task = {
    
  45.   id: number,
    
  46.   callback: Callback | null,
    
  47.   priorityLevel: PriorityLevel,
    
  48.   startTime: number,
    
  49.   expirationTime: number,
    
  50.   sortIndex: number,
    
  51.   isQueued?: boolean,
    
  52. };
    
  53. 
    
  54. // Max 31 bit integer. The max integer size in V8 for 32-bit systems.
    
  55. // Math.pow(2, 30) - 1
    
  56. // 0b111111111111111111111111111111
    
  57. var maxSigned31BitInt = 1073741823;
    
  58. 
    
  59. // Times out immediately
    
  60. var IMMEDIATE_PRIORITY_TIMEOUT = -1;
    
  61. // Eventually times out
    
  62. var USER_BLOCKING_PRIORITY_TIMEOUT = 250;
    
  63. var NORMAL_PRIORITY_TIMEOUT = 5000;
    
  64. var LOW_PRIORITY_TIMEOUT = 10000;
    
  65. // Never times out
    
  66. var IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt;
    
  67. 
    
  68. // Tasks are stored on a min heap
    
  69. var taskQueue: Array<Task> = [];
    
  70. var timerQueue: Array<Task> = [];
    
  71. 
    
  72. // Incrementing id counter. Used to maintain insertion order.
    
  73. var taskIdCounter = 1;
    
  74. 
    
  75. // Pausing the scheduler is useful for debugging.
    
  76. var isSchedulerPaused = false;
    
  77. 
    
  78. var currentTask = null;
    
  79. var currentPriorityLevel = NormalPriority;
    
  80. 
    
  81. // This is set while performing work, to prevent re-entrance.
    
  82. var isPerformingWork = false;
    
  83. 
    
  84. var isHostCallbackScheduled = false;
    
  85. var isHostTimeoutScheduled = false;
    
  86. 
    
  87. let currentMockTime: number = 0;
    
  88. let scheduledCallback:
    
  89.   | null
    
  90.   | ((
    
  91.       hasTimeRemaining: boolean,
    
  92.       initialTime: DOMHighResTimeStamp | number,
    
  93.     ) => boolean) = null;
    
  94. let scheduledTimeout: (number => void) | null = null;
    
  95. let timeoutTime: number = -1;
    
  96. let yieldedValues: Array<mixed> | null = null;
    
  97. let expectedNumberOfYields: number = -1;
    
  98. let didStop: boolean = false;
    
  99. let isFlushing: boolean = false;
    
  100. let needsPaint: boolean = false;
    
  101. let shouldYieldForPaint: boolean = false;
    
  102. 
    
  103. var disableYieldValue = false;
    
  104. 
    
  105. function setDisableYieldValue(newValue: boolean) {
    
  106.   disableYieldValue = newValue;
    
  107. }
    
  108. 
    
  109. function advanceTimers(currentTime: number) {
    
  110.   // Check for tasks that are no longer delayed and add them to the queue.
    
  111.   let timer = peek(timerQueue);
    
  112.   while (timer !== null) {
    
  113.     if (timer.callback === null) {
    
  114.       // Timer was cancelled.
    
  115.       pop(timerQueue);
    
  116.     } else if (timer.startTime <= currentTime) {
    
  117.       // Timer fired. Transfer to the task queue.
    
  118.       pop(timerQueue);
    
  119.       timer.sortIndex = timer.expirationTime;
    
  120.       push(taskQueue, timer);
    
  121.       if (enableProfiling) {
    
  122.         markTaskStart(timer, currentTime);
    
  123.         timer.isQueued = true;
    
  124.       }
    
  125.     } else {
    
  126.       // Remaining timers are pending.
    
  127.       return;
    
  128.     }
    
  129.     timer = peek(timerQueue);
    
  130.   }
    
  131. }
    
  132. 
    
  133. function handleTimeout(currentTime: number) {
    
  134.   isHostTimeoutScheduled = false;
    
  135.   advanceTimers(currentTime);
    
  136. 
    
  137.   if (!isHostCallbackScheduled) {
    
  138.     if (peek(taskQueue) !== null) {
    
  139.       isHostCallbackScheduled = true;
    
  140.       requestHostCallback(flushWork);
    
  141.     } else {
    
  142.       const firstTimer = peek(timerQueue);
    
  143.       if (firstTimer !== null) {
    
  144.         requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
    
  145.       }
    
  146.     }
    
  147.   }
    
  148. }
    
  149. 
    
  150. function flushWork(hasTimeRemaining: boolean, initialTime: number) {
    
  151.   if (enableProfiling) {
    
  152.     markSchedulerUnsuspended(initialTime);
    
  153.   }
    
  154. 
    
  155.   // We'll need a host callback the next time work is scheduled.
    
  156.   isHostCallbackScheduled = false;
    
  157.   if (isHostTimeoutScheduled) {
    
  158.     // We scheduled a timeout but it's no longer needed. Cancel it.
    
  159.     isHostTimeoutScheduled = false;
    
  160.     cancelHostTimeout();
    
  161.   }
    
  162. 
    
  163.   isPerformingWork = true;
    
  164.   const previousPriorityLevel = currentPriorityLevel;
    
  165.   try {
    
  166.     if (enableProfiling) {
    
  167.       try {
    
  168.         return workLoop(hasTimeRemaining, initialTime);
    
  169.       } catch (error) {
    
  170.         if (currentTask !== null) {
    
  171.           const currentTime = getCurrentTime();
    
  172.           // $FlowFixMe[incompatible-call] found when upgrading Flow
    
  173.           markTaskErrored(currentTask, currentTime);
    
  174.           // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  175.           currentTask.isQueued = false;
    
  176.         }
    
  177.         throw error;
    
  178.       }
    
  179.     } else {
    
  180.       // No catch in prod code path.
    
  181.       return workLoop(hasTimeRemaining, initialTime);
    
  182.     }
    
  183.   } finally {
    
  184.     currentTask = null;
    
  185.     currentPriorityLevel = previousPriorityLevel;
    
  186.     isPerformingWork = false;
    
  187.     if (enableProfiling) {
    
  188.       const currentTime = getCurrentTime();
    
  189.       markSchedulerSuspended(currentTime);
    
  190.     }
    
  191.   }
    
  192. }
    
  193. 
    
  194. function workLoop(hasTimeRemaining: boolean, initialTime: number): boolean {
    
  195.   let currentTime = initialTime;
    
  196.   advanceTimers(currentTime);
    
  197.   currentTask = peek(taskQueue);
    
  198.   while (
    
  199.     currentTask !== null &&
    
  200.     !(enableSchedulerDebugging && isSchedulerPaused)
    
  201.   ) {
    
  202.     if (
    
  203.       currentTask.expirationTime > currentTime &&
    
  204.       (!hasTimeRemaining || shouldYieldToHost())
    
  205.     ) {
    
  206.       // This currentTask hasn't expired, and we've reached the deadline.
    
  207.       break;
    
  208.     }
    
  209.     // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  210.     const callback = currentTask.callback;
    
  211.     if (typeof callback === 'function') {
    
  212.       // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  213.       currentTask.callback = null;
    
  214.       // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  215.       currentPriorityLevel = currentTask.priorityLevel;
    
  216.       // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  217.       const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
    
  218.       if (enableProfiling) {
    
  219.         // $FlowFixMe[incompatible-call] found when upgrading Flow
    
  220.         markTaskRun(currentTask, currentTime);
    
  221.       }
    
  222.       const continuationCallback = callback(didUserCallbackTimeout);
    
  223.       currentTime = getCurrentTime();
    
  224.       if (typeof continuationCallback === 'function') {
    
  225.         // If a continuation is returned, immediately yield to the main thread
    
  226.         // regardless of how much time is left in the current time slice.
    
  227.         // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  228.         currentTask.callback = continuationCallback;
    
  229.         if (enableProfiling) {
    
  230.           // $FlowFixMe[incompatible-call] found when upgrading Flow
    
  231.           markTaskYield(currentTask, currentTime);
    
  232.         }
    
  233.         advanceTimers(currentTime);
    
  234. 
    
  235.         if (shouldYieldForPaint) {
    
  236.           needsPaint = true;
    
  237.           return true;
    
  238.         } else {
    
  239.           // If `shouldYieldForPaint` is false, we keep flushing synchronously
    
  240.           // without yielding to the main thread. This is the behavior of the
    
  241.           // `toFlushAndYield` and `toFlushAndYieldThrough` testing helpers .
    
  242.         }
    
  243.       } else {
    
  244.         if (enableProfiling) {
    
  245.           // $FlowFixMe[incompatible-call] found when upgrading Flow
    
  246.           markTaskCompleted(currentTask, currentTime);
    
  247.           // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  248.           currentTask.isQueued = false;
    
  249.         }
    
  250.         if (currentTask === peek(taskQueue)) {
    
  251.           pop(taskQueue);
    
  252.         }
    
  253.         advanceTimers(currentTime);
    
  254.       }
    
  255.     } else {
    
  256.       pop(taskQueue);
    
  257.     }
    
  258.     currentTask = peek(taskQueue);
    
  259.   }
    
  260.   // Return whether there's additional work
    
  261.   if (currentTask !== null) {
    
  262.     return true;
    
  263.   } else {
    
  264.     const firstTimer = peek(timerQueue);
    
  265.     if (firstTimer !== null) {
    
  266.       requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
    
  267.     }
    
  268.     return false;
    
  269.   }
    
  270. }
    
  271. 
    
  272. function unstable_runWithPriority<T>(
    
  273.   priorityLevel: PriorityLevel,
    
  274.   eventHandler: () => T,
    
  275. ): T {
    
  276.   switch (priorityLevel) {
    
  277.     case ImmediatePriority:
    
  278.     case UserBlockingPriority:
    
  279.     case NormalPriority:
    
  280.     case LowPriority:
    
  281.     case IdlePriority:
    
  282.       break;
    
  283.     default:
    
  284.       priorityLevel = NormalPriority;
    
  285.   }
    
  286. 
    
  287.   var previousPriorityLevel = currentPriorityLevel;
    
  288.   currentPriorityLevel = priorityLevel;
    
  289. 
    
  290.   try {
    
  291.     return eventHandler();
    
  292.   } finally {
    
  293.     currentPriorityLevel = previousPriorityLevel;
    
  294.   }
    
  295. }
    
  296. 
    
  297. function unstable_next<T>(eventHandler: () => T): T {
    
  298.   var priorityLevel;
    
  299.   switch (currentPriorityLevel) {
    
  300.     case ImmediatePriority:
    
  301.     case UserBlockingPriority:
    
  302.     case NormalPriority:
    
  303.       // Shift down to normal priority
    
  304.       priorityLevel = NormalPriority;
    
  305.       break;
    
  306.     default:
    
  307.       // Anything lower than normal priority should remain at the current level.
    
  308.       priorityLevel = currentPriorityLevel;
    
  309.       break;
    
  310.   }
    
  311. 
    
  312.   var previousPriorityLevel = currentPriorityLevel;
    
  313.   currentPriorityLevel = priorityLevel;
    
  314. 
    
  315.   try {
    
  316.     return eventHandler();
    
  317.   } finally {
    
  318.     currentPriorityLevel = previousPriorityLevel;
    
  319.   }
    
  320. }
    
  321. 
    
  322. function unstable_wrapCallback<T: (...Array<mixed>) => mixed>(callback: T): T {
    
  323.   var parentPriorityLevel = currentPriorityLevel;
    
  324.   // $FlowFixMe[incompatible-return]
    
  325.   // $FlowFixMe[missing-this-annot]
    
  326.   return function () {
    
  327.     // This is a fork of runWithPriority, inlined for performance.
    
  328.     var previousPriorityLevel = currentPriorityLevel;
    
  329.     currentPriorityLevel = parentPriorityLevel;
    
  330. 
    
  331.     try {
    
  332.       return callback.apply(this, arguments);
    
  333.     } finally {
    
  334.       currentPriorityLevel = previousPriorityLevel;
    
  335.     }
    
  336.   };
    
  337. }
    
  338. 
    
  339. function unstable_scheduleCallback(
    
  340.   priorityLevel: PriorityLevel,
    
  341.   callback: Callback,
    
  342.   options?: {delay: number},
    
  343. ): Task {
    
  344.   var currentTime = getCurrentTime();
    
  345. 
    
  346.   var startTime;
    
  347.   if (typeof options === 'object' && options !== null) {
    
  348.     var delay = options.delay;
    
  349.     if (typeof delay === 'number' && delay > 0) {
    
  350.       startTime = currentTime + delay;
    
  351.     } else {
    
  352.       startTime = currentTime;
    
  353.     }
    
  354.   } else {
    
  355.     startTime = currentTime;
    
  356.   }
    
  357. 
    
  358.   var timeout;
    
  359.   switch (priorityLevel) {
    
  360.     case ImmediatePriority:
    
  361.       timeout = IMMEDIATE_PRIORITY_TIMEOUT;
    
  362.       break;
    
  363.     case UserBlockingPriority:
    
  364.       timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
    
  365.       break;
    
  366.     case IdlePriority:
    
  367.       timeout = IDLE_PRIORITY_TIMEOUT;
    
  368.       break;
    
  369.     case LowPriority:
    
  370.       timeout = LOW_PRIORITY_TIMEOUT;
    
  371.       break;
    
  372.     case NormalPriority:
    
  373.     default:
    
  374.       timeout = NORMAL_PRIORITY_TIMEOUT;
    
  375.       break;
    
  376.   }
    
  377. 
    
  378.   var expirationTime = startTime + timeout;
    
  379. 
    
  380.   var newTask: Task = {
    
  381.     id: taskIdCounter++,
    
  382.     callback,
    
  383.     priorityLevel,
    
  384.     startTime,
    
  385.     expirationTime,
    
  386.     sortIndex: -1,
    
  387.   };
    
  388.   if (enableProfiling) {
    
  389.     newTask.isQueued = false;
    
  390.   }
    
  391. 
    
  392.   if (startTime > currentTime) {
    
  393.     // This is a delayed task.
    
  394.     newTask.sortIndex = startTime;
    
  395.     push(timerQueue, newTask);
    
  396.     if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
    
  397.       // All tasks are delayed, and this is the task with the earliest delay.
    
  398.       if (isHostTimeoutScheduled) {
    
  399.         // Cancel an existing timeout.
    
  400.         cancelHostTimeout();
    
  401.       } else {
    
  402.         isHostTimeoutScheduled = true;
    
  403.       }
    
  404.       // Schedule a timeout.
    
  405.       requestHostTimeout(handleTimeout, startTime - currentTime);
    
  406.     }
    
  407.   } else {
    
  408.     newTask.sortIndex = expirationTime;
    
  409.     push(taskQueue, newTask);
    
  410.     if (enableProfiling) {
    
  411.       markTaskStart(newTask, currentTime);
    
  412.       newTask.isQueued = true;
    
  413.     }
    
  414.     // Schedule a host callback, if needed. If we're already performing work,
    
  415.     // wait until the next time we yield.
    
  416.     if (!isHostCallbackScheduled && !isPerformingWork) {
    
  417.       isHostCallbackScheduled = true;
    
  418.       requestHostCallback(flushWork);
    
  419.     }
    
  420.   }
    
  421. 
    
  422.   return newTask;
    
  423. }
    
  424. 
    
  425. function unstable_pauseExecution() {
    
  426.   isSchedulerPaused = true;
    
  427. }
    
  428. 
    
  429. function unstable_continueExecution() {
    
  430.   isSchedulerPaused = false;
    
  431.   if (!isHostCallbackScheduled && !isPerformingWork) {
    
  432.     isHostCallbackScheduled = true;
    
  433.     requestHostCallback(flushWork);
    
  434.   }
    
  435. }
    
  436. 
    
  437. function unstable_getFirstCallbackNode(): Task | null {
    
  438.   return peek(taskQueue);
    
  439. }
    
  440. 
    
  441. function unstable_cancelCallback(task: Task) {
    
  442.   if (enableProfiling) {
    
  443.     if (task.isQueued) {
    
  444.       const currentTime = getCurrentTime();
    
  445.       markTaskCanceled(task, currentTime);
    
  446.       task.isQueued = false;
    
  447.     }
    
  448.   }
    
  449. 
    
  450.   // Null out the callback to indicate the task has been canceled. (Can't
    
  451.   // remove from the queue because you can't remove arbitrary nodes from an
    
  452.   // array based heap, only the first one.)
    
  453.   task.callback = null;
    
  454. }
    
  455. 
    
  456. function unstable_getCurrentPriorityLevel(): PriorityLevel {
    
  457.   return currentPriorityLevel;
    
  458. }
    
  459. 
    
  460. function requestHostCallback(callback: (boolean, number) => boolean) {
    
  461.   scheduledCallback = callback;
    
  462. }
    
  463. 
    
  464. function requestHostTimeout(callback: number => void, ms: number) {
    
  465.   scheduledTimeout = callback;
    
  466.   timeoutTime = currentMockTime + ms;
    
  467. }
    
  468. 
    
  469. function cancelHostTimeout(): void {
    
  470.   scheduledTimeout = null;
    
  471.   timeoutTime = -1;
    
  472. }
    
  473. 
    
  474. function shouldYieldToHost(): boolean {
    
  475.   if (
    
  476.     (expectedNumberOfYields === 0 && yieldedValues === null) ||
    
  477.     (expectedNumberOfYields !== -1 &&
    
  478.       yieldedValues !== null &&
    
  479.       yieldedValues.length >= expectedNumberOfYields) ||
    
  480.     (shouldYieldForPaint && needsPaint)
    
  481.   ) {
    
  482.     // We yielded at least as many values as expected. Stop flushing.
    
  483.     didStop = true;
    
  484.     return true;
    
  485.   }
    
  486.   return false;
    
  487. }
    
  488. 
    
  489. function getCurrentTime(): number {
    
  490.   return currentMockTime;
    
  491. }
    
  492. 
    
  493. function forceFrameRate() {
    
  494.   // No-op
    
  495. }
    
  496. 
    
  497. function reset() {
    
  498.   if (isFlushing) {
    
  499.     throw new Error('Cannot reset while already flushing work.');
    
  500.   }
    
  501.   currentMockTime = 0;
    
  502.   scheduledCallback = null;
    
  503.   scheduledTimeout = null;
    
  504.   timeoutTime = -1;
    
  505.   yieldedValues = null;
    
  506.   expectedNumberOfYields = -1;
    
  507.   didStop = false;
    
  508.   isFlushing = false;
    
  509.   needsPaint = false;
    
  510. }
    
  511. 
    
  512. // Should only be used via an assertion helper that inspects the yielded values.
    
  513. function unstable_flushNumberOfYields(count: number): void {
    
  514.   if (isFlushing) {
    
  515.     throw new Error('Already flushing work.');
    
  516.   }
    
  517.   if (scheduledCallback !== null) {
    
  518.     const cb = scheduledCallback;
    
  519.     expectedNumberOfYields = count;
    
  520.     isFlushing = true;
    
  521.     try {
    
  522.       let hasMoreWork = true;
    
  523.       do {
    
  524.         hasMoreWork = cb(true, currentMockTime);
    
  525.       } while (hasMoreWork && !didStop);
    
  526.       if (!hasMoreWork) {
    
  527.         scheduledCallback = null;
    
  528.       }
    
  529.     } finally {
    
  530.       expectedNumberOfYields = -1;
    
  531.       didStop = false;
    
  532.       isFlushing = false;
    
  533.     }
    
  534.   }
    
  535. }
    
  536. 
    
  537. function unstable_flushUntilNextPaint(): false {
    
  538.   if (isFlushing) {
    
  539.     throw new Error('Already flushing work.');
    
  540.   }
    
  541.   if (scheduledCallback !== null) {
    
  542.     const cb = scheduledCallback;
    
  543.     shouldYieldForPaint = true;
    
  544.     needsPaint = false;
    
  545.     isFlushing = true;
    
  546.     try {
    
  547.       let hasMoreWork = true;
    
  548.       do {
    
  549.         hasMoreWork = cb(true, currentMockTime);
    
  550.       } while (hasMoreWork && !didStop);
    
  551.       if (!hasMoreWork) {
    
  552.         scheduledCallback = null;
    
  553.       }
    
  554.     } finally {
    
  555.       shouldYieldForPaint = false;
    
  556.       didStop = false;
    
  557.       isFlushing = false;
    
  558.     }
    
  559.   }
    
  560.   return false;
    
  561. }
    
  562. 
    
  563. function unstable_hasPendingWork(): boolean {
    
  564.   return scheduledCallback !== null;
    
  565. }
    
  566. 
    
  567. function unstable_flushExpired() {
    
  568.   if (isFlushing) {
    
  569.     throw new Error('Already flushing work.');
    
  570.   }
    
  571.   if (scheduledCallback !== null) {
    
  572.     isFlushing = true;
    
  573.     try {
    
  574.       const hasMoreWork = scheduledCallback(false, currentMockTime);
    
  575.       if (!hasMoreWork) {
    
  576.         scheduledCallback = null;
    
  577.       }
    
  578.     } finally {
    
  579.       isFlushing = false;
    
  580.     }
    
  581.   }
    
  582. }
    
  583. 
    
  584. function unstable_flushAllWithoutAsserting(): boolean {
    
  585.   // Returns false if no work was flushed.
    
  586.   if (isFlushing) {
    
  587.     throw new Error('Already flushing work.');
    
  588.   }
    
  589.   if (scheduledCallback !== null) {
    
  590.     const cb = scheduledCallback;
    
  591.     isFlushing = true;
    
  592.     try {
    
  593.       let hasMoreWork = true;
    
  594.       do {
    
  595.         hasMoreWork = cb(true, currentMockTime);
    
  596.       } while (hasMoreWork);
    
  597.       if (!hasMoreWork) {
    
  598.         scheduledCallback = null;
    
  599.       }
    
  600.       return true;
    
  601.     } finally {
    
  602.       isFlushing = false;
    
  603.     }
    
  604.   } else {
    
  605.     return false;
    
  606.   }
    
  607. }
    
  608. 
    
  609. function unstable_clearLog(): Array<mixed> {
    
  610.   if (yieldedValues === null) {
    
  611.     return [];
    
  612.   }
    
  613.   const values = yieldedValues;
    
  614.   yieldedValues = null;
    
  615.   return values;
    
  616. }
    
  617. 
    
  618. function unstable_flushAll(): void {
    
  619.   if (yieldedValues !== null) {
    
  620.     throw new Error(
    
  621.       'Log is not empty. Assert on the log of yielded values before ' +
    
  622.         'flushing additional work.',
    
  623.     );
    
  624.   }
    
  625.   unstable_flushAllWithoutAsserting();
    
  626.   if (yieldedValues !== null) {
    
  627.     throw new Error(
    
  628.       'While flushing work, something yielded a value. Use an ' +
    
  629.         'assertion helper to assert on the log of yielded values, e.g. ' +
    
  630.         'expect(Scheduler).toFlushAndYield([...])',
    
  631.     );
    
  632.   }
    
  633. }
    
  634. 
    
  635. function log(value: mixed): void {
    
  636.   // eslint-disable-next-line react-internal/no-production-logging
    
  637.   if (console.log.name === 'disabledLog' || disableYieldValue) {
    
  638.     // If console.log has been patched, we assume we're in render
    
  639.     // replaying and we ignore any values yielding in the second pass.
    
  640.     return;
    
  641.   }
    
  642.   if (yieldedValues === null) {
    
  643.     yieldedValues = [value];
    
  644.   } else {
    
  645.     yieldedValues.push(value);
    
  646.   }
    
  647. }
    
  648. 
    
  649. function unstable_advanceTime(ms: number) {
    
  650.   // eslint-disable-next-line react-internal/no-production-logging
    
  651.   if (console.log.name === 'disabledLog' || disableYieldValue) {
    
  652.     // If console.log has been patched, we assume we're in render
    
  653.     // replaying and we ignore any time advancing in the second pass.
    
  654.     return;
    
  655.   }
    
  656.   currentMockTime += ms;
    
  657.   if (scheduledTimeout !== null && timeoutTime <= currentMockTime) {
    
  658.     scheduledTimeout(currentMockTime);
    
  659.     timeoutTime = -1;
    
  660.     scheduledTimeout = null;
    
  661.   }
    
  662. }
    
  663. 
    
  664. function requestPaint() {
    
  665.   needsPaint = true;
    
  666. }
    
  667. 
    
  668. export {
    
  669.   ImmediatePriority as unstable_ImmediatePriority,
    
  670.   UserBlockingPriority as unstable_UserBlockingPriority,
    
  671.   NormalPriority as unstable_NormalPriority,
    
  672.   IdlePriority as unstable_IdlePriority,
    
  673.   LowPriority as unstable_LowPriority,
    
  674.   unstable_runWithPriority,
    
  675.   unstable_next,
    
  676.   unstable_scheduleCallback,
    
  677.   unstable_cancelCallback,
    
  678.   unstable_wrapCallback,
    
  679.   unstable_getCurrentPriorityLevel,
    
  680.   shouldYieldToHost as unstable_shouldYield,
    
  681.   requestPaint as unstable_requestPaint,
    
  682.   unstable_continueExecution,
    
  683.   unstable_pauseExecution,
    
  684.   unstable_getFirstCallbackNode,
    
  685.   getCurrentTime as unstable_now,
    
  686.   forceFrameRate as unstable_forceFrameRate,
    
  687.   unstable_flushAllWithoutAsserting,
    
  688.   unstable_flushNumberOfYields,
    
  689.   unstable_flushExpired,
    
  690.   unstable_clearLog,
    
  691.   unstable_flushUntilNextPaint,
    
  692.   unstable_hasPendingWork,
    
  693.   unstable_flushAll,
    
  694.   log,
    
  695.   unstable_advanceTime,
    
  696.   reset,
    
  697.   setDisableYieldValue as unstable_setDisableYieldValue,
    
  698. };
    
  699. 
    
  700. export const unstable_Profiling: {
    
  701.   startLoggingProfilingEvents(): void,
    
  702.   stopLoggingProfilingEvents(): ArrayBuffer | null,
    
  703. } | null = enableProfiling
    
  704.   ? {
    
  705.       startLoggingProfilingEvents,
    
  706.       stopLoggingProfilingEvents,
    
  707.     }
    
  708.   : null;