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 {
    
  11.   Lane,
    
  12.   Lanes,
    
  13.   DevToolsProfilingHooks,
    
  14.   WorkTagMap,
    
  15.   CurrentDispatcherRef,
    
  16. } from 'react-devtools-shared/src/backend/types';
    
  17. import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
    
  18. import type {Wakeable} from 'shared/ReactTypes';
    
  19. import type {
    
  20.   BatchUID,
    
  21.   InternalModuleSourceToRanges,
    
  22.   LaneToLabelMap,
    
  23.   ReactComponentMeasure,
    
  24.   ReactLane,
    
  25.   ReactMeasure,
    
  26.   ReactMeasureType,
    
  27.   ReactScheduleStateUpdateEvent,
    
  28.   SchedulingEvent,
    
  29.   SuspenseEvent,
    
  30.   TimelineData,
    
  31. } from 'react-devtools-timeline/src/types';
    
  32. 
    
  33. import isArray from 'shared/isArray';
    
  34. import {
    
  35.   REACT_TOTAL_NUM_LANES,
    
  36.   SCHEDULING_PROFILER_VERSION,
    
  37. } from 'react-devtools-timeline/src/constants';
    
  38. import {describeFiber} from './DevToolsFiberComponentStack';
    
  39. 
    
  40. // Add padding to the start/stop time of the profile.
    
  41. // This makes the UI nicer to use.
    
  42. const TIME_OFFSET = 10;
    
  43. 
    
  44. let performanceTarget: Performance | null = null;
    
  45. 
    
  46. // If performance exists and supports the subset of the User Timing API that we require.
    
  47. let supportsUserTiming =
    
  48.   typeof performance !== 'undefined' &&
    
  49.   // $FlowFixMe[method-unbinding]
    
  50.   typeof performance.mark === 'function' &&
    
  51.   // $FlowFixMe[method-unbinding]
    
  52.   typeof performance.clearMarks === 'function';
    
  53. 
    
  54. let supportsUserTimingV3 = false;
    
  55. if (supportsUserTiming) {
    
  56.   const CHECK_V3_MARK = '__v3';
    
  57.   const markOptions: {
    
  58.     detail?: mixed,
    
  59.     startTime?: number,
    
  60.   } = {};
    
  61.   Object.defineProperty(markOptions, 'startTime', {
    
  62.     get: function () {
    
  63.       supportsUserTimingV3 = true;
    
  64.       return 0;
    
  65.     },
    
  66.     set: function () {},
    
  67.   });
    
  68. 
    
  69.   try {
    
  70.     performance.mark(CHECK_V3_MARK, markOptions);
    
  71.   } catch (error) {
    
  72.     // Ignore
    
  73.   } finally {
    
  74.     performance.clearMarks(CHECK_V3_MARK);
    
  75.   }
    
  76. }
    
  77. 
    
  78. if (supportsUserTimingV3) {
    
  79.   performanceTarget = performance;
    
  80. }
    
  81. 
    
  82. // Some environments (e.g. React Native / Hermes) don't support the performance API yet.
    
  83. const getCurrentTime =
    
  84.   // $FlowFixMe[method-unbinding]
    
  85.   typeof performance === 'object' && typeof performance.now === 'function'
    
  86.     ? () => performance.now()
    
  87.     : () => Date.now();
    
  88. 
    
  89. // Mocking the Performance Object (and User Timing APIs) for testing is fragile.
    
  90. // This API allows tests to directly override the User Timing APIs.
    
  91. export function setPerformanceMock_ONLY_FOR_TESTING(
    
  92.   performanceMock: Performance | null,
    
  93. ) {
    
  94.   performanceTarget = performanceMock;
    
  95.   supportsUserTiming = performanceMock !== null;
    
  96.   supportsUserTimingV3 = performanceMock !== null;
    
  97. }
    
  98. 
    
  99. export type GetTimelineData = () => TimelineData | null;
    
  100. export type ToggleProfilingStatus = (value: boolean) => void;
    
  101. 
    
  102. type Response = {
    
  103.   getTimelineData: GetTimelineData,
    
  104.   profilingHooks: DevToolsProfilingHooks,
    
  105.   toggleProfilingStatus: ToggleProfilingStatus,
    
  106. };
    
  107. 
    
  108. export function createProfilingHooks({
    
  109.   getDisplayNameForFiber,
    
  110.   getIsProfiling,
    
  111.   getLaneLabelMap,
    
  112.   workTagMap,
    
  113.   currentDispatcherRef,
    
  114.   reactVersion,
    
  115. }: {
    
  116.   getDisplayNameForFiber: (fiber: Fiber) => string | null,
    
  117.   getIsProfiling: () => boolean,
    
  118.   getLaneLabelMap?: () => Map<Lane, string> | null,
    
  119.   currentDispatcherRef?: CurrentDispatcherRef,
    
  120.   workTagMap: WorkTagMap,
    
  121.   reactVersion: string,
    
  122. }): Response {
    
  123.   let currentBatchUID: BatchUID = 0;
    
  124.   let currentReactComponentMeasure: ReactComponentMeasure | null = null;
    
  125.   let currentReactMeasuresStack: Array<ReactMeasure> = [];
    
  126.   let currentTimelineData: TimelineData | null = null;
    
  127.   let currentFiberStacks: Map<SchedulingEvent, Array<Fiber>> = new Map();
    
  128.   let isProfiling: boolean = false;
    
  129.   let nextRenderShouldStartNewBatch: boolean = false;
    
  130. 
    
  131.   function getRelativeTime() {
    
  132.     const currentTime = getCurrentTime();
    
  133. 
    
  134.     if (currentTimelineData) {
    
  135.       if (currentTimelineData.startTime === 0) {
    
  136.         currentTimelineData.startTime = currentTime - TIME_OFFSET;
    
  137.       }
    
  138. 
    
  139.       return currentTime - currentTimelineData.startTime;
    
  140.     }
    
  141. 
    
  142.     return 0;
    
  143.   }
    
  144. 
    
  145.   function getInternalModuleRanges() {
    
  146.     /* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */
    
  147.     if (
    
  148.       typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' &&
    
  149.       typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.getInternalModuleRanges ===
    
  150.         'function'
    
  151.     ) {
    
  152.       // Ask the DevTools hook for module ranges that may have been reported by the current renderer(s).
    
  153.       // Don't do this eagerly like the laneToLabelMap,
    
  154.       // because some modules might not yet have registered their boundaries when the renderer is injected.
    
  155.       const ranges = __REACT_DEVTOOLS_GLOBAL_HOOK__.getInternalModuleRanges();
    
  156. 
    
  157.       // This check would not be required,
    
  158.       // except that it's possible for things to override __REACT_DEVTOOLS_GLOBAL_HOOK__.
    
  159.       if (isArray(ranges)) {
    
  160.         return ranges;
    
  161.       }
    
  162.     }
    
  163. 
    
  164.     return null;
    
  165.   }
    
  166. 
    
  167.   function getTimelineData(): TimelineData | null {
    
  168.     return currentTimelineData;
    
  169.   }
    
  170. 
    
  171.   function laneToLanesArray(lanes: Lane) {
    
  172.     const lanesArray = [];
    
  173. 
    
  174.     let lane = 1;
    
  175.     for (let index = 0; index < REACT_TOTAL_NUM_LANES; index++) {
    
  176.       if (lane & lanes) {
    
  177.         lanesArray.push(lane);
    
  178.       }
    
  179.       lane *= 2;
    
  180.     }
    
  181. 
    
  182.     return lanesArray;
    
  183.   }
    
  184. 
    
  185.   const laneToLabelMap: LaneToLabelMap | null =
    
  186.     typeof getLaneLabelMap === 'function' ? getLaneLabelMap() : null;
    
  187. 
    
  188.   function markMetadata() {
    
  189.     markAndClear(`--react-version-${reactVersion}`);
    
  190.     markAndClear(`--profiler-version-${SCHEDULING_PROFILER_VERSION}`);
    
  191. 
    
  192.     const ranges = getInternalModuleRanges();
    
  193.     if (ranges) {
    
  194.       for (let i = 0; i < ranges.length; i++) {
    
  195.         const range = ranges[i];
    
  196.         if (isArray(range) && range.length === 2) {
    
  197.           const [startStackFrame, stopStackFrame] = ranges[i];
    
  198. 
    
  199.           markAndClear(`--react-internal-module-start-${startStackFrame}`);
    
  200.           markAndClear(`--react-internal-module-stop-${stopStackFrame}`);
    
  201.         }
    
  202.       }
    
  203.     }
    
  204. 
    
  205.     if (laneToLabelMap != null) {
    
  206.       const labels = Array.from(laneToLabelMap.values()).join(',');
    
  207.       markAndClear(`--react-lane-labels-${labels}`);
    
  208.     }
    
  209.   }
    
  210. 
    
  211.   function markAndClear(markName: string) {
    
  212.     // This method won't be called unless these functions are defined, so we can skip the extra typeof check.
    
  213.     ((performanceTarget: any): Performance).mark(markName);
    
  214.     ((performanceTarget: any): Performance).clearMarks(markName);
    
  215.   }
    
  216. 
    
  217.   function recordReactMeasureStarted(
    
  218.     type: ReactMeasureType,
    
  219.     lanes: Lanes,
    
  220.   ): void {
    
  221.     // Decide what depth thi work should be rendered at, based on what's on the top of the stack.
    
  222.     // It's okay to render over top of "idle" work but everything else should be on its own row.
    
  223.     let depth = 0;
    
  224.     if (currentReactMeasuresStack.length > 0) {
    
  225.       const top =
    
  226.         currentReactMeasuresStack[currentReactMeasuresStack.length - 1];
    
  227.       depth = top.type === 'render-idle' ? top.depth : top.depth + 1;
    
  228.     }
    
  229. 
    
  230.     const lanesArray = laneToLanesArray(lanes);
    
  231. 
    
  232.     const reactMeasure: ReactMeasure = {
    
  233.       type,
    
  234.       batchUID: currentBatchUID,
    
  235.       depth,
    
  236.       lanes: lanesArray,
    
  237.       timestamp: getRelativeTime(),
    
  238.       duration: 0,
    
  239.     };
    
  240. 
    
  241.     currentReactMeasuresStack.push(reactMeasure);
    
  242. 
    
  243.     if (currentTimelineData) {
    
  244.       const {batchUIDToMeasuresMap, laneToReactMeasureMap} =
    
  245.         currentTimelineData;
    
  246. 
    
  247.       let reactMeasures = batchUIDToMeasuresMap.get(currentBatchUID);
    
  248.       if (reactMeasures != null) {
    
  249.         reactMeasures.push(reactMeasure);
    
  250.       } else {
    
  251.         batchUIDToMeasuresMap.set(currentBatchUID, [reactMeasure]);
    
  252.       }
    
  253. 
    
  254.       lanesArray.forEach(lane => {
    
  255.         reactMeasures = laneToReactMeasureMap.get(lane);
    
  256.         if (reactMeasures) {
    
  257.           reactMeasures.push(reactMeasure);
    
  258.         }
    
  259.       });
    
  260.     }
    
  261.   }
    
  262. 
    
  263.   function recordReactMeasureCompleted(type: ReactMeasureType): void {
    
  264.     const currentTime = getRelativeTime();
    
  265. 
    
  266.     if (currentReactMeasuresStack.length === 0) {
    
  267.       console.error(
    
  268.         'Unexpected type "%s" completed at %sms while currentReactMeasuresStack is empty.',
    
  269.         type,
    
  270.         currentTime,
    
  271.       );
    
  272.       // Ignore work "completion" user timing mark that doesn't complete anything
    
  273.       return;
    
  274.     }
    
  275. 
    
  276.     const top = currentReactMeasuresStack.pop();
    
  277.     if (top.type !== type) {
    
  278.       console.error(
    
  279.         'Unexpected type "%s" completed at %sms before "%s" completed.',
    
  280.         type,
    
  281.         currentTime,
    
  282.         top.type,
    
  283.       );
    
  284.     }
    
  285. 
    
  286.     // $FlowFixMe[cannot-write] This property should not be writable outside of this function.
    
  287.     top.duration = currentTime - top.timestamp;
    
  288. 
    
  289.     if (currentTimelineData) {
    
  290.       currentTimelineData.duration = getRelativeTime() + TIME_OFFSET;
    
  291.     }
    
  292.   }
    
  293. 
    
  294.   function markCommitStarted(lanes: Lanes): void {
    
  295.     if (isProfiling) {
    
  296.       recordReactMeasureStarted('commit', lanes);
    
  297. 
    
  298.       // TODO (timeline) Re-think this approach to "batching"; I don't think it works for Suspense or pre-rendering.
    
  299.       // This issue applies to the User Timing data also.
    
  300.       nextRenderShouldStartNewBatch = true;
    
  301.     }
    
  302. 
    
  303.     if (supportsUserTimingV3) {
    
  304.       markAndClear(`--commit-start-${lanes}`);
    
  305. 
    
  306.       // Some metadata only needs to be logged once per session,
    
  307.       // but if profiling information is being recorded via the Performance tab,
    
  308.       // DevTools has no way of knowing when the recording starts.
    
  309.       // Because of that, we log thie type of data periodically (once per commit).
    
  310.       markMetadata();
    
  311.     }
    
  312.   }
    
  313. 
    
  314.   function markCommitStopped(): void {
    
  315.     if (isProfiling) {
    
  316.       recordReactMeasureCompleted('commit');
    
  317.       recordReactMeasureCompleted('render-idle');
    
  318.     }
    
  319. 
    
  320.     if (supportsUserTimingV3) {
    
  321.       markAndClear('--commit-stop');
    
  322.     }
    
  323.   }
    
  324. 
    
  325.   function markComponentRenderStarted(fiber: Fiber): void {
    
  326.     if (isProfiling || supportsUserTimingV3) {
    
  327.       const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
    
  328. 
    
  329.       if (isProfiling) {
    
  330.         // TODO (timeline) Record and cache component stack
    
  331.         if (isProfiling) {
    
  332.           currentReactComponentMeasure = {
    
  333.             componentName,
    
  334.             duration: 0,
    
  335.             timestamp: getRelativeTime(),
    
  336.             type: 'render',
    
  337.             warning: null,
    
  338.           };
    
  339.         }
    
  340.       }
    
  341. 
    
  342.       if (supportsUserTimingV3) {
    
  343.         markAndClear(`--component-render-start-${componentName}`);
    
  344.       }
    
  345.     }
    
  346.   }
    
  347. 
    
  348.   function markComponentRenderStopped(): void {
    
  349.     if (isProfiling) {
    
  350.       if (currentReactComponentMeasure) {
    
  351.         if (currentTimelineData) {
    
  352.           currentTimelineData.componentMeasures.push(
    
  353.             currentReactComponentMeasure,
    
  354.           );
    
  355.         }
    
  356. 
    
  357.         // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  358.         currentReactComponentMeasure.duration =
    
  359.           // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  360.           getRelativeTime() - currentReactComponentMeasure.timestamp;
    
  361.         currentReactComponentMeasure = null;
    
  362.       }
    
  363.     }
    
  364. 
    
  365.     if (supportsUserTimingV3) {
    
  366.       markAndClear('--component-render-stop');
    
  367.     }
    
  368.   }
    
  369. 
    
  370.   function markComponentLayoutEffectMountStarted(fiber: Fiber): void {
    
  371.     if (isProfiling || supportsUserTimingV3) {
    
  372.       const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
    
  373. 
    
  374.       if (isProfiling) {
    
  375.         // TODO (timeline) Record and cache component stack
    
  376.         if (isProfiling) {
    
  377.           currentReactComponentMeasure = {
    
  378.             componentName,
    
  379.             duration: 0,
    
  380.             timestamp: getRelativeTime(),
    
  381.             type: 'layout-effect-mount',
    
  382.             warning: null,
    
  383.           };
    
  384.         }
    
  385.       }
    
  386. 
    
  387.       if (supportsUserTimingV3) {
    
  388.         markAndClear(`--component-layout-effect-mount-start-${componentName}`);
    
  389.       }
    
  390.     }
    
  391.   }
    
  392. 
    
  393.   function markComponentLayoutEffectMountStopped(): void {
    
  394.     if (isProfiling) {
    
  395.       if (currentReactComponentMeasure) {
    
  396.         if (currentTimelineData) {
    
  397.           currentTimelineData.componentMeasures.push(
    
  398.             currentReactComponentMeasure,
    
  399.           );
    
  400.         }
    
  401. 
    
  402.         // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  403.         currentReactComponentMeasure.duration =
    
  404.           // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  405.           getRelativeTime() - currentReactComponentMeasure.timestamp;
    
  406.         currentReactComponentMeasure = null;
    
  407.       }
    
  408.     }
    
  409. 
    
  410.     if (supportsUserTimingV3) {
    
  411.       markAndClear('--component-layout-effect-mount-stop');
    
  412.     }
    
  413.   }
    
  414. 
    
  415.   function markComponentLayoutEffectUnmountStarted(fiber: Fiber): void {
    
  416.     if (isProfiling || supportsUserTimingV3) {
    
  417.       const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
    
  418. 
    
  419.       if (isProfiling) {
    
  420.         // TODO (timeline) Record and cache component stack
    
  421.         if (isProfiling) {
    
  422.           currentReactComponentMeasure = {
    
  423.             componentName,
    
  424.             duration: 0,
    
  425.             timestamp: getRelativeTime(),
    
  426.             type: 'layout-effect-unmount',
    
  427.             warning: null,
    
  428.           };
    
  429.         }
    
  430.       }
    
  431. 
    
  432.       if (supportsUserTimingV3) {
    
  433.         markAndClear(
    
  434.           `--component-layout-effect-unmount-start-${componentName}`,
    
  435.         );
    
  436.       }
    
  437.     }
    
  438.   }
    
  439. 
    
  440.   function markComponentLayoutEffectUnmountStopped(): void {
    
  441.     if (isProfiling) {
    
  442.       if (currentReactComponentMeasure) {
    
  443.         if (currentTimelineData) {
    
  444.           currentTimelineData.componentMeasures.push(
    
  445.             currentReactComponentMeasure,
    
  446.           );
    
  447.         }
    
  448. 
    
  449.         // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  450.         currentReactComponentMeasure.duration =
    
  451.           // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  452.           getRelativeTime() - currentReactComponentMeasure.timestamp;
    
  453.         currentReactComponentMeasure = null;
    
  454.       }
    
  455.     }
    
  456. 
    
  457.     if (supportsUserTimingV3) {
    
  458.       markAndClear('--component-layout-effect-unmount-stop');
    
  459.     }
    
  460.   }
    
  461. 
    
  462.   function markComponentPassiveEffectMountStarted(fiber: Fiber): void {
    
  463.     if (isProfiling || supportsUserTimingV3) {
    
  464.       const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
    
  465. 
    
  466.       if (isProfiling) {
    
  467.         // TODO (timeline) Record and cache component stack
    
  468.         if (isProfiling) {
    
  469.           currentReactComponentMeasure = {
    
  470.             componentName,
    
  471.             duration: 0,
    
  472.             timestamp: getRelativeTime(),
    
  473.             type: 'passive-effect-mount',
    
  474.             warning: null,
    
  475.           };
    
  476.         }
    
  477.       }
    
  478. 
    
  479.       if (supportsUserTimingV3) {
    
  480.         markAndClear(`--component-passive-effect-mount-start-${componentName}`);
    
  481.       }
    
  482.     }
    
  483.   }
    
  484. 
    
  485.   function markComponentPassiveEffectMountStopped(): void {
    
  486.     if (isProfiling) {
    
  487.       if (currentReactComponentMeasure) {
    
  488.         if (currentTimelineData) {
    
  489.           currentTimelineData.componentMeasures.push(
    
  490.             currentReactComponentMeasure,
    
  491.           );
    
  492.         }
    
  493. 
    
  494.         // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  495.         currentReactComponentMeasure.duration =
    
  496.           // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  497.           getRelativeTime() - currentReactComponentMeasure.timestamp;
    
  498.         currentReactComponentMeasure = null;
    
  499.       }
    
  500.     }
    
  501. 
    
  502.     if (supportsUserTimingV3) {
    
  503.       markAndClear('--component-passive-effect-mount-stop');
    
  504.     }
    
  505.   }
    
  506. 
    
  507.   function markComponentPassiveEffectUnmountStarted(fiber: Fiber): void {
    
  508.     if (isProfiling || supportsUserTimingV3) {
    
  509.       const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
    
  510. 
    
  511.       if (isProfiling) {
    
  512.         // TODO (timeline) Record and cache component stack
    
  513.         if (isProfiling) {
    
  514.           currentReactComponentMeasure = {
    
  515.             componentName,
    
  516.             duration: 0,
    
  517.             timestamp: getRelativeTime(),
    
  518.             type: 'passive-effect-unmount',
    
  519.             warning: null,
    
  520.           };
    
  521.         }
    
  522.       }
    
  523. 
    
  524.       if (supportsUserTimingV3) {
    
  525.         markAndClear(
    
  526.           `--component-passive-effect-unmount-start-${componentName}`,
    
  527.         );
    
  528.       }
    
  529.     }
    
  530.   }
    
  531. 
    
  532.   function markComponentPassiveEffectUnmountStopped(): void {
    
  533.     if (isProfiling) {
    
  534.       if (currentReactComponentMeasure) {
    
  535.         if (currentTimelineData) {
    
  536.           currentTimelineData.componentMeasures.push(
    
  537.             currentReactComponentMeasure,
    
  538.           );
    
  539.         }
    
  540. 
    
  541.         // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  542.         currentReactComponentMeasure.duration =
    
  543.           // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  544.           getRelativeTime() - currentReactComponentMeasure.timestamp;
    
  545.         currentReactComponentMeasure = null;
    
  546.       }
    
  547.     }
    
  548. 
    
  549.     if (supportsUserTimingV3) {
    
  550.       markAndClear('--component-passive-effect-unmount-stop');
    
  551.     }
    
  552.   }
    
  553. 
    
  554.   function markComponentErrored(
    
  555.     fiber: Fiber,
    
  556.     thrownValue: mixed,
    
  557.     lanes: Lanes,
    
  558.   ): void {
    
  559.     if (isProfiling || supportsUserTimingV3) {
    
  560.       const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
    
  561.       const phase = fiber.alternate === null ? 'mount' : 'update';
    
  562. 
    
  563.       let message = '';
    
  564.       if (
    
  565.         thrownValue !== null &&
    
  566.         typeof thrownValue === 'object' &&
    
  567.         typeof thrownValue.message === 'string'
    
  568.       ) {
    
  569.         message = thrownValue.message;
    
  570.       } else if (typeof thrownValue === 'string') {
    
  571.         message = thrownValue;
    
  572.       }
    
  573. 
    
  574.       if (isProfiling) {
    
  575.         // TODO (timeline) Record and cache component stack
    
  576.         if (currentTimelineData) {
    
  577.           currentTimelineData.thrownErrors.push({
    
  578.             componentName,
    
  579.             message,
    
  580.             phase,
    
  581.             timestamp: getRelativeTime(),
    
  582.             type: 'thrown-error',
    
  583.           });
    
  584.         }
    
  585.       }
    
  586. 
    
  587.       if (supportsUserTimingV3) {
    
  588.         markAndClear(`--error-${componentName}-${phase}-${message}`);
    
  589.       }
    
  590.     }
    
  591.   }
    
  592. 
    
  593.   const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
    
  594. 
    
  595.   // $FlowFixMe[incompatible-type]: Flow cannot handle polymorphic WeakMaps
    
  596.   const wakeableIDs: WeakMap<Wakeable, number> = new PossiblyWeakMap();
    
  597.   let wakeableID: number = 0;
    
  598.   function getWakeableID(wakeable: Wakeable): number {
    
  599.     if (!wakeableIDs.has(wakeable)) {
    
  600.       wakeableIDs.set(wakeable, wakeableID++);
    
  601.     }
    
  602.     return ((wakeableIDs.get(wakeable): any): number);
    
  603.   }
    
  604. 
    
  605.   function markComponentSuspended(
    
  606.     fiber: Fiber,
    
  607.     wakeable: Wakeable,
    
  608.     lanes: Lanes,
    
  609.   ): void {
    
  610.     if (isProfiling || supportsUserTimingV3) {
    
  611.       const eventType = wakeableIDs.has(wakeable) ? 'resuspend' : 'suspend';
    
  612.       const id = getWakeableID(wakeable);
    
  613.       const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
    
  614.       const phase = fiber.alternate === null ? 'mount' : 'update';
    
  615. 
    
  616.       // Following the non-standard fn.displayName convention,
    
  617.       // frameworks like Relay may also annotate Promises with a displayName,
    
  618.       // describing what operation/data the thrown Promise is related to.
    
  619.       // When this is available we should pass it along to the Timeline.
    
  620.       const displayName = (wakeable: any).displayName || '';
    
  621. 
    
  622.       let suspenseEvent: SuspenseEvent | null = null;
    
  623.       if (isProfiling) {
    
  624.         // TODO (timeline) Record and cache component stack
    
  625.         suspenseEvent = {
    
  626.           componentName,
    
  627.           depth: 0,
    
  628.           duration: 0,
    
  629.           id: `${id}`,
    
  630.           phase,
    
  631.           promiseName: displayName,
    
  632.           resolution: 'unresolved',
    
  633.           timestamp: getRelativeTime(),
    
  634.           type: 'suspense',
    
  635.           warning: null,
    
  636.         };
    
  637. 
    
  638.         if (currentTimelineData) {
    
  639.           currentTimelineData.suspenseEvents.push(suspenseEvent);
    
  640.         }
    
  641.       }
    
  642. 
    
  643.       if (supportsUserTimingV3) {
    
  644.         markAndClear(
    
  645.           `--suspense-${eventType}-${id}-${componentName}-${phase}-${lanes}-${displayName}`,
    
  646.         );
    
  647.       }
    
  648. 
    
  649.       wakeable.then(
    
  650.         () => {
    
  651.           if (suspenseEvent) {
    
  652.             suspenseEvent.duration =
    
  653.               getRelativeTime() - suspenseEvent.timestamp;
    
  654.             suspenseEvent.resolution = 'resolved';
    
  655.           }
    
  656. 
    
  657.           if (supportsUserTimingV3) {
    
  658.             markAndClear(`--suspense-resolved-${id}-${componentName}`);
    
  659.           }
    
  660.         },
    
  661.         () => {
    
  662.           if (suspenseEvent) {
    
  663.             suspenseEvent.duration =
    
  664.               getRelativeTime() - suspenseEvent.timestamp;
    
  665.             suspenseEvent.resolution = 'rejected';
    
  666.           }
    
  667. 
    
  668.           if (supportsUserTimingV3) {
    
  669.             markAndClear(`--suspense-rejected-${id}-${componentName}`);
    
  670.           }
    
  671.         },
    
  672.       );
    
  673.     }
    
  674.   }
    
  675. 
    
  676.   function markLayoutEffectsStarted(lanes: Lanes): void {
    
  677.     if (isProfiling) {
    
  678.       recordReactMeasureStarted('layout-effects', lanes);
    
  679.     }
    
  680. 
    
  681.     if (supportsUserTimingV3) {
    
  682.       markAndClear(`--layout-effects-start-${lanes}`);
    
  683.     }
    
  684.   }
    
  685. 
    
  686.   function markLayoutEffectsStopped(): void {
    
  687.     if (isProfiling) {
    
  688.       recordReactMeasureCompleted('layout-effects');
    
  689.     }
    
  690. 
    
  691.     if (supportsUserTimingV3) {
    
  692.       markAndClear('--layout-effects-stop');
    
  693.     }
    
  694.   }
    
  695. 
    
  696.   function markPassiveEffectsStarted(lanes: Lanes): void {
    
  697.     if (isProfiling) {
    
  698.       recordReactMeasureStarted('passive-effects', lanes);
    
  699.     }
    
  700. 
    
  701.     if (supportsUserTimingV3) {
    
  702.       markAndClear(`--passive-effects-start-${lanes}`);
    
  703.     }
    
  704.   }
    
  705. 
    
  706.   function markPassiveEffectsStopped(): void {
    
  707.     if (isProfiling) {
    
  708.       recordReactMeasureCompleted('passive-effects');
    
  709.     }
    
  710. 
    
  711.     if (supportsUserTimingV3) {
    
  712.       markAndClear('--passive-effects-stop');
    
  713.     }
    
  714.   }
    
  715. 
    
  716.   function markRenderStarted(lanes: Lanes): void {
    
  717.     if (isProfiling) {
    
  718.       if (nextRenderShouldStartNewBatch) {
    
  719.         nextRenderShouldStartNewBatch = false;
    
  720.         currentBatchUID++;
    
  721.       }
    
  722. 
    
  723.       // If this is a new batch of work, wrap an "idle" measure around it.
    
  724.       // Log it before the "render" measure to preserve the stack ordering.
    
  725.       if (
    
  726.         currentReactMeasuresStack.length === 0 ||
    
  727.         currentReactMeasuresStack[currentReactMeasuresStack.length - 1].type !==
    
  728.           'render-idle'
    
  729.       ) {
    
  730.         recordReactMeasureStarted('render-idle', lanes);
    
  731.       }
    
  732. 
    
  733.       recordReactMeasureStarted('render', lanes);
    
  734.     }
    
  735. 
    
  736.     if (supportsUserTimingV3) {
    
  737.       markAndClear(`--render-start-${lanes}`);
    
  738.     }
    
  739.   }
    
  740. 
    
  741.   function markRenderYielded(): void {
    
  742.     if (isProfiling) {
    
  743.       recordReactMeasureCompleted('render');
    
  744.     }
    
  745. 
    
  746.     if (supportsUserTimingV3) {
    
  747.       markAndClear('--render-yield');
    
  748.     }
    
  749.   }
    
  750. 
    
  751.   function markRenderStopped(): void {
    
  752.     if (isProfiling) {
    
  753.       recordReactMeasureCompleted('render');
    
  754.     }
    
  755. 
    
  756.     if (supportsUserTimingV3) {
    
  757.       markAndClear('--render-stop');
    
  758.     }
    
  759.   }
    
  760. 
    
  761.   function markRenderScheduled(lane: Lane): void {
    
  762.     if (isProfiling) {
    
  763.       if (currentTimelineData) {
    
  764.         currentTimelineData.schedulingEvents.push({
    
  765.           lanes: laneToLanesArray(lane),
    
  766.           timestamp: getRelativeTime(),
    
  767.           type: 'schedule-render',
    
  768.           warning: null,
    
  769.         });
    
  770.       }
    
  771.     }
    
  772. 
    
  773.     if (supportsUserTimingV3) {
    
  774.       markAndClear(`--schedule-render-${lane}`);
    
  775.     }
    
  776.   }
    
  777. 
    
  778.   function markForceUpdateScheduled(fiber: Fiber, lane: Lane): void {
    
  779.     if (isProfiling || supportsUserTimingV3) {
    
  780.       const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
    
  781. 
    
  782.       if (isProfiling) {
    
  783.         // TODO (timeline) Record and cache component stack
    
  784.         if (currentTimelineData) {
    
  785.           currentTimelineData.schedulingEvents.push({
    
  786.             componentName,
    
  787.             lanes: laneToLanesArray(lane),
    
  788.             timestamp: getRelativeTime(),
    
  789.             type: 'schedule-force-update',
    
  790.             warning: null,
    
  791.           });
    
  792.         }
    
  793.       }
    
  794. 
    
  795.       if (supportsUserTimingV3) {
    
  796.         markAndClear(`--schedule-forced-update-${lane}-${componentName}`);
    
  797.       }
    
  798.     }
    
  799.   }
    
  800. 
    
  801.   function getParentFibers(fiber: Fiber): Array<Fiber> {
    
  802.     const parents = [];
    
  803.     let parent: null | Fiber = fiber;
    
  804.     while (parent !== null) {
    
  805.       parents.push(parent);
    
  806.       parent = parent.return;
    
  807.     }
    
  808.     return parents;
    
  809.   }
    
  810. 
    
  811.   function markStateUpdateScheduled(fiber: Fiber, lane: Lane): void {
    
  812.     if (isProfiling || supportsUserTimingV3) {
    
  813.       const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
    
  814. 
    
  815.       if (isProfiling) {
    
  816.         // TODO (timeline) Record and cache component stack
    
  817.         if (currentTimelineData) {
    
  818.           const event: ReactScheduleStateUpdateEvent = {
    
  819.             componentName,
    
  820.             // Store the parent fibers so we can post process
    
  821.             // them after we finish profiling
    
  822.             lanes: laneToLanesArray(lane),
    
  823.             timestamp: getRelativeTime(),
    
  824.             type: 'schedule-state-update',
    
  825.             warning: null,
    
  826.           };
    
  827.           currentFiberStacks.set(event, getParentFibers(fiber));
    
  828.           // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  829.           currentTimelineData.schedulingEvents.push(event);
    
  830.         }
    
  831.       }
    
  832. 
    
  833.       if (supportsUserTimingV3) {
    
  834.         markAndClear(`--schedule-state-update-${lane}-${componentName}`);
    
  835.       }
    
  836.     }
    
  837.   }
    
  838. 
    
  839.   function toggleProfilingStatus(value: boolean) {
    
  840.     if (isProfiling !== value) {
    
  841.       isProfiling = value;
    
  842. 
    
  843.       if (isProfiling) {
    
  844.         const internalModuleSourceToRanges: InternalModuleSourceToRanges =
    
  845.           new Map();
    
  846. 
    
  847.         if (supportsUserTimingV3) {
    
  848.           const ranges = getInternalModuleRanges();
    
  849.           if (ranges) {
    
  850.             for (let i = 0; i < ranges.length; i++) {
    
  851.               const range = ranges[i];
    
  852.               if (isArray(range) && range.length === 2) {
    
  853.                 const [startStackFrame, stopStackFrame] = ranges[i];
    
  854. 
    
  855.                 markAndClear(
    
  856.                   `--react-internal-module-start-${startStackFrame}`,
    
  857.                 );
    
  858.                 markAndClear(`--react-internal-module-stop-${stopStackFrame}`);
    
  859.               }
    
  860.             }
    
  861.           }
    
  862.         }
    
  863. 
    
  864.         const laneToReactMeasureMap = new Map<ReactLane, ReactMeasure[]>();
    
  865.         let lane = 1;
    
  866.         for (let index = 0; index < REACT_TOTAL_NUM_LANES; index++) {
    
  867.           laneToReactMeasureMap.set(lane, []);
    
  868.           lane *= 2;
    
  869.         }
    
  870. 
    
  871.         currentBatchUID = 0;
    
  872.         currentReactComponentMeasure = null;
    
  873.         currentReactMeasuresStack = [];
    
  874.         currentFiberStacks = new Map();
    
  875.         currentTimelineData = {
    
  876.           // Session wide metadata; only collected once.
    
  877.           internalModuleSourceToRanges,
    
  878.           laneToLabelMap: laneToLabelMap || new Map(),
    
  879.           reactVersion,
    
  880. 
    
  881.           // Data logged by React during profiling session.
    
  882.           componentMeasures: [],
    
  883.           schedulingEvents: [],
    
  884.           suspenseEvents: [],
    
  885.           thrownErrors: [],
    
  886. 
    
  887.           // Data inferred based on what React logs.
    
  888.           batchUIDToMeasuresMap: new Map(),
    
  889.           duration: 0,
    
  890.           laneToReactMeasureMap,
    
  891.           startTime: 0,
    
  892. 
    
  893.           // Data only available in Chrome profiles.
    
  894.           flamechart: [],
    
  895.           nativeEvents: [],
    
  896.           networkMeasures: [],
    
  897.           otherUserTimingMarks: [],
    
  898.           snapshots: [],
    
  899.           snapshotHeight: 0,
    
  900.         };
    
  901.         nextRenderShouldStartNewBatch = true;
    
  902.       } else {
    
  903.         // Postprocess Profile data
    
  904.         if (currentTimelineData !== null) {
    
  905.           currentTimelineData.schedulingEvents.forEach(event => {
    
  906.             if (event.type === 'schedule-state-update') {
    
  907.               // TODO(luna): We can optimize this by creating a map of
    
  908.               // fiber to component stack instead of generating the stack
    
  909.               // for every fiber every time
    
  910.               const fiberStack = currentFiberStacks.get(event);
    
  911.               if (fiberStack && currentDispatcherRef != null) {
    
  912.                 event.componentStack = fiberStack.reduce((trace, fiber) => {
    
  913.                   return (
    
  914.                     trace +
    
  915.                     describeFiber(workTagMap, fiber, currentDispatcherRef)
    
  916.                   );
    
  917.                 }, '');
    
  918.               }
    
  919.             }
    
  920.           });
    
  921.         }
    
  922. 
    
  923.         // Clear the current fiber stacks so we don't hold onto the fibers
    
  924.         // in memory after profiling finishes
    
  925.         currentFiberStacks.clear();
    
  926.       }
    
  927.     }
    
  928.   }
    
  929. 
    
  930.   return {
    
  931.     getTimelineData,
    
  932.     profilingHooks: {
    
  933.       markCommitStarted,
    
  934.       markCommitStopped,
    
  935.       markComponentRenderStarted,
    
  936.       markComponentRenderStopped,
    
  937.       markComponentPassiveEffectMountStarted,
    
  938.       markComponentPassiveEffectMountStopped,
    
  939.       markComponentPassiveEffectUnmountStarted,
    
  940.       markComponentPassiveEffectUnmountStopped,
    
  941.       markComponentLayoutEffectMountStarted,
    
  942.       markComponentLayoutEffectMountStopped,
    
  943.       markComponentLayoutEffectUnmountStarted,
    
  944.       markComponentLayoutEffectUnmountStopped,
    
  945.       markComponentErrored,
    
  946.       markComponentSuspended,
    
  947.       markLayoutEffectsStarted,
    
  948.       markLayoutEffectsStopped,
    
  949.       markPassiveEffectsStarted,
    
  950.       markPassiveEffectsStopped,
    
  951.       markRenderStarted,
    
  952.       markRenderYielded,
    
  953.       markRenderStopped,
    
  954.       markRenderScheduled,
    
  955.       markForceUpdateScheduled,
    
  956.       markStateUpdateScheduled,
    
  957.     },
    
  958.     toggleProfilingStatus,
    
  959.   };
    
  960. }