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 {Fiber} from './ReactInternalTypes';
    
  11. 
    
  12. import {
    
  13.   enableProfilerCommitHooks,
    
  14.   enableProfilerNestedUpdatePhase,
    
  15.   enableProfilerTimer,
    
  16. } from 'shared/ReactFeatureFlags';
    
  17. import {HostRoot, Profiler} from './ReactWorkTags';
    
  18. 
    
  19. // Intentionally not named imports because Rollup would use dynamic dispatch for
    
  20. // CommonJS interop named imports.
    
  21. import * as Scheduler from 'scheduler';
    
  22. 
    
  23. const {unstable_now: now} = Scheduler;
    
  24. 
    
  25. export type ProfilerTimer = {
    
  26.   getCommitTime(): number,
    
  27.   isCurrentUpdateNested(): boolean,
    
  28.   markNestedUpdateScheduled(): void,
    
  29.   recordCommitTime(): void,
    
  30.   startProfilerTimer(fiber: Fiber): void,
    
  31.   stopProfilerTimerIfRunning(fiber: Fiber): void,
    
  32.   stopProfilerTimerIfRunningAndRecordDelta(fiber: Fiber): void,
    
  33.   syncNestedUpdateFlag(): void,
    
  34.   ...
    
  35. };
    
  36. 
    
  37. let commitTime: number = 0;
    
  38. let layoutEffectStartTime: number = -1;
    
  39. let profilerStartTime: number = -1;
    
  40. let passiveEffectStartTime: number = -1;
    
  41. 
    
  42. /**
    
  43.  * Tracks whether the current update was a nested/cascading update (scheduled from a layout effect).
    
  44.  *
    
  45.  * The overall sequence is:
    
  46.  *   1. render
    
  47.  *   2. commit (and call `onRender`, `onCommit`)
    
  48.  *   3. check for nested updates
    
  49.  *   4. flush passive effects (and call `onPostCommit`)
    
  50.  *
    
  51.  * Nested updates are identified in step 3 above,
    
  52.  * but step 4 still applies to the work that was just committed.
    
  53.  * We use two flags to track nested updates then:
    
  54.  * one tracks whether the upcoming update is a nested update,
    
  55.  * and the other tracks whether the current update was a nested update.
    
  56.  * The first value gets synced to the second at the start of the render phase.
    
  57.  */
    
  58. let currentUpdateIsNested: boolean = false;
    
  59. let nestedUpdateScheduled: boolean = false;
    
  60. 
    
  61. function isCurrentUpdateNested(): boolean {
    
  62.   return currentUpdateIsNested;
    
  63. }
    
  64. 
    
  65. function markNestedUpdateScheduled(): void {
    
  66.   if (enableProfilerNestedUpdatePhase) {
    
  67.     nestedUpdateScheduled = true;
    
  68.   }
    
  69. }
    
  70. 
    
  71. function resetNestedUpdateFlag(): void {
    
  72.   if (enableProfilerNestedUpdatePhase) {
    
  73.     currentUpdateIsNested = false;
    
  74.     nestedUpdateScheduled = false;
    
  75.   }
    
  76. }
    
  77. 
    
  78. function syncNestedUpdateFlag(): void {
    
  79.   if (enableProfilerNestedUpdatePhase) {
    
  80.     currentUpdateIsNested = nestedUpdateScheduled;
    
  81.     nestedUpdateScheduled = false;
    
  82.   }
    
  83. }
    
  84. 
    
  85. function getCommitTime(): number {
    
  86.   return commitTime;
    
  87. }
    
  88. 
    
  89. function recordCommitTime(): void {
    
  90.   if (!enableProfilerTimer) {
    
  91.     return;
    
  92.   }
    
  93.   commitTime = now();
    
  94. }
    
  95. 
    
  96. function startProfilerTimer(fiber: Fiber): void {
    
  97.   if (!enableProfilerTimer) {
    
  98.     return;
    
  99.   }
    
  100. 
    
  101.   profilerStartTime = now();
    
  102. 
    
  103.   if (((fiber.actualStartTime: any): number) < 0) {
    
  104.     fiber.actualStartTime = now();
    
  105.   }
    
  106. }
    
  107. 
    
  108. function stopProfilerTimerIfRunning(fiber: Fiber): void {
    
  109.   if (!enableProfilerTimer) {
    
  110.     return;
    
  111.   }
    
  112.   profilerStartTime = -1;
    
  113. }
    
  114. 
    
  115. function stopProfilerTimerIfRunningAndRecordDelta(
    
  116.   fiber: Fiber,
    
  117.   overrideBaseTime: boolean,
    
  118. ): void {
    
  119.   if (!enableProfilerTimer) {
    
  120.     return;
    
  121.   }
    
  122. 
    
  123.   if (profilerStartTime >= 0) {
    
  124.     const elapsedTime = now() - profilerStartTime;
    
  125.     fiber.actualDuration += elapsedTime;
    
  126.     if (overrideBaseTime) {
    
  127.       fiber.selfBaseDuration = elapsedTime;
    
  128.     }
    
  129.     profilerStartTime = -1;
    
  130.   }
    
  131. }
    
  132. 
    
  133. function recordLayoutEffectDuration(fiber: Fiber): void {
    
  134.   if (!enableProfilerTimer || !enableProfilerCommitHooks) {
    
  135.     return;
    
  136.   }
    
  137. 
    
  138.   if (layoutEffectStartTime >= 0) {
    
  139.     const elapsedTime = now() - layoutEffectStartTime;
    
  140. 
    
  141.     layoutEffectStartTime = -1;
    
  142. 
    
  143.     // Store duration on the next nearest Profiler ancestor
    
  144.     // Or the root (for the DevTools Profiler to read)
    
  145.     let parentFiber = fiber.return;
    
  146.     while (parentFiber !== null) {
    
  147.       switch (parentFiber.tag) {
    
  148.         case HostRoot:
    
  149.           const root = parentFiber.stateNode;
    
  150.           root.effectDuration += elapsedTime;
    
  151.           return;
    
  152.         case Profiler:
    
  153.           const parentStateNode = parentFiber.stateNode;
    
  154.           parentStateNode.effectDuration += elapsedTime;
    
  155.           return;
    
  156.       }
    
  157.       parentFiber = parentFiber.return;
    
  158.     }
    
  159.   }
    
  160. }
    
  161. 
    
  162. function recordPassiveEffectDuration(fiber: Fiber): void {
    
  163.   if (!enableProfilerTimer || !enableProfilerCommitHooks) {
    
  164.     return;
    
  165.   }
    
  166. 
    
  167.   if (passiveEffectStartTime >= 0) {
    
  168.     const elapsedTime = now() - passiveEffectStartTime;
    
  169. 
    
  170.     passiveEffectStartTime = -1;
    
  171. 
    
  172.     // Store duration on the next nearest Profiler ancestor
    
  173.     // Or the root (for the DevTools Profiler to read)
    
  174.     let parentFiber = fiber.return;
    
  175.     while (parentFiber !== null) {
    
  176.       switch (parentFiber.tag) {
    
  177.         case HostRoot:
    
  178.           const root = parentFiber.stateNode;
    
  179.           if (root !== null) {
    
  180.             root.passiveEffectDuration += elapsedTime;
    
  181.           }
    
  182.           return;
    
  183.         case Profiler:
    
  184.           const parentStateNode = parentFiber.stateNode;
    
  185.           if (parentStateNode !== null) {
    
  186.             // Detached fibers have their state node cleared out.
    
  187.             // In this case, the return pointer is also cleared out,
    
  188.             // so we won't be able to report the time spent in this Profiler's subtree.
    
  189.             parentStateNode.passiveEffectDuration += elapsedTime;
    
  190.           }
    
  191.           return;
    
  192.       }
    
  193.       parentFiber = parentFiber.return;
    
  194.     }
    
  195.   }
    
  196. }
    
  197. 
    
  198. function startLayoutEffectTimer(): void {
    
  199.   if (!enableProfilerTimer || !enableProfilerCommitHooks) {
    
  200.     return;
    
  201.   }
    
  202.   layoutEffectStartTime = now();
    
  203. }
    
  204. 
    
  205. function startPassiveEffectTimer(): void {
    
  206.   if (!enableProfilerTimer || !enableProfilerCommitHooks) {
    
  207.     return;
    
  208.   }
    
  209.   passiveEffectStartTime = now();
    
  210. }
    
  211. 
    
  212. function transferActualDuration(fiber: Fiber): void {
    
  213.   // Transfer time spent rendering these children so we don't lose it
    
  214.   // after we rerender. This is used as a helper in special cases
    
  215.   // where we should count the work of multiple passes.
    
  216.   let child = fiber.child;
    
  217.   while (child) {
    
  218.     // $FlowFixMe[unsafe-addition] addition with possible null/undefined value
    
  219.     fiber.actualDuration += child.actualDuration;
    
  220.     child = child.sibling;
    
  221.   }
    
  222. }
    
  223. 
    
  224. export {
    
  225.   getCommitTime,
    
  226.   isCurrentUpdateNested,
    
  227.   markNestedUpdateScheduled,
    
  228.   recordCommitTime,
    
  229.   recordLayoutEffectDuration,
    
  230.   recordPassiveEffectDuration,
    
  231.   resetNestedUpdateFlag,
    
  232.   startLayoutEffectTimer,
    
  233.   startPassiveEffectTimer,
    
  234.   startProfilerTimer,
    
  235.   stopProfilerTimerIfRunning,
    
  236.   stopProfilerTimerIfRunningAndRecordDelta,
    
  237.   syncNestedUpdateFlag,
    
  238.   transferActualDuration,
    
  239. };